Copyright 2025-2026 Ardan Labs
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"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-969-8c442fb | 1.1.6-1.1.7 |
| master-929-3f8527a | 1.1.4–1.1.5 |
| master-908-88411ef | 1.1.3 |
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.
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
The fastest way to install on any supported platform is with Go:
$ go install github.com/ardanlabs/malina@latest
$ malina --helpThen 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 systemMalina 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.safetensorsHere 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.
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-969)
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. The catalog includes the complete Wan S2V video/audio workflow. PiD is not curated because its 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.
The master-969-8c442fb release adds Iris-3B, Z-Image L2P, FLUX.3 Image
Klein/Klein Base, and MoSR upscalers. These are not curated yet:
Iris's official F32 diffusion weights are approximately 12 GB plus a Qwen3-VL
encoder, and L2P's diffusion download is approximately 18.6 GB plus a Qwen3
encoder. Both pixel-space workflows need no VAE but lack verified compact
bundles. Official FLUX.3 weights are gated. The linked 17 MB MoSR checkpoint
4xNomos2_hq_mosr.pth
fits the upscaler role, but its weight release states the ambiguous license
CC-BY-0.4; curation is deferred until the weight license is clarified.
Existing ungated bundles remain the recommended validation fixtures.
The catalog also includes Z-Image-Turbo: an ungated Apache-2.0 bundle
with Q4_K diffusion weights, a Q4_K_M Qwen3-4B-Instruct-2507 text encoder,
and a VAE (three revision-pinned files, approximately 6.7 GB total).
Use 8 steps and CFG 1 for its distilled text-to-image workflow. Resolve the
bundle's diffusion, LLM, and VAE roles to ContextParams.DiffusionModelPath,
LLMPath, and VAEPath; no additional tokenizer or vision projector is needed.
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 wan2.2-s2v-14b # four files; approximately 18.4 GB
$ malina model pull sdxl-base-1.0
$ malina model pull llada-image-turbo # five files; approximately 20.2 GB
$ malina model pull z-image-turbo # three files; approximately 6.7 GB; ungated
$ malina model pull flux2-klein-4b # license-gated; export HF_TOKEN first
$ malina model pull flux2-klein-9b # license-gated; export HF_TOKEN firstEach bundle drops every required file into $HOME/.kronk/malina-models/<bundle>/ along with a manifest.json the examples use to resolve paths.
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, the approximately 20.2 GB LLaDA-Image-Turbo bundle, and the
approximately 18.4 GB Wan2.2 S2V 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.
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.5SYSTEM — 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-systemHELLO — load a stable-diffusion model, generate one image from a text prompt, and save it as hello.png.
$ make example-helloCONCURRENT — 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-concurrentIMG2IMG — 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 styleCONTROLNET — 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-controlnetUPSCALE — enlarge an image 4× with the compact Real-ESRGAN anime model.
$ malina model pull realesrgan-x4-anime
$ make example-upscaleADETAILER — detect faces with YOLOv8n and refine each detected region with Stable Diffusion inpainting.
$ malina model pull adetailer-face-yolov8n
$ make example-adetailerANIMATEDIFF — generate a temporally conditioned sequence with an AnimateDiff motion module and save it as an AVI.
$ malina model pull animatediff-sd1.5
$ make example-animatediffSD-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// 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.842sApache-2.0 — see LICENSE.