Comfy-Org / Comfy-Org/ComfyUI

Wan 2.1/2.2 silent progressive output corruption on Apple Silicon MPS — temporal-length dependent, silicon-generation dependent, independent of precision/attention/torch version

Open
#15,793 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
133k
Forks
15.7k
Avg merge
1d 7h
Merged PRs (30d)
158

Description

|---|---|
| Hardware | MacBook Pro M3 Pro, 36GB | Mac Mini M4 Pro, 24GB |
| macOS | 26.6.2 (25G83) | 26.6 (25G70) AND 26.7 (25G220) — corrupt on both |
| torch tested | 2.15.0.dev20260819 (nightly), 2.13.0 | 2.12.1, 2.13.0 |
| ComfyUI | master `c67885b14556` (2026-08-19) | master `5ab2f7a` |
| Python | 3.12.14 | 3.12.x |
| Model | wan2.2_ti2v_5B_fp16.safetensors (Comfy-Org repackage) + GGUF Q8_0 | same fp16 repackage |

**Cross-machine divergence (key finding):** at 9 frames (3 latent), Machine A is
consistently CLEAN while Machine B is consistently corrupt (+126–169% saturation
end-drift, Laplacian collapse) — including i2v with pinned start frame, so content/
conditioning is not the differentiator. Corruption on Machine B reproduces across
macOS 25G70→25G220 and torch 2.12.1→2.13.0 while absent on Machine A at identical
settings. The graph-delta hypothesis was then eliminated directly: Machine A's exact
node topology (incl. its inert strength-0 LoRA chain, prompt/negative, RIFE ×2, VP9)
replayed on Machine B → still fully corrupt (+333% raw end-drift; prompt shifts severity,
not verdict). Post-processing was also exonerated as a metric mask on Machine B
(RIFE+VP9 moved corrupt metrics only ~4 points), so Machine A's clean 9f is genuinely
clean. **Structure-eliminated; exact-file replay pending; all evidence indicates
silicon-generation-dependent expression of the MPS defect** (M4 Pro corrupts at 3 latent
frames under all configs; M3 Pro clean at identical settings).
**Byte-exact cross-replay** (identical graph, start image, LoRA weights, model files,
ComfyUI commit `c67885b1`, torch 2.13.0; input SHAs dce3d8b0…/a853876e…/5bdbf699…):
M3 Pro +1.5% raw end-drift (clean, output sha f219fcda…); M4 Pro +43.7% with progressive
chroma contamination (structure coherent, blue patches spreading across skin/walls/fabric;
output sha eea5e584…). Combined with M4's full elimination matrix (dtype, torch 2.12/2.13,
core versions, attention backends, cfg 1/2/5, batch-of-1, conditioning content, macOS
25G70/25G220, deterministic byte-identical repeats), the defect's expression is
**silicon-generation-dependent**: M4 Pro (20-core GPU) corrupts at 3 latent frames where
M3 Pro (18-core) is clean.

Secondary datum for discussion: corruption severity on affected silicon is strongly
content-modulated (+43.7% on the mildest prompt/start-image measured, +105–481% on
others) — consistent with a numerically-unstable kernel whose error growth depends on
activation statistics rather than a hard shape threshold alone.

## Temporal boundary (Machine A, 832×480, 20 steps, uni_pc/simple, cfg 5, seed 5001)

| Video frames | Latent frames | Result |
|---|---|---|
| 9 | 3 | CLEAN — raw lossless per-frame metrics: peak +3.3% saturation drift mid-clip, +1.5% at end, Laplacian stable (355→388). (Earlier "+4.5% swell" was measured post-RIFE/VP9; raw-to-raw is the comparable number.) |
| 17 | 5 | TRANSITIONAL — parts of frame melting |
| 25 | 7 | FULL MELT |
| 49 | 13 | FULL MELT, progressive runaway |

Boundary arithmetic vs the uint32/2³¹ overflow class: at 7+ latent frames the temporal
attention reaches ≈ 24 heads × (11k)² ≈ 3.0e9 elements, crossing 2³¹ ≈ 2.15e9 — consistent
with the pytorch#179352 family. However (see falsification list) the melt persists under
ComfyUI's `--use-split-cross-attention`, which routes Wan's temporal attention away from
SDPA (module-level optimized_attention swap, attention.py:868 in master); the mechanism is
therefore **not confined to the SDPA kernel** — consistent with the broader uint32-overflow
class (pytorch#185663, pytorch#189960).

## Falsified as cause/fix (controlled, one variable per run, same seed)

- **Precision:** fp16, bf16 (`--bf16-unet`), fp32 (`--force-fp32`, 19GB weights) — all melt
(fp16 additionally overflows: solarized frames; bf16/fp32 melt identically to each other)
- **Attention implementation:** default SDPA vs `--use-split-cross-attention` (verified
active in logs) — identical melt
- **torch version:** 2.12.1, 2.13.0 stable, 2.15.0-dev nightly — metrically indistinguishable
- **Weight path:** safetensors fp16 vs GGUF Q8_0 (city96 loader) — identical melt →
not weight-handling/dequant
- **VAE:** regular vs temporally-tiled decode of the *same latent* — identically corrupt →
latent is corrupt before decode; not the (new-in-2.2) VAE
- **Resolution:** 832×480 vs native 1280×704 — both corrupt (704p slightly more stable) →
contributing, not root
- **CFG / text encoder:** cfg 1.0 vs 5.0 at 9f — both clean on Machine A → fp8 TE cleared
- **CPU fallback ops:** full 25f run with `PYTORCH_ENABLE_MPS_FALLBACK` unset — no crash,
no NotImplementedError, byte-identical melt → zero CPU-fallback ops involved; the defect
executes entirely in native MPS kernels
- **RoPE:** comfy_kitchen eager kernel verified numerically equivalent to reference impl
- **Graph:** ModelSamplingSD3 shift 8.0 present per official template; LoRA nodes removed
- **Memory pressure:** deterministic recolor, not OOM/stutter; 36GB and 24GB machines
show length-thresholded (not memory-thresholded) behavior

## Determinism

Both machines produce byte-identical outputs across repeated same-seed runs (Machine B
verified via SHA-256 across repeats; Machine A's fallback-unset run reproduced its prior
melt byte-exactly). Cross-replay reference outputs: Machine A clean `f219fcda…`,
Machine B corrupt `eea5e584…` from identical input bytes.

## Prior art

- pytorch#179352 — SDPA silent corruption >2³¹ elements on MPS (Apple-confirmed);
boundary arithmetic matches, but see split-attention persistence above
- pytorch#185663, pytorch#189960 — broader uint32-overflow class in MPS kernels
- pytorch#187280 — MPSGraph OOB read, macOS-version-dependent, memory-state-sensitive
- Comfy-Org/ComfyUI#11574 — same model (TI2V-5B) corrupt on ROCm, 14B clean, unresolved

## Repro

Minimal API graph (both machines): UNETLoader(wan2.2_ti2v_5B_fp16) → ModelSamplingSD3(8.0)
→ KSampler(seed 5001, 20 steps, cfg 5, uni_pc/simple) with Wan22ImageToVideoLatent
(832×480, length 9 vs 49, photographic start image) → VAEDecode → SaveWEBM.
Compare length 9 vs 49. Full scripts, frame-metric tooling (per-frame Laplacian variance +
saturation drift), and the 18-row Machine A / 9-row Machine B run logs available on request.

## Ask

Triage guidance: is this reproducible on Apple's side at the 3.0e9-element temporal
attention size outside SDPA? Which MPS kernels does the Wan temporal path hit in the
split-attention configuration? We can run instrumented builds on both machines.

Contributor guide

Open the contributing guide

Research direction

Start with the minimal API graph in the issue and compare 9-frame versus 49-frame runs on Apple Silicon. Read ComfyUI's attention.py around line 868 and inspect the split-cross-attention path, then identify which MPS kernels the Wan temporal path uses. Done means determining whether the corruption reproduces outside SDPA and providing triage guidance or instrumented-build evidence.

Written by the indexing model from the issue text.

Assessment

Tech stack
python, pytorch
Domain
machine-learning, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.