我經營一套 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 #7 | RES 音訊改用原生 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 裡:
- 新增
.c/.h檔必須同步改 Makefile。 實際發生過:sigma-shift patch 加了新檔 卻沒進LIB_C,任何重編都ld: symbol not found,還把現役 binary 一起刪了。 - 引擎自帶的
.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站同晚上架)。全系列見頻道總覽。