Skip to content

feat: Apple Silicon (MPS) compatibility — fix PR #170 quality & memory issues - #816

Open
donghao1393 wants to merge 6 commits into
lllyasviel:mainfrom
donghao1393:mps-compat
Open

feat: Apple Silicon (MPS) compatibility — fix PR #170 quality & memory issues#816
donghao1393 wants to merge 6 commits into
lllyasviel:mainfrom
donghao1393:mps-compat

Conversation

@donghao1393

@donghao1393 donghao1393 commented Jul 19, 2026

Copy link
Copy Markdown

Summary

Adds native Apple Silicon (MPS) support to FramePack, resolving the three quality/memory thresholds documented in #170 by @brandon929: blown-out colours, blocky noise, and OOM.

Changes (6 files, +144/-32)

File Change
diffusers_helper/memory.py Auto-detect GPU: MPS → CUDA → CPU. MPS memory reporting via psutil with sysctl fallback. Unified _empty_cache() helper replaces scattered torch.cuda.empty_cache() calls.
diffusers_helper/models/hunyuan_video_packed.py Guard torch.backends.cuda checks (no-op on Mac). Dynamic device in get_cu_seqlens — uses text_mask.device instead of hardcoded "cuda".
diffusers_helper/utils.py print_free_mem() with MPS fallback. save_bcthw_as_mp4() uses PyAV when torchvision >=0.28 removed write_video.
diffusers_helper/dit_common.py LayerNorm/RMSNorm compute in fp32 internally — eliminates cumulative bf16 truncation across ~21,000 block boundaries that caused blown-out colours (#170 problem 3). Output cast back to input dtype for downstream GEMM compatibility.
demo_gradio.py Adaptive VAE decode: direct on ≥96 GB free RAM (Studio-class machines), chunked otherwise (128 GB Mac safe). HF_HOME override disabled — uses global ~/.cache/huggingface instead of a local hf_download/ directory (avoids re-downloading 45 GB if models are already cached).
requirements.txt Relaxed version pins for MPS-compatible package versions.

Backward Compatibility

All CUDA code paths are preserved behind is_available() guards. Existing CUDA users are unaffected — this is purely additive.

Tested

  • Hardware: M4 Max 40-core GPU, 128 GB unified memory
  • Steps: 10–15 steps recommended (MPS sweet spot; 25 steps shows minor dark-region drift per Amdahl + UniPC bf16 amplification)
  • Memory: High-VRAM path runs at ~80 GB peak (no swap). Low-VRAM path (DynamicSwap) preserved for <96 GB machines.

Demo — FramePack on Apple Silicon (M4 Max)

20260715_201422_flux-2-pro_1920x1088_A_sleek_modern_fighter_jet_str_c16f8483 ough clouds at sunset*

*Input image — a fighter jet soaring thr


CleanShot 2026-07-19 at 19 04 08@2x

Gradio WebUI running natively on Mac — no CUDA required. High-VRAM mode detected (128 GB unified memory), all models on MPS.


260719_172543_926_3463_37.mp4

Generated video: 5 seconds, 15 steps, 480×832 resolution. Output is temporally coherent with crisp detail — no blown-out colours, no blocky noise, no structural collapse (issues documented in #170, resolved by fp32 LayerNorm computation and adaptive VAE chunking).
Prompt: Launches a missile, turns right and banks away, cinematic aerial combat (I don't know why the missile not launched, maybe the model did not spot out the missile.


Quick start on Mac:

pip install -r requirements.txt
python demo_gradio.py

Models auto-download to ~/.cache/huggingface on first run. 10–15 steps recommended for MPS (optimal quality/speed balance).

Additional Context

This work implements the Mac compatibility layer discussed in #170 — the blown-out colours at the "640" resolution bucket are resolved by the fp32 norm computation. The adaptive VAE chunking prevents OOM on machines with ≤128 GB unified memory.

© 2026 董昊.

- memory.py: auto device detection (MPS→CUDA→CPU), MPS memory via psutil,
  unified _empty_cache()
- hunyuan_video_packed.py: torch.backends.cuda guard, dynamic cu_seqlens device
- utils.py: MPS print_free_mem, PyAV mp4 save fallback
- dit_common.py: LayerNorm/RMSNorm fp32 internal compute to prevent bf16
  accumulation drift on MPS
- demo_gradio.py: global HF cache, adaptive VAE (direct on ≥96GB, chunked
  otherwise for 128GB Mac safety)
- requirements.txt: relaxed version pins for MPS compatibility

All CUDA code paths preserved when CUDA is available.
Backward compatible — no breaking changes for existing CUDA users.

Fixes PR lllyasviel#170: blown-out colours, blocky noise, OOM on Apple Silicon.
© 2026 董昊.
On ≥128 GB Macs (free >= 82 GB): offload DiT to CPU before VAE decode,
use direct vae_decode() instead of chunked — eliminates chunk-boundary
soft-blending entirely, removing ghosting on fast limb motion.

DiT is reloaded automatically at the next section's sampling start
(device check).  Offload/reload overhead ~2s/section.

Lower-memory Macs (≤96 GB) keep the chunked path unchanged.
@donghao1393
donghao1393 marked this pull request as draft July 19, 2026 17:04
MPS SDPA backend loses bf16 softmax precision beyond ~3068 tokens.
Chunk Q into 3068-token windows with full K/V (mathematically exact).
Also fixes varlen path that raised NotImplementedError on MPS.

CUDA paths unchanged.
MPS bf16 SDPA loses ~2x softmax precision vs fp32 on large sequences
(14k+ tokens), manifesting as edge smearing / stutter on fast limb
motion.  Convert Q/K/V to fp32 before attention, cast output back.

Overhead: ~1ms dtype conversion per attention call.
@donghao1393

donghao1393 commented Jul 20, 2026

Copy link
Copy Markdown
Author

Update: fp32 chunked SDPA — fast-motion ghosting resolved

The latest commit (77ead07) upgrades the MPS attention path to fp32 compute inside the chunked SDPA kernel. MPS bf16 softmax loses ~2× precision at FramePack's 14k+ token scale, causing edge smearing and frame-to-frame stutter on rapid limb motion (dancing, hand-waving). Converting Q/K/V to fp32 before attention eliminates this — the output is cast back to the input dtype for downstream compatibility.

Results

Before (bf16 SDPA): hands and feet appeared as smeared blobs during fast motion — "ghost fist" effect.

After (fp32 SDPA): limbs are clearly resolved frame-to-frame. The dancer's hands and feet maintain structural integrity even at peak velocity. A slight softness remains on the fastest-moving edges — this is residual bf16 accumulation across ~900 DiT block passes (60 blocks × 15 steps), below the detection threshold of any individual operator and attributable to MPS hardware-level bf16 accumulation — same category as the avg_pool3d / upsample_nearest3d MPS fallback issues documented in #170.

Demo

  • Rigid-body (fighter jet)
260720_205630_332_8244_10.mp4

1 s, 15 steps — crisp, zero ghosting

  • Fast-motion (dancer)
260720_202648_046_8466_10.mp4

1 s, 15 steps — limbs resolved, slight softness on fastest edges

All 16 MPS compatibility fixes (device detection, fp32 norms, chunked+fp32 SDPA, adaptive VAE, PyAV mp4, global HF cache) are now integrated and tested on M4 Max 128 GB. Ready for review.

@donghao1393
donghao1393 marked this pull request as ready for review July 20, 2026 17:20
donghao1393 added a commit to donghao1393/pytorch that referenced this pull request Aug 3, 2026
bf16/fp16 softmax loses numerical precision on sequences > ~3068
tokens — MPSGraph softMax does not internally accumulate in fp32
for half-precision inputs.  At 14k tokens the relative error vs
fp32 grows to ~2.4%, producing visible edge smearing in video DiT.

Split Q into 3068-token chunks when dtype is bf16 or fp16 and
qSize exceeds threshold.  K/V are kept full-size for the complete
softmax denominator — row-independence makes this exact.

Test Plan:
  python -c "import torch; q=torch.randn(1,24,14000,128,device='mps',dtype=torch.bfloat16); k=torch.randn(1,24,14000,128,device='mps',dtype=torch.bfloat16); v=torch.randn(1,24,14000,128,device='mps',dtype=torch.bfloat16); out=torch.nn.functional.scaled_dot_product_attention(q,k,v); print(out.shape)"

Discovered via FramePack image-to-video generation (lllyasviel/FramePack#816).
Co-authored with AI assistant.
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.

1 participant