Repository navigation
feat(models): calibrate VAE working memory per model from the Model Manager - #9910
Pfannkuchensack wants to merge 108 commits into
Conversation
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.
…runs' into feat/family-e2e-calibration
The calibration status test keeps an unverified case with Linux ROCm's fdinfo reader.
…tribute submodel windows on reset The runner no longer records completion ahead of the queue and hands the final observation the item as the queue settled it, so a cleared item never reads completed and a pruned failure keeps its error. Resets and the write fence attribute `<key>:<submodel>` windows to their model, and the NVML hardware test accepts the documented torch-only fallback.
…points span a fit, and keep unsaved stores A line is fitted only once its fresh-process points span two sizes; probe points can raise a stand-in or a fitted line but never lower it. A failed store save keeps the scratch root, and a resume saves a kept root's store before rebuilding it.
…e actually runs in The plan takes the loaders' own dtype rules for standalone, Diffusers, SDNQ and component-source VAEs, so a VAE kept in fp32 is sized as fp32. A VAE target is not calibrated through a FLUX.1 SDNQ pipeline that drops the VAE pick, and the loader's fp16-avoidance rule moves into TorchDevice.
…ser-calibration # Conflicts: # docs/src/content/docs/development/Architecture/model-manager.mdx # invokeai/app/services/working_memory_calibration/working_memory_calibration_default.py # scripts/working_memory/build_shipped.py # tests/test_build_shipped_working_memory.py
|
@joshistoast Thanks, fixed in e00e817. Your case reproduced exactly. On a 24 GiB card with the shipped cuDNN/native lines, bf16 plans
|
… measured A model whose decode force_tiled_decode tiles, or that needs a standalone VAE when none of its compatible VAEs runs on the GPU, is reported as not calibratable with the reason and the fix.
…ust NVML only in the host PID namespace Each terminal queue transition hands the runner the item it settled, and a transition whose announcement raised reads its committed status back. NVML attributes memory only when the process runs in the initial PID namespace; elsewhere the counters fall back to torch-only.
| // The plan measures the GPU's free memory on the server, so it loads only once the panel was opened. | ||
| const [wasOpened, setWasOpened] = useState(false); | ||
| const local = useLocalAttempt(modelKey); | ||
| const plan = useQuery({ ...workingMemoryPlanQueryOptions(modelKey), enabled: wasOpened }); |
There was a problem hiding this comment.
[P2] Refresh the memory-dependent plan after its inputs change
The plan depends on current free GPU memory and fitted estimates, but reopening the panel does not refresh it. If another process occupies GPU memory when the plan loads, Calibrate can remain disabled after memory recovers. Completion events refresh summaries/status only, so recalibration can also reuse sizes calculated before previous measurements. Refresh the plan when reopening or preparing calibration, and invalidate it after completed measurements.
There was a problem hiding this comment.
Fixed in a1d9318. The section now re-reads the plan every time it opens, not only the first time, and Calibrate re-reads it right before it starts: the button shows a loading state meanwhile, and the dialog and the run use the plan just read. If that plan has become unsupported, no dialog opens and Calibrate is disabled with the new reason; a failed read shows the error with Retry. Calibration status events now mark every plan stale (they share fits and recorded memory), so the next opening or start reads it fresh. I mark rather than refetch on the event itself, because free memory during or right after a calibration is not what a normal generation sees. Covered by unit and browser tests.
…calibration starts The plan depends on free GPU memory and fitted estimates, so the section re-reads it whenever it opens and before Calibrate, and calibration events mark every plan stale.
JPPhoto
left a comment
There was a problem hiding this comment.
Findings/issues:
-
invokeai/app/services/working_memory_calibration/working_memory_calibration_default.py:1101: What:_live_runs()checks only queue item IDs already stored on a run. Problem: a retry is a new same-origin item, absent from the store until admitted, so a failed run's valid points can affect estimates while its retry waits. Effect: queued generations can use a partial calibration fit and under-reserve VRAM. Likelihood: plausible when an admin retries an item behind queued work. Recovery: a later refresh may replace the fit. Fix: include pending retry items when deriving live runs. If skipped: reservations can stay wrong while the retry is pending. Test: fail a run after recording a valid point, queue a retry without dequeuing it, refresh fits, and assert the run remains excluded. -
invokeai/app/services/working_memory_calibration/working_memory_calibration_plan.py:224: What:vae_source()falls back to the first FLUX.2 pipeline when none matches the target variant. Problem: with only an incompatible source and no standalone VAE or encoder, the plan says calibration is supported, but graph compilation rejects the source and queues no cases. Effect: the Model Manager offers calibration that cannot run. Likelihood: limited to installs with only an incompatible component source. Recovery: install a compatible source or standalone components. Fix: mark the plan unsupported unless its graph can compile. If skipped: users get a compile error instead of calibration. Test: compile a Klein 9B plan with only a Klein 4B source and no standalone components; assert it is unsupported or compiles with compatible components. -
invokeai/frontend/webv2/src/features/models/ui/detail/WorkingMemorySection.tsx:279: What: the confirmation dialog starts the plan fetched before it opened. Problem: if free GPU memory or fitted measurements change while the dialog is open, confirmation still queues the old cases. Effect: runs may be tiled, partly resident, or fail, wasting calibration work. Likelihood: plausible if the dialog stays open while GPU use or another calibration changes. Recovery: manually retry after memory stabilizes. Fix: refresh the plan on confirmation and stop if it is no longer supported. If skipped: users can confirm stale sizes. Test: change the plan while the dialog is open; confirm and assert it refreshes or queues nothing if unsupported. -
invokeai/frontend/webv2/src/features/models/data/workingMemoryCalibration.ts:108: What:postStep()stops retrying on everyApiError. Problem: the API documents that a keyed request can return 503 after its queue items were committed; retrying the same key can recover that run, but the client stops and skips later steps. Effect: the attempt is partial, and a manual retry can duplicate already queued work. Likelihood: low, during transient store-lock or write failures. Recovery: manually retry the calibration. Fix: retry 503 with the same idempotency key within the existing bound. If skipped: transient storage errors leave incomplete attempts. Test: return 503 after enqueue, then success for the same key; assert the step is not duplicated and later steps are posted.
…o feat/working-memory-user-calibration # Conflicts: # invokeai/frontend/webv2/performance/browser-baseline.json
…name cards apart `stop` marks the run stopped even when saving a calibration store fails, keeps that scratch root and exits 2. Shipped working-memory points are grouped per device class id, so cards with the same name keep separate points and spreads.
…ibrations that cannot compile A run stays live while the queue holds any unfinished item with its origin, so a pending retry keeps its failed run's points out of fits. FLUX.2 plans without a usable text encoder are unsupported, the section compiles a plan's cases before the dialog opens and re-reads the plan on confirm, and keyed posts retry a 503 with the same key.
|
@JPPhoto Thanks. All four are fixed in 623a5e3, and every new test fails without its fix. The branch also has main and the stack below merged in (4339e78). Its only conflict was the generated byte fields in 1. A pending retry and live runs
2. A FLUX.2 plan with only an incompatible source
3. A stale plan at confirmation
4. A 503 after the enqueue
Checks: backend tests for working-memory calibration, session queue and processor, ruff, frontend lint, unit and browser tests, a production-build browser check of the new flows, and the frontend performance gate. MySQL/MariaDB was not run locally; the external-database CI job covers the new query. |
…o feat/working-memory-user-calibration # Conflicts: # invokeai/frontend/webv2/performance/browser-baseline.json # scripts/working_memory/build_shipped.py
Summary
The shipped VAE working-memory estimates come from a few reference GPUs. A user's card, driver or allocator can need less, or more. Admins can now calibrate a model's VAE decode on their own GPU from the Model Manager, and estimates on that GPU then use the measurement.
Model detail, "Working memory" section (actions are admin-only)
Estimates
device_working_mem_gb), nothing changes.Robustness found in hardware runs
invokeai/backend/working_memory/fragmentation.py).Before/after on an RX 9060 XT (SDXL, fp32 decode)
Related Issues / Discussions
QA Instructions
Tests
uv run --no-sync pytest -n 4: 4818 passed, 158 skipped, 5 xfailed. Covered: working-memory backend, calibration service, router and runner, session processor,tests/backend/model_manager/load,tests/app/invocations, the builder,tests/family_e2e.uv tool run ruff@0.11.2 checkandformat --check.make frontend-openapi/frontend-typegenequivalent);pnpm lintandpnpm testininvokeai/frontend/api.pnpm build(format, oxlint, tsc, architecture);src/features/models255;node --test scripts/mock-backend-fixtures.test.mjs28;pnpm test:performance:architecturepasses.pnpm testhere: 3 failures from the de-DE locale of this machine (number formatting) and 2 load timeouts that pass alone. All unrelated.Mutation checks, each caught:
Hardware, SDXL from the UI (18 runs per attempt) on an RTX 4090 (CUDA,
cudaMallocAsync) and an RX 9060 XT (ROCm 10, Windows, expandable segments):Browser
pnpm dev:mock; fault scenarios throughPOST /__faults) and on both real servers.Linux NVIDIA: the NVML counter reader and this calibration round ran on 2× RTX PRO 6000 Blackwell (#9910 (comment)); with #9897's
edba0dd882it counts as verified.Not verified on hardware: the Linux AMD, MPS and XPU counter readers. The UI marks them as unverified.
Review
Resolved material findings
Remaining limits
Compatibility / Rollout
<db_dir>/working_memory/comes from feat(working-memory): measure working memory in admin calibration runs #9897. New optional fields (attempt_id, runsoft) are left out when absent, so older files load. No database migration.GET /api/v2/models/i/{key}/working_memory/planand/summary(404 for unknown models).attempt_idon calibrate. Line fieldsmeasured_by,own_windows, and size fieldscounted,excluded_reason. Generatedopenapi.jsonandschema.tsare updated.browser-baseline.jsonrecords the measuredotherRawBytes(215114 B on every route, main 211323 B); nothing else in the baseline changed.Checklist
What's Newcopy (if doing a release after this PR)