Skip to content

feat(working-memory): reserve Z-Image denoise working memory from measured lines - #9919

Open
Pfannkuchensack wants to merge 70 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/working-memory-denoise
Open

Pfannkuchensack wants to merge 70 commits into
invoke-ai:mainfrom
Pfannkuchensack:feat/working-memory-denoise

Conversation

@Pfannkuchensack

@Pfannkuchensack Pfannkuchensack commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Stacked on #9910 (and through it #9894, #9897, #9898, #9899). Until those merge, the diff also shows their commits. This PR's own changes are f11f6029f7 and f0349da68d.

Summary

The Z-Image denoise reserved a hand-set 0.25 MiB per attended token plus a fixed 1 GiB on every platform and dtype. Fresh-process measurements on an RTX 4090 and an RX 9060 XT show that this is twice what bf16 needs at 1024², and less than fp32 needs at large sizes. The denoise now runs through the working-memory estimator framework, and its per-token lines are fitted from those measurements.

Z-Image denoise reservation, 512-token caption 1024² bf16 2048² bf16 2048² fp32
Before (every platform) 2176 MiB 5248 MiB 5248 MiB
RTX 4090, native allocator 1172 4297 7406
RTX 4090, cudaMallocAsync 841 3058 5506
RX 9060 XT, expandable segments (Windows) 993 2954 5256
RX 9060 XT, native (Windows) 1400 4538 8021
Unmeasured: XPU, MPS, CPU, Linux ROCm, other allocators at least the previous values

Estimator

  • denoise.z_image prices a fitted line per dtype over the attended tokens.
  • Regional bias, the SDPA score matrix, ControlNet hints (n + 4), LoRA patches, the dequantization copy and a new fp8 cast transient are analytic terms. The fp8 term is the largest layer's widened weights; FP8 Storage hooks and kept fp8 checkpoint weights are both covered.
  • The node passes model_on_device(working_memory=…), so calibration runs now record denoise windows.
  • User calibration from the Model Manager stays VAE-only.

Framework

  • Fitted lines are declared by each EstimatorSpec (fit.Line), so they can run over tokens as well as output pixels.
  • Points with an analytic term never join a fit.
  • unmeasured_floor keeps every unmeasured platform at or above the previous values.
  • Entries can ship as legacy, i.e. hand-set; only Linux ROCm native does.
  • scripts/working_memory/build_shipped.py takes fresh-process point files. It replaces a legacy entry only when capping points measure every line.

Measurement

  • scripts/calibrate_z_image_working_memory.py measures one forward per fresh process, with demand = max(driver, reserved, allocated) growth. The cold and warm runs both count.
  • It uses the server's runtime: run_app.apply_server_runtime shares the steps with run_app(), and a test pins that both apply the same sequence.
  • It uses the probe's window definition, now shared in invokeai/backend/working_memory/measurement.py.

Related Issues / Discussions

QA Instructions

Measurements

  • RTX 4090 (torch 2.13+cu130, native and cudaMallocAsync) and RX 9060 XT (torch 2.13+rocm10.0, Windows, expandable and native, AOTriton fused).
  • bf16, fp16 and fp32 at 512–2048 px, with a 512-token caption plus 77-token captions at two sizes.
  • Random-init model at the real geometry with 2 blocks; 4 blocks measured the same.
  • 84 points ship in working_memory/data/denoise.json. Each sits at 1.05–1.22× its estimate.

Checks of the analytic terms

  • ControlNet with 15 blocks: covered at 1.05–1.08×.
  • Forced math kernel: the 14.2 bytes per score element measured is below the 17 charged.

In-server check with the real Z-Image Turbo, through the calibrate route: every window is below its new estimate.

  • RTX 4090, native allocator, bf16: ratio 1.06–1.07 at 1024², 1536² and 2048².
  • RTX 4090, cudaMallocAsync, bf16.
  • RX 9060 XT, expandable segments, int8 build.

A/B, Z-Image Turbo bf16, 8 steps, family E2E tool, interleaved runs before → after → before, 1 warm-up + 3 measured per size:

  • Times are within the spread of the two "before" runs on both cards: RTX 4090 at 1024²/1536²/2048², RX 9060 XT at 1024²/1536².
  • No paging is caused by this change.
  • On these roots device_working_mem_gb: 3 decides at ≤ 1536², so the gain shows above that, or with a lower floor.

Tests

  • uv run --no-sync pytest -n 4: working memory, calibration services, router and runner, tests/app/invocations, tests/backend/model_manager/load, fp8, builder, calibration script and run_app environment. 4848 passed.
  • Ruff is clean. The VAE data, fits and reservations are byte-identical to the base.
  • Mutation checks, each caught:
    • the legacy pinning;
    • the term-derived line eligibility;
    • the floors;
    • legacy replacement by probe windows;
    • the builder's larger-reading rule;
    • the server/script runtime steps;
    • demand without the driver figure.

Review

Resolved findings

  • Unmeasured platforms (XPU, MPS, CUDA with expandable segments, ROCm hipMallocAsync) had dropped to a measured card's line. They are floored at the previous values now.
  • The measurement script had copied the server startup and the probe's window. Both are shared now.
  • Line eligibility is derived from the terms instead of hand lists.
  • The builder reports a lower remeasurement and never replaces legacy values from probe windows alone.
  • The calibration summary's excluded_windows description is updated, and the VAE plan's capacity input ignores denoise windows.

Remaining limits

  • Linux ROCm stays on the previous values: unmeasured, with fp32 below what Windows measured. A Z-Image fp32 transformer (24 GB) does not occur on ROCm cards that lack bf16.
  • Only the transformer forward is measured. Sage attention and partial-load weight streaming are not measured.

Compatibility / Rollout

  • No persisted state. The API changes only in a field description; openapi.json and schema.ts are regenerated.
  • EstimatorSpec.line is now a required argument; the five VAE specs pass convolution_line.

Checklist

  • The PR has a short but descriptive title, suitable for a changelog
  • Meaningful regression coverage added / updated where needed; obsolete tests/code removed
  • Persisted-state and API changes include required migrations / compatibility validation
  • Relevant performance/efficiency opportunities considered; material claims have evidence
  • Material review findings resolved and relevant checks rerun
  • Documentation added / updated (if applicable)
  • Updated What's New copy (if doing a release after this PR)

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

Runs each installed model family through a launched scratch server and compares PSNR, time and driver-level VRAM against accepted references.
Graphs come from the frontend builders via an offline graph export; the generate submission plan is shared with the workbench.
VAE estimators return a WorkingMemoryEstimate built from fitted and analytic terms; model_on_device accepts it as working_memory=.
The AutoencoderKL coefficients and their measured points move to backend/working_memory/data, checked by a data test; reservations are unchanged.
A probe records each estimated model_on_device window's device-level demand inside a calibration scope; results go to a per-device-class store under databases/working_memory.
Admin endpoints start, inspect, reset and export runs; the reserved queue origin is refused for ordinary enqueues.
VAE estimators price a measured slope and base per conv backend, allocator and dtype instead of scaling one bf16 constant by element size; CogView4 gets its own lines.
SD/SDXL nodes and their estimate share one run-dtype rule; on ROCm Windows an SDXL fp32 1024² decode reserves 6.2 instead of 14.6 GB and the UNet stays resident.
A lower second reading at a measured size no longer tilts a fitted line, and points that include cast weight growth no longer fit one; two shipped lines move by under 1 %.
…n fact

Environment ids now include windows, linux or other, so calibrations from different operating systems are kept apart; the OS build string moves to soft fact os_build.
… runs

start --calibrate submits each case through the calibration endpoint and saves the store export per server lifetime; scripts/working_memory/build_shipped.py turns exports into probe points and refits.
Probe points bound estimates from below only, and the builder refuses data that the shared bound rule rejects.
# Conflicts:
#	docs/src/content/docs/development/Architecture/model-manager.mdx
…anager

Adds a working-memory section to the model detail: plan, calibrate, stop and reset per GPU, with the measured, built-in and calibrated estimate per size.
User fits from finished calibration attempts price VAE decodes within their measured range (+10 % + 256 MiB on the fitted part) and never below built-in values above it.
New plan and summary routes; summary reports lines shared with other models and returns 404 for unknown models.
…calibration

Judges allocator fragmentation per calibration attempt with one rule shared with the shipped-data builder, and moves other models off the GPU before a measured calibration lock.
User fits use the shipped 5 % margin; the plan sizes cases from the capacity the cache will grant, including memory the driver keeps after earlier runs.
The table marks uncounted sizes with their reason and explains each value column.
# Conflicts:
#	invokeai/backend/util/vae_working_memory.py
#	tests/app/invocations/test_flux2_working_memory.py
# Conflicts:
#	invokeai/backend/util/vae_working_memory.py
Main added useNsfwChecker and useWatermark to the generation project settings; exported calibration and E2E graphs run without both, as the defaults do.
…ser-calibration

# Conflicts:
#	invokeai/frontend/webv2/performance/browser-baseline.json
#	invokeai/frontend/webv2/scripts/mock-backend-fixtures.test.mjs
#	invokeai/frontend/webv2/scripts/mock-backend.mjs
…e baseline

Raises otherRawBytes to the measured 215114 B on every route; the 3791 B growth are the Model Manager working-memory strings.
…f the model page

The section starts collapsed and shows the GPU with the calibration state, or the run progress, in its header; the plan loads on first open.
The mock prices sizes above the measured range with the larger of the user line and built-in, as the server does.
Main added useNsfwChecker and useWatermark to the generation project settings; exported calibration and E2E graphs run without both, as the defaults do.
…sured lines

Prices the Z-Image denoise through an EstimatorSpec whose per-token lines are fitted from fresh-process measurements on CUDA and ROCm, with an fp8 cast term and the legacy constants as the floor where nothing was measured.
Fitted lines can now run over tokens; the builder takes measured point files, and calibration scripts share the server runtime and the probe's measurement window.
@lstein lstein added the 7.0.0 label Oct 19, 2026
@lstein lstein moved this to 7.0 Theme: Modular Design in Invoke - Community Roadmap Oct 19, 2026
…stworthy

The session processor starts after every other service; windows of failed or canceled nodes, failed counter resets and locks racing a window are stored invalid.
Keyed requests are identified by model, graph and batch data, are created once per run and admit all of their items or none; the reserved-origin 400 is declared.
…ser-calibration

# Conflicts:
#	docs/src/content/docs/development/Architecture/model-manager.mdx
#	invokeai/app/services/working_memory_calibration/working_memory_calibration_default.py
#	invokeai/backend/model_manager/load/load_base.py
#	invokeai/frontend/api/openapi.json
#	tests/app/services/session_processor/conftest.py
#	tests/app/services/working_memory_calibration/test_working_memory_calibration_service.py
…-memory-denoise

# Conflicts:
#	docs/src/content/docs/development/Architecture/model-manager.mdx
#	invokeai/app/invocations/z_image/z_image_denoise.py
#	invokeai/backend/working_memory/probe.py
@Pfannkuchensack
Pfannkuchensack marked this pull request as ready for review October 21, 2026 00:45
…ration writes behind resets

Stop joins the workers within a shared 30 s deadline and refuses a restart while one still runs; the cache counts locks apart from its records.
A model reset raises a per-model epoch the runner reads at node start, keyed runs leave a tombstone on reset, retries keep their lineage before admission, and a recorded receipt replays without the model.
…ser-calibration

# Conflicts:
#	docs/src/content/docs/development/Architecture/model-manager.mdx
#	invokeai/app/services/working_memory_calibration/working_memory_calibration_default.py
#	invokeai/frontend/api/openapi.json
#	tests/app/services/working_memory_calibration/test_working_memory_calibration_service.py
…ration offload

The calibration offload still called the cache's own helper that main replaced with devices.device_has_dedicated_vram.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

7.0.0 api backend PRs that change backend files docs PRs that change docs frontend PRs that change frontend files frontend-deps PRs that change frontend dependencies invocations PRs that change invocations python PRs that change python files python-deps PRs that change python dependencies python-tests PRs that change python tests Root services PRs that change app services

Projects

Status: 7.0 Theme: Modular Design

Development

Successfully merging this pull request may close these issues.

3 participants