Skip to content

SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler with fixes #600

Description

@RDouglasSharp

SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler
Page 1 of 6
SeedVR2: MPS (Apple Silicon) Compatibility
Fixes
Bug Report — ComfyUI-SeedVR2_VideoUpscaler standalone runner
June 2025
Environment
Hardware Apple M5 Max, 128 GB unified memory
OS macOS (Apple Silicon)
Backend PyTorch MPS (Metal Performance Shaders)
Compute dtype bfloat16
Model SeedVR2 3B (NaDiT + VAE v3)
Runner standalone_upscale.py
Summary
Running the SeedVR2 standalone upscaler on Apple Silicon with the MPS backend and
bfloat16 compute dtype exposed five separate bugs — three hard crashes and two silent
numerical failures. All five have been diagnosed and fixed. The fixes require no changes to
model weights or configuration; they are all PyTorch-level workarounds for known MPS/Metal
limitations.

Description Type Files

1 torch.tile MPSGraph INT32 crash in
extend_head / cache_send_recv
Hard crash inflated_lib.py,
context_parallel_li
b.py,
causal_inflation_li
b.py
2 torch.cat MPSGraph INT32 overflow in
memory_limit_conv spatial reassembly
Hard crash causal_inflation_li
b.py
3 batched_sdpa zero-sized tensor index crash in
NaSwinAttention
Hard crash dit_3b/
attention.py,
dit_7b/attention.py
,
standalone_upscale.
py
4 CustomRMSNorm bfloat16 sqrt NaN on MPS —
root cause of black video output
Silent NaN dit_3b/
normalization.py,
dit_7b/normalizatio
n.py
5 pytorch_varlen_attention bfloat16 SDPA NaN Silent NaN dit_3b/
attention.py,
SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler
Page 2 of 6

Description Type Files

on MPS (defense-in-depth fix) dit_7b/attention.py
Bug 1: torch.tile MPSGraph INT32 crash in extend_head / cache_send_recv
[CRASH]
Affected Files
• src/models/video_vae_v3/modules/inflated_lib.py — extend_head()
• src/models/video_vae_v3/modules/context_parallel_lib.py — cache_send_recv()
• src/models/video_vae_v3/modules/causal_inflation_lib.py — extend_head usage
Root Cause
MPSGraph (the Metal backend’s graph compiler) uses int32 for all tensor-index arithmetic.
When torch.tile is called on a large spatial tensor — e.g. a single-frame slice of shape (1, 256, 1,
1088, 1920) that gets tiled 2× along the temporal axis during the VAE encoder’s causal head
extension — MPSGraph’s internal stride calculation overflows int32 and crashes with:
RuntimeError: MPSGraph does not support tensor dims larger than INT_MAX
Fix
Perform the tile operation on CPU for MPS devices. On Apple Silicon’s unified memory
architecture the mps→cpu→mps round-trip is a cache-coherency operation, not a bus transfer,
so performance impact is negligible.
t = tensor[:, :, :1]
if t.device.type == 'mps':
tiled = torch.tile(t.cpu(), tile_repeat).to(t.device)
else:
tiled = torch.tile(t, tile_repeat)
Applied identically in all three files wherever torch.tile is called on a potentially large spatial
tensor.
Bug 2: torch.cat MPSGraph INT32 overflow in memory_limit_conv spatial
reassembly [CRASH]
Affected File
• src/models/video_vae_v3/modules/causal_inflation_lib.py — memory_limit_conv(), final
torch.cat
Root Cause
During the INITIALIZING pass of VAE temporal slicing, the InflatedCausalConv3d decoder
processes the input in horizontal (H) strips for memory efficiency. When the strips are
reassembled with torch.cat, the resulting tensor has shape (1, 256, 5, 1088, 1920) =
2,684,354,560 total elements — exceeding the MPSGraph int32 index limit of 2,147,483,647.
The crash message is:
SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler
Page 3 of 6
RuntimeError: integer out of range for 'int'
Fix
Check the total element count before calling torch.cat. If it would overflow int32 on MPS, cat the
strips on CPU first, then move the result back to MPS. Unified memory makes this essentially
free.
total_elements = sum(xi.numel() for xi in x)
if x[0].device.type == 'mps' and total_elements > 2_147_483_647:
output = torch.cat([xi.cpu() for xi in x], split_dim).to(x[0].device)
else:
output = retry_on_oom(torch.cat, x, split_dim, ...)
Note: a previous workaround forced VAE tiling on MPS to avoid this crash by keeping tensors
below the INT32 limit. With this fix applied at source, the force-tiling code was removed. Tiled
VAE decode on MPS was taking 32+ minutes per chunk; non-tiled decode now completes
normally.
Bug 3: batched_sdpa zero-sized tensor crash in NaSwinAttention [CRASH]
Affected Files
• src/models/dit_3b/attention.py
• src/models/dit_7b/attention.py
• standalone_upscale.py (config wiring)
Root Cause
The _BATCHED_SDPA fast path calls NaSwinAttention.unconcat_coalesce, which on MPS +
PyTorch 2.12 returns a zero-sized txt_attn tensor. The subsequent ada modulation call fails with
a shape mismatch. The crash message is:
IndexError: index 9205498103099064256 is out of bounds for dimension 0 with
size 0
Fix
Disable batched_sdpa by default. The original code read the SEEDVR2_BATCHED_SDPA
environment variable at import time, before the config file was loaded, making it effectively
impossible to set reliably. Two changes:

  1. Change the default to off:
    _BATCHED_SDPA = os.environ.get("SEEDVR2_BATCHED_SDPA", "0") != "0" # default
    OFF
  2. Add a post-import setter function that standalone_upscale.py calls after reading the config:
    def set_batched_sdpa(enabled: bool) -> None:
    global _BATCHED_SDPA
    _BATCHED_SDPA = enabled
    standalone_upscale.py then calls set_batched_sdpa(config.get("batched_sdpa", False)) for both
    the 3B and 7B attention modules after loading the config JSON.
    SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler
    Page 4 of 6
    Bug 4: CustomRMSNorm bfloat16 sqrt NaN — root cause of completely
    black output video [SILENT NaN]
    Affected Files
    • src/models/dit_3b/normalization.py — CustomRMSNorm.forward()
    • src/models/dit_7b/normalization.py — CustomRMSNorm.forward()
    Root Cause
    MPS’s bfloat16 torch.sqrt kernel produces NaN for near-zero input values. This is a known bug
    in the MPS backend with no upstream fix as of PyTorch 2.12.
    In CustomRMSNorm.forward(), the RMS normalization computes variance =
    input.pow(2).mean(...) and then torch.sqrt(variance + eps). The K-vectors produced by the QKV
    linear projection in the first attention block contained many near-zero bfloat16 values. Feeding
    these through the MPS bfloat16 sqrt produced NaN in the norm_k output. NaN then propagated
    through all 32 transformer blocks, producing an entirely NaN output tensor from the DiT. The
    VAE decoded NaN latents as a completely black video with no error or warning printed.
    Diagnostic path taken:
    • [DIAG] after Phase 2 showed: min=nan max=nan mean=nan on upscaled latents
    • [DIAG-early] forward hook on first block output confirmed 100% NaN
    • [DIAG-src] in nadit.py: vid OK, txt OK, emb OK — NaN not present in inputs
    • [DIAG-blk] in mmsr_block.py: vid after attn_norm OK, vid after attn ❌ NaN
    • [DIAG-attn] in mmattn.py: vid_q after norm_q OK, vid_k after norm_k ❌ NaN
    (9,781,888 / 376,012,800 elements)
    The sqrt NaN in norm_k — but not norm_q — was explained by the distribution of activation
    values: K-projections happened to have more near-zero elements than Q-projections in this
    model’s first block.
    Fix
    Upcast to float32 for the variance and sqrt computation when on MPS with a non-float32 dtype,
    then restore the original dtype. This is identical to the pattern already used in rope.py for
    apply_rotary_emb.
    orig_dtype = input.dtype
    if input.device.type == 'mps' and orig_dtype != torch.float32:
    input_f32 = input.float()
    variance = input_f32.pow(2).mean(dim=dims, keepdim=True)
    rms = torch.sqrt(variance + self.eps)
    normalized = (input_f32 / rms).to(orig_dtype)
    else:
    variance = input.pow(2).mean(dim=dims, keepdim=True)
    rms = torch.sqrt(variance + self.eps)
    normalized = input / rms
    This fix covers every CustomRMSNorm instance in the DiT: attn_norm, mlp_norm (block
    norms), norm_q, norm_k (QK norms), and vid_out_norm.
    SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler
    Page 5 of 6
    Bug 5: pytorch_varlen_attention bfloat16 SDPA NaN on MPS (defense-indepth) [SILENT NaN]
    Affected Files
    • src/models/dit_3b/attention.py — pytorch_varlen_attention()
    • src/models/dit_7b/attention.py — pytorch_varlen_attention()
    Root Cause
    F.scaled_dot_product_attention with bfloat16 inputs on MPS does not have a native bf16 SDPA
    kernel. The MPS fallback path has precision issues that can produce NaN under certain input
    distributions. While Bug 4’s fix prevents NaN from entering SDPA inputs (via norm_k), SDPA
    itself is also fragile with bf16 on MPS and could produce NaN independently.
    Fix
    Upcast q, k, v to float32 before SDPA on MPS, then restore the original dtype on return from
    both the fast path and the slow per-sequence loop path:
    original_dtype = q.dtype
    if q.device.type == 'mps' and original_dtype != torch.float32:
    q, k, v = q.float(), k.float(), v.float()

... SDPA call ...

return out.to(original_dtype) # both fast-path and slow-path returns
This is applied independently of Bug 4’s fix. Even with correct RMSNorm output, bf16 SDPA on
MPS should be avoided.
Testing
All five fixes were validated on an Apple M5 Max with 128 GB unified memory running the
standalone_upscale.py runner against a real 4K video:
• No crashes during encode, upscale (DiT inference), or decode phases
• Decoded video contains correct image content (not black)
• Multi-chunk runs with temporal overlap produce smooth transitions
• VAE decode completes without tiling enabled, in normal time
Diagnostic Instrumentation
All [DIAG-*] prints added during debugging are guarded by a SEEDVR2_DEBUG=1
environment variable and are disabled by default. To re-enable diagnostic output for future
debugging:
SEEDVR2_DEBUG=1 python standalone_upscale.py config.json 2>&1 | tee debug.log
The diagnostic prints cover: per-batch latent NaN checks after VAE encode, first-block NaN
forward hook during DiT inference, per-stage input NaN reports in NaDiT.forward(), step-by-step
SeedVR2 — MPS (Apple Silicon) Compatibility Bug Report ComfyUI-SeedVR2_VideoUpscaler
Page 6 of 6
NaN trace in the first NaMMSRTransformerBlock, per-tensor NaN trace in NaSwinAttention, and
SDPA upcast confirmation in pytorch_varlen_attention.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions