Repository navigation
fix(wan): keep system RAM within the model cache budget - #10192
Open
Pfannkuchensack wants to merge 6 commits into
Open
Pfannkuchensack wants to merge 6 commits into
Pfannkuchensack wants to merge 6 commits into
Conversation
Free the outgoing expert before the next one loads, so a swap no longer holds both. Stop fp32-kept tensors pinning whole fp32 shards in Diffusers folders. Read single files without mmap and reserve before the fold and cast.
Pfannkuchensack
requested review from
blessedcoolant,
joshistoast and
lstein
as code owners
November 7, 2026 04:24
6 of 7 tasks
4 of 7 tasks
joshistoast
requested changes
Nov 7, 2026
# Conflicts: # invokeai/backend/model_manager/load/model_loaders/wan.py # tests/app/invocations/test_wan_expert_swapper.py # tests/backend/model_manager/load/test_wan_checkpoint_loader.py
Since invoke-ai#10185 a marked int8 file takes the int8 branch and stays int8, and an unmarked one is refused, so no int8 weight reaches the fold any more. The int8 branch reserves before its split and has its own reservation test.
… on mmap The single-file loader decides the key set, quantization and RAM reservation from the header, then reads one tensor at a time through pread and folds or casts it before the next, so bundled VAE and text-encoder weights are never read and the source is never held in full. The int8 branch reserves before its first read too. The Diffusers loader drops the blanket disable_mmap, so bf16 folders load as on main. After the load it copies only the tensors left on the mapping off the bf16 dtype, the fp32 _keep_in_fp32_modules views that pinned whole fp32 shards. Header reads now map F8_E8M0, so MXFP8 grids reach their refusal, and name any other dtype they cannot read.
…hides in-place writes The macOS runners keep the bytes a tensor was mapped with, so the probe cannot tell a view from a copy there and the fp32-folder check would pass without testing anything.
joshistoast
requested changes
Nov 10, 2026
joshistoast
left a comment
Collaborator
There was a problem hiding this comment.
Overall, the expert-reference fix is small and sound, and the dense streaming approach and regression tests are strong. The documented fp32 Diffusers peak remains a limitation.
…ypes The int8 install read every header tensor before casting, so an fp32 file's dense tensors were held at twice the width the reservation charged. Each dense tensor is now cast as it arrives, and no local keeps the stored copy alive. The header dtype table lacked F8_E4M3FNUZ, F8_E5M2FNUZ and C64, which load_file reads, so such single files installed and then failed to load.
joshistoast
approved these changes
Nov 10, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wan 2.2 held far more system RAM than the model cache accounts for. Three separate causes, all in Wan code, each fixed here:
_ExpertSwapper.get()and by the denoise loop'stransformerlocal while the next expert loaded. The cache evicted it, but its tensors could not be freed, so both experts were resident during every swap. The default cache is about 1x VRAM, below two experts, so this hits default settings._keep_in_fp32_modulesleaves some tensors fp32. Those stay views of the mmap, and each pins its whole shard for the life of the model, while the cache only counts the bf16 size. Afterfrom_pretrained, the loader now copies only the tensors still on the mapping that are not bf16. bf16 tensors stay on the mapping as onmain, because they are the model itself, so bf16 folders load exactly as before.preadand folds or casts each one before the next. Dropped keys and scale metadata are never read, and no tensor stays a view of a file-wide mapping. ComfyUI int8 files reserve before their first read too.Peak and final RAM (working set), Windows, CPU-only loads, TI2V-5B:
mainChecksums match across all three versions in every case. The fp32 Diffusers peak equals
main's, see the known limits.From the first version, with the expert-swap fix (unchanged since):
max_cache_ram_gb: 16, real clipsRelated Issues / Discussions
Follow-up announced in #10185. Both PRs edit the cast block in
WanCheckpointModel._load_from_singlefile, so the second to merge needs a small rebase. The RAM figures inwan-2.2.mdxfrom #10185 describe the state before this PR and need updating once both are in.QA Instructions
Peak RAM above is process working set. Windows commit reads higher because VRAM counts toward it 1:1 (12 GB on the device added 12 GB of commit and no working set, with either CUDA allocator).
use_cache=false. All three seeds are bit-identical before and after (same SHA-256). 65 s per clip after, 68-72 s before.mainand the first version run fromgit archivetrees. The working set is read after touching every byte, as a device move would. The bundled-VAE single file was built locally from the TI2V-5B folder, because no real one was at hand.pread, reading from disk showed no penalty: 462 MiB/s against 431 MiB/s for mmap on a cold 19 GiB file.safe_openboundary:F8_E8M0gets its refusal by name.tests/backend/model_manager/load,tests/backend/model_manager/test_model_on_disk.py,tests/backend/model_manager/configs,tests/model_identificationandtests/backend/quantization: 3499 passed, 161 skipped. Wan loader and invocation tests: 411 passed, 1 skipped.uv lock --check,scripts/check_platform_locks.pyand Ruff are clean.Not run: Linux, macOS, ROCm, multi-GPU, the family E2E (Wan has no manifest yet). The 14B fp32 expert was not re-measured after the review round: its peak would not fit the free RAM of the test machine. No real scaled-fp8 Wan file was at hand, so the fold is covered by tests only.
Review
Fixed during review:
preadbefore it dropped or cast anything; it now works from the header and reads tensor by tensor.disable_mmapdoubled unsharded bf16 folders; it is gone, and only the tensors that pin a mapping are copied.F8_E8M0failed with a bareKeyErrorin the header read; it reaches its refusal by name again, and any other unreadable dtype is named.Known limits:
from_pretrainedreturns, so the fp32 copies are dropped only then. The peak ismain's: on Linux the extra pages are clean and reclaimable but count toward RSS and cgroup limits, and on Windows the mapping is charged to commit. For an A14B expert that is about 57 GB of shards plus about 28.5 GB of bf16, against a 57 GB reservation. Dropping them per shard would mean patching diffusers' loading loop.preadreads a tensor. A cold read stalls other threads for up to one tensor's read time: 1.9 s measured on a USB disk. This matters most with multiple GPUs.load_fileand cast, or use_keep_in_fp32_modules, are unchanged.Compatibility / Rollout
safetensors>=0.8.0is a new floor, becausebackend=does not exist before it.uv.lockwas already on 0.8.0, so only its metadata line changes. No migrations, no API change.Checklist
What's Newcopy (if doing a release after this PR)Open in [code]smith · Need help on this PR? Tag
@codesmith-botwith what you need.