Repository navigation
fix(working-memory): reserve VAE working memory per dtype and allocator - #9898
Pfannkuchensack wants to merge 11 commits into
Conversation
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.
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 %.
# Conflicts: # invokeai/backend/util/vae_working_memory.py # tests/app/invocations/test_flux2_working_memory.py
# Conflicts: # invokeai/backend/util/vae_working_memory.py
# Conflicts: # docs/src/content/docs/development/Architecture/model-manager.mdx # invokeai/backend/util/vae_working_memory.py # invokeai/backend/working_memory/coefficients.py # invokeai/backend/working_memory/data/vae.json # invokeai/backend/working_memory/platform.py # scripts/calibrate_flux2_working_memory.py # tests/app/invocations/test_flux2_working_memory.py # tests/app/invocations/test_vae_working_memory_backend.py # tests/app/invocations/test_z_image_tiled_decode.py # tests/backend/util/test_rocm_sdpa_head_dim_guard.py # tests/backend/working_memory/test_shipped_coefficients.py
There was a problem hiding this comment.
@Pfannkuchensack findings/issues to resolve:
-
Should fix 1.
invokeai/backend/util/vae_working_memory.py:160-169: What this is:_compute_dtype()picks the first floating-point parameter wider than one byte. Problem: For an SDNQ VAE, that can be an fp32 bias even though packed weights dequantize to bf16. Fix: Prefer the packed tensor’scompute_dtype. Effect: On cuDNN/native, the wrong fp32 line adds about 3.1 GiB for a 1024px decode, evicting other models or forcing earlier tiling. Likelihood: Supported SDNQ VAE with bf16 compute and fp32 side parameters. Recovery: Tile the decode or use a non-SDNQ VAE. Test: Reproduced with an SDNQ weight and fp32 bias; add a regression test asserting the bf16 line. If we skip it: Generation can use more memory and run slower. -
Should fix 2.
invokeai/backend/util/vae_working_memory.py:285;invokeai/backend/model_manager/load/model_cache/cached_model/cached_model_only_full_load.py:129,187: What this is: The estimate adds cast growth only while parameters are below fp32 size. Problem: Withkeep_ram_copy_of_weights=false, an fp32 VAE can be offloaded to CPU without restoring its original dtype, while the cache keeps its original fp16 byte count. The next estimate adds zero for the cast. Fix: Restore the dtype on offload or update cache byte accounting. Effect: A repeated fp32 run can OOM when VRAM is tight; the VAE’s uncounted growth is about 160 MiB. Likelihood: No RAM copy and an offload after an fp32 VAE run. Recovery: Restart to reload the original weights, or tile/reduce the output size. Test: Exercise cast, offload, and reload with no RAM copy; assert accounting includes the expanded size. If we skip it: Repeated runs can fail under memory pressure. -
Should fix 3.
scripts/calibrate_flux2_working_memory.py:79-84,612: What this is: The CLI accepts--dtype float16. Problem: FLUX.2 VAE ships only bf16 and fp32 lines, so--only vae --dtype float16raisesKeyError: 'k_decode_fp16'. Fix: Reject or skip unsupported VAE dtype combinations with a clear message. Effect: VAE calibration aborts before measuring. Likelihood: Any operator choosing the accepted float16 option. Recovery: Use bfloat16 or float32. Test: Reproduced on the pinned checkout; add a CLI test for a clear rejection. If we skip it: Operators get a traceback instead of calibration results.
Suggested follow-up work (pre-existing):
invokeai/app/invocations/vae/flux_vae_encode.py:95andinvokeai/app/invocations/vae/flux_vae_decode.py:97: When the first parameter is an SDNQ packed weight, the input is cast to its packed dtype. These lines are unchanged from the PR base. Suggested issue: use the SDNQ compute dtype for VAE inputs.
|
@Pfannkuchensack See prior review. |
…re cast weights on offload An SDNQ VAE is priced in its packed weights' compute dtype, and weights a node cast in place go back to the dtype the cache counts when offloaded without a RAM copy. The FLUX.2 calibration script refuses a VAE dtype it has no shipped line for.
|
@JPPhoto Thanks. All three points are addressed in e3bb17e, and each has a test that fails without its fix.
Follow-up you suggested. The FLUX VAE nodes cast inputs with |
JPPhoto
left a comment
There was a problem hiding this comment.
Merge blocker
invokeai/backend/model_manager/load/model_cache/cached_model/cached_model_with_partial_load.py:513 can leave weights at the wrong dtype after a partial offload. A VAE upcast to fp32 has both CPU and GPU weights changed. The offload skips weights already on CPU and restores only the GPU weights it selects. Remaining weights stay fp32 while cache accounting still records fp16 sizes. A later load can move fp32 weights into VRAM without accounting for their extra size, causing overcommit or OOM.
Restore all changed weights to their counted dtypes on partial offload, including weights already on CPU, or make cache sizing track their actual dtypes. Add a regression test for partial residency, fp32 cast, and partial offload.
… at the last unlock The last unlock re-points every weight a holder cast in place (the SD VAE upcast) back to the dtype the cache counts, on its device, keeping ties and the RAM copy, without load_state_dict. Loads and offloads then never move cast weights, and the SD VAE estimate prices the full upcast on every run.
|
@JPPhoto Thanks. Fixed in 47eac03. The fix does more than restore the dtype on a partial offload: the cache now puts every weight a holder cast back to its counted dtype when the last lock ends. The fix
Tests
|
JPPhoto
left a comment
There was a problem hiding this comment.
The latest commit restores cast weights to their counted dtypes on the last cache unlock, resolving the partial-offload blocker. But there's a merge blocker:
invokeai/backend/model_manager/load/model_cache/counted_dtypes.py:35: A tied group of distinctParameterobjects can be cast to fp32 and back before unlock. The helper skips every member once its dtype matches the counted dtype, leaving separate storages. Multi-GPU shared-weight adoption can create this alias shape viaload_state_dict(assign=True)incached_model_only_full_load.py:74-75. The cache counts one storage while two remain resident, risking memory overcommit and OOM. Effect: later cache loads may fail. Likelihood: requires a holder to change a tied model's dtype; the cache supports these aliases. Recovery: evict and reload the entry. Test: reproduced 32 resident bytes versus 16 counted; add a shared-store cast-round-trip test asserting storage identity and byte counts.
Suggested follow-up work (pre-existing):
invokeai/app/invocations/vae/flux_vae_encode.py:95andinvokeai/app/invocations/vae/flux_vae_decode.py:97: An SDNQ packed first weight can make input tensors cast to uint8. Suggested issue: cast inputs to the VAE compute dtype.
Summary
VAE working memory was priced as
pixels × element size × k, with one k per convolution backend fitted on bf16 decodes. Measurements show three problems with that:On an RX 9060 XT this evicted the SDXL UNet on every 1024² fp32 image.
The five AutoencoderKL-family estimators now price a measured slope and base per (conv backend, allocator, dtype). The values come from a shipped upper-envelope fit (+5 %) over the measured points, and a test checks they reproduce from the points.
cudaMallocAsync, SDXL fp32 decodeexpandable_segments:False, FLUX bf16 decodefp32=Falsedecode runs fp16 whatever the loaded dtype.Related Issues / Discussions
Builds on #9896. Measurement report and runs: SD/SDXL VAE decode measurements (both cards, fp16/bf16/fp32, native/
cudaMallocAsync, isolated and in real generations).QA Instructions
Measurements:
cudaMallocAsync) and the RX 9060 XT (expandable and native).working_memory/data/vae.json.A/B on the RX 9060 XT (SDXL txt2img, 25 steps, E2E tool; before =
upstream/main, after = this branch):The two runs were not interleaved.
Tests: affected suites 4945 passed, 0 failed. Each changed file also passes alone.
Review
Resolved findings
block_out_channels[128, 512, 1024, 1024], 3 layers per block) had been borrowing the family's lines. It is now measured and has its own lines.ValueError.Remaining limits
Compatibility / Rollout
Checklist
What's Newcopy (if doing a release after this PR)Need help on this PR? Tag
@codesmith-botwith what you need.