Image generation · Kromoss DMD2 8-step · diffusers · GB10 · 40 GB VRAM
published by @dh7 on
m64 m64_67cfbdf8a0d8An image model in a capsule: Kromoss with its 8-step DMD2 LoRA on diffusers, text to 1024x1024 PNG in about 19 s on a GB10. Shows the image.generate kind, [[artifacts]] for 40 GB of weights, [params] and an extra route.
Lineage
- parent
- none declared
- derived from
- no published capsule shares half its files
- children
- none
Files
7 files- human/
- index.html4.0 KB
- AGENT.md3.3 KB
- capsule.toml2.7 KB
- Dockerfile1.1 KB
- kromoss_server.py6.9 KB
- README.md2.3 KB
- start.shexec491 B
AGENT.md
Kromoss + DMD2 8-step text-to-image
This service turns a text prompt into a 1024x1024 PNG. It runs lodestones/Kromoss (a full fine-tune of MOSS-Image-Base, Apache-2.0) with the author's 8-step DMD2 turbo LoRA, through diffusers on one GPU. The text encoder is Qwen3.5-9B, so long, detailed prompts work well. Generation settings are fixed: 1024x1024, 8 steps, guidance (CFG) 1 with no negative prompt, flow-matching shift 5. You send a prompt and, optionally, a seed.
Base URL: {{BASE_URL}}
Routes
POST /v1/images/generations
OpenAI-style image generation. Returns one image as base64 PNG.
curl -s {{BASE_URL}}/v1/images/generations {{AUTH}} \
-H "Content-Type: application/json" \
-d '{"prompt": "a tabby cat holding a sign that says MOSS", "seed": 42}' \
| python3 -c 'import sys,json,base64; d=json.load(sys.stdin); open("out.png","wb").write(base64.b64decode(d["data"][0]["b64_json"])); print(d["seed"], d["params_used"])'
Request fields:
prompt(string, required): what to draw. English works best.seed(integer >= 0, optional): the random seed. The same prompt and seed give the same image, byte for byte. Leave it out for a random one; the response's top-levelseedfield says which seed was used, so you can reproduce a result you liked.
Also accepted, for stock OpenAI clients: model (any string), n (must be 1),
response_format (must be "b64_json"), size (must be "1024x1024"), user.
Any other field, or a different value for a fixed one (for example
"num_inference_steps": 50 or "negative_prompt": "..."), is rejected with 400.
Response:
{"created": 1761530000, "model": "kromoss-dmd2", "seed": 42, "elapsed_seconds": 19.0,
"data": [{"b64_json": "<PNG, base64>"}],
"params_used": {"size": "1024x1024", "num_inference_steps": 8, "guidance_scale": 1.0,
"shift": 5.0, "lora": "8-steps-dmd2-lora"}}
The OpenAI Python SDK works too:
from openai import OpenAI
client = OpenAI(base_url="{{BASE_URL}}/v1", api_key="unused")
r = client.images.generate(model="kromoss-dmd2", prompt="a lighthouse at sunset",
response_format="b64_json", extra_body={"seed": 7})
GET /v1/config
The fixed settings and the request fields this service accepts.
curl -s {{BASE_URL}}/v1/config {{AUTH}}
GET /status
Where the service is in its start-up: phase is one of building, downloading,
starting, loading, warming, ready, error, with eta_seconds and, while starting, a wait_hint.
curl -s {{BASE_URL}}/status {{AUTH}}
Timings and concurrency
- First start: builds a container image and downloads about 30 GB of weights. After that, loading and a warm-up take a few minutes (budgeted at 420 s).
- Each image takes about 19 s on a DGX Spark (GB10).
- Requests run one at a time; extra requests wait in line. Send them one by one, or a few in parallel at most, and allow at least 120 s per request in your client timeout.
Errors
503withRetry-Afterwhile the service is still starting: wait that many seconds and retry.GET /statusshows progress.400: a field was rejected; the body'serrorsays which and why.422: a request field has the wrong type (for examplepromptmissing or empty,seednegative).5xxotherwise: retry once, then checkGET /status.
capsule.toml
# kromoss-image: an image model inside a capsule.## What this example adds to model-for-opencode:# - the image.generate kind: m64's front lets POST /v1/images/generations# through, and only `prompt` and `seed` in its body;# - [params]: the settings the server bakes in, published so callers (and the# site) can see them; a caller may send the same value, never another;# - [routes] extra: one more path, /v1/config, that the front lets through;# - a Dockerfile on NVIDIA's PyTorch image, with every pip package pinned.schema = "m64/capsule@1"description = "An image model in a capsule: Kromoss with its 8-step DMD2 LoRA on diffusers, text to 1024x1024 PNG in about 19 s on a GB10. Shows the image.generate kind, [[artifacts]] for 40 GB of weights, [params] and an extra route."kinds = ["image.generate"]authors = ["motif42"]license = "mit"[hardware]# Built and measured on an NVIDIA GB10 (DGX Spark class, ARM). The base image is# also built for x86_64; on a big x86 card set platform and gpu to match it.platform = "linux-aarch64"gpu = "NVIDIA GB10"gpu_count = 1# ~10 GB transformer + ~17 GB Qwen3.5-9B text encoder (bf16) + fp32 VAE and LoRA.# On unified memory (GB10) m64 adds ram_gb to this; here it is all in vram_gb.vram_gb = 40driver = ">=580"disk_gb = 40[runtime]sandbox = "container"# No image: m64 builds the Dockerfile (nvcr.io/nvidia/pytorch pinned by digest).gpu = true# PyTorch's data loaders and CUDA IPC use shared memory: give it more than the 1 GB default.shm_gb = 8internal_port = 9219health = "/health"# Reading ~40 GB of weights and moving them to the GPU takes minutes.expected_load_seconds = 420model_name = "kromoss-dmd2"user = "65534:65534"# The weights: one Hugging Face repo at a fixed commit, only the files the# diffusers pipeline needs (the repo also has ComfyUI repackings and card images).# m64 downloads them on the first serve (or `m64 install`), verifies them, and# mounts them read-only at /weights/kromoss. With models_dir set# (`m64 config set models_dir /mnt/models`), they live there, shared by capsules.[[artifacts]]type = "hf"repo = "lodestones/Kromoss"revision = "7f9bb7b06d7b6c27d294b0bab5a2b3056c379279"include = [ ".gitattributes", "LICENSE", "README.md", "model_index.json", "*.py", "8-steps-*.safetensors", "scheduler/*", "text_encoder/*", "tokenizer/*", "transformer/*", "vae/*",]mount = "/weights/kromoss"# Baked into kromoss_server.py; returned as `params_used` with every image.[params]size = "1024x1024"num_inference_steps = 8guidance_scale = 1.0shift = 5.0lora = "8-steps-dmd2-lora"# /v1/config describes the fixed settings and the accepted fields. The front# lets it through; every other path that isn't a kind route is a 404.[routes]extra = ["/v1/config"]