我經營一套 100% 跑在本機的 AI 影片工作室(專案 next-gen-video-creation)。上一篇 《MiniMax H3 品質基準測試》記錄了我們怎麼把模型調到品質甜點;這篇往下一層,談引擎本身——現役的 h3c,從 antirez 的 h3.c 專案出發,經過一連串客製,變成一顆在 MacBook Pro M5 Max 上量產「有聲影片」的本機引擎。記錄我們改了什麼、為什麼改,以及支撐這些修改的版控紀律。

為什麼是 C + Metal

一開始所有影片生成都走 ComfyUI(Python + PyTorch MPS)。它不是不能用,但長期量產的成本很高:torch 版本得釘死在 2.11.0(macOS 26 上 MPS 的命脈)、H3 權重存 fp16 會 NaN、音訊路徑要靠自寫的 WAV 節點繞過壞掉的 PyAV、每個工作流程啟動都要付模型載入時間。2026-09-17,ComfyUI 全面退役。

h3.c 是 antirez 對 MiniMax H3 的原生實作,對我們的吸引力很直接:

  • 單一 binary:C11 + Metal shader,重建就是一句 make,沒有 Python 環境地獄
  • 權重直讀:直接吃 HuggingFace safetensors 快照,403 GB 的目錄佈局就是它的輸入格式
  • 互動 REPL:權重常駐記憶體,這個特性後來成為整條量產佇列的基礎

三顆引擎樹

engine/ 整個目錄被 git ignore、隨時可從上游重建;所有手寫修改一律鏡像到版控內的 patches/(相同相對路徑),由重建腳本自動套回。h3c 依這套規則維護三棵樹:

graph LR
  UP["antirez/h3.c<br/>@ 8974cc0"] --> M["h3c/<br/>上游 main + 實驗 patch"]
  UP --> P14["h3c-pr14/<br/>回滾備援(turbo)"]
  P14 --> P56["h3c-pr56/<br/>現役量產 bin(turbo5)"]
  PT["patches/<br/>(版控內的鏡像)"] -.rsync/cherry-pick.-> M
  PT -.-> P14
  PT -.-> P56
樹角色內容
h3c/實驗場上游 main + 解析度上限實驗 patch
h3c-pr14/回滾備援PR #14(turbo 摺疊)+ 自寫 sigma-shift patch
h3c-pr56/現役量產pr14 之上 cherry-pick PR #56 + #7,再加 6 個 fork commit

三棵樹各自有明確角色:實驗搞壞了不影響生產;生產引擎壞了有備援可退;而每一棵都能 從零重建——git-ignore 的目錄裡不該有任何「只存在本地」的修改,這條規則救過我們不止一次。

客訂清單:七項修改,每項對應一個生產問題

現役 h3c-pr56 相對上游的差異,全部收斂成鏡像在 patches/h3c-pr56/ 的 patch 檔:

修改內容解決的生產問題
PR #56補註冊 7 顆 H3 control tokens(<d> 等)沒有它們,對白條件化不上身,對白鏡頭產出亂語
PR #7RES 音訊改用原生 audio sigma schedule低步數(turbo)下音訊品質崩壞
H3_MAX_PIXELS 放寬1152×1536 上限 + 畫布斷言同步更新原生解析度上探,不再被預設上限卡住
pruned(查表)AdaLN支援 hybrid25 分解式權重 + golden parity 閘40 GB pruned 權重能跑,記憶體減半、品質不變
safetensors header 對齊測試修正為 8-byte pad上游的 guard 與自家測試互相矛盾,照上游必炸
runtime LoRA L1--lora/--lora-strength 結構驗證 + 四目標白名單壞 LoRA 檔在載入期就擋下,而不是渲染到一半爛掉
funcontrol 注入control-load + forward-inject 兩段 patch控制訊號(姿態/運動)實驗的引擎端入口

經驗法則:每一項客訂都必須回答「它解決哪個生產問題」——回答不出來的修改不進現役樹。 解析度上限就是好例子:放寬 H3_MAX_PIXELS 不是為了數字好看,而是 864×480 原生檔位 (影音俱全、約 25 分鐘)驗證可行後,往上探 720p 原生的必經之路。

權重與數值紀律

引擎客製一半在工作,一半在不讓自己踩雷:

  • 只用 pruned bf16(40 GB)。完整版 66 GB transformer 在 MPS 上靜默 NaN(全黑輸出、 無 OOM、無錯誤),已從磁碟刪除,重建清單不會再抓。
  • fp8 / nvfp4 全面禁用——MPS 崩潰或僅限 NVIDIA。scripts/check_no_fp8.sh 在每次 下載後把關。
  • 數值行為的判決一律來自基準測試(上一篇的 Phase A/B/C),引擎 session 的實作(如 pruned AdaLN)必須過 golden parity 測試才算數:同一輸入,分解式查表與原始矩陣 相乘的輸出必須位元級一致。

產線整合:把 REPL 變成佇列

h3.c 內建 Interactive Mode(linenoise REPL、權重常駐)。我們實測:同一 session 餵兩顆 鏡頭,第二顆起模型載入成本 ≈ 0(對照:分開呼叫每顆多付 40–70 秒)。於是量產管線 把 REPL 當佇列用:

  • gen --queue:多鏈頭同 preset 自動分組,每個 session 一次載入
  • 佇列腳本依序送 !refs clear → !ref-image → !seed → !frames → 摺疊成單行的 prompt (多行摺疊經 whisper 相似度驗證無損,sim 0.889 vs 多行基線 0.89)
  • 檔序映射 video-%04u,缺檔即失敗鏈,自動 seed+1 退逐鏈重抽
  • 另有靜態影像模式(scripts/h3c_still.sh)與三個 preset——base20 / base12 / turbo5, 每一檔的存在都有 benchmark 判決背書

踩雷換來的版控紀律

兩條用真實事故換來的規則,寫在 patches/README.md 裡:

  1. 新增 .c/.h 檔必須同步改 Makefile。 實際發生過:sigma-shift patch 加了新檔 卻沒進 LIB_C,任何重編都 ld: symbol not found,還把現役 binary 一起刪了。
  2. 引擎自帶的 .gitignore 會藏住本地成果。 ComfyUI 的 .gitignore 讓 custom_nodes/ 裡的自寫節點在 git status 上永遠隱形——所以自訂一律進 patches/ 鏡像,不留在 engine 樹裡。

結語

回頭看,這顆引擎的客製沒有一項是「炫技」:控制 tokens 為了對白、audio sigma 為了 低步數音訊、pruned AdaLN 為了把 66 GB 砍成 40 GB、LoRA 檢查為了半夜批次跑不完—— 全部對應真實的生產事故或品質判決。真正的護城河不是某一個 patch,而是重建可複製、 判決可追溯:引擎樹能從上游一行指令重建,每個行為差異都能指回一個 benchmark 或 一次盲測。

想看這顆引擎在品質上怎麼被調到甜點,接著讀 《MiniMax H3 品質基準測試》。


看影片

引擎長出來之後怎麼排程餵三條生產線,教學系列有一集講它的憲法:EP13〈一張 GPU 的憲法〉(YouTube,2026-10-05 19:00 公開;B站同晚上架)。全系列見頻道總覽。