Writing
在 M2 Ultra 128GB 上把 H3 跑起來:我的 mlx-h3 運行參數
這篇文章,我分享一下如何在我的 macOS 上,把 MiniMax H3 這個模型成功跑起來。
為何我要堅持在 macOS 跑起來這件事呢?
這是因為,8bit 量化永遠比 nvfp4 具備更好的質量,無論在提示詞的遵守能力,以及畫面元素的運動表達下。
weights/
├── tokenizer/tokenizer.json
├── mlx-8bit/te_qwen3vl_a8g32.safetensors
├── mlx-8bit/dit_fl2va_a8g32.safetensors
├── mlx-8bit/dit_ref2va_a8g32.safetensors
├── adapters/minimax-h3-turbo/
│ ├── minimax_h3_turbo_v4_step600_ema.safetensors
│ └── minimax_h3_turbo_4step_ema_ckpt850.safetensors
└── bf16/vae/
├── minimax_h3_video_vae_fp16.safetensors
└── minimax_h3_audio_vae_fp32.safetensors為了在 M2 Ultra 128g 平穩時間長時間運行,分段和 AGX RELAX 是必不可少的參數。
QKV 2,048/SDPA 512/MLP 2,048 三段 chunking + AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1然後,我的 4070Ti 12G vRAM 機器,作為快速預覽機。
故事流程
- H3.c 成功編譯、通過 1,768 項檢查,也能生成短片。
- 真正的長序列正常退出,但畫面與聲音靜默失效。
- H3.c 的 MPSGraph SDPA 可以用 2,048-row query chunking 避開部分錯誤,但速度代價與其他畫質問題促使我轉向
mlx-h3。 mlx-h3先跑通 18,916-row、28,548-row 任務,接着挑戰 1280×736、362-frame Base-20。- 首次 DiT 完成後才在 VAE 發現 non-finite;因為沒有保存 latent,數小時運算無法恢復,於是我加入 rolling checkpoint。
- 從 step 4 resume 時,顯示器被喚醒,step 5 出現 ImpactingInteractivity watchdog kill。
- 網絡資料顯示,display-active MLX 工作和 65K+ full SDPA 都可能觸發 watchdog;AGX relaxation 只是 workaround,chunked full-attention PR #3307 也已關閉、未合併到我使用的 MLX v0.32.2。
- 單獨加入
AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1後,明確 Metal error 變成全 NaN。它改變了失敗表面,沒有修好 correctness。 - block/stage probe 把 non-finite 前移到 raw Q;QKV 2,048 讓前六個 blocks finite,但 block 7 仍被 watchdog 終止。
- SDPA 降到 512 後,單次工作變短,但 block 3、chunk 154/205 仍被 display-active watchdog 終止。
- DeepSeek Pro Max 根據已有證據,獨立建議用
QKV 2048 + SDPA 512 + AGX=1 + finite guards做 bounded step-5 qualification,並判斷 chunk 154 沒有特殊數學意義。 - 解題目標由證明 macOS driver 的唯一內部真相,轉為把會觸發 watchdog 的工作拆成較短、受保護、可恢復的單位。
- bounded step 5 通過 50/50 blocks,peak 39.2 GiB、swap 0,之後完成 20 steps。
- 成片仍從 decoded frame 251 開始花屏;decoder-only replay 排除 VAE、FFmpeg 和 mux。
- 最後定位到 104,595-row MLP
fc1約 5.586 GiB 的 intermediate;row 74,899 對應 4 GiB 邊界。加入 MLP 2,048 後,完整生成通過。
Kimi K3 與 DeepSeek Pro Max:兩條解題路線
兩個顧問都把問題拆成大型 QKV correctness 與 display-active watchdog,但完成標準不同。
| 項目 | Kimi K3-256K max | DeepSeek V4-Pro max |
|---|---|---|
| 主要路線 | 繼續追 mechanism:online K/V softmax-LSE、每個 K/V chunk 單獨 eval,再用 falsifier matrix 區分機制。 | 先做 bounded qualification:QKV 2,048、SDPA 512、AGX=1、stage/block finite guards。 |
| 重點判斷 | 大型 fused QKV 的 index/offset 邊界,和 display-active watchdog,是兩個獨立故障。 | 同樣拆成兩個故障,並量化未分塊 QKV 約 4,498,421,760 bytes,超過 2^32。 |
| 本機驗證 | 有兩項上下文誤讀:把當前 run 當成已啟用 AGX,也把未採用的 PR #3307 LSE workspace 當成現有負擔,所以「不要再測 AGX」沒有採納。 | 建議的 bounded 組合通過 50/50 blocks,checkpoint 到 5/20;但這不代表完整 correctness 已解決。 |
Kimi 的路線是繼續追查 mechanism;DeepSeek 的路線是用受控實驗繞開觸發點。兩條路都有價值,而這次先取得本機完整進展的是後者。
參數來源和證據邊界
模型、runtime 與版本
- MiniMax-H3 官方模型:tokenizer、Video VAE、Audio VAE 與官方模型 contract。
- minimax-h3-base-8bit-mlx:這次使用的 W8A16 Text Encoder/DiT bundle。
- qoli/mlx-h3 fork(main):我維護的 mlx-h3 fork,收錄本文使用的長序列執行修正。
- commit 76cde5a:QKV、SDPA、MLP 分塊、finite guards、checkpoint 與長序列執行邊界的精確版本 permalink。
網絡資料
- MLX #3267:display-active 時的
ImpactingInteractivity、display asleep 對照,以及 AGX relaxation 只屬 workaround。 - MLX #3302:65K+ full SDPA、單一 command buffer 與 GPU watchdog 風險;AGX 不應被當成永久安全修復。
- MLX PR #3307:chunked full-attention/online-LSE 的探索。PR 已 closed、未合併,不能假定 MLX v0.32.2 已包含,也不能直接把短 query 假設套到 H3 的
qL=kL=104,595。 - PortOS watchdog research、mlx-lm #1662 和 Apple Developer thread 653206:只作 display-active/WindowServer watchdog 故障家族的旁證,不是本次 H3 唯一 root cause 的證明。
本機觀察與實作
- QKV 2,048:未分塊 QKV 約 4,498,421,760 bytes,raw Q 首次 non-finite;2,048-row chunking 在本機通過。
- SDPA 512:我在 SDPA 2,048 遇到 watchdog 後先自行採用並測試;DeepSeek 後來獨立提出相同數值。
- MLP 2,048:本機 forensic 發現
fc1約 5.586 GiB、row 74,899 對應 4 GiB 邊界;2,048-row chunk 約 112 MiB,並通過完整生成。 - 70 GiB guard、rolling checkpoint、finite guard 和 swap guard,都是這次 本機運行設計。
顧問意見
- DeepSeek Pro Max 建議用
QKV=2048、SDPA=512、AGX=1與 finite guards 做 bounded step-5 qualification,並判斷 chunk 154 更像 display-active scheduling deadline,而不是特殊數學邊界。 - 4 GiB/32-bit overflow 不能寫成已證明的 MLX upstream root cause。直接證據是 raw Q 首次 non-finite,以及 QKV chunking 後消失。
我最後使用的設定
模型檔案來自 MiniMax-H3 官方權重與 8-bit MLX bundle:
MLX 8-bit Text Encoder:W8A16
MLX 8-bit Ref2VA DiT:W8A16
Video VAE:FP16
Audio VAE:FP32
Tokenizer:MiniMax tokenizer.json完整生成命令的核心是:
AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1 \
uv run mlx-h3 \
--prompt-file "$MLX_H3_PROMPT_FILE" \
--width 1280 \
--height 736 \
--frames 362 \
--steps 20 \
--seed 42 \
--budget 70 \
--ref-image-size max \
--ref-image "$MLX_H3_REFERENCE_IMAGE" \
--qkv-projection-chunk-size 2048 \
--sdpa-query-chunk-size 512 \
--mlp-row-chunk-size 2048 \
--latent-cache outputs/result.mp4.latents.safetensors \
--output outputs/result.mp4實際長任務還應該放在 caffeinate -is 下運行,避免系統睡眠。重新執行時不要建立另一個輸出 identity,而是用完全相同的 prompt、references、weights、geometry、seed 和 sampling profile,加入:
--resume-checkpoint outputs/result.mp4.latents.safetensors如果 20 steps 已經完成,只是在 VAE 或 mux 階段失敗,則不應再跑一次 DiT:
uv run mlx-h3 \
--decode-latents outputs/result.mp4.latents.safetensors \
--output outputs/result.mp4這些參數代表甚麼
1280×736、362 frames
mlx-h3 的畫布長寬必須是 32 的倍數,總面積不能超過 768 × 1344。1280×736 在這個限制內。
Video VAE 的 frame count 使用 17n + 5 對齊。362 frames 在 24 fps 下是 15.083 秒,也是我這次完整品牌片的長度。
這個尺寸不是「Mac 能跑 H3」的最低要求。低成本驗證可以先用 416×224、124 frames;我也用 864×480、192 frames 跑過 8 秒、四張 Ref2VA 圖片的小貓故事。那次 sequence length 是 28,548,80.8 分鐘完成,peak MLX allocation 為 28.7 GiB。
W8A16,而不是先追求 W8A8
8-bit DiT 和 Text Encoder 的目的首先是降低 residency。計算時 activation 仍然是 BF16,所以 W8A16 並不是用低精度換取一個漂亮的速度數字。
我沒有在這次 M2 Ultra profile 裡加入 NAX W8A8,也沒有使用 Turbo LoRA 或 cache approximation。我要先建立 H3 Base 的品質基準,不想同時疊加 sampler、量化和 cache 近似帶來的變數。
Video VAE 保持 FP16,Audio VAE 則使用 FP32。影片和音頻在 H3 中共同 denoise,但最後由不同 VAE 解碼;音頻曾經生成接近滿幅的噪聲,所以我沒有再在 Audio VAE 上壓低精度。
Base 20 steps、simple + res_multistep
20 steps 是這個 runtime 的品質基線。Base 路徑使用 simple schedule 和二階 res_multistep solver,影片與音頻在同一個 joint sequence 中一起前進。
seed 42 只是讓驗證可重現,不是一個提升品質的魔法數字。
QKV 2,048、SDPA 512、MLP 2,048
這三個 chunk size 處理三個不同邊界,而且來源不完全相同。
--qkv-projection-chunk-size 2048 來自本機 block/stage probe 與 chunking 實作。未分塊 QKV 約 4,498,421,760 bytes,raw Q 首次出現 non-finite;改成 2,048-row chunking 後,這個失敗在實機消失。這支持「大型 fused output 是觸發條件」,但不足以把 32-bit overflow 寫成已證明的 MLX upstream root cause。
--sdpa-query-chunk-size 512 是我在 2,048-row SDPA 仍遇到 watchdog 後先自行採用的數值;DeepSeek Pro Max 後來獨立提出相同方向。MLX #3302 提供的是長 full-SDPA command buffer 的 watchdog 背景,不是這個 H3 數值的來源。512 把 query 分段,縮短每個 full-attention Metal command buffer;每段 query 仍然看到完整 key/value sequence,所以不是 local attention,也沒有近似 attention semantics。512 單獨仍曾在 chunk 154/205 被 watchdog 終止,真正通過的是後來的 bounded 組合。
--mlp-row-chunk-size 2048 則來自成片 decoded frame 251 之後的 forensic。H3 的 fc1 每個 sequence row 輸出 28,672 個 BF16 values;sequence length 104,595 時,未分塊 fused output 是 5.586 GiB。污染對應 packed row 74,899,也就是 output 跨過 4 GiB 的位置。這是很強的本機邊界相關性,不等於已證明底層 driver 的唯一原因。
2,048-row MLP chunk 約 112 MiB。它不改變每一 row 的計算,只是不再要求 Metal 一次 materialize 超過 5 GiB 的中間 tensor。
AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1
這不是 MLX 的品質參數。它放寬 macOS 對長時間 Metal command 的 interactivity watchdog。
MLX #3267 與 MLX #3302 分別支持 display-active workload、長 full-SDPA command buffer 與 watchdog 風險,但不能證明這次 H3 的唯一 root cause。我的本機結果也顯示,單獨開啟 AGX 後,明確 Metal error 一度變成全 NaN;它只改變失敗表面,沒有修好 correctness。
真正的轉折,是不再等待一個已證明的 driver 內部真相才繼續,而是把 QKV、SDPA 和後來的 MLP 工作拆短,加入 finite、swap 和 checkpoint 保護,再用 bounded step 5 驗證。這個組合通過 50/50 blocks 後,才繼續完整 20 steps。
代價是桌面可能出現更長時間的停頓,所以 mlx-h3 沒有把它做成預設值。我只會在長序列、登入中、display-active 的 Mac 上明確開啟。
70 GiB memory budget
--budget 70 不是說這次生成必須吃掉 70 GiB,而是這台 128GB Mac 的 fail-fast 上限。
實際 1280×736 sustained run 的 DiT active memory 約 21.8 GiB,peak 約 36.6 GiB,沒有增加 swap。我之前因舊 guard 的 free-memory 判斷遇到 false positive,所以這裡只把 70 GiB 當作安全柵欄,不當作預期運行值。
每一步都保存 checkpoint
第一次 1280×736 Base-20 跑完 DiT 後,Video VAE 才顯示 frames 已經包含 non-finite values。當時沒有保存 latent,前面的數小時計算完全無法恢復。
現在每個 finite sampling step 都會 atomic replace 同一個 rolling checkpoint。失敗的 step 不會覆蓋上一個可用狀態;request、model file、reference、schedule 或 geometry 對不上時也會直接拒絕 resume。
checkpoint 對這種任務不是方便功能。最終 run 由已驗證的 step 1 checkpoint 接續,完成剩餘 steps、Video VAE、Audio VAE 和 mux,該段 log 記錄 694.2 分鐘。
為甚麼我最後沒有繼續使用 H3.c
我最早選擇的是 antirez/h3.c。它的吸引力很直接:原生 C、Apple Silicon/Metal、可以使用官方 BF16 模型、沒有 Python 和 ComfyUI,而且作者本身已經能帶來很高的開發者注意力。
截至 2026 年 9 月 1 日,專案建立約三星期,GitHub 已經有約 2,520 stars 和 186 forks。這種聲量很容易放大一個印象:MiniMax H3 已經在 Mac 上跑起來了。
但我的實際驗收標準不是 binary 成功退出,也不是目錄裡出現了一個 MP4。
H3.c 可以通過自己的 host checks、tokenizer tests 和 Metal AudioVAE tests,也可以生成一支正常的短片;它同樣可以在沒有 OOM、NaN、Metal exception 或 non-zero exit 的情況下,輸出整段灰色紋理和全頻噪聲。
在我重現的長序列上,2,048-row query chunking 可以避開 MPSGraph SDPA 的靜默錯值,代價是 DiT 約慢 2.51 倍。這是本機 workaround 證據,不是對 display driver 唯一 root cause 的證明。更重要的是,在只有 4,420 rows、遠低於該失敗範圍的白熊動作中,局部融化、毛髮結塊和動作跳變仍然存在。這是另一個品質問題,不能拿 SDPA workaround 當答案。
H3.c Repo 它拿到了明星開發者的名氣,但是沒有對應的質量。我在 H3.c 如何繼續使用這件事,我沒辦法推進了,我認為問題發生在 MPSGraph 領域。
因此,我切換到 upstream appautomaton/mlx-h3,並在我維護的 qoli/mlx-h3 fork 完成這次長序列修正。它使用 MLX Steel/Metal attention,先在相同的 M2 Ultra 上完成 18,916-row 和 28,548-row 任務,沒有重現 H3.c 的長序列 block corruption;再經過 QKV、SDPA、MLP 分塊和 checkpoint 修正,完成 104,595-row 的完整生成。
這不是一個嚴格的 H3.c 對 mlx-h3 畫質排行榜。兩邊的 sampler、量化、RNG 和部分 conditioning 並不完全相同。我能確認的是:在我真正需要的長度和輸出驗收下,mlx-h3 成為了可以繼續工作的 runtime。
目前,這就是我在 M2 Ultra 128GB 上運行長序列 H3 Base 的設定:
W8A16
Base 20
QKV 2048
SDPA 512
MLP 2048
70 GiB guard
rolling checkpoint
AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1