Fix: compute device partial load#9375
Conversation
…residency (invoke-ai#9373) VAE decode inferred its device via get_effective_device(vae). Under partial loading with VRAM pressure, all VAE weights can be temporarily offloaded to RAM, so that returned CPU — placing the latents on CPU and, because the autocast layers follow the input device, running the entire decode on CPU. Expose LoadedModel.compute_device (the model's intended device, stable across partial-load residency and still honoring cpu_only) and use it in all 8 decode invocations. Adds a regression test reproducing the offloaded-weights case.
… residency Same class of bug as invoke-ai#9373, for text encoders: get_effective_device() returns CPU when partial loading has offloaded all weights, running the whole encode on CPU. Fully autocast-capable encoders (e.g. CLIP) are affected because repair_required_tensors_on_device() pins nothing to the compute device. Use LoadedModel.compute_device in compel, sd3, z_image, flux2_klein, cogview4 and qwen_image encoders, and thread it through HFEncoder for the FLUX encoder. Updates the affected tests to assert the intended-device behavior.
Like the anima decode test, the qwen-image and z-image working-memory tests build vae_info as a bare MagicMock and drive invoke(). Since invoke-ai#9373 places latents on vae_info.compute_device, latents.to(device=...) raised TypeError — but both tests wrapped invoke() in `except Exception: pass`, so the failure was silently swallowed and the decode path never actually ran. Set compute_device to torch.device("cpu") so the tests exercise the real decode path instead of masking the error.
|
That did fix the issue, Thank you! |
…current residency The anima text encoder inferred its device via text_encoder.device (HF PreTrainedModel residency), which returns CPU when partial loading has temporarily offloaded all Qwen3 weights to RAM — running the whole encode on the CPU. This is the same class of bug as invoke-ai#9373; the other text encoders and VAE decodes in this PR were already fixed, but the anima encoder was missed. Use LoadedModel.compute_device instead. This also corrects the device passed to TorchDevice.choose_anima_inference_dtype(). Adds a regression test covering both the offloaded-accelerator case and the cpu_only case.
lstein
left a comment
There was a problem hiding this comment.
Approving — well-scoped fix for a real, environment-dependent bug
The root-cause analysis is correct: under partial loading with VRAM pressure, all of a model's weights can be temporarily offloaded to RAM, so get_effective_device(model) (and HF model.device) report cpu. Because the custom autocast layers follow the input device, the whole decode/encode then silently runs on the CPU — slow, and for the SD/SDXL VAE it also forces fp32. Replacing residency inference with the stable, intent-based LoadedModel.compute_device is the right fix.
What I verified:
LoadedModel.compute_devicecleanly delegates tocached_model.compute_device, which exists on bothCachedModelOnlyFullLoadandCachedModelWithPartialLoadand correctly honorscpu_only(checked againstmodel_cache.put'sexecution_deviceplumbing). No behavioral change in the common fully-resident case.HFEncoder's newdeviceparam is backward-compatible (defaults to the oldget_effective_devicefallback).- All 8 VAE decodes + the text encoders (compel/SD3/z_image/flux2_klein/cogview4/qwen_image/FLUX) are converted consistently; the SD3 refactor to hoist
t5_text_encoder_infois correct. - The tests are excellent — the
torch.device("meta")stand-in for an accelerator lets the offloaded-residency mechanism be reproduced on CPU-only CI, and both#9373variants (conv VAE + fully-autocast CLIP-like) are exercised end-to-end. Ran the affected suite locally: green,ruffclean.
One gap I found and fixed (pushed to this branch)
anima_text_encoder.py still inferred its device from residency:
device = text_encoder.device # HF PreTrainedModel residency → CPU under full offloadThis is the same class of bug the PR sets out to eliminate. The Qwen3 0.6B encoder is fully autocast-capable and this path doesn't call repair_required_tensors_on_device(), so under a full offload text_encoder.device returns cpu and the whole encode runs there. The impact is slightly broader here because device also feeds TorchDevice.choose_anima_inference_dtype(device). Notably the PR already fixed the anima VAE decode but left the anima text encoder on the buggy pattern.
Fix (commit ed77554f79):
device = text_encoder_info.compute_deviceplus a new tests/app/invocations/test_anima_text_encoder.py mirroring the SD3/compel tests — covering both the offloaded-accelerator case and the cpu_only case. I confirmed the test actually pins the behavior by temporarily reverting the fix (it fails) before restoring it.
With that, the "same class of bug for text encoders" claim is complete across every encoder. Nice catch on the root cause. 🚀
Summary
fix: VAE decode — and, in the same class of bug, text encoding — could silently run entirely on the CPU when partial loading is active, even on machines with a configured CUDA/MPS device.
Why: Under partial loading with VRAM pressure, the model cache can temporarily offload all of a model's weights back to RAM. The affected invocations inferred their execution device from the model's current parameter residency via
get_effective_device(model), which returnscpuin that state. The latents/tokens were then placed on the CPU, and because the custom autocast layers cast their weights to the input's device, the whole forward pass ran on the CPU — very slow, and for the SD/SDXL VAE it additionally forced fp32. The bug is environment-dependent (only triggers when no weight happens to stay resident on the accelerator), which is why it reproduces inconsistently.This was introduced by #9293 (VAE) — commit
82e2681126— which replacedTorchDevice.choose_torch_device()withget_effective_device(vae). The text-encoder invocations (from #8777) shared the same trap for models whose modules are all autocast-capable (e.g. CLIP), whererepair_required_tensors_on_device()pins nothing to the compute device.How: Use the model's intended compute device instead of inferring it from residency:
LoadedModel.compute_device— stable regardless of partial-load state, and still honorscpu_only.*_info.compute_device.HFEncoder(FLUX conditioner) gained an optionaldeviceparam (fallback = old behavior); the FLUX text-encoder invocation threadscompute_devicethrough for T5 & CLIP.repair_required_tensors_on_device()calls are unchanged — they solve a separate problem.Related Issues / Discussions
Closes #9373
The issue only reports the VAE path; the text-encoder fix is the same root cause and is validated below.
QA Instructions
Repro (before this PR):
device_working_mem_gbto4.0.With this PR: the decode/encode runs on the configured compute device regardless of partial-load state;
cpu_only-configured VAEs/encoders still correctly run on the CPU.Automated: new regression test
tests/backend/model_manager/load/test_loaded_model_compute_device.pyreproduces the offloaded-weights case for both a VAE-like conv model and a fully-autocast (CLIP-like) encoder. Updatedtest_compel,test_sd3_text_encoder,test_cogview4_text_encoder,test_conditionerto assert the intended-device behavior. All green (10 passed, 2 skipped [mps]);ruffclean.Merge Plan
Standard merge — no DB/schema changes, no migrations.
Checklist
What's Newcopy (if doing a release after this PR)