Skip to main content
Version: 0.14

Image Generation: Text-to-Image

Atelico ships several on-device diffusion models for turning a text prompt into a PNG image — no external API, no network round-trip once the weights are cached.

model valueFamilyDefault stepsDefault sizeNotes
pixart-sigmaPixArt-Sigma20512×512Multi-step DPM-Solver; the steps slider is meaningful
pixart-alphaPixArt-Alpha1512×512Distilled one-step (DMD) generation — fastest
sana-sprintSana21024×1024SCM 2-step distilled
sana-0.6bSana 0.6B201024×1024Flow matching
sana-1.6bSana 1.6B201024×1024Flow matching, higher quality
animaAnima DiT20512×512Supports the distilled rCM few-step sampler
anima-rcmAnima rCM student2512×512Distilled few-step checkpoint used for on-device deployment

Prefix with the backend as usual: in-memory::pixart-sigma. Unknown ids return a clear error listing the supported models.

The flow is the same for every model: a text prompt goes in, a base64-encoded PNG comes out. Generation takes seconds, so every binding also exposes a non-blocking async variant (*_async) and a step-by-step streaming variant.

Request fields​

The request deserializes into an ImageGenerationRequest. The OpenAI-compatible top-level fields:

FieldTypeDefaultDescription
modelstringrequiredModel id, e.g. in-memory::pixart-sigma
promptstringrequiredText description of the image
nu81Number of images to generate
sizestringmodel-specificDimensions as "WxH" (e.g. "512x512", "1024x1024")
seedu64randomRandom seed for reproducible generation
negative_promptstringnoneTerms to steer the model away from
response_formatstringb64_jsonb64_json or url
output_formatstringpngpng, jpeg, or webp
streamboolfalseIf true, emit intermediate denoising steps

Inference-pipeline knobs live under a nested pipeline_config object:

pipeline_config fieldTypeDescription
num_inference_stepsu32Diffusion steps (higher = better quality, slower)
guidance_scalef32Classifier-free guidance (higher = more prompt adherence)
vae_modestring"full" (high quality) or "tiny" (fast, softer)
adapter_weightf32LoRA adapter strength, 0.0–1.0 (if an adapter is loaded)
use_chiboolSana-only Complex Human Instruction prompt enhancement (default true)
samplingobjectAnima-only sampler selector, tagged by mode (see below)
rcm_t_schedulef64 array or stringAnima rCM: the nfe − 1 intermediate re-noise times, strictly descending and inside (0, 1.5583), as [0.8] or "0.8". The length fixes the step count. Default for anima-rcm: [1.3] (2 steps)
dit_precisionstringAnima rCM load-time DiT weights: "full" (default), "mlxq4", "svdq4", "svdq4_calibrated". Part of the model cache key, so a dense and a quantised load never collide
model_assetstringAnima rCM: store key under models/ the bundle is fetched from, cached under and loaded from (default "anima-rcm"). A single path segment [A-Za-z0-9._-]+
low_memoryboolAnima: drop the text encoder right after the prompt encode and rebuild it next time — lower peak memory, one reload per generation (default false)

sampling takes one of two shapes. Unknown modes, or a knob given without a mode, are rejected rather than silently ignored:

{"pipeline_config": {"sampling": {"mode": "base", "steps": 20, "guidance": 4.5}}}
{"pipeline_config": {"sampling": {"mode": "rcm", "nfe": 2, "mid_t": 1.3}}}

nfe is one of 1, 2, 4; mid_t applies to nfe = 2 only. When rcm_t_schedule is also given it fixes the step count, and a disagreeing nfe, a mid_t, or mode: "base" is an error naming both values.

note

LoRA suffixes work here too: in-memory::pixart-sigma::chibi-style loads the pixart-sigma model with the chibi-style adapter. See LoRA Adapters.

Response​

The response is an ImageGenerationResponse. Each entry in data carries the generated image as a base64-encoded PNG in b64_json:

{
"created": 1234567890,
"data": [
{"b64_json": "iVBORw0KGgo..."}
],
"output_format": "png",
"size": "512x512"
}

Streaming progress​

Pass "stream": true to receive one intermediate ImageGenerationResponse per denoising step — useful for showing a live preview that sharpens as the image resolves. The final event carries the completed image. In the native SDKs this is exposed through the same streaming primitive used elsewhere (Rust StreamHandle, FFI poll loop, etc.).

SDK usage (in-process)​

Rust SDK​

use atelico_sdk::{Engine, ImageGenerationRequest};

let engine = Engine::new()?;

let request: ImageGenerationRequest = serde_json::from_value(serde_json::json!({
"model": "in-memory::pixart-sigma",
"prompt": "a cat sitting on a roof at sunset, watercolor",
"size": "512x512",
"seed": 42,
"pipeline_config": { "num_inference_steps": 20 }
}))?;

// Blocking — returns once the image is fully generated.
let response = engine.images().generate_sync(request)?;
let b64 = response.data[0].b64_json.as_deref().unwrap();
let png_bytes = base64::engine::general_purpose::STANDARD.decode(b64)?;
std::fs::write("cat.png", &png_bytes)?;

Generation is seconds-long, so prefer the non-blocking variant in a game loop: engine.images().generate_async(request) returns a [StreamHandle] you can drive via poll() (frame integration), next() (async), or next_blocking() (sync). generate_stream(request).await yields the per-step previews.

Python​

import base64, json
from atelico import Engine

engine = Engine()

resp = json.loads(engine.image_generate(json.dumps({
"model": "in-memory::pixart-sigma",
"prompt": "a cat sitting on a roof at sunset, watercolor",
"size": "512x512",
"seed": 42,
"pipeline_config": {"num_inference_steps": 20},
})))
png = base64.b64decode(resp["data"][0]["b64_json"])
open("cat.png", "wb").write(png)

# Non-blocking — returns a JsonResultStream you poll for the final response.
handle = engine.image_generate_async(json.dumps({
"model": "in-memory::pixart-alpha",
"prompt": "a fantasy castle on a cliff",
}))

C / FFI​

// Blocking image generation
const char* request =
"{\"model\":\"in-memory::pixart-sigma\","
"\"prompt\":\"a cat sitting on a roof at sunset\","
"\"size\":\"512x512\",\"seed\":42,"
"\"pipeline_config\":{\"num_inference_steps\":20}}";
const char* response_json = NULL;
int rc = atelico_image_generate(engine, request, &response_json);
// response_json: {"created":..,"data":[{"b64_json":"iVBORw0KGgo..."}],
// "output_format":"png","size":"512x512"}
// base64-decode data[0].b64_json to recover the PNG bytes.

For non-blocking generation, atelico_image_generate_async takes the same request JSON and returns a stream handle; drive it with the shared atelico_stream_poll / atelico_stream_destroy loop (see the C FFI getting started guide). The output pointer is owned by the FFI layer's thread-local return buffer — copy it before the next API call on the same thread.

Demo​

demos/image-gen is a desktop app (also iOS-buildable) that exercises the full path: pick a model (pixart-sigma, pixart-alpha, or anima), type a prompt, adjust steps/seed, and view the result. Select the model via the ATELICO_DEMO_MODEL env var (anima | pixart-sigma | pixart-alpha). It is the recommended starting point before wiring image generation into a game engine.

See also​

Scheduling priority​

Image generation holds the GPU longer than anything else the engine does, so what it declares matters most:

{ "model": "in-memory::my-model", "prompt": "a quiet village at dawn", "priority": "background" }

A background generation hands the GPU over between denoise steps, so an interactive chat or embedding queued behind it waits for one step rather than the whole image. See GPU Scheduling.