Copyright 2025-2026 Ardan Labs
This project lets you use Go for hardware accelerated local image generation with stable-diffusion.cpp directly integrated into your applications. Malina provides a high-level API that mirrors stable-diffusion.h 1-to-1 plus pure-Go PNG/JPEG I/O and Motion-JPEG AVI muxing so you can hand any prompt to a Stable Diffusion model and get an image (or a short video) back.
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 -lib ./lib
$ export MALINA_LIB=$(pwd)/lib
$ malina model pull sd-1.5
$ export MALINA_TEST_MODEL=$HOME/models/sd-1.5/v1-5-pruned-emaonly.safetensors
$ 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-656-0e4ee04 | 0.1.x |
The core FFI binding (context init, generate_image, image I/O, log/progress callbacks, GGML backend introspection), pure-Go PNG/JPEG decode + Motion-JPEG AVI mux, CLI (install, system, info, model list|pull), and examples (hello, system, sd-encode, flux2) have all 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 darwin, DLLs on windows, .tar.gz on linux — all sourced from the upstream leejet/stable-diffusion.cpp releases):
$ malina install -lib ./lib
$ export MALINA_LIB=$(pwd)/lib
$ malina systemAnd pull a model bundle from the bundled catalog:
$ malina model list
$ malina model pull sd-1.5
$ malina model info -m ~/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 1-to-1 mirror of stable-diffusion.h │
│ (context, gen_params, generate, video, │
│ image I/O, log, system) │
│ pkg/download go-getter-driven release-archive resolver + │
│ bundle catalog (sd-1.5, sdxl, flux2) │
│ 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-656)
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.
Malina ships a small bundled catalog so you can malina model pull sd-1.5 instead of pasting URLs:
$ malina model list
$ malina model pull sd-1.5
$ malina model pull sdxl-base-1.0
$ malina model pull flux2-klein-9b # license-gated; export HF_TOKEN firstEach bundle drops every required file into $HOME/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.
| OS | CPU | GPU | Source |
|---|---|---|---|
| Linux | amd64 | CPU (avx2) | sd-master-…-linux-avx2-x64.tar.gz (upstream) |
| macOS | arm64 | Metal | sd-master-…-bin-MacOS-arm64.tar.gz (upstream) |
| Windows | amd64 | CPU, CUDA 12 | sd-master-…-bin-win-avx2-x64.zip / -cuda12-… (upstream) |
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. Bump DefaultSDVersion, regenerate any struct-size assertions in pkg/sd/*_test.go, and let CI verify.
There are examples in the examples/ directory. Each one
expects MALINA_LIB and (for the model-loading examples) MALINA_TEST_MODEL
to be set:
$ export MALINA_LIB=$(pwd)/lib
$ export MALINA_TEST_MODEL=$HOME/models/sd-1.5/v1-5-pruned-emaonly.safetensorsSYSTEM — 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-helloIMG2IMG — 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 styleFLUX2 — multi-file FLUX.2 [klein] 9B pipeline using a quantized diffusion model + VAE + Qwen3 LLM text encoder. The example reads the bundle's manifest.json to resolve each file's on-disk path.
$ export HF_TOKEN=hf_... # FLUX.2 license must be accepted
$ malina model pull flux2-klein-9b # one-time
$ make example-flux2SD-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 (
"fmt"
"log"
"os"
"time"
"github.com/ardanlabs/malina/pkg/sd"
)
func main() {
prompt := "a lovely cat"
if len(os.Args) >= 2 {
prompt = os.Args[1]
}
libPath := os.Getenv("MALINA_LIB")
if libPath == "" {
log.Fatal("MALINA_LIB must point to the directory containing libstable-diffusion")
}
modelPath := os.Getenv("MALINA_TEST_MODEL")
if modelPath == "" {
log.Fatal("MALINA_TEST_MODEL must point to a stable-diffusion model file (.gguf or .safetensors)")
}
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/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.