fix: run text encoders on MPS instead of CPU on Apple Silicon#14770
fix: run text encoders on MPS instead of CPU on Apple Silicon#14770ChrisLundquist wants to merge 2 commits into
Conversation
On Apple Silicon vram_state is VRAMState.SHARED (unified memory), but text_encoder_device() only returned the GPU for HIGH_VRAM/NORMAL_VRAM, so text encoders ran on the CPU. For LM-style encoders like ACE-Step 1.5 the text encode stage dominates generation time on Mac. This re-lands Comfy-Org#12809, which was reverted in Comfy-Org#13070 because it broke quantized text encoders on Mac: the MPS backend cannot cast float8 dtypes (pytorch/pytorch#132624), and the existing supports_cast() fallback in CLIP.__init__ only inspects declared model dtypes, which never reflect fp8 weights behind comfy_quant quantization metadata (QuantizedTensor reports the compute dtype, not the fp8 storage dtype). To keep quantized text encoders working, CLIP.__init__ now decides the device before any weights are allocated: it checks supports_cast() on the resolved dtype, and when the load device cannot cast fp8 it scans the incoming state dict for fp8 tensors or comfy_quant markers and keeps those text encoders on the offload device. Devices that can cast fp8 (cuda etc.) skip the scan entirely. The pre-existing shift-back path now also updates the current device so the load log stays accurate. Adds MPS unit tests: fp16 placement on the GPU, fp8 fallback via declared dtype, secondary dtype (dtype_llama style), state-dict fp8 weights, and comfy_quant markers, plus a canary that fails when a torch release adds fp8 casts on MPS so the fallback can be relaxed.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📜 Recent review details🧰 Additional context used📓 Path-based instructions (2)**/*.py📄 CodeRabbit inference engine (AGENTS.md)
Files:
**⚙️ CodeRabbit configuration file
Files:
⚙️ CodeRabbit configuration file
Files:
🧠 Learnings (1)📚 Learning: 2026-02-21T14:01:41.482ZApplied to files:
🔇 Additional comments (2)
📝 WalkthroughWalkthroughChangesThis pull request updates text encoder device selection to include the Sequence Diagram(s)Not applicable; the change is mainly conditional device-selection logic plus unit tests. Related Issues: Not specified. Related PRs: Not specified. Suggested labels: bug, tests, mps Suggested reviewers: Not specified. Poem 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Pins that supports_cast() lets fp8-capable devices skip the quantized text encoder fallback (these run on non-MPS CI too), that low vram states still place text encoders on the CPU, and adds bf16 placement, dict-form (full checkpoint) state dicts, and non-tensor state dict entries to the MPS tests.
Re-lands #12809 (reverted in #13070) with guards that keep fp8/quantized text encoders on the CPU.
Problem
On Apple Silicon
vram_stateisVRAMState.SHARED, sotext_encoder_device()never returns the GPU and text encoders always run on the CPU. For LM-style encoders this dominates generation time: ACE-Step 1.5 on an M5 Max spends 6m19s in LM sampling on CPU vs ~40s on MPS (measured via--gpu-only, which takes the same placement path). Reported for MPS in #12271.Why #12809 was reverted, and how this addresses it
PyTorch's MPS backend has no float8 support beyond raw storage — any op touching an fp8 MPS tensor, including the
.to(fp16)dequantization cast, raisesTypeError(pytorch/pytorch#132624, still open, not fixed in nightly). The existingsupports_cast()fallback inCLIP.__init__only inspects declared model dtypes, which never reflect fp8 weights behindcomfy_quantmetadata (QuantizedTensorreports the compute dtype, not the fp8 storage dtype). So #12809 let quantized text encoders load onto MPS and crash at encode time.Changes
VRAMState.SHAREDtotext_encoder_device()—SHAREDis only ever set on MPS, so other platforms are unaffectedCLIP.__init__decides the device before any weights are allocated: it checkssupports_cast()on the resolved dtype, and when the load device cannot cast fp8 it scans the incoming state dict for fp8 tensors orcomfy_quantmarkers and keeps those text encoders on the offload device; devices that can cast fp8 (cuda etc.) skip the scan entirelyTests
tests-unit/comfy_test/text_encoder_mps_test.py(skipped off-MPS; runs on macOS CI runners): fp16 placement on the GPU, fp8 fallback via declared dtype / secondary dtype (dtype_llamastyle) / state-dict fp8 weights /comfy_quantmarkers, and a canary that fails when a torch release adds fp8 casts on MPS sosupports_cast()can be relaxed.Verified with real checkpoints on an M5 Max (torch 2.10.0): the ACE-Step qwen 0.6b+4b text encoders load on
mps(CLIP/text encoder model load device: mps); the same LM checkpoint cast to fp8 stays oncpu.🤖 Generated with Claude Code