Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9e49c17
chore: open 0.12.0-alpha.1 on develop
dkackman Oct 10, 2026
0627882
docs(plan): workflow speed plan
dkackman Oct 10, 2026
912c9d3
fix(catalog): drop stale step counts, dead turbo caches and a no-op r…
dkackman Oct 10, 2026
8c64ef3
docs(catalog): image-to-video no longer points at ref2va caches that …
dkackman Oct 10, 2026
e6f60a7
perf(restore_faces): build the facexlib helper once, not per frame
dkackman Oct 10, 2026
2a4d3cc
perf(media): decode picture frames with libav AUTO threading
dkackman Oct 10, 2026
b4f122b
perf(cache): LoRA scale/alpha and scheduler shift re-apply on a warm …
dkackman Oct 10, 2026
5d7ba47
fix(cache): removing an alpha or shift reloads; a borrower misses on …
dkackman Oct 10, 2026
db3ad69
perf(worker): keep warm pipelines a switched workflow loads anyway
dkackman Oct 10, 2026
06d8d06
fix(cache): a pipeline_reference runs at the referenced step's own sc…
dkackman Oct 10, 2026
2a94297
docs(release): note the workflow speed changes
dkackman Oct 10, 2026
1f700b1
fix(cache): pipeline_keys hashes the realized steps a run keys on
dkackman Oct 10, 2026
802d26c
Merge feat/workflow-speed: warm-pipeline reuse, decode threading, cat…
dkackman Oct 10, 2026
860671a
fix(cache): a warm pipeline's LoRA scale is set per layer, not throug…
dkackman Oct 10, 2026
7e5b5d5
perf(catalog): FLUX streams its transformer from pinned host memory, …
dkackman Oct 10, 2026
a578f7f
test: bring the speed tests back under the patch-target ratchet
dkackman Oct 11, 2026
e351795
docs(release): draft the 0.11.1 notes
dkackman Oct 11, 2026
a9b23b4
test(e2e): the fixture server keeps its own settings root
dkackman Oct 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion docs/ACCELERATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,18 @@ Select the attention implementation diffusers uses for the duration of each pipe

Common values: `"flash"`, `"flash_hub"`, `"sage"`, `"sage_hub"`, `"native"`, `"flex"`. The full set is diffusers' `AttentionBackendName` enum - availability depends on what's installed (`flash-attn`, `sageattention`, etc.) and the platform. `_hub`-suffixed backends are fetched from the Hugging Face Hub kernel registry on first use, which needs the `kernels` package installed (`pip install kernels`) - it is not a dw dependency, and no bundled workflow sets a backend, so each runs on a plain install. diffusers also refuses a Hub kernel whose publisher it does not list as trusted - `sage_hub` among them, since SageAttention 2 comes from `SageAttention/sage-attention` - until `DIFFUSERS_TRUST_REMOTE_KERNELS=true`, which allows remote code from any such publisher. On an RTX 3090 `sage_hub` measured about 30% faster per MiniMax-H3 step (13.8 s against 19.2 s at 960x544x124, 54.7 s against 81.2 s at 345 frames); the local `sage` backend needs `sageattention>=2.1.1`, which PyPI does not carry.

The MiniMax-H3 baselines expose this as a variable: `attention_backend` on
[video-with-audio.json](../workflows/templates/minimax/video-with-audio.json),
[video-with-audio-768p.json](../workflows/templates/minimax/video-with-audio-768p.json),
[image-to-video.json](../workflows/templates/minimax/image-to-video.json) and
[reference-to-video.json](../workflows/templates/minimax/reference-to-video.json), pinned
on the transformer and `null` by default. Pass `attention_backend=sage_hub` to a box with
the `kernels` package and `DIFFUSERS_TRUST_REMOTE_KERNELS=true` in its `.env`; on MPS
leave it null. The other H3 templates take the same key under `components.transformer`
(`transformer_ref` for a ref2va workflow). LTX-2.5 cannot use it: its transformer
passes an attention mask, and sage attention refuses one ("`attn_mask` is not supported
for sage attention", measured 2026-10-10), so the LTX templates expose no backend.

A component can also pin its backend persistently instead, via `set_attention_backend`:

```json
Expand Down Expand Up @@ -232,7 +244,11 @@ Three older per-component knobs, set in the pipeline `configuration` beside

## Memory Offloading

`offload` (`"model"` or `"sequential"`) and `group_offload` trade speed for VRAM by streaming weights between system memory and the accelerator instead of keeping everything resident. `"model"` moves whole submodules and costs the least speed; `"sequential"` moves individual layers and is the slowest but uses the least memory; block/leaf-level `group_offload` sits between the two and is what a modular pipeline's self-loaded components use, since they aren't reachable in time for `offload`. Full configuration syntax is in [WORKFLOW_GUIDE.md](WORKFLOW_GUIDE.md#memory-offloading). Omit both for the fastest run, when VRAM allows it.
`offload` (`"model"` or `"sequential"`) and `group_offload` trade speed for VRAM by streaming weights between system memory and the accelerator instead of keeping everything resident. `"model"` moves whole submodules and costs the least speed; `"sequential"` moves individual layers and is the slowest but uses the least memory; block/leaf-level `group_offload` sits between the two and is what a modular pipeline's self-loaded components use, since they aren't reachable in time for `offload`. Full configuration syntax is in [WORKFLOW_GUIDE.md](WORKFLOW_GUIDE.md#memory-offloading). Omit both for the fastest run, when VRAM allows it. Measured on an RTX 3090 (RECIPES_24GB.md): sequential
offload costs about 6x per step on FLUX dev and 3.4x on Z-Image against model offload;
a block-level, streamed `group_offload` of the transformer from pinned host memory
matches the resident per-step cost while holding one block on the card, at ~22 GB of
pinned host RAM per FLUX pipeline - the placement the unquantized FLUX templates use.

`"residency": "on_demand"` on a component is the cheap case of the same trade: the model rests in system memory and is moved to the device whole around each of its own calls. That is a bad deal for anything called once per step, and a good one for a VAE called twice a run - it frees the VAE's VRAM for the denoise loop at the cost of two transfers, where group offloading the same VAE would restream it once per decode tile. See [On-demand components](WORKFLOW_GUIDE.md#on-demand-components).

Expand Down
4 changes: 4 additions & 0 deletions docs/LORAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ LoRA (Low-Rank Adaptation) models apply lightweight style or subject modificatio

Any other property (e.g. `revision`) is forwarded as-is to the underlying `load_lora_weights()` call.

`scale` and `alpha` are set in place on the loaded adapter, so a server run that changes
only them reuses the warm pipeline instead of reloading it; changing `model_name` or
`weight_name` reloads.

## Multiple LoRAs

Stack multiple LoRAs. They are blended via weighted adapter composition:
Expand Down
90 changes: 73 additions & 17 deletions docs/RECIPES_24GB.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,79 @@ The general recipe, in order of impact:

## Flux dev (12B)

The bf16 transformer is ~24GB - it does not fit alongside the T5 encoder, and it does
not fit alongside the VAE either: a resident bf16 transformer runs the denoise and then
fails at decode. `offload: "model"` is what makes bf16 work on 24GB, and on an RTX 3090
it is also the fastest configuration measured (1024x1024, 28 steps, pipeline loaded):

| Approach | Config | Measured on RTX 3090 |
| -------- | ------ | -------------------- |
| **bf16 + model offload** | [flux-dev.json](../workflows/models/flux-dev.json) | 55s per image (72s cold) |
| **bf16 + model offload + compile** | [flux-dev-compile.json](../workflows/models/flux-dev-compile.json) - `compile: {repeated_blocks: true}` on the transformer | 52s per image (64s cold) |
| **int8 TorchAO** | transformer `quant_type: "torchao.quantization.Int8WeightOnlyConfig"` + `compile`, with or without `cache: first_block` | over 60s per *denoising step* - do not use on Ampere |
| **float8 TorchAO** (RTX 40-series+) | `Float8DynamicActivationFloat8WeightConfig` + `compile` | needs compute capability 8.9+ (Ada); unmeasured |
| **GGUF Q8** | [flux-gguf.json](../workflows/models/flux-gguf.json) - `from_single_file` Q8_0 transformer + `offload: "model"` | unmeasured; smallest VRAM, best quality retention |

Measured 2026-09-07. A number in this table came from a run on the named card; a row
without one is a configuration that loads, not a speed claim.

**Examples:** [flux-dev-compile.json](../workflows/models/flux-dev-compile.json), [flux-gguf.json](../workflows/models/flux-gguf.json), [step-caching.json](../workflows/templates/step-caching.json)
The bf16 transformer is ~22 GB, so on 24 GB it cannot sit beside its own activations
with any margin, and it cannot sit beside the T5 encoder or the VAE at all. Three
placements, measured on an RTX 3090 (2026-10-10, cold load included):

| Placement | 1024x1024, 25 steps, 1 image, dev + LoRA | 768x768, 25 steps, 4 images, Krea | 1232x1632, 50 steps, Fill |
| --------- | ----------------------------------------- | --------------------------------- | ------------------------- |
| `offload: "sequential"` | 273 s (10.8 s/step) | not measured - same per-step cost | not measured |
| `offload: "model"` | 61 s (1.7 s/step) at **23.3 GB reserved, 491 MB free** | **OOM** at 23.45 GiB | not attempted |
| **transformer `group_offload`, block-level, streamed, pinned host copies** | 78 s (1.5 s/step), 17.7 GB peak reserved, 6 GB free | 131 s (3.2 s/step) | 152 s denoise (3.0 s/step), 1 s decode |

Model offload is the fastest placement that loads, and the one #580 (9c708a12) retired:
at 1024x1024 it runs with a few hundred MB to spare and fails on anything larger -
a batch, a second encoder resident for prompt weighting, a cache's state. Sequential
offload is safe everywhere and 6x slower per step. The streamed group offload is both:
the transformer's blocks are copied in one at a time from pinned host memory under a
CUDA stream, so each step costs the same as the resident model's, and the card holds
one block plus the activations. It is what every unquantized FLUX template now uses:

```json
"configuration": {
"component_type": "FluxPipeline",
"components": {
"transformer": {
"group_offload": {
"offload_type": "block_level",
"num_blocks_per_group": 1,
"use_stream": true,
"record_stream": true
}
},
"text_encoder": { "device": "cuda" },
"text_encoder_2": { "device": "cuda" },
"vae": { "device": "cuda" }
}
}
```

Two things the shape depends on. The non-offloaded components are placed explicitly:
with no pipeline-level `offload`, nothing else moves them, and a T5 or VAE left on the
CPU costs a minute per prompt encode and per decode (measured: 70 s before the first
step, 185 s decoding). And the host copies are pinned - diffusers' default,
`low_cpu_mem_usage: false` - which holds ~22 GB of unswappable host memory per loaded
FLUX pipeline and takes ~35 s once per load; with `low_cpu_mem_usage: true` the copies
come from pageable memory at 8 s/step, barely ahead of sequential. A host with less
than 32 GB free should take `offload: "sequential"` instead.

Compile and caching stack on top as before: `compile: {repeated_blocks: true}` measured
52 s vs 55 s per image on the old model-offload placement; `cache: first_block` is
[step-caching.json](../workflows/templates/step-caching.json). TorchAO int8 is over 60 s
per *denoising step* on Ampere - do not use it there; float8 needs compute capability
8.9+ (Ada) and is unmeasured; GGUF Q8 ([flux-gguf.json](../workflows/models/flux-gguf.json))
loads with the least VRAM and is unmeasured for speed.

**Examples:** [lora.json](../workflows/templates/lora.json), [flux-dev-compile.json](../workflows/models/flux-dev-compile.json), [flux-gguf.json](../workflows/models/flux-gguf.json), [step-caching.json](../workflows/templates/step-caching.json)

## Z-Image Turbo (6B)

Fits a 24 GB card in bf16 with model offload, and that is the configuration to use.
Measured on an RTX 3090 (960x544, 9 steps, 4 images per prompt, cold load included,
2026-10-10):

| Offload | Load | Denoise | Decode | Total | Peak VRAM reserved |
| ------- | ---- | ------- | ------ | ----- | ------------------ |
| `sequential` | 18 s | 84 s | 2 s | 107 s | 1.1 GB |
| **`model`** | 4 s | 25 s | 9 s | 41 s | 13.0 GB |
| none (resident) | 11 s | 17 s | 2 s | 33 s | 23.7 GB, 95 MB free at decode |

Sequential offload streams the transformer's layers every step and costs 3.4x on the
denoise for a model that does not need the room. Resident is faster still but decodes
four images with nothing to spare, so [z-image.json](../workflows/models/z-image.json)
and the Z-Image portrait steps of the MiniMax templates use `"offload": "model"`. The
decode's 9 s under model offload is the VAE swapping in; `vae.enable_slicing` would
trade it for per-image decodes.

## Qwen-Image (20B)

Expand Down
41 changes: 41 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,47 @@ notes from commits at tag time (see below). This section is a scratch pad
for items a branch's author wants the next release note to name; clear it
when a release ships.

### 0.11.1

<!-- Drafted from v0.11.0..develop. Paste into the GitHub release body once the tag has published: gh release edit v0.11.1 --notes-file ... -->

A speed release. The unquantized FLUX templates run about 3.5x faster on
a 24 GB card, Z-Image Turbo about 2.6x, a LoRA scale or scheduler shift
change no longer reloads the model, and switching workflows keeps the
models the next one shares. Every number below was measured on an RTX 3090;
the placement tables are in docs/RECIPES_24GB.md.

**Workflow speed**

- The 14 unquantized FLUX templates and `models/flux-krea` stream the
transformer block by block from pinned host memory (`group_offload`)
instead of `offload: "sequential"`: a 25-step 1024x1024 image takes 78 s
instead of 273 s (1.5 s/step against 10.8), with 6 GB of VRAM to spare -
enough for the 4-image batches `offload: "model"` runs out of memory on.
It holds ~22 GB of pinned host RAM per loaded FLUX pipeline.
- Z-Image Turbo (`models/z-image` and the MiniMax portrait steps) takes
`offload: "model"`: four 960x544 images in 41 s instead of 107 s.
- The four MiniMax-H3 family baselines expose `attention_backend` (null by
default; `sage_hub` measured about 30% faster per step on a 3090; it needs
the `kernels` package and `DIFFUSERS_TRUST_REMOTE_KERNELS=true`, see
docs/ACCELERATION.md). LTX-2.5 cannot use sage attention (its attention
mask is refused).
- A LoRA `scale`/`alpha` change or a scheduler `shift` change re-applies on
the warm pipeline instead of reloading it. Removing an alpha or shift still
reloads, and a `pipeline_reference` runs at the referenced step's own values.
- Switching workflows in the worker keeps the warm pipelines the next
workflow loads anyway, instead of releasing everything first.
- `restore_faces` builds its face helper once per run instead of once per
frame, and picture frames decode with libav threading, so video inputs
process faster.
- Catalog defaults corrected: compose-workflows video steps run 5 steps
(was 20) and the prompt-weighting schnell workflow runs 4 steps at guidance
0 (was 20).
- The six 9-step minimax ref2va templates drop a dead `first_block` cache,
and minimax/shots-batch drops its `release_pipeline`: a no-op inside the
run, but it unloaded H3 at job end, so H3 now stays resident after a
shots-batch job, which is the win.

### 0.11.0

<!-- Drafted from v0.10.0..develop. Paste into the GitHub release body once the tag has published: gh release edit v0.11.0 --notes-file ... -->
Expand Down
4 changes: 3 additions & 1 deletion docs/SERVER.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,9 @@ Among the free cards a job fits, dispatch prefers the one it has an affinity
for: a rerun goes to the card that ran the original, and any job to the card
whose worker last ran the same workflow (the same file, or an inline
definition's `id`) - that worker's pipelines and step cache are warm, and a
worker frees both when the workflow changes. A busy preferred card is not
worker's step cache and any model the next workflow does not load are freed
when the workflow changes, while a model both workflows load (same weights
identity) stays warm. `clear_memory` still empties the card by hand. A busy preferred card is not
waited for. A validation's cache probe and its `plan.estimate` ask about the
card the job would be dispatched to now, and the estimate names it in
`priced_for`. Memory is per card: `GET /api/memory` and `POST
Expand Down
2 changes: 1 addition & 1 deletion docs/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2632,7 +2632,7 @@ Restore and enhance faces in images using spandrel-compatible face restoration m
| `eye_dist_threshold` | No | Skip faces with eye distance below this (default: 5) |
| `upsample_img` | No | Pre-upscaled background image (e.g., from a prior upscale step) |

Models are loaded via spandrel, so any `.pth`/`.safetensors` face restoration weights work. CodeFormer requires `pip install spandrel-extra-arches` (non-commercial license).
Models are loaded via spandrel, so any `.pth`/`.safetensors` face restoration weights work. CodeFormer requires `pip install spandrel-extra-arches` (non-commercial license). The detector and parser are loaded once per settings/device and reused across frames; `release_models` drops them with the restorer.

**Example:** [restore-faces.json](../workflows/templates/restore-faces.json) — Generate a portrait, then restore faces with GFPGAN v1.4.

Expand Down
7 changes: 7 additions & 0 deletions docs/WORKER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ The worker cleans up automatically between runs (garbage collection + GPU
cache clearing). If memory grows unexpectedly, `GET /api/memory` reports the
current reading and `POST /api/memory/clear` resets it.

Switching to a different workflow releases only the models the next workflow
does not load: the worker prepares the incoming definition, and a pipeline
both workflows load (same weights identity) stays warm. The step cache and
the task model cache are still emptied, and if the incoming definition cannot
be prepared everything is released. `clear_memory` (`POST /api/memory/clear`)
remains the way to empty the card by hand.

## Troubleshooting

**Worker crashes**: `JobManager` detects it and starts a fresh worker on the
Expand Down
13 changes: 12 additions & 1 deletion docs/WORKFLOW_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@ inverted latents:
```

`reference_name` must name a step earlier in the same workflow that has a `pipeline`.
Two steps whose pipelines differ only in a LoRA's `scale` or `alpha` or a scheduler's
`shift` share one warm model, and a reference always runs at the referenced step's own
values; `release_pipeline` on either step frees the shared model, as it does for two
identical steps.
See [workflows/templates/community-pipeline.json](../workflows/templates/community-pipeline.json) for a full example.

### Task Steps
Expand Down Expand Up @@ -1173,7 +1177,7 @@ Control how models use memory:
```

- `"model"` — Moves entire models between CPU and GPU. Good balance of speed and memory.
- `"sequential"` — Moves individual layers. Slowest but uses least GPU memory. On MPS it is downgraded to `"model"` with a warning: with unified memory there is no separate pool to keep small, so the per-layer copies cost speed and save nothing.
- `"sequential"` — Moves individual layers. Slowest but uses least GPU memory (measured at 6x the per-step cost of model offload on FLUX dev and 3.4x on Z-Image; a streamed block-level `group_offload` of the transformer is the usual better answer - see [RECIPES_24GB.md](RECIPES_24GB.md#flux-dev-12b)). On MPS it is downgraded to `"model"` with a warning: with unified memory there is no separate pool to keep small, so the per-layer copies cost speed and save nothing.
`exclude_from_cpu_offload` names components the sweep should leave alone.
- Omit for no offloading (fastest, requires enough VRAM).

Expand Down Expand Up @@ -1325,6 +1329,13 @@ The step-level `release_pipeline` flag unloads the step's pipeline after its res
saved. A later `pipeline_reference` to a released step is an error, and the process-wide
step cache will not retain it.

What keeps a pipeline warm: the server's worker keeps a pipeline loaded across runs for as
long as what it *loads* is unchanged. A LoRA's `scale` and `alpha` and a scheduler's (or
`audio_scheduler`'s) `shift` are applied in place on the warm pipeline, so iterating on
them costs no reload; removing (nulling) an alpha or a shift reloads, so the checkpoint
default comes back. Changing a LoRA's `model_name` or `weight_name`, the quantization,
the placement or the scheduler type reloads it.

#### Releasing task models mid-workflow

Task models - the checkpoints behind `text_generation`, `segment`, `depth_estimator` and
Expand Down
Loading
Loading