This optional path runs MOSS-Image through PyTorch Metal/MPS. Recorded real-weight validation is on an M1 Max / 32 GB unified-memory Mac, with SFT. Other chips, lower-memory systems, Base and older dependency versions need testing. No quantization is applied.
Use native arm64 Python 3.12 and a compatible macOS/PyTorch installation. Allow about 40 GB free disk for one full model bundle (~25 GB), the environment and download files. From the cloned repository root:
MOSS_PYTHON=python3.12 bash scripts/setup-mac.sh
.venv/bin/hf download OpenMOSS-Team/MOSS-Image-SFT --local-dir models/MOSS-Image-SFT
bash scripts/gradio-mac.shOpen http://127.0.0.1:7860. Start with 512×512, 10 steps and one image. Keep the terminal
running; loading and inference can take minutes. Original PNGs and parameters.json are saved
under outputs/gradio/. Stop with Ctrl+C. Subsequent runs only need the launch command.
The Mac launcher uses local files only and disables Gradio analytics.
constraints-mac.txt lists the versions used for the recorded local runs: torch 2.14.1,
diffusers 0.41.0, transformers 5.20.1, accelerate 1.15.0 and Gradio 6.30.0. These constraints
apply to the optional Mac setup script, not to the existing installation path.
The launcher defaults the DiT to FP16. To compare BF16:
MOSS_MPS_DIT_DTYPE=bfloat16 bash scripts/gradio-mac.shThe text encoder remains BF16 and the VAE FP32. Direct mps_runtime.load_pipeline defaults
to BF16 unless MOSS_MPS_DIT_DTYPE is set before import. CUDA offload modes and Compile DiT
do not apply to this staged MPS path; the UI disables those controls on MPS.
The Qwen encoder (~17 GB) and DiT (~10 GB) are staged so only one is resident on MPS at a time. Positive and negative prompts are encoded before the DiT is loaded. An eight-entry LRU prompt cache permits reuse when only seed, dimensions or sampling settings change.
The downloaded custom pipeline computes a timestep sinusoid in FP64, unsupported by MPS. The small sinusoid operation is moved to CPU at FP64 and its result copied back. Because the standalone pipeline is distributed in the model bundle, this currently uses a narrowly matched source replacement and stops explicitly if that source changes. The ComfyUI model has the equivalent conditional CPU calculation; the full ComfyUI path was not validated here.
A locally observed loading deadlock involved pending Metal copies and cache/synchronization
calls holding the GIL. The current workaround drains copies through a CPU transfer before
calling those methods. It wraps torch.mps.empty_cache and torch.mps.synchronize when MPS
is available, including other callers in the process; that process-wide effect is an explicit
review point for this experimental implementation.
Recorded SFT calls on M1 Max / 32 GB:
| Output | Settings | Full recorded generation call |
|---|---|---|
| Chinese chalkboard | 1024×1024, 20 steps, BF16, seed 42 | 320.362 s |
| English character posters | 768×1024, 28 steps, FP16 | 395.150 / 367.596 / 344.072 s |
Images, original metadata and prompts are available as validation evidence. These are end-to-end records, not controlled throughput, precision-equivalence or peak-memory measurements. CUDA execution is retained but was not hardware-regression-tested by this Mac contribution.
CPU regression checks, without downloading weights:
python -m pip install -e '.[ui]' -c constraints-mac.txt
python -m unittest discover -s tests -p test_mps_runtime.py -vThe checks cover staging order, prompt cache eviction, CPU sinusoid arithmetic, source-change failure, MPS routing and UI construction. Real MPS generation remains a separate hardware check. If MPS is unavailable, verify native arm64 Python and macOS compatibility. For memory pressure, reduce resolution/count and close heavy applications. For a source-patch mismatch, report the model revision and error rather than bypassing the check.