This note documents the image-pool runtime API for inspecting models, loading and unloading models, running image generation/edit requests, and starting LoRA training jobs.
The goal is similar to the llm-pool runtime admin API: routine model management
should not require editing local.json and restarting the service.
It is intentionally a v1 design:
- live runtime control only
- no automatic writes back to
settings.jsonorlocal.json - no arbitrary model definitions via API
- no runtime load overrides in the load request body
- no background job system for model loads
- no force unload or graceful drain state yet
Current reality note:
- this admin API is implemented and is the live control plane used by the workbench
- model definitions still come from merged
settings.json + local.json - load/unload only changes in-process runtime state
generation_parametersandedit_parametersare surfaced by public and admin model endpointsrecommended_stepsandrecommended_guidancestill exist as compatibility fields- imported LoRA records can be listed, inspected, imported, edited, and deleted
- Flux and Z-Image LoRA training endpoints exist and share one in-process training slot
- Purpose
- Core Concepts
- State Semantics
- Model Definition Fields
- Endpoints
GET /v1/modelsGET /v1/admin/modelsGET /v1/admin/gpu-memoryGET /v1/admin/lorasPOST /v1/admin/loras/inspectPOST /v1/admin/loras/importPATCH /v1/admin/loras/{slug}DELETE /v1/admin/loras/{slug}POST /v1/admin/models/{model_name}/loadPOST /v1/admin/models/{model_name}/unloadPOST /v1/images/generationsPOST /v1/images/editsGET /v1/training/flux-loraPOST /v1/training/flux-loraPOST /v1/training/flux-lora/stopGET /v1/training/z-image-loraPOST /v1/training/z-image-loraPOST /v1/training/z-image-lora/stop
- Image Request Parameters
- Parameter Schema
- Unload And In-Flight Requests
- Errors
The current service merges settings.json and local.json into one effective
config, then loads enabled models at startup.
The admin API adds a separate live control plane on top of that merged config:
- the merged config tells us which models are known to the service
- the live runtime state tells us which of those models are currently loaded
- generation and edit endpoints accept only loaded models
That distinction must stay explicit in both the API and the UI.
A configured model definition comes from the merged settings.json + local.json
payload.
This is static process input. It includes fields such as:
backendenabledtarget_inflightmodel_pathbase_model_pathtransformer_config_pathmodalitiesoutput_modalitiestasksmax_imagesmax_output_imagesvram_estimate_mibrecommended_stepsrecommended_guidancegeneration_parametersedit_parameters
This definition is not modified by the admin API in v1.
Each configured model also has live runtime state inside the process.
Current states are represented by booleans:
loadedloading
The GPU memory view maps those booleans to:
unloadedloadingloaded
There is no separate unloading state yet.
Capabilities are derived from the model definition.
Current shape:
{
"input_modalities": ["text", "image"],
"output_modalities": ["image"],
"tasks": ["image_generation", "image_to_image"],
"max_images": 1,
"max_output_images": 1
}tasks is the field the UI should use to decide whether a model can be shown
for text-to-image, image-to-image, true image edit, or a combination.
image_generation: generate from textimage_to_image: use input image(s) as generation references or img2img sourceimage_edit: instruction editing where preserving unchanged image regions is part of the model contract
- the model exists in merged config
- no runtime is currently loaded
- image requests for this model are rejected
- the model may be loaded through the admin API
- a runtime load has started but is not complete yet
- image requests for this model are rejected
- duplicate load requests currently race with the in-progress load rather than joining a background job
- a runtime exists and may serve image requests
- the model may be unloaded through the admin API
Failed load attempts store last_error on the model state. The model remains
unloaded and may be loaded again through the admin API.
Example model definition:
{
"backend": "diffusers_z_image",
"enabled": false,
"target_inflight": 1,
"model_path": "/home/gunnar/models/Z-Image-Turbo",
"modalities": ["text", "image"],
"output_modalities": ["image"],
"tasks": ["image_generation", "image_to_image"],
"max_images": 1,
"max_output_images": 1,
"vram_estimate_mib": 16000,
"recommended_steps": 9,
"recommended_guidance": 0.0,
"generation_parameters": {
"size": {
"kind": "enum",
"target": "request",
"default": "1024x1024",
"allowed_values": ["768x768", "1024x1024", "1024x768", "768x1024"]
},
"steps": {
"kind": "integer",
"target": "metadata",
"default": 9,
"minimum": 1,
"maximum": 80,
"step": 1
},
"guidance": {
"kind": "number",
"target": "metadata",
"default": 0.0,
"minimum": 0.0,
"maximum": 20.0,
"step": 0.1
},
"seed": {
"kind": "integer_or_null",
"target": "request",
"default": null,
"minimum": 0,
"step": 1
},
"lora_scale": {
"kind": "number",
"target": "metadata",
"default": 1.75,
"minimum": 0.0,
"maximum": 2.0,
"step": 0.05
}
},
"edit_parameters": {
"strength": {
"kind": "number",
"target": "metadata",
"default": 0.35,
"minimum": 0.0,
"maximum": 1.0,
"step": 0.05
}
}
}The parameter objects above are shortened for readability. In the live config,
edit_parameters usually repeats the shared generation fields and adds
edit-only fields.
Known backend ids:
stubdiffusers_flux2_kleindiffusers_firered_ggufdiffusers_qwen_image_editdiffusers_sdxldiffusers_z_image
vram_estimate_mib is a configured estimate unless the runtime has observed a
load delta for the model during this process lifetime. See
GET /v1/admin/gpu-memory.
Returns loaded models only. This is the public model list for image requests.
Response shape:
{
"object": "list",
"data": [
{
"id": "z-image-turbo",
"object": "model",
"owned_by": "image-pool",
"backend": "diffusers_z_image",
"capabilities": {
"input_modalities": ["text", "image"],
"output_modalities": ["image"],
"tasks": ["image_generation", "image_to_image"],
"max_images": 1,
"max_output_images": 1
},
"recommended_steps": 9,
"recommended_guidance": 0.0,
"generation_parameters": {
"size": {
"kind": "enum",
"target": "request",
"default": "1024x1024",
"allowed_values": ["768x768", "1024x1024", "1024x768", "768x1024"]
},
"steps": {
"kind": "integer",
"target": "metadata",
"default": 9,
"minimum": 1,
"maximum": 80,
"step": 1
},
"guidance": {
"kind": "number",
"target": "metadata",
"default": 0.0,
"minimum": 0.0,
"maximum": 20.0,
"step": 0.1
},
"lora_scale": {
"kind": "number",
"target": "metadata",
"default": 1.75,
"minimum": 0.0,
"maximum": 2.0,
"step": 0.05
}
},
"edit_parameters": {
"strength": {
"kind": "number",
"target": "metadata",
"default": 0.35,
"minimum": 0.0,
"maximum": 1.0,
"step": 0.05
}
}
}
]
}Schema objects in endpoint examples may be shortened. Clients should treat
generation_parameters and edit_parameters as dictionaries keyed by parameter
name.
Returns all known models from merged config together with their live runtime state. This endpoint is the main UI source of truth for model management.
Response shape:
{
"object": "list",
"data": [
{
"id": "z-image-turbo",
"backend": "diffusers_z_image",
"enabled": false,
"loaded": true,
"loading": false,
"loaded_at": 1782500000.0,
"last_error": null,
"scheduler": {
"target_inflight": 1,
"inflight": 0,
"queued": 0
},
"capabilities": {
"input_modalities": ["text", "image"],
"output_modalities": ["image"],
"tasks": ["image_generation", "image_to_image"],
"max_images": 1,
"max_output_images": 1
},
"model_path": "/home/gunnar/models/Z-Image-Turbo",
"base_model_path": null,
"vram_estimate_mib": 20600,
"vram_estimate_source": "observed_load_delta",
"recommended_steps": 9,
"recommended_guidance": 0.0,
"generation_parameters": {
"size": {
"kind": "enum",
"target": "request",
"default": "1024x1024",
"allowed_values": ["768x768", "1024x1024", "1024x768", "768x1024"]
},
"steps": {
"kind": "integer",
"target": "metadata",
"default": 9,
"minimum": 1,
"maximum": 80,
"step": 1
}
},
"edit_parameters": {
"strength": {
"kind": "number",
"target": "metadata",
"default": 0.35,
"minimum": 0.0,
"maximum": 1.0,
"step": 0.05
}
}
}
]
}vram_estimate_source can currently be:
observed_load_deltaconfiguredmodel_artifact_sizeunavailable
Returns GPU memory data plus a model-oriented projection for the workbench model table.
Response shape:
{
"gpus": [
{
"index": 0,
"name": "NVIDIA ...",
"memory_total_mib": 98304,
"memory_used_mib": 68200,
"memory_free_mib": 30104
}
],
"models": [
{
"name": "z-image-turbo",
"runtime_state": "loaded",
"is_loaded": true,
"configured_target_inflight": 1,
"effective_target_inflight": 1,
"vram_estimate_mib": 20600,
"vram_estimate_source": "observed_load_delta"
}
],
"error": null
}If GPU probing fails, gpus may be empty and error contains the probe error.
Returns imported LoRA records and the edit schema for imported LoRA metadata.
The edit_schema field tells the workbench which fields can be edited and which
values are valid for model-bound fields.
Response shape:
{
"object": "list",
"data": [
{
"id": "imported/c64style-z-image-turbo",
"name": "C64style Z Image Turbo",
"family": "z-image",
"source_type": "imported",
"artifact_type": "imported",
"trained_on_model_id": "z-image-turbo",
"compatible_models": ["z-image-turbo"],
"trigger_words": ["C64style pixel art"],
"default_strength": 1.75,
"path": "/home/gunnar/projects/image-pool/data/loras/imported/c64style-z-image-turbo/adapter.safetensors",
"deletable": true,
"delete_slug": "c64style-z-image-turbo",
"editable": true,
"update_slug": "c64style-z-image-turbo",
"inspection": {
"format_guess": "diffusers-transformer",
"family_guess": "z-image",
"confidence": 0.65,
"detected_modules": ["transformer"],
"key_count": 480
}
}
],
"edit_schema": {
"source_types": ["imported"],
"fields": {
"family": {
"kind": "enum",
"required": true,
"allowed_values": ["flux2-klein", "sdxl", "z-image"]
},
"compatible_models": {
"kind": "string_list",
"required": true,
"allowed_values_by_family": {
"z-image": ["z-image-base", "z-image-turbo"]
}
},
"default_strength": {
"kind": "number_or_null",
"minimum": 0.0,
"maximum": 2.0,
"step": 0.05
}
}
}
}Training-run LoRAs are listed by the workbench, not by this image-pool endpoint. This endpoint owns imported LoRAs stored under image-pool data.
Inspects a local .safetensors file before import.
Request shape:
{
"source_path": "/tmp/uploaded-lora.safetensors"
}The response includes:
format_guessfamily_guesscompatible_model_suggestionsdefault_strength_suggestion- safetensors metadata and sample tensor keys
Inspection is best-effort. A successful load/generation is still the final compatibility check.
Copies a .safetensors file into the image-pool imported LoRA library and
writes its editable metadata.
Required fields:
source_pathnamefamilycompatible_models
family and compatible_models are validated against configured LoRA-capable
models. A model must support LoRA and belong to the selected family.
Updates metadata for an imported LoRA.
Editable fields:
namefamilycompatible_modelstrained_on_model_idtrigger_wordsdefault_strengthdescriptionsource_url
default_strength may be null. family and compatible_models are validated
against the same rules as import.
Request shape:
{
"family": "z-image",
"compatible_models": ["z-image-turbo"],
"trained_on_model_id": "z-image-turbo",
"default_strength": 1.75
}The response returns the updated LoRA payload.
Deletes an imported LoRA directory from image-pool data.
Only imported LoRAs have a delete_slug.
Loads a configured model into the current process.
Request body: none.
Returns the same state object used by GET /v1/admin/models.
Notes:
- this does not change
enabledin config - this does not persist anything to
settings.jsonorlocal.json - if the model is already loaded, the current state is returned
- load failures are returned as
500 Internal Server Errorby FastAPI unless caught by a more specific handler
Unloads a configured model from the current process.
Request body: none.
Returns the same state object used by GET /v1/admin/models.
Notes:
- this does not change
enabledin config - this does not persist anything to
settings.jsonorlocal.json - if the model is already unloaded, the current unloaded state is returned
Runs text-to-image generation.
Request shape:
{
"model": "z-image-turbo",
"prompt": "A product photo of black running shoes on a concrete floor",
"n": 1,
"size": "1024x1024",
"quality": "auto",
"response_format": "b64_json",
"seed": 1234,
"allow_remote": false,
"metadata": {
"steps": 9,
"guidance": 0.0
}
}Response shape:
{
"id": "img-...",
"object": "image.generation",
"created": 1782500000,
"model": "z-image-turbo",
"data": [
{
"b64_json": "...",
"mime_type": "image/png",
"revised_prompt": null
}
],
"metrics": {
"steps": 9,
"guidance": 0.0,
"engine_queue_wait_ms": 0.2,
"engine_total_wall_ms": 12345.0,
"pool_total_wall_ms": 12345.5
}
}Runs image-to-image or image edit generation. The selected model must advertise
image_to_image or image_edit in capabilities.tasks.
Request shape:
{
"model": "sdxl-base-1.0",
"prompt": "The same dog wearing sunglasses",
"images": [
{
"name": "dog.png",
"data_url": "data:image/png;base64,..."
}
],
"n": 1,
"size": "1024x1024",
"quality": "auto",
"response_format": "b64_json",
"seed": 1234,
"allow_remote": false,
"metadata": {
"steps": 30,
"guidance": 5.0,
"sampler": "euler",
"strength": 0.35
}
}Response shape is the same as POST /v1/images/generations, with
object: "image.edit".
Returns Flux LoRA training backend availability and current run state.
Response shape:
{
"backend": {
"id": "diffusers_flux2_lora",
"label": "Flux",
"available": true,
"message": ""
},
"run": {
"status": "idle",
"run_id": "",
"pid": null,
"returncode": null,
"started_at": "",
"completed_at": "",
"output_path": "",
"log_tail": "",
"message": "",
"backend_id": "",
"progress": {
"step": 0,
"steps": 0,
"loss": null,
"learning_rate": null
}
}
}Starts a Flux LoRA training run.
Request shape:
{
"model": "flux2-klein-base-4b",
"dataset_path": "/home/gunnar/projects/llm-workbench/data/image_pool/training/datasets/example",
"output_path": "/home/gunnar/projects/llm-workbench/data/image_pool/training/flux2-klein/runs",
"trigger_word": "GFX_IMPR5N",
"steps": 3000,
"learning_rate": 0.000095,
"rank": 128,
"alpha": 64,
"batch_size": 1,
"checkpoint_interval": 500,
"resolution": [256, 512, 768, 1024, 1280, 1536],
"metadata": {}
}Returns the same shape as GET /v1/training/flux-lora.
Requests stop for the active training run. The worker finishes the current step before stopping when possible.
Returns the same shape as GET /v1/training/flux-lora.
Returns Z-Image LoRA training backend availability and current run state.
Response shape is the same as GET /v1/training/flux-lora, with
backend.id: "diffusers_z_image_lora".
Starts a Z-Image LoRA training run.
Request shape:
{
"model": "z-image-base",
"dataset_path": "/home/gunnar/projects/llm-workbench/data/image_pool/training/datasets/example",
"output_path": "/home/gunnar/projects/llm-workbench/data/image_pool/training/z-image/runs",
"trigger_word": "GFX_IMPR5N",
"steps": 500,
"learning_rate": 0.0001,
"rank": 4,
"alpha": 4,
"batch_size": 1,
"checkpoint_interval": 500,
"resolution": 1024,
"metadata": {}
}Returns the same shape as GET /v1/training/z-image-lora.
Requests stop for the active training run. The worker finishes the current step before stopping when possible.
Returns the same shape as GET /v1/training/z-image-lora.
The common request fields are currently:
model: configured model idprompt: positive promptn: output image count, currently 1 to 4 by schema and additionally limited by modelmax_output_imagessize: output size string such as1024x1024quality:auto,low,medium, orhighresponse_format: currently onlyb64_jsonseed: optional integer seedallow_remote: reserved request flagmetadata: backend-specific runtime parameters
Current metadata keys used by backends:
steps: integer, 1 to 80guidance: number, 0.0 to 20.0sampler: SDXL only; one ofeuler,euler_a, ordpmpp_2mnegative_prompt: SDXL onlystrength: image edit/img2img strength, 0.0 to 1.0lora_id: optional UI/runtime identifier for metricslora_path: path to.safetensorsLoRA weightslora_scale: LoRA adapter weight, 0.0 to 2.0
Current backend defaults:
| Backend | Steps | Guidance | Strength | Sampler | Notes |
|---|---|---|---|---|---|
diffusers_flux2_klein |
4 | 1.0 | n/a | n/a | Supports LoRA metadata |
diffusers_sdxl |
recommended_steps or 30 |
recommended_guidance or 5.0 |
0.35 | euler |
Supports negative_prompt |
diffusers_z_image |
recommended_steps or 9 |
recommended_guidance or 0.0 |
0.35 | n/a | Supports LoRA metadata |
diffusers_qwen_image_edit |
recommended_steps or 20 |
recommended_guidance or 4.0 |
n/a | n/a | Uses true_cfg_scale internally |
diffusers_firered_gguf |
40 | 4.0 | n/a | n/a | Image edit backend |
The workbench needs enough model-specific parameter metadata to build the image
generation details section for the selected model and to implement Reset defaults without hardcoding per-model rules in the UI.
Capabilities should answer what the model can do. Parameter schemas should answer which controls the UI should show for that model.
generation_parameters is used for text-to-image requests.
edit_parameters is used for image-to-image/edit requests.
edit_parameters is a full schema for edit mode. It is not a patch on top of
generation_parameters. Models usually repeat shared fields such as size,
n, steps, guidance, and seed there, then add edit-only fields such as
strength.
Each entry has a target:
request: the value is sent as a top-level image request fieldmetadata: the value is sent insidemetadata
Entries may include label. The workbench uses it as the visible control name.
Use model-native names when possible, for example SDXL uses Comfy-like labels
such as cfg, sampler_name, and denoise.
Enum entries may also include labels. The keys are wire values. The values are
option labels, for example euler -> Euler (native).
Implemented model definition shape, including an optional sampler enum:
{
"generation_parameters": {
"size": {
"kind": "enum",
"target": "request",
"default": "1024x1024",
"allowed_values": ["768x768", "1024x1024", "1024x768", "768x1024"]
},
"steps": {
"kind": "integer",
"target": "metadata",
"label": "steps",
"default": 9,
"minimum": 1,
"maximum": 80,
"step": 1
},
"guidance": {
"kind": "number",
"target": "metadata",
"label": "cfg",
"default": 0.0,
"minimum": 0.0,
"maximum": 20.0,
"step": 0.1
},
"sampler": {
"kind": "enum",
"target": "metadata",
"label": "sampler_name",
"default": "euler",
"allowed_values": ["euler", "euler_a", "dpmpp_2m"],
"labels": {
"euler": "Euler (native)",
"euler_a": "Euler a",
"dpmpp_2m": "DPM++ 2M"
}
},
"seed": {
"kind": "integer_or_null",
"target": "request",
"default": null,
"minimum": 0,
"step": 1
}
},
"edit_parameters": {
"strength": {
"kind": "number",
"target": "metadata",
"label": "denoise",
"default": 0.35,
"minimum": 0.0,
"maximum": 1.0,
"step": 0.05
}
}
}Current API behavior:
GET /v1/modelsincludes parameter schemas for loaded modelsGET /v1/admin/modelsincludes the same parameter schemas plus runtime state- the workbench renders controls from these schemas
Reset defaultsapplies the selected model's schema defaultsrecommended_stepsandrecommended_guidanceare still returned for compatibility
Current UI mapping:
integer-> stepper or numeric inputnumber-> numeric input, or slider forstrengthandlora_scaleenum-> selectstring-> text inputinteger_or_null-> numeric input with empty value as unset
Current unload behavior is immediate:
- the model executor is unregistered
- queued requests are failed with
model_not_loaded - worker tasks are cancelled
- the runtime is closed
- Torch CUDA cache is released
There is no graceful drain mode yet. If the UI needs safer unload behavior
during active generation, image-pool should grow an explicit unloading state
and reject new requests while allowing in-flight requests to finish.
Current explicit error handlers:
| Condition | HTTP status | Error type |
|---|---|---|
| Unknown model | 404 | unknown_model |
| Model not loaded | 409 | model_not_loaded |
| Unsupported backend | 400 | unsupported_backend |
| Bad request / validation-like runtime error | 400 | bad_request |
Training preflight errors are returned as HTTPException details. Common
training error ids:
training_dataset_not_readytraining_model_not_readytraining_backend_unavailabletraining_run_active