~/blog/dgx-spark-deepseek-v4-flash-mtp-workload-asymmetry

DeepSeek-V4-Flash on DGX Spark · part 5

[本地 LLM] V4-Flash 的 depth-1 MTP:agent 回合 +9%、散文 −4%——推測解碼要看工作型態決定開不開

cat --toc

TL;DR

推測解碼常被講成免費加速。在 bandwidth-bound 的 GB10 上,它其實是個會變負的、看工作型態的取捨。depth-1 MTP(ds4 PR #371,官方叫 "depth-1 continuous")就往前打一個 token 的草稿、驗一次。它是無損的——輸出 byte-identical,草稿只改速度、不改是哪些 token。但這個加速有正負號,而正負號看你在生什麼:agent/工具回合 +9.4%、程式碼 +6.5%、技術文字 +4.4%——以及自由散文 −3.6%、中文閒聊 −3.7%。機制是:這裡的吐字卡在驗證——一次驗證 ~108ms、一次草稿只要 ~4ms,所以被接受的草稿幾乎免費,被拒絕的草稿等於白付了一次驗證。好猜的輸出(程式碼、JSON、工具呼叫)會被接受,開放的文字不會。所以 MTP-1 不是一個全域「變快」的開關——程式碼/agent 開它,閒聊要嘛認了那點成本,要嘛丟去 Part 2 講的那條 non-thinking deepseek-chat 路

白話版:一個幾乎免費的搶答,但只在你的句子「好猜」的時候划算

推測解碼(speculative decoding)講白了就是一個「搶答」的助理:它一邊聽你講,一邊猜你下一個字要說什麼。猜對了,一句話兩個字一次吐出來,省時間;猜錯了,那個猜白猜,你還是得自己把字講出來。到底划不划算,完全看你這句話「好不好猜」。

這篇的全部重點就這句。在我的 GB10 上,「檢查一次猜測」(驗證)要 ~108 毫秒,「猜一次」(打草稿)只要 ~4 毫秒——所以猜這件事幾乎免費,貴的是檢查。當模型在寫程式碼、JSON、工具呼叫這種「下一個字很好猜」的東西,它多半猜對,等於每次貴貴的檢查都白撿一個字,這就是 +6 到 +9%。當它在寫散文、或用中文閒聊這種「下一個字真的很開放」的東西,猜錯的次數變多,那 4 毫秒就白花了,這就是 −4%。

所以 depth-1 MTP 不是一個「開了就變快」的開關,它是一個正負號會隨工作型態翻面的取捨。結構化的東西開它;日常閒聊要嘛認了那點稅,要嘛把這種流量丟去別條路。


前言

MTP 這種推測解碼,很多人開著就不管,當它免費加速——反正無損,開了不虧。我本來也這麼想,直到把它按 workload 一個一個切開量:結果它在散文上是倒貼的。depth-1 MTP(ds4 PR #371,官方叫 "depth-1 continuous")做的很單純——往前猜一個字、驗一次。它到底幫不幫你,答案不是「開還關」,是「你在生什麼」。

下面所有數字都是我自己在單顆 128GB GB10、ds4 CUDA build 上量的,而且輸出是無損的——開不開草稿,吐出來的 token 一個 bit 都不差,我驗過。草稿只改「字冒出來的速度」,不改「是哪些字」。

一句話結論:正負號跟著工作型態走

MTP-1 加速依工作型態:agent +9.4%/程式碼 +6.5%/技術文字 +4.4%,散文 −3.6%/中文閒聊 −3.7%

工作型態MTP-1 對比不開草稿
agent / 工具呼叫+9.4%
程式碼+6.5%
技術文字+4.4%
自由散文−3.6%
中文閒聊−3.7%

同一個模型、同一台機器、同一個 flag——每一列之間唯一變的,是我叫它生什麼。結構化、好猜的輸出變快;開放式的輸出變慢。最好到最差差 13 個百分點,而且跨過了零。最後這件事才是有意思的地方:這個模型根本沒有一個「MTP-1 到底快多少」的單一答案。「推測解碼快多少?」的誠實回答是「你在生什麼?」

為什麼正負號會翻:這裡的吐字卡在驗證

驗證 ~108ms 主導、草稿 ~4ms:草稿被接受=多一個 token 賺,被拒=白花 4ms 賠

depth-1 推測解碼每一步做兩件事:先往前打一個 token 的草稿(~4ms),再跑一次驗證(~108ms)決定接不接受。為什麼驗證貴這麼多?因為驗證要把整顆模型跑一遍(把權重從記憶體整個讀出來)才能判斷那個字對不對——這台 GB10 是 bandwidth-bound,每一步的成本就是「從記憶體搬多少 bytes」,而搬整顆模型很貴;草稿只是個很小的頭,便宜得多。這個 27 倍的不對稱,就是整個機制。

  • 草稿被接受:這次驗證一口氣提交兩個 token(本來那個 + 草稿那個),而不是一個。等於同樣 108ms 白撿一個字。淨賺。
  • 草稿被拒絕:這次驗證只提交一個 token,你打草稿花的 4ms 沒了。淨賠——賠得小,因為 4ms 很小,但還是賠。

所以速度的正負號,就是(接受換來的免費 token)減掉(拒絕浪費的草稿)。而一個草稿會不會被接受,是工作型態的性質:當下一個 token 很好猜——一個 block 收尾的大括號、一個已知工具 schema 裡的參數名、樣板碼裡的下一個關鍵字——草稿一猜就中,你就白撿。當下一個 token 真的很開放——句子裡的下一個詞、對話裡的下一輪——草稿變成擲銅板,拒絕越積越多,浪費掉的那些 4ms 就蓋過賺到的。

所以排名剛好是 agent > 程式碼 > 技術文字 > 散文 > 閒聊:這是一張「好不好猜」的排名。不是速度在排這些工作型態,是接受率在排,而接受率不過就是「這段文字有多好猜」。

卡住的不是草稿品質,是一道邊界閘

這段是讓我意外的。我記了 231 個推測 cycle(一個 cycle = 打一次草稿、驗一次),平均每個 cycle 提交的 τ(tokens per cycle)≈ 1.54——平均每個 cycle 大概多賺半個 token。你看到 1.54 這種軟軟的數字,可能會解讀成「這個 drafter 也就普普」。不是。當一個 cycle 真的跑了驗證,~97% 的草稿是被接受的。草稿品質好得很。

把 τ 壓下來的,是一道邊界閘(margin gate)在 ~44% 的 cycle 上直接不推測——drafter 猜下一個字時,會看自己最想猜的那個字有沒有明顯領先第二名;領先夠多才敢出手,領先太小(margin 太薄)就乾脆不賭這一把,照常一個一個吐。所以 depth-1 MTP 在這裡的天花板不是「drafter 猜錯」(它一旦出手幾乎不錯),是「drafter 只在有把握的時候才下注,有 44% 的時候它直接 fold」(一開始我還嫌它太不敢賭,後來發現這正是散文沒被拖垮的原因)。那道閘在大多數爛賭注花掉一次驗證之前,就先把它們擋掉了——所以散文只賠 −3.6%,不是 −15%。

這也代表 depth-1 這條路沒什麼好調的。一個 97% 接受率的 drafter 你很難再讓它更好。真正能把 τ 拉高的,是一次驗證多打幾個 token——那是另一個更大的賭,我下面講。

那為什麼不一次多打幾個 token?

很自然的下一個念頭是「如果一個幾乎免費的草稿 token 有幫助,那打五個、把 108ms 的驗證分攤到五個字上不是更好?」。這條路確實可以走,只是賭得更大。問題在:打一整塊 token 的草稿,只有在整塊都通過驗證時才划算;塊裡只要有一個被拒,分攤就崩了,而「整塊全過」的機率隨著文字越不好猜、掉得非常快。打一整塊,其實是在賭模型接下來好幾個 token 都好猜——那比「下一個好猜」強得多的宣稱,而且塊越長越站不住。那是另一回事、有它自己的取捨(也是另一篇)。

depth-1 算是下限:一次只賭一個 token,所以最容易過接受這關——而在好猜的工作型態上,它 97% 的時候都過。這篇講的是怎麼讓這個地板真的賺,而那整個問題,其實是個路由問題。

怎麼路由:程式碼/agent 開,閒聊走別條

實務上不能只問「推測解碼要不要開」——切法要改成按工作型態分流:

  • 程式碼、agent、工具呼叫 → MTP-1 開。 這幾種工作型態開了會快 +4 到 +9%,也剛好是最在乎延遲的那些——一個 agent 回合是一串工具呼叫,每一次都是你在等的往返。這是白撿的錢。
  • 日常閒聊 / 創作 → 別付那 −4%。 兩個選擇。認了——3.6% 很小,你也未必想維護兩條 serving 路。或者把這種流量丟去 Part 2 講的那條 non-thinking deepseek-chat,它本來就比較適合閒聊回合,還順便完全避掉浪費草稿的成本。

再上一層看:一個推測解碼器就是一個「賭好不好猜」的賭注,而你其實早就知道自己哪些工作型態好猜。你不用每個請求去量——程式碼跟工具呼叫就是結構化的,閒聊跟散文就不是。按工作型態的類別去設這個 flag,別全域一刀切。

收穫

最花時間的地方

不在調參——depth-1 MTP 就一個 flag。花時間的是不要停在平均值。一個混在一起的「MTP-1 大概 +2%」是真的、但沒用:它把一個 +9.4% 跟一個 −3.7% 藏起來互相抵消掉。時間全花在把 benchmark 按工作型態切開,切到那個正負號翻面露出來為止,因為那個翻面就是整個發現。把不同工作型態混在一起算平均,得到的數字對你任何一個真實工作都沒用。

可搬走的診斷方法

當一個推測解碼的結果又小又正,別信那個平均——按「輸出好不好猜」切開,去找正負號翻面。要判斷它是不是卡在驗證、又被閘門擋住,看兩個數字:驗證對草稿的成本比(這裡 ~108ms 對 ~4ms = 27 倍),加上有提交的 cycle 的接受率(~97%)。如果驗證遠大於草稿、而且有提交的草稿幾乎都被接受,那你的瓶頸就是那道「要不要推測」的閘門,不是 drafter 的品質——你的加速會跟著工作型態好不好猜走,不會跟著任何你調得動的 flag 走。

通用原則

說穿了,推測解碼不是「變快」的開關,是一個賭注——賭你接下來這段好不好猜。 好猜就賺、不好猜就賠,幅度都差不多小。所以別問「要不要開」,問「這段我在生什麼」:好猜的開它,不好猜的吞下那點成本,收工。

結論 checklist

  1. 程式碼、agent、工具呼叫,把 MTP-1 開起來。 剛好在最在乎延遲的工作型態上 +4 到 +9%,而且無損。
  2. 別全域開。 自由散文跟閒聊要付 −4%;加速的正負號跟著「輸出好不好猜」走。
  3. 閒聊走別條。 要嘛認了那點成本,要嘛把閒聊回合丟去 non-thinking 的 deepseek-chat 路(Part 2)。
  4. 別想調 depth-1。 有提交的草稿已經 ~97% 接受;瓶頸是那道 fold 掉 44% cycle 的邊界閘,不是草稿品質。
  5. 別一想到「做得更好」就直接去打一整塊草稿。 一次驗證多打幾個 token,是對「好猜」下更重的一注,複合的拒絕風險是真的——先量再說,別預設它會贏。

本系列其他篇:Part 2 — 把 15 tok/s 的 284B 當每天的 agent 大腦 · Part 4 — 養了一個月,agent 悄悄不再快取:ds4 的 evict 風暴 · 上游 ds4 PR #371

常見問題

depth-1 MTP 推測解碼是無損的嗎?
是,byte-identical。草稿(MTP-1 往前打一個 token,ds4 PR #371)只改 token 冒出來的速度,不改是哪些 token。我驗過開不開草稿輸出完全一樣。推測解碼是速度優化、不是品質優化;要是它改了輸出,那就是 bug。
為什麼 MTP-1 在 GB10 上讓程式碼變快、卻讓散文變慢?
因為這裡的吐字卡在驗證:一次驗證 ~108ms、一次草稿只要 ~4ms,所以被接受的草稿幾乎免費,被拒絕的草稿等於白付了一次驗證。程式碼、JSON、工具呼叫好猜,草稿多半被接受(程式碼 +6.5%、agent +9.4%);自由散文跟閒聊很開放,草稿比較常被拒,你就得吃掉那個浪費草稿的成本(散文 −3.6%、中文閒聊 −3.7%)。
DeepSeek-V4-Flash 的推測解碼該全域開嗎?
不該。它是看工作型態的取捨,不是一個全域變快的開關。程式碼、agent、工具呼叫這種它會贏 +4 到 +9% 的工作,把 MTP-1 開起來。日常閒聊跟創作,要嘛認了那小小的 −4%,要嘛把流量丟去 non-thinking 的 deepseek-chat 路(見 Part 2)。按工作型態的類別設這個 flag,別一次設完全部。
depth-1 MTP 還能再調快嗎?
空間不大。一旦某個 cycle 真的提交,~97% 的草稿都被接受——drafter 本身已經很強。壓住平均加速的,是一個 margin gate:模型自己信心不夠時,它有 ~44% 的 cycle 乾脆不猜。所以限制不是你能改善的草稿品質,是這個保守的 gate 在壞注花掉一次驗證之前先收手。唯一能把它再拉高的,是一次驗證多打幾個 token——那是另一個更大的賭,有它自己的取捨。

接著讀

不想錯過新文章?

訂閱我確保不漏接!

隨時一鍵退訂。