Skip to content

Repository files navigation

Copyright 2025-2026 Ardan Labs

hello@ardanlabs.com

Malina

This project lets you use Go for hardware accelerated local image and video generation with stable-diffusion.cpp directly integrated into your applications. Malina maps the safe public stable-diffusion.h API plus pure-Go PNG/JPEG I/O and Motion-JPEG AVI muxing.

Malina is the image-generation sibling of ardanlabs/bucky (which binds whisper.cpp) and hybridgroup/yzma (which binds llama.cpp). The end goal is to give Kronk a native, OpenAI-compatible POST /v1/images/generations endpoint without the CGo toolchain.

Malina is the Russian word for "raspberry" — a small, dense, fast-growing fruit. Naming a stable-diffusion binding after a fast little thing that sprouts colorful pictures is just good taste.

To install malina, fetch the stable-diffusion.cpp shared libraries, and generate the bundled cat sample:

$ go install github.com/ardanlabs/malina@latest

$ malina install -u
$ malina model pull sd-1.5
$ go run ./examples/hello "a lovely cat"

Project Status

Go Reference go.mod Go version stable-diffusion.cpp Release

Linux Windows

Sometimes there are breaking changes to stable-diffusion.cpp that require an update to malina. Here are the known compatible versions:

stable-diffusion.cpp malina
master-929-3f8527a 1.1.4
master-908-88411ef 1.1.3
master-869-07a85c7 1.1.2

The FFI binding includes image and native video generation, upscaling, ADetailer, ControlNet hot-swap, conversion, Canny preprocessing, cancellation, preview/backend callbacks, device and loaded-model identification, and every generation parameter in the target header. Pure-Go PNG/JPEG decode + Motion-JPEG AVI mux, the CLI (install, system, info, model list|pull), and runnable examples for the generation APIs have also landed. Kronk integration (an OpenAI-compatible POST /v1/images/generations endpoint) lives in the kronk repo.

Owner Information

Name:     Bill Kennedy
Company:  Ardan Labs
Title:    Managing Partner
Email:    bill@ardanlabs.com
BlueSky:  https://bsky.app/profile/goinggo.net
LinkedIn: www.linkedin.com/in/william-kennedy-5b318778/
Twitter:  https://x.com/goinggodotnet

Install Malina

The fastest way to install on any supported platform is with Go:

$ go install github.com/ardanlabs/malina@latest

$ malina --help

Then fetch the stable-diffusion.cpp shared library bundle (dylib on macOS, DLLs on Windows, and .so files on Linux, all distributed in ZIP archives from the upstream leejet/stable-diffusion.cpp releases):

$ malina install
$ malina system

Malina verifies an upstream asset's GitHub SHA-256 digest before extracting it. DefaultSDVersion includes the SHA-256 of the embedded trusted manifest, which authenticates the asset ID, size, archive digest, and every installed shared library for Malina's pinned stable-diffusion.cpp release. Installs created before this verification metadata was introduced must be refreshed once with malina install --upgrade.

And pull a model bundle from the bundled catalog:

$ malina model list
$ malina model pull sd-1.5
$ malina model info -m ~/.kronk/malina-models/sd-1.5/v1-5-pruned-emaonly.safetensors

Issues/Features

Here is the existing Issues/Features for the project and the things being worked on or things that would be nice to have.

If you are interested in helping in any way, please send an email to Bill Kennedy.

Architecture

The architecture of malina mirrors bucky and yzma file-for-file so anyone who knows either can drop straight in. There is no CGo: every C call goes through purego + JupiterRider/ffi.

┌─────────────────────────────────────────────────────────────┐
│  cmd/         malina CLI (install, system, model, sd)       │
├─────────────────────────────────────────────────────────────┤
│  pkg/sd       safe stable-diffusion.h FFI surface           │
│               (image/video generation, upscaler, callbacks, │
│                conversion, image I/O, log, system)          │
│  pkg/download go-getter-driven release-archive resolver +   │
│               curated generation and tool-model catalog     │
│  pkg/loader   MALINA_LIB-aware purego library loader        │
│  pkg/utils    cross-platform Go ↔ C string helpers          │
└─────────────────────────────────────────────────────────────┘
                          │
                          ▼
            libstable-diffusion.{dylib|so|dll}
              (stable-diffusion.cpp master-929)

FFI API coverage

pkg/sd prepares 61 of the 64 functions exported by the pinned stable-diffusion.h. This includes all functions with a safe ownership contract. Newer optional symbols are resolved at load time so an explicitly requested older compatible library can still load; calling an unavailable feature returns sd.ErrUnsupportedAPI.

The only functions intentionally not called are sd_ctx_params_to_str, sd_sample_params_to_str, and sd_img_gen_params_to_str. Each returns a newly allocated char *, but upstream provides no matching public deallocator. Freeing those pointers from Go would be unsafe across Windows CRT boundaries, and not freeing them would leak. Malina will bind them when upstream exposes a matched free API. The exported sample_method_to_str and scheduler_to_str data arrays are represented by the safe name and parse functions instead of directly exposing C global memory.

The master-846-d8fb10c ABI replaces ContextParams.StreamLayers with ContextParams.DisablePrefetch, adds ContextParams.DisableSegmentedCompute, and inserts the LogVerbose level. Code setting StreamLayers must migrate to the new controls; the old field's storage now has the opposite meaning.

The master-869-07a85c7 ABI adds ContextParams.AudioEncoderPath for audio-conditioned video models and ContextParams.Tokenizer for external tokenizer JSON files. It also adds an output parameter to native video generation, so older Malina releases must not load this library build. Use GenerateVideoWithFPS when muxing output so models that force a fixed frame rate, such as Wan2.2 S2V and MiniMax-H3, report the effective value.

The master-908-88411ef ABI grows the context, image-generation, and video-generation parameter structs. Malina 1.1.3 exposes native SageAttention, the per-context conditioning-cache limit, LLaDA-Image's scheduler, and shared image-input preprocessing rules through ContextParams, ImgGenParams, and VideoGenParams. Older Malina releases must not load this library build.

The master-929-3f8527a header keeps every struct size, field offset, enum, callback, and existing function signature unchanged. It renames VAE tiling's X/Y fields to width/height and defines tile dimensions in image pixels; Malina keeps the source-compatible TileSizeX/TileSizeY and RelativeSizeX/RelativeSizeY names while documenting those semantics. Malina 1.1.4 also exposes GetUpscalerModelScale, which reads an ESRGAN model's native scale without first loading an upscaler context.

Models

Malina works with any model stable-diffusion.cpp accepts: .safetensors and .gguf checkpoints for SD 1.x / SD 2.x / SDXL, plus the multi-file FLUX and SD3 layouts (separate diffusion model + VAE + text-encoder files). Recommended hosts are stable-diffusion-v1-5/stable-diffusion-v1-5 and the GGUF quants under city96.

The target library also supports SenseNova U1.5 directories. It is not in the curated catalog because upstream requires a complete repository directory with eight weight shards plus tokenizer and configuration files, while catalog roles currently resolve to individual files.

The target library adds Wan2.2 S2V 14B audio-conditioned video support and requires external tokenizer JSON files for PiD and Lens. These are not curated bundles: Wan S2V needs a large multi-file video/audio workflow that the current catalog roles cannot represent, PiD's official weights are non-commercial, and Lens requires multiple large model components plus an external tokenizer.

The master-908-88411ef release also supports Qwen Image 2.1 and LLaDA-Image. Qwen Image 2.1 is not curated because its license restricts use to non-commercial research and evaluation. The catalog includes the Apache-2.0 LLaDA-Image-Turbo model for 4-step text-to-image generation and image editing.

The master-929-3f8527a release adds PixArt-α/Σ and Ming-Image Design. Neither is curated yet: the official PixArt distribution is a multi-file transformer/text-encoder/VAE layout and upstream does not yet apply PixArt-α's resolution micro-conditioning, while Ming-Image requires a 6B diffusion model, a BF16 Ling-mini-2.0 text encoder, a VAE, and an external tokenizer. Those official workflows are impractically large for the curated catalog without stable compact variants, though callers can supply their files directly.

Malina ships a curated catalog so you can pull complete generation workflows and standalone tool models instead of pasting URLs:

$ malina model list
$ malina model pull sd-1.5
$ malina model pull controlnet-canny-sd1.5
$ malina model pull realesrgan-x4-anime
$ malina model pull adetailer-face-yolov8n
$ malina model pull animatediff-sd1.5
$ malina model pull sdxl-base-1.0
$ malina model pull llada-image-turbo # five files; approximately 20.2 GB
$ malina model pull flux2-klein-4b   # license-gated; export HF_TOKEN first
$ malina model pull flux2-klein-9b   # license-gated; export HF_TOKEN first

Each bundle drops every required file into $HOME/.kronk/malina-models/<bundle>/ along with a manifest.json the examples use to resolve paths.

Support

Malina uses the prebuilt stable-diffusion.cpp release artifacts from leejet/stable-diffusion.cpp directly — there is no companion builder repo. The pinned version is captured in pkg/download/install.go as DefaultSDVersion; its trusted release metadata, archive hashes, installed-file hashes, and symlink targets are captured in pkg/download/library_manifest.json. A dynamically selected release such as -v latest still receives archive-level verification from GitHub. Malina saves that GitHub Release API response and the resulting extracted-file hashes beside the installed libraries, so later offline checks can detect changed metadata or local corruption. Only the pinned release has an authenticated post-install baseline embedded in the Malina binary.

OS CPU Backend Upstream artifact pattern
macOS arm64 Metal sd-master-…-bin-Darwin-macOS-…-arm64.zip
Windows amd64 CPU sd-master-…-bin-win-cpu-x64.zip
Windows amd64 CUDA 12 sd-master-…-bin-win-cuda12-x64.zip plus cudart-…-cu12-…zip
Windows amd64 Vulkan sd-master-…-bin-win-vulkan-x64.zip
Windows amd64 ROCm sd-master-…-bin-win-rocm-…-x64.zip
Linux amd64 CPU sd-master-…-bin-Linux-Ubuntu-…-x86_64.zip
Linux amd64 Vulkan / ROCm CPU pattern plus -vulkan.zip or -rocm-….zip

Whenever there is a new release of stable-diffusion.cpp, the FFI struct mirrors in pkg/sd and the version constant in pkg/download may need a refresh. Generate and review the new trusted manifest with make generate-library-manifest VERSION=master-N-shortsha, bump DefaultSDVersion, regenerate any struct-size assertions in pkg/sd/*_test.go, and let CI verify. Manifest generation downloads and hashes every supported release asset, so it can take several minutes and several gigabytes of transfer.

The malina_model_tests suite exercises SD 1.5, SDXL, and the advanced APIs against real models configured by the Makefile. The license-gated FLUX.2 Klein bundles and the approximately 20.2 GB LLaDA-Image-Turbo bundle remain available as opt-in catalog entries, but are deliberately not used by tests, benchmarks, examples, or make download-models.

Environment variable Catalog bundle / functional test
MALINA_CONTROLNET_TEST_DIR controlnet-canny-sd1.5 controlled image generation
MALINA_UPSCALER_TEST_DIR realesrgan-x4-anime 4× image upscaling
MALINA_ADETAILER_TEST_DIR adetailer-face-yolov8n face detection/refinement
MALINA_VIDEO_TEST_DIR animatediff-sd1.5 multi-frame video generation

Each advanced test skips when its fixture variable is unset and fails when a configured fixture is missing. GitHub Actions downloads and caches each advanced bundle before running its corresponding native functional test on Linux.

API Examples

There are examples in the examples/ directory. They always load libraries and models from Malina's default locations under ~/.kronk; no library or model path configuration is required or accepted. Before loading, each example verifies that the installed libraries exactly match the authenticated download.DefaultSDVersion pin:

$ malina install -u
$ malina model pull sd-1.5

SYSTEM — the smallest possible malina program: load libstable-diffusion and print the library version, system info, and GGML backend device count. No model required.

$ make example-system

HELLO — load a stable-diffusion model, generate one image from a text prompt, and save it as hello.png.

$ make example-hello

CONCURRENT — compare serial generation with concurrent generation on independent native contexts. Contexts cannot be shared concurrently, and each independent context loads another copy of the model weights.

$ make example-concurrent

IMG2IMG — image-to-image: load a source PNG or JPEG, hand it to stable-diffusion as the starting latent, and let the prompt repaint it. The default chain consumes hello.png written by the previous example.

$ make example-hello       # writes hello.png
$ make example-img2img     # writes img2img.png in oil-painting style

CONTROLNET — derive Canny edges from an input image and use them to constrain the generated image's composition.

$ malina model pull controlnet-canny-sd1.5
$ make example-controlnet

UPSCALE — enlarge an image 4× with the compact Real-ESRGAN anime model.

$ malina model pull realesrgan-x4-anime
$ make example-upscale

ADETAILER — detect faces with YOLOv8n and refine each detected region with Stable Diffusion inpainting.

$ malina model pull adetailer-face-yolov8n
$ make example-adetailer

ANIMATEDIFF — generate a temporally conditioned sequence with an AnimateDiff motion module and save it as an AVI.

$ malina model pull animatediff-sd1.5
$ make example-animatediff

SD-ENCODE — mux a directory of PNG / JPEG frames into a Motion-JPEG AVI. No model is loaded; this is the pure-Go encoder built on top of pkg/sd's SaveAVI helper.

$ make example-sd-encode

Sample API Program — Hello Example

// hello is the smallest possible malina example: load a stable-diffusion
// model, generate one image from a text prompt, and save it as PNG.
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/ardanlabs/malina/pkg/download"
	"github.com/ardanlabs/malina/pkg/sd"
)

func main() {
	prompt := "a lovely cat"
	if len(os.Args) >= 2 {
		prompt = os.Args[1]
	}

	libPath := download.DefaultLibrariesDir()
	bundleDir := filepath.Join(download.DefaultModelsDir(), "sd-1.5")
	manifest, err := download.LoadManifest(bundleDir)
	if err != nil {
		log.Fatalf("load model bundle from %s: %v (did you run `malina model pull sd-1.5`?)", bundleDir, err)
	}
	modelPath := manifest.Files[string(download.RoleModel)]

	if err := download.VerifyDefaultInstall(context.Background(), libPath); err != nil {
		log.Fatalf("verify default libraries: %v (did you run `malina install -u`?)", err)
	}
	if err := sd.Load(libPath); err != nil {
		log.Fatalf("sd.Load: %v", err)
	}
	if err := sd.Init(libPath); err != nil {
		log.Fatalf("sd.Init: %v", err)
	}

	cparams := sd.ContextParamsInit()
	cparams.ModelPath = modelPath

	fmt.Println("loading model from", modelPath, "...")
	ctx, err := sd.NewContext(cparams)
	if err != nil {
		log.Fatalf("sd.NewContext: %v", err)
	}
	defer sd.FreeContext(ctx)

	params := sd.ImgGenParamsInit()
	params.Prompt = prompt

	fmt.Println("generating image for prompt:", prompt)
	start := time.Now()
	img, err := sd.GenerateImage(ctx, params)
	if err != nil {
		log.Fatalf("sd.GenerateImage: %v", err)
	}
	elapsed := time.Since(start)

	const outPath = "hello.png"
	if err := img.SavePNG(outPath); err != nil {
		log.Fatalf("SavePNG: %v", err)
	}
	fmt.Printf("wrote %s (%dx%d, %d channels) in %s\n", outPath, img.Width, img.Height, img.Channel, elapsed.Round(time.Millisecond))
}

This example produces the following output:

$ make example-hello
go run ./examples/hello "a lovely cat"
loading model from /Users/bill/.kronk/malina-models/sd-1.5/v1-5-pruned-emaonly.safetensors ...
generating image for prompt: a lovely cat
wrote hello.png (512x512, 3 channels) in 6.842s

License

Apache-2.0 — see LICENSE.

About

Native Go binding for the stable-diffusion.cpp libraries

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages