vatt'ghern jaskier's ballads
本文 6 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

傳統 speculative decoding 一次猜一個 token,猜對了往前一步、猜錯了回頭重來——本質還是自回歸。MiMo 的 DFlash 直接把 8 個草稿位置 block-level 並行猜出來,coding 場景平均一輪接受 6.30 個。但這個演算法只有在 FP4 模型與常駐 kernel 同時就位時才跑得動。

MiMo 把 1T 模型推到每秒千 token——FP4、DFlash 與常駐 kernel 的三層 co-design

米 TileRT 團隊把一個 1T 參數的 MoE 模型(MiMo-V2.5-Pro 的 UltraSpeed 版本)在一台標準 8 卡 commodity node 上推到每秒 1000+ token、尖峰約 1200 token/s。這個數字本身不稀奇——稀奇的是它沒有靠換更貴的硬體,而是把推理棧拆成三層同時改:模型側只把 MoE expert 量化到 FP4、演算法側用 DFlash 取代自回歸 speculative decoding、系統側用 persistent engine kernel 把整條 pipeline 常駐在 GPU 上。這三層不是各做各的優化疊加,而是互為前提——任何一層單獨上線,另外兩層的收益都會被它自己的瓶頸吃掉。這篇拆解這三層各自換掉了傳統推理棧的哪一段,以及它們為什麼必須一起設計。

先把問題定位清楚。在 1000 token/s 這個工作頻率下,每生出一個 token 的時間預算是 1 毫秒。一個 1T MoE 模型即使每個 token 只啟用一小部分 expert,單次 forward 的計算量也遠超 1 毫秒能塞下的範圍——所以「每個 token 跑一次完整 forward」這條路從一開始就走不通。唯一的出路是讓一次昂貴的 forward 同時驗證多個 token,這就是 speculative decoding 的核心動機。但傳統 speculative decoding 的草稿階段本身是自回歸的:draft model 一個一個吐 token,吐 8 個就要 8 次 draft forward,草稿階段的延遲又把驗證階段省下的時間吃回去。MiMo 的三層 co-design,本質是把這條「猜得快、驗得省、跑得不卡」的鏈條上的三個瓶頸分別拆掉。

在進入細節之前,先看三層各自負責什麼、又換掉了傳統推理棧的哪一段瓶頸。點任一層展開它的責任邊界——三層的關係是互為前提,不是疊加。

點任一層展開它換掉的瓶頸 · 3 層 co-design

三層 co-design——各自換掉傳統推理棧的一段瓶頸

三層 co-design——各自換掉傳統推理棧的一段瓶頸,互為前提而非疊加 模型側 · FP4 選擇性量化 只把 MoE expert 量化到 MXFP4、attn / router / embed 維持原精度——換掉 memory bandwidth 瓶頸 MXFP4 QAT 演算法側 · DFlash 並行草稿 block-level masked 一次猜 8 個草稿 token,取代自回歸——換掉草稿階段的序列延遲 block-level masked 系統側 · TileRT 常駐 kernel persistent engine kernel + warp specialization——換掉 operator 邊界的 launch / 同步 overhead persistent kernel

click a layer above

模型側 · FP4 · 責任邊界

把 1T MoE 裡佔絕大多數參數的 expert 權重降到 MXFP4,attention、router、embedding 維持原精度。它換掉的是 memory bandwidth 瓶頸——在 1 毫秒 token 預算裡,搬權重往往比算力先見底。

不負責的事:草稿怎麼猜、kernel 怎麼跑。它只保證模型小到一次 forward 跑得動、並騰出並行驗證的 memory headroom。

演算法側 · DFlash · 責任邊界

用 block-level masked parallel prediction 一次並行猜 8 個草稿 token,取代「一次猜一個」的自回歸 draft。它換掉的是草稿階段隨長度線性增長的序列延遲。coding 場景平均接受 6.30 個。

不負責的事:模型精度、kernel 執行。它假設一次 target forward 跑得動、且驗證 8 個位置的 batch 效率發揮得出來。

系統側 · TileRT · 責任邊界

把整條 pipeline 常駐 GPU(persistent engine kernel),用 warp specialization 讓通訊 / 搬資料 / tensor 運算的 warp 各司其職、overlap 執行。它換掉的是 operator 邊界的 launch / 同步 / memory round-trip overhead。

不負責的事:模型大小、草稿命中率。它只保證上面兩層省下的時間不在 operator 邊界上漏掉。

互動圖表

FP4、DFlash、常駐 kernel 三層互為前提,缺任一層其餘的收益都會被吃掉。

下面這個互動模擬是這篇的主軸:它把 DFlash 的 draft→verify 循環一輪一輪跑出來,讓你看到「並行猜 8 個、驗證後只接受前 k 個」這個動作隨時間如何累積成 throughput。切換任務類型(coding / math / agent),接受長度會跟著 MiMo 公布的實測值改變——接受長度越長,同樣一次驗證 forward 換到的有效 token 越多,這正是 1000 token/s 的算術來源。

play 觀察 draft→verify 一輪一輪推進 · 切換任務改變接受長度 · 3 種任務

round 0 · 0 tok
每一輪 draft model 並行吐 8 個草稿 token(block-level masked parallel),target model 一次 forward 驗證,接受前 k 個、丟掉其餘;k 的期望值就是 MiMo 公布的接受長度。throughput ≈ 接受長度 × 驗證輪頻率——接受長度越長,同一次驗證 forward 換到的有效 token 越多。

每一輪 draft model 並行吐 8 個草稿 token(block-level masked parallel…

coding 場景每輪平均接受 6.30 個草稿 token,是 agent 4.29 的 1.5 倍,決定了 throughput 的上限。

注意模擬裡的 commit 階段:8 個草稿 token 並排,驗證後只有前 k 個被接受(綠色)、後面的被丟掉(淡色)。被丟掉的 token 不是浪費——target model 的單次 forward 同時算了全部 8 個位置的 logits,丟掉只是因為某個位置的草稿跟 target 分布不合、從那裡截斷。關鍵在於:無論接受 1 個還是 8 個,都只花一次 target forward。所以「平均每輪接受幾個」直接決定了「一次昂貴的 forward 攤到幾個有效 token」。下面三節依序拆三層,順序對應它們在 critical path 上的角色:先讓模型小到一次 forward 跑得動(FP4),再讓草稿階段不要拖後腿(DFlash),最後讓系統不在 operator 邊界上漏掉時間(persistent kernel)。

FP4:只量化 MoE expert,其餘維持原精度

第一層是把模型本身縮小。1T 參數即使是 MoE、每個 token 只啟用一部分 expert,權重從 GPU memory 搬到 tensor core 的頻寬仍然是硬瓶頸——在 1 毫秒的 token 預算裡,memory bandwidth 往往比算力先見底。降低精度是最直接的解法:把權重從 BF16/FP8 降到 FP4,每個權重的 byte 數砍掉,搬運量同步下降。但 FP4 只有 4 個 bit 的動態範圍,整模型無腦量化會把模型能力打殘。

MiMo 的選擇是「只量化 MoE expert」——把 expert 的權重降到 MXFP4(OCP Microscaling Formats 的 FP4 變體),而 attention、router、embedding 這些非 expert 的模組維持原精度。理由有兩層:第一,在一個 1T 的 MoE 裡,expert 佔了絕大多數參數,量化 expert 拿到的 memory 節省最大;第二,expert 對量化的容忍度最高——MoE 的 router 已經把輸入路由到專門化的 expert,每個 expert 處理的是輸入空間的一個子區域,本身的數值分布相對集中,降到 FP4 損失較小。反過來,attention 的 softmax、router 的 top-k 選擇對數值精度敏感,一旦量化會放大路由錯誤、雪崩到後面所有 expert 選錯。

MXFP4 不是裸 FP4。Microscaling 的做法是把權重分成小 block,每個 block 共享一個 scale factor——block 內用 FP4 存 mantissa,scale 用較高精度存。這樣每個 block 可以各自對齊到自己的數值範圍,等效動態範圍遠大於裸 FP4。裸 FP4 的問題在於:一個固定的 4-bit 編碼必須同時覆蓋很大的權重與很小的權重,大值佔滿了編碼空間、小值被量化成零,模型裡那些「小但重要」的權重直接消失。per-block scale 把這個問題局部化——每個 block 只需要表示自己那一小段範圍內的相對大小,FP4 的 4 個 bit 在局部範圍內反而夠用。MiMo 走的是 FP4 QAT(Quantization-Aware Training)——量化不是訓練完才套上去的後處理,而是在訓練過程中就讓模型「知道」自己會被量化,反向傳播時把量化誤差納入梯度。post-training 量化是「先訓好、再硬壓」,模型沒有機會適應;QAT 讓模型在訓練時就把權重往「量化後仍然好用」的方向調,這對 FP4 這種激進精度尤其關鍵。結果是量化後模型能力跟原模型「essentially on par」。要強調的是:原文只給了「能力基本持平」的定性結論與一張對照圖,沒有公布逐 benchmark 的數值 delta,也沒有單獨拆出「FP4 這層貢獻了多少 throughput」——這層的收益是隱性的,它讓後面兩層有足夠的 memory headroom 跑得動,而不是直接體現在某個獨立的加速倍數上。

// 選擇性量化:只有 MoE expert 降到 MXFP4
for module in model:
    if module.is_moe_expert:
        module.weight = mxfp4_quantize(module.weight)   // 4-bit + per-block scale
    else:
        module.weight = keep_original_precision(module.weight)  // attn / router / embed

// QAT:量化誤差在訓練時就進入反向傳播
// 結果:能力 essentially on par(原文僅給定性對照,未公布逐 benchmark delta)
精度地圖——降精度跟著參數量走,敏感模組留原精度 1T 參數預算(依佔比,非等寬) MoE expert · 絕大多數參數 → MXFP4(4-bit + per-block scale) 其餘 原精度 維持原精度的敏感模組——量化會放大路由錯誤: attention · softmax 對數值精度敏感 router · top-k 路由一錯雪崩到後面所有 expert embedding · 輸入表示,誤差會傳遍全程 取捨:量化參數最多、容忍度最高的 expert,省最大 memory; 留下參數少、卻牽一髮動全身的 attn / router / embed。
選擇性量化的精度地圖:MiMo 只把佔絕大多數參數的 MoE expert 降到 MXFP4,attention / router / embedding 這些對精度敏感的少數模組維持原精度。原文僅給定性對照、未公布各模組精確參數佔比,長條寬度為示意。

選擇性量化的精度地圖:MiMo 只把佔絕大多數參數的 MoE expert 降到 MXFP4,attention / …

只有 MoE expert 降到 MXFP4,attention、router、embedding 維持原精度以避免路由誤差雪崩。

為什麼這層必須跟另外兩層一起設計?因為 FP4 換來的 memory headroom,是 DFlash 並行驗證 8 個 token 的前提。並行驗證意味著一次 forward 要同時處理 block size 個位置的 activation,KV cache 與中間 activation 的 memory 佔用隨之放大。如果模型還是 FP8、權重已經把 memory 吃滿,根本騰不出空間做 block-level 並行驗證。FP4 把權重壓下去,省出來的空間正好給 DFlash 的並行驗證用——這是三層 co-design 第一個「互為前提」的接點。

DFlash:block-level masked 並行草稿

第二層是把草稿階段從自回歸改成並行。傳統 speculative decoding 的 draft model 是個小型自回歸模型:要猜 8 個 token,就得跑 8 次 draft forward,每次把上一個草稿 token 餵回去當輸入。草稿階段本身的延遲,隨草稿長度線性增長——猜越多、草稿越慢,到某個點草稿的成本就抵銷掉驗證省下的時間。這是傳統 speculative decoding 草稿長度做不長的根本原因。

DFlash 的做法是 block-level masked parallel prediction:不再一個一個自回歸地猜,而是一次性地、並行地預測一整個 block(8 個位置)的草稿 token。做法上借鑑 masked / diffusion language model 的思路——把要預測的 8 個位置全部設成 mask,讓 draft model 在單次 forward 裡同時輸出所有 mask 位置的分布。草稿階段從「8 次序列 forward」壓成「1 次並行 forward」,草稿延遲不再隨 block size 線性增長。draft model 用 Sliding Window Attention(SWA),剛好對齊 MiMo-V2 系列本身的 SWA 設計,draft 與 target 共享 attention 結構讓兩者的中間表示更容易對齊、提升接受率。

接受長度是衡量這套機制有效性的核心指標——每輪驗證後,平均有幾個草稿 token 通過驗證被接受。MiMo 公布的實測值分任務:coding 6.30(最高可達 7.14)、math/reasoning 5.56、agent 4.29。換句話說在 coding 場景,每輪 8 個草稿 token 大約有 6–7 個被接受。下面這張圖把三類任務的接受長度跟 block size 上限(8)放在一起,凸顯「每類任務距離理論上限還有多遠」。

每輪 block size = 8 個草稿 token;長條是各任務的平均接受長度(coding 6.30、math/reasoning 5.56、agent 4.29),虛線是 8 的理論上限,coding 的最高記錄 7.14 以空心標記標出。數字取自 MiMo TileRT blog。

每輪 block size = 8 個草稿 token;長條是各任務的平均接受長度(coding 6.30、math/…

coding 任務平均接受長度 6.30(最高 7.14),agent 只有 4.29,差距反映任務可預測性。

圖裡的梯度有意思:coding 的接受長度最高,agent 最低。這不是隨機差異,而是反映任務本身的「可預測性」。程式碼有強語法結構——縮排、括號配對、變數命名慣例、常見的 boilerplate 樣板,下一段 token 高度可預測,draft model 並行猜 8 個很容易整段猜中。math/reasoning 居中:推理鏈有結構但分支多。agent 最難:工具呼叫、外部狀態、多輪互動讓下文更發散,並行草稿的命中率自然下降。原文也誠實點出一般對話場景(general conversation)的接受率「尚不高」、仍在持續優化——這類開放式生成的可預測性最低,是這套機制目前的弱項。

接受長度直接換算成 throughput:如果每輪驗證 forward 的成本固定,coding 一輪接受 6.30 個 token,等於把一次昂貴的 forward 攤到 6.30 個有效 token 上,相對於「一次 forward 一個 token」的自回歸基線,有效輸出速率拉高約 6 倍。這就是 1000 token/s 算術的另一半——FP4 讓單次 forward 跑得動,DFlash 讓單次 forward 換到更多 token。但這裡有個前提條件容易被忽略:block-level 並行驗證要真的快,target model 的單次 forward 必須能高效地同時算 8 個位置的 activation。如果系統棧每跑一個 operator 就要 launch 一次 kernel、在 operator 邊界上漏掉時間,那麼把驗證從 1 個位置擴到 8 個位置帶來的 batch 效率根本發揮不出來——這就把問題交給了第三層。

DFlash 訓練側還有一個值得記的工程細節:mask-signal sampling 被下推到 GPU-local shard,讓每個訓練 step 能產生數萬個獨立訓練訊號。並行草稿模型要學的是「在給定前文下、同時預測多個 mask 位置」的能力,訓練訊號的密度直接決定它學得多好——把 sampling 放到 GPU 本地分片,避免了 host-device 之間搬訊號的瓶頸,讓每個 step 的有效訓練量級拉高一個數量級。

persistent engine kernel 與 warp specialization

第三層是系統側,也是最容易被低估的一層。在 1000 token/s 下,每個 operator 的生命週期被壓縮到微秒級。傳統推理棧的執行模型是 per-operator launch:framework 把模型拆成一連串 operator(matmul、layernorm、all-reduce、activation),每個 operator 是一次獨立的 GPU kernel launch——CPU 端準備參數、發 launch 指令、GPU 排程、執行、寫回、再回到 CPU 發下一個。每次 launch 都有固定 overhead(kernel launch latency、CPU-GPU 同步、operator 之間的 memory round-trip)。當每個 operator 本身只要幾微秒,這些邊界 overhead 就從「可忽略」變成「主導項」。

下面這個 tab 把傳統 per-operator launch 跟 TileRT 的 persistent engine kernel 並排,逐項對照執行模型的差別。

切換分頁對照三種執行模型 · 3 tabs

傳統 per-operator launch——邊界 overhead 主導

framework 把 forward 拆成數百個 operator,每個 operator 一次 kernel launch。在每個 operator 只要幾微秒的工作頻率下,launch latency、CPU-GPU 同步、operator 之間經 global memory 的中間結果 round-trip,三者加起來主導了 wall time。

執行單位每 operator 獨立 launch 一次 kernel
operator 邊界每個邊界一次同步 + memory round-trip
資料流中間結果寫回 global memory、下一個 op 再讀回
微秒帳operator 本身幾 µs,邊界 overhead 同量級——overhead 從可忽略變主導

persistent engine kernel——整條 pipeline 常駐 GPU

TileRT 完全捨棄 per-operator launch,把整條 compute pipeline 常駐並持續流動在 GPU 內。operator 之間不再回到 CPU 重新 launch、不再經 global memory round-trip——當前 tile 還在 tensor core 上算的同時,後續資料已經在預取流入,operator 邊界的固定開銷被整段消掉。

執行單位整條 pipeline 一個常駐 kernel,不退出
operator 邊界消掉——pipeline 內部直接銜接,無 re-launch
資料流full-pipeline 連續預取:當前 tile 計算時下一段資料已在流入
微秒帳不再被 launch / 同步主導,微秒級 operator 的有效利用率拉滿

warp specialization——異質流水線協作

在常駐 kernel 內部,不同的 warp(thread group)甚至整個 GPU 上的異質執行域各司其職、獨立運轉又精準協調:一組 warp 專責通訊、一組搬資料、一組做 tensor 運算。把通訊、資料搬移、tensor 計算在細粒度上解耦,三者可以重疊而不互相等待——這是讓 persistent kernel 內部的連續預取真正不停頓的底層機制。

通訊 warp專責跨卡 / 跨域通訊,與計算重疊
資料搬移 warp負責把下一段 tile 預取到位
tensor warp專注 tensor core 運算,不被搬運阻塞
協調方式細粒度解耦、獨立運轉又精準同步,三類工作 overlap
三種執行模型對照。原文把 operator 邊界與執行間隙點名為 1000 token/s 工作頻率下的核心瓶頸,但未公布具體的微秒級延遲拆分——這裡的「微秒帳」是定性描述,不是量化測量。

三種執行模型對照

persistent kernel 把整條 pipeline 常駐 GPU,消掉 operator 邊界的 launch 與同步 overhead。

persistent engine kernel 的核心是「不退出」。傳統模型把每個 operator 當成一次「進 GPU、算完、出來」的往返;persistent kernel 把整條 pipeline 寫成一個常駐的 kernel,資料在 GPU 內部從一個計算階段直接流到下一個,不回 CPU、不經 global memory 中轉。原文的說法是 full-pipeline continuous prefetching——當前 tile 還在 tensor core 上算時,後續資料已經在流入。這把 operator 之間的 memory round-trip 與 re-launch 的固定開銷整段消掉。

warp specialization 是讓常駐 kernel 內部不停頓的機制。一個 GPU 的 warp(thread group)可以被分工:一組專責通訊(跨卡的權重 / activation 交換)、一組專責搬資料(把下一段 tile 從 memory 預取到 register / shared memory)、一組專責 tensor 運算。三類工作在硬體上重疊執行——tensor core 在算當前 tile 時,搬運 warp 已經在預取下一段、通訊 warp 已經在交換需要的資料。如果不做這個分工,一個 warp 又要算又要搬又要通訊,三者只能序列化、互相等待,連續預取就無從談起。這也是為什麼 warp specialization 跟 persistent kernel 必須一起設計:persistent kernel 提供「不退出的常駐執行體」,warp specialization 填進「在這個執行體內部如何不讓任何一類工作阻塞其他」。

warp specialization——三類 warp 同時段 overlap,互不阻塞 同一段時間(→),三類 warp 各跑各的、精準協調 通訊 warp 跨卡交換 tile n 交換 tile n+1 資料搬移 warp 預取 tile n+1 n+2 tensor warp 算 tile n 算 tile n+1 此刻:算 n 的同時,n+1 已在預取、已在通訊 不分工的話:一個 warp 又算又搬又通訊,三者只能序列化、互相等待。 分工後:三條 lane 在時間軸上重疊,連續預取不停頓——這是 persistent kernel 內部 不漏掉微秒的底層機制。
warp specialization 的重疊時序:通訊 / 資料搬移 / tensor 三類 warp 在同一段時間各司其職、overlap 執行。tile 編號為示意,原文未公布逐 warp 的微秒級時序。

warp specialization 的重疊時序:通訊 / 資料搬移 / tensor 三類 warp 在同一段時間…

warp specialization 讓通訊、搬資料、tensor 三類 warp 同時段重疊,使連續預取不停頓。

把三層串起來看 1000 token/s 的完整算術。FP4 把 1T 模型的權重壓到單次 forward 的 memory 預算內,並騰出空間給並行驗證;DFlash 讓一次 target forward 同時驗證 8 個草稿位置、coding 場景平均接受 6.30 個有效 token;persistent kernel + warp specialization 讓這次 forward 不在 operator 邊界上漏掉微秒,把微秒級 operator 的有效利用率拉滿。三者缺一:少了 FP4,模型大到一次 forward 跑不動,DFlash 的並行驗證沒空間;少了 DFlash,一次昂貴的 forward 只換一個 token,FP4 與常駐 kernel 省下的時間被自回歸吃光;少了 persistent kernel,block-level 並行驗證的 batch 效率被 launch overhead 抵銷,DFlash 的接受長度換不成 throughput。

最後是落地代價,原文也給了數字。這套 UltraSpeed 棧透過 API 開放,定價是 MiMo-V2.5-Pro 的 3 倍成本、換取約 10 倍的生成速度——對延遲敏感、token 量大的 coding / agent workload,這個 trade 可能划算;對成本敏感、延遲不關鍵的批次任務,3 倍單價未必合算。換個角度看這個比例:3 倍成本買 10 倍速度,等於每個 token 的有效單價降到原本的 0.3 倍,前提是你的 workload 真的吃得下這個速度——一個每秒只產出幾十 token 需求的離線批次任務,把它丟到 1000 token/s 的棧上只是在為用不到的延遲付溢價。原文也標注了一個受限的試用窗口(6 月 9 日至 23 日),每天有排隊名額上限、單次 session 有時長與閒置逾時限制,這暗示這套棧的單機資源仍然吃緊、還沒到能無限水平擴張的成熟度。原文未公布 GPU 型號、未給標準 MiMo-V2.5-Pro 不加優化的 baseline throughput、未給 FP4 的 memory 節省百分比與逐 operator 的微秒拆分——這些是評估「能不能複製到自己的棧」時會想要、但目前缺席的數字。對打算自建類似棧的團隊來說,最該記住的不是「1000 token/s」這個結果,而是三層互為前提這個結構約束:任何只挑一層來抄的嘗試,都會撞上另外兩層沒跟上而留下的瓶頸。

What this enables:當 1T 模型的單機推理從「每秒幾十 token」進到「每秒上千 token」,互動式 coding agent、即時推理鏈、長 context 的 agent loop 這類「要嘛快、要嘛沒人用」的場景,第一次有了在單台 8 卡 node 上落地的算術基礎——前提是你願意接受 FP4 的隱性能力損失、為 DFlash 重訓 draft model、並把推理棧改寫成常駐 kernel。三層一起改才有 10 倍,這正是 co-design 這個詞的重量。