Repository navigation
feat(e2e): calibrate working memory and build shipped data from probe runs - #9899
Pfannkuchensack merged 132 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
# Conflicts: # invokeai/backend/util/vae_working_memory.py # tests/app/invocations/test_flux2_working_memory.py
# Conflicts: # invokeai/backend/util/vae_working_memory.py
…ast unlock The last unlock now checks every name of a tied weight and every CPU weight with a RAM copy against the tensor the cache counts, not only weights whose dtype differs. A cast to fp32 and back left tied names (distinct Parameters over one tensor, as adoption and cache moves create) and CPU weights on their own copies, holding more memory than counted.
… a process survives The runner writes the server launcher into heartbeat.json right after the spawn, before it waits for startup. stop checks that every killed process exited; while one still runs it keeps the scratch roots and stores and exits with 2.
…d trusts no reused server pid Once the runner is dead, stop reads heartbeat.json again and kills and checks the server it names now. The heartbeat records the serving process with the create time captured at its start.
…n unlock A failed lock or paced continuation that drops the last pin now returns cast weights to their counted dtypes too, all in one release path. Records that are detached or about to be evicted as stale are skipped, and the docstrings state how a tied weight split across devices is re-pointed.
…ixel line Only a spec keyed by no platform trait may carry a hand-set value; a keyed spec's value off a pixel line fails the data test.
joshistoast
left a comment
There was a problem hiding this comment.
The shared bound rule and reuse of existing graph compilers are sound choices, but these defects undermine calibration output, recovery safety, and regression evidence. The full diff also affects ordinary inference through cache restoration and VAE reservations; no actionable backend regression was confirmed.
…ibration # Conflicts: # docs/src/content/docs/development/Architecture/model-manager.mdx # invokeai/app/util/torch_cuda_allocator.py # invokeai/backend/working_memory/coefficients.py # invokeai/backend/working_memory/fit.py # invokeai/backend/working_memory/platform.py # tests/backend/model_manager/load/model_cache/test_model_cache_unlock_restores_casts.py # tests/backend/working_memory/test_fit.py # tests/backend/working_memory/test_shipped_coefficients.py
The runner checks the run is still running, spawns the server and records it in the heartbeat in one run-lock hold, so stop either sees the server or no server starts. A server whose identity cannot be recorded is killed before the lock is released. Points of estimators keyed by no platform trait name no platform in build_shipped.
…ibration # Conflicts: # docs/src/content/docs/development/Guides/family-e2e.mdx # scripts/family_e2e/__main__.py # scripts/family_e2e/client.py # scripts/family_e2e/records.py # scripts/family_e2e/references.py # scripts/family_e2e/report.py # scripts/family_e2e/runner.py # scripts/family_e2e/server.py # tests/family_e2e/fake_invokeai_server.py # tests/family_e2e/test_cli.py # tests/family_e2e/test_references.py # tests/family_e2e/test_report.py # tests/family_e2e/test_runner.py
A probe that needs a raise now gets the lowest line that covers every point by the margin and keeps every upper bound, found exactly over the feasible region; a line that already covers its points stays unchanged. A probe no line can cover within the bounds is refused with the conflicting point and bound named, and the build exits 1 like any other bound conflict.
Cleanup copies the store and removes the scratch root only after the server, its launcher and any survivor are confirmed exited; otherwise the run keeps both and records the survivors for stop and resume. The report fails a case whose planned measures did not all finish, and the time per step counts the steps the compiled denoise node runs.
|
@joshistoast The fixes for your seven points are in f343ae1 (fitter and build) and 4e6f6b7 (family E2E). Replies are on each thread. Main has since squash-merged #9919, which restructures One test from #9919 changed. Its 2 GiB token window above 16384 tokens cannot be covered within the 1.3× bound of the fresh 4096-token forward. The old fit returned a line that broke that bound, and the test never checked bounds. It now uses a 512 MiB window, and a new test covers the refusal of the 2 GiB case. |
…y ship A raise now takes the cheapest line with a whole-byte slope and a whole-MiB base that keeps every bound, instead of rounding the real optimum and subtracting a rounding allowance from every ceiling. A conflict is reported only when no shipped line satisfies the constraints.
joshistoast
left a comment
There was a problem hiding this comment.
started from the bottom now we here
terminate() waited only for the captured tree, then checked the group's living members once, microseconds after SIGKILL. An orphan outside the tree had not exited yet on a loaded runner, so it was reported as a survivor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Shipped working-memory coefficients can now come from probe runs through the API, without one-off calibration scripts.
start --calibratein the family E2E toolPOST /api/v2/models/i/{key}/working_memory/calibrateinstead ofenqueue_batch.calibration/. After a crash orstopit copies the store file instead.scripts/working_memory/build_shipped.pyprobe_demandpoints. It takes valid, fit-eligible windows, keeps the largest per card, estimator, platform and features, and keeps the larger value when it meets an existing point.platform_for_environment, which shares its rules withplatform_for.--writerefuses any result that the shared bound rule rejects. A raise driven by a fragmentation-flagged point needs--accept-fragmentation.max_ratio) stays with those points, because a cold first run after a server start is the worst case.invokeai/backend/working_memory/bounds.pyis used by both the data test and the builder.Related Issues / Discussions
Builds on #9894, #9897 and #9898.
QA Instructions
tests/family_e2e, package data, invocations, the calibration service/runner/router) pass: 3438 passed, 8 skipped, 5 xfailed. Each new or changed file also passes alone.enqueue_batch;cudaMallocAsync) and an RX 9060 XT (ROCm, Windows). Both completed and exported their stores.k_decode_fp325988 → 7661 B/px), which would push fresh-process points over 1.3×. The shipped data is unchanged.Review
Resolved findings
--writecould write data that failed the data test. The upper-bound check only looked at new points, and every new point is a probe point. Now one shared bound rule runs over all points after a refit.resumecould not get past.oshard fact moved into feat(working-memory): measure working memory in admin calibration runs #9897, so the environment-id scheme ships once.stop, and the probe-wiring regression.Remaining limits
--calibraterun on 2× RTX PRO 6000 Blackwell under Linux gave 24 valid NVML windows andbuild_shipped.pyread the export. Other machines' exports come fromGET /api/v2/models/working_memory/export.fingerprint.os_familyandplatform._host_osrepeat the samesys.platformmapping; a test catches drift between them.Compatibility / Rollout
probe_demandholds lower bounds only.MeasuredPointgainsholds_upper_bound; amax_ratioon a probe point is rejected.Checklist
What's Newcopy (if doing a release after this PR)Need help on this PR? Tag
@codesmith-botwith what you need.