Skip to content

fix: materialize weights before VRAM-management wrapping (#1596)#2041

Open
MushiSenpai wants to merge 1 commit into
kijai:mainfrom
MushiSenpai:fix/vram-management-meta-tensor-1596
Open

fix: materialize weights before VRAM-management wrapping (#1596)#2041
MushiSenpai wants to merge 1 commit into
kijai:mainfrom
MushiSenpai:fix/vram-management-meta-tensor-1596

Conversation

@MushiSenpai

Copy link
Copy Markdown

Fixes #1596 — and the same root cause behind #843 / #1188 (related: #409, #506).

The bug

Enabling the WanVideoVRAMManagement node crashes model loading with
Cannot copy out of meta tensor; no data!.

Root cause

The transformer is built with init_empty_weights(), so every parameter starts on
the meta device. On the no-lora / no-scaled-quant path (e.g. a plain
Wan2.1-VACE workflow), load_weights() is not called before the vram block —
neither the lora is not None and merge_loras branch nor the
"scaled" in quantization or lora is not None branch runs — so the parameters are
still meta tensors when enable_vram_management executes.

enable_vram_management wraps each norm / Conv3d / LayerNorm in
AutoWrappedModule, whose __init__ does:

self.module = module.to(dtype=offload_dtype, device=offload_device)

Calling .to() to move a meta tensor raises
NotImplementedError: Cannot copy out of meta tensor; no data!. The crash is in the
wrapper construction, before any weight lookup — not in an sd[name] miss.

Observed traceback (live, on this stack):

File "nodes_model_loading.py", line 1768, in loadmodel
    enable_vram_management( ... )
File "diffsynth/vram_management/layers.py", line 92, in enable_vram_management_recursively
    module_ = target_module(module, **module_config_)
File "diffsynth/vram_management/layers.py", line 14, in __init__
    self.module = module.to(dtype=offload_dtype, device=offload_device)
...
NotImplementedError: Cannot copy out of meta tensor; no data!

The fix

nodes_model_loading.py

  1. Materialize before wrapping — in the vram block, call load_weights(...) to
    fill the meta tensors from sd before enable_vram_management, so it wraps
    real (non-meta) modules. This is what removes the crash.
  2. onload_device=offload_device (was =device) in module_config. With
    CPU-resident weights, keeping onload_device == computation_device makes the
    wrappers assume weights are already on the compute device; setting it to the
    offload (CPU) device makes onload_device != computation_device, which routes
    forward through the cast path that moves weights to CUDA on demand.
  3. Place the non-wrapped params — params that enable_vram_management does not
    wrap (raw nn.Parameter like block modulation, and excluded modules such as
    patch_embedding/rope_embedder) stay on CPU and would mismatch CUDA tensors in
    the forward pass; move them to the compute device. Then clear sd so a later
    load_weights over the now-renamed .module. param names can't KeyError.

diffsynth/vram_management/layers.py
4. AutoWrappedModule.__getattr__ proxy so model code that reaches the wrapped
module's attributes (e.g. norm_layer.weight.dtype) through the wrapper still
works.
5. AutoWrappedModule.forward moves the inner module to the compute device for the
call and back to the offload device afterwards (no deepcopy), which is correct
regardless of where the weights were loaded.

Validation

End-to-end on a Wan2.1-VACE workflow (wan2.1_vace_14B_fp16, base precision fp16,
quantization disabled, no lora) with WanVideoVRAMManagement (offload 0.9):

  • Before (upstream main): crash at enable_vram_management
    Cannot copy out of meta tensor; no data! (full traceback above; captured live
    by forcing the loader to run in isolation).
  • After (this branch): Transformer weights loaded, the VACE edit completes
    (~490–520 s/clip on an RTX 5090, well under 32 GB).

A CPU-only mechanism check (verify_1596.py) reproduces the meta-tensor crash on a
wrapped meta module and confirms that materializing first lets enable_vram_management
succeed, plus the __getattr__ proxy and the device-routed forward.

Scope / alternatives

The .module.-name KeyError some reports mention is a secondary symptom (it only
occurs when load_weights runs after wrapping, over renamed params); this PR
sidesteps it by materializing before wrapping and clearing sd. Stripping the
.module. infix in load_weights is a viable complementary hardening but is not
required once weights are materialized up front.


Disclosure: drafted and validated with AI assistance (Claude); reviewed and
submitted by me. The commit carries a Co-Authored-By trailer.

WanVideoVRAMManagement crashes model loading with
"Cannot copy out of meta tensor; no data!" (also kijai#843 / kijai#1188).

Root cause: the transformer is built with init_empty_weights() (meta device).
On the no-lora / no-scaled-quant path, load_weights() is never called before the
vram block, so every parameter is still a meta tensor when enable_vram_management
runs. AutoWrappedModule.__init__ does `self.module = module.to(offload_device)`,
and moving a meta tensor with .to() raises NotImplementedError. The crash is in
the wrapper construction, not in any sd[name] lookup.

Fix:
- nodes_model_loading.py: materialize weights from sd via load_weights() *before*
  wrapping, so enable_vram_management wraps real (non-meta) modules.
- module_config: onload_device=offload_device (was =device). With CPU-resident
  weights this makes onload_device != computation_device, routing forward through
  the cast path instead of assuming weights are already on the compute device.
- after wrapping, move params NOT inside an AutoWrapped* module (e.g. block
  modulation, patch_embedding) to the compute device, and clear sd so a later
  load_weights over the now-renamed (.module.) param names can't KeyError.
- layers.py: AutoWrappedModule.__getattr__ proxy so model code reaching
  e.g. norm_layer.weight.dtype through the wrapper still works; forward() moves
  the inner module to the compute device for the call and back to offload after
  (no deepcopy), correct regardless of where the weights were loaded.

Validated end-to-end on a Wan2.1-VACE workflow (wan2.1_vace_14B_fp16, fp16,
quantization disabled, no lora) with WanVideoVRAMManagement: before = crash at
enable_vram_management; after = weights load and the VACE edit completes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Is the WanVideo VRAM Management node working?

1 participant