moss-image / API_SPEC.md
mando2222's picture
Docs: release update
c1b700d verified
|
Raw History Blame
7.82 kB
# MOSS-Image API spec (node4)
HTTP image-generation API for the MOSS-Image TTNN bundle. Diffusion model β€” **no tokens, no
KV cache, no streaming**; requests are image jobs.
## Base URL
- From node4 itself: `http://127.0.0.1:8000`
- From other boxes on the LAN: `http://192.168.50.97:8000` (node4), or `http://node4:8000` if
your `/etc/hosts`/DNS resolves it.
- The server binds `0.0.0.0:$PORT` (default **8000**). Open that port in any firewall between
boxes. No auth (intended for a trusted LAN); put it behind a proxy if you need auth.
> Status note: the API shape below is identical whether it is served by the device bundle
> (TTNN on the Blackhole card) or the CPU reference β€” same endpoints, same schemas. Only speed
> differs.
## Endpoints
### `GET /health`
Liveness + which model/mesh is loaded, and the capability contract.
```json
{ "status": "ok", "model": "OpenMOSS-Team/MOSS-Image-Base",
"mesh": "P150", "mesh_shape": "1x1", "vae_device": true,
"pipeline_loaded": true, "kind": "tt-dit-server",
"progress_reporting": true, "job_persistence": true,
"private_ephemeral_jobs": true, "private_result_ttl_s": 900 }
```
- `vae_device` β€” `true` when the VAE decodes on the device (pure `ttnn`, the default;
validated vs host fp32 to PCC β‰₯0.9999 at all resolutions). Force host with `MOSS_VAE_DEVICE=0`.
- `mesh_shape` β€” DiT topology (`MOSS_MESH`): `1x1` single-chip (default), `1x2`/`1x4` mesh.
- `private_ephemeral_jobs` / `private_result_ttl_s` β€” see **Private jobs** below.
### Private jobs (default for new `POST /generate`)
Send an `X-Moss-Job-Token` header on `POST /generate` (or let the server mint one and return it
as `job_token`). The job is then token-scoped: **not** listed in `GET /jobs`, **never** written
to disk (result held in memory only), and reaped after a **900 s** TTL. The same token is
required on `GET /jobs/{id}`, `GET /images/{id}.png`, and `DELETE /jobs/{id}`; a missing/wrong
token returns **404**. Limits: max 8 private jobs, 64 MiB encoded result each. Legacy disk-backed
jobs (created without a token) remain listable and survive restarts.
### `GET /v1/models`
OpenAI-style model list.
```json
{ "object": "list", "data": [
{ "id": "OpenMOSS-Team/MOSS-Image-Base", "object": "model", "owned_by": "openmoss" } ] }
```
### `POST /generate` β†’ submit an async job (recommended)
Request body (JSON):
| field | type | default | notes |
| --- | --- | --- | --- |
| `prompt` | string | β€” | **required** |
| `height` | int | 1024 | 256–2048, multiple of 16 |
| `width` | int | 1024 | 256–2048, multiple of 16 |
| `num_inference_steps` | int | 50 | 1–200; β‰₯28 for quality |
| `guidance_scale` | float | 3.0 | 0–20; CFG scale (1.0 disables CFG) |
| `negative_prompt` | string | `""` | used when `guidance_scale != 1` |
| `seed` | int\|null | null | reproducibility (CPU RNG) |
| `max_sequence_length` | int | 4096 | 1–4096; max prompt tokens |
| `shift` | float\|null | nullβ†’5.0 | 1–10; flow-match scheduler shift |
| `uncond_skip_layers` | list[int] | `[22]` | DiT layers skipped on the CFG negative branch; `[]` disables |
| `slg_layers` | list[int]\|null | null | skip-layer-guidance layers; null/`[]` disables the extra forward |
| `slg_scale` | float | 0.0 | 0–20; `prediction += slg_scale*(cond βˆ’ cond_skipped)` |
| `slg_start` | float | 0.0 | 0–1; SLG/skip window start (fraction of steps) |
| `slg_stop` | float | 1.0 | 0–1; SLG/skip window stop (fraction of steps) |
The advanced knobs mirror `reference/pipeline_moss_image.py __call__`. Call **`GET /v1/parameters`**
(alias `GET /config`) for this schema with ranges at runtime.
Response (every new job is private by default β€” keep the `job_token`):
```json
{ "job_id": "ff70ee2b28e4", "status": "queued", "poll": "/jobs/ff70ee2b28e4",
"job_token": "<43–128 url-safe chars>", "storage": "memory", "private": true, "result_ttl_s": 900 }
```
Jobs are serialized through one worker (one model / one device); submitting many just queues them.
### `GET /jobs/{job_id}` β†’ poll status
```json
{ "job_id": "ff70ee2b28e4", "status": "done",
"request": { ... }, "image_url": "/images/ff70ee2b28e4.png" }
```
`status` ∈ `queued` | `running` | `done` | `error`. On `error`, an `error` field carries the message.
### `GET /images/{job_id}.png` β†’ fetch the result
Returns `image/png` once the job is `done` (404 until then). Finished images are
saved to disk (`$MOSS_OUTPUT_DIR`, default `./moss_outputs`) and freed from memory, so
this is served from the saved file after the job completes.
### `GET /jobs` β†’ list jobs
```json
{ "jobs": [ { "job_id": "ff70ee2b28e4", "status": "done",
"file_path": "/.../moss_outputs/ff70ee2b28e4.png", "prompt": "a red fox in snow" } ] }
```
### `DELETE /jobs/{job_id}` β†’ delete a job + its saved image
Drops the job from memory and removes its PNG from disk.
```json
{ "deleted": true, "job_id": "ff70ee2b28e4", "file_removed": true }
```
### `POST /generate/sync` β†’ blocking, returns PNG directly
Same body as `/generate`; responds with `image/png`. Convenient but holds the connection for the
full generation (minutes at 1024Β²/50) β€” prefer the async flow for anything non-trivial.
### `POST /v1/images/generations` β†’ OpenAI-compatible (blocking)
Request (superset of the OpenAI schema β€” the full control set is accepted here too):
```json
{ "prompt": "a red fox in snow", "n": 1, "size": "1024x1024", "seed": 42,
"width": 1024, "height": 1024, "num_inference_steps": 50, "guidance_scale": 3.0,
"negative_prompt": "", "shift": 5.0, "slg_layers": [24], "slg_scale": 3.0 }
```
`size` is `WIDTHxHEIGHT`; explicit `width`+`height` override it. All `/generate` advanced knobs
(`max_sequence_length`, `shift`, `uncond_skip_layers`, `slg_*`) are accepted. Response (base64
PNGs, OpenAI Images shape):
```json
{ "created": 1760000000, "data": [ { "b64_json": "<base64 png>" } ] }
```
### `GET /v1/parameters` (alias `GET /config`) β†’ controllable-parameter schema
A UI reads this to render generation controls. Each entry has `name`, `type`, `default`, and
`min`/`max`/`multiple_of`/`max_length` where applicable.
```json
{ "model": "OpenMOSS-Team/MOSS-Image-Base",
"endpoints": ["/generate", "/v1/images/generations"],
"parameters": [
{ "name": "num_inference_steps", "type": "int", "default": 50, "min": 1, "max": 200 },
{ "name": "guidance_scale", "type": "float", "default": 3.0, "min": 0.0, "max": 20.0 },
{ "name": "shift", "type": "float|null", "default": 5.0, "min": 1.0, "max": 10.0 },
{ "name": "width", "type": "int", "default": 1024, "min": 256, "max": 2048, "multiple_of": 16 }
/* …height, seed, negative_prompt, max_sequence_length, uncond_skip_layers, slg_* … */
] }
```
## Example (async)
```bash
HOST=http://192.168.50.97:8000
JID=$(curl -s -X POST $HOST/generate -H 'content-type: application/json' \
-d '{"prompt":"a cozy bookshop, warm light, a cat asleep on a chair","height":1024,"width":1024,"num_inference_steps":50,"seed":7}' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["job_id"])')
until curl -s $HOST/jobs/$JID | grep -q '"status":"done"'; do sleep 5; done
curl -s $HOST/images/$JID.png -o out.png
```
## Notes for client/agent builders
- **Latency** (single p150, device backend): ~12–25 s for 256Β²/4-step warm; ~235 s for
1024Β²/50-step. Set generous client timeouts; prefer `/generate` + poll over `/generate/sync`.
- **Concurrency:** requests queue (single device). Don't fan out expecting parallelism.
- **Resolution:** `height`/`width` must be multiples of 16.
- **Determinism:** pass `seed` for reproducible images.
- **Errors:** poll `/jobs/{id}` and check `status == "error"` + the `error` string; HTTP 4xx for
bad input (e.g. malformed `size`), 404 for an unknown job or an image not yet ready.