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 機器,作為快速預覽機。

故事流程

  1. H3.c 成功編譯、通過 1,768 項檢查,也能生成短片。
  2. 真正的長序列正常退出,但畫面與聲音靜默失效。
  3. H3.c 的 MPSGraph SDPA 可以用 2,048-row query chunking 避開部分錯誤,但速度代價與其他畫質問題促使我轉向 mlx-h3
  4. mlx-h3 先跑通 18,916-row、28,548-row 任務,接着挑戰 1280×736、362-frame Base-20。
  5. 首次 DiT 完成後才在 VAE 發現 non-finite;因為沒有保存 latent,數小時運算無法恢復,於是我加入 rolling checkpoint。
  6. 從 step 4 resume 時,顯示器被喚醒,step 5 出現 ImpactingInteractivity watchdog kill
  7. 網絡資料顯示,display-active MLX 工作65K+ full SDPA 都可能觸發 watchdog;AGX relaxation 只是 workaround,chunked full-attention PR #3307 也已關閉、未合併到我使用的 MLX v0.32.2
  8. 單獨加入 AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1 後,明確 Metal error 變成全 NaN。它改變了失敗表面,沒有修好 correctness。
  9. block/stage probe 把 non-finite 前移到 raw Q;QKV 2,048 讓前六個 blocks finite,但 block 7 仍被 watchdog 終止。
  10. SDPA 降到 512 後,單次工作變短,但 block 3、chunk 154/205 仍被 display-active watchdog 終止。
  11. DeepSeek Pro Max 根據已有證據,獨立建議用 QKV 2048 + SDPA 512 + AGX=1 + finite guards 做 bounded step-5 qualification,並判斷 chunk 154 沒有特殊數學意義。
  12. 解題目標由證明 macOS driver 的唯一內部真相,轉為把會觸發 watchdog 的工作拆成較短、受保護、可恢復的單位。
  13. bounded step 5 通過 50/50 blocks,peak 39.2 GiB、swap 0,之後完成 20 steps。
  14. 成片仍從 decoded frame 251 開始花屏;decoder-only replay 排除 VAE、FFmpeg 和 mux。
  15. 最後定位到 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 maxDeepSeek 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 與版本

網絡資料

  • 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 researchmlx-lm #1662Apple 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=2048SDPA=512AGX=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 後消失。
Attachment from the original Notion note
H3.c 的 22-frame 短序列成功樣本。短片能正常生成;問題是在真正有用的長序列才穩定重現。

Open in the original Notion note

Attachment from the original Notion note
H3.c 未分塊長序列失敗樣本:程序正常退出、MP4 可播放,但畫面只剩灰褐紋理。播放時請留意音量;音頻接近全頻噪聲。

Open in the original Notion note

失敗樣本的音頻頻譜。以靜態圖呈現噪聲證據,避免頁面主動播放高音量音頻。
失敗樣本的音頻頻譜。以靜態圖呈現噪聲證據,避免頁面主動播放高音量音頻。
Attachment from the original Notion note
H3.c 的 q2048 SDPA diagnostic:864×480、192 frames。場景和電車重新出現。

Open in the original Notion note

q2048 diagnostic contact sheet。這不是與 15 秒失敗樣本完全相同條件的嚴格 A/B,只用來展示 chunking 後可讀場景重新出現。
q2048 diagnostic contact sheet。這不是與 15 秒失敗樣本完全相同條件的嚴格 A/B,只用來展示 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。

失敗輸出相鄰幀:左為 decoded frame 250,右為第一個可見污染的 decoded frame 251。
失敗輸出相鄰幀:左為 decoded frame 250,右為第一個可見污染的 decoded frame 251。

AGX_RELAX_CDM_CTXSTORE_TIMEOUT=1

這不是 MLX 的品質參數。它放寬 macOS 對長時間 Metal command 的 interactivity watchdog。

MLX #3267MLX #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 當作安全柵欄,不當作預期運行值。

終端機 2026-08-31 上午1.07.55.png

每一步都保存 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 當答案。

Attachment from the original Notion note
白熊動作比較片段。這段短序列不在長序列 SDPA 危險區,仍可觀察局部融化、毛髮結塊與動作跳變。

Open in the original Notion note

白熊動作 contact sheet。保留完整比較畫面,不把短序列畫質問題混同為長序列 SDPA failure。
白熊動作 contact sheet。保留完整比較畫面,不把短序列畫質問題混同為長序列 SDPA failure。

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。

Attachment from the original Notion note
mlx-h3 小貓故事:864×480、192 frames、8 秒、四張 Ref2VA 圖片,完整影音解碼。

Open in the original Notion note

小貓故事 contact sheet:四個場景保持可讀,角色延續到最後的夜景。
小貓故事 contact sheet:四個場景保持可讀,角色延續到最後的夜景。
Attachment from the original Notion note
最終品牌片:1280×736、362 frames、24 fps、32 kHz stereo,Base-20 完整生成。

Open in the original Notion note

最終品牌片 contact sheet,由 ronnie-full-direct-mac-1280x736-362f-base20-mlx0322-mlp2048-accept-step1-20260831.mp4 直接以 2 fps 抽取。尾段 RECOVER → INTERFACE → SHIP → READY 保持可讀。
最終品牌片 contact sheet,由 ronnie-full-direct-mac-1280x736-362f-base20-mlx0322-mlp2048-accept-step1-20260831.mp4 直接以 2 fps 抽取。尾段 RECOVER → INTERFACE → SHIP → READY 保持可讀。

目前,這就是我在 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