iT邦幫忙

2026 iThome 鐵人賽

DAY 20
0
AI Engineering

不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天系列 第 20

Day 20 - 接受率 1.00,為什麼反而輸給 0.94?投機解碼不是猜準就會快

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20260831/20183550QK7LDlUT4q.png

先看一行 log。

speculative decode stats  iterations=57 drafted=108 accepted=70
                          acceptance=0.65 avg_draft=1.89 avg_accepted=1.23

acceptance=0.65。我第一眼看到也想:六成五,至少過半,應該有賺。

結果把同一台機器、同一顆模型的八筆紀錄攤開,最準的那一筆,每輪只拿回 2.37 個 token;接受率 0.940 的那筆是 3.76。滿分那筆低了 37%。

# iterations drafted accepted 接受率 每輪 target 產出
3 39 93 41 0.441 2.05
4 36 76 44 0.579 2.22
1 57 108 70 0.648 2.23
5 19 51 45 0.882 3.37
8 20 47 44 0.936 3.20
6 18 49 46 0.939 3.56
7 17 50 47 0.940 3.76
2 27 37 37 1.000 2.37

【T1・Ollama MLX runner・qwen3.8:27b-mlx・2026-08-27 與 08-30 兩段 session 的全部八筆。】

(「每輪 target 產出」= 每次 target decode step 平均拿回幾個 token,包含沒有草稿、退回普通 decode 的那些輪。)

依接受率由低到高排,最後一列直接掉下來。這不代表它一定跑得最慢——表上沒有時間——但已經夠說明接受率和每輪換回幾個不是同一件事。

一輪 verification 裡到底發生什麼

一個便宜的來源先猜幾個 token,主模型用一次 batched forward 把這幾個候選一起驗完,可能一次就確定好幾個。這就是它能突破 Day 11 那條「一步一顆 token」上界的地方。

一輪結束你拿到的是 被接受的草稿數 + 1。那個 +1 是主模型自己確定的,不是猜的——所以就算一個草稿都沒中,那一輪也一定產出 1 個 token。

**產出有地板,但成本沒有。**猜它、驗它的時間已經付掉了,整體速度照樣可能倒退。這篇後面就有一格倒退到 0.52×。

回頭看那兩筆。第 2 筆平均只猜 1.37 個,全中也只換回 1.37 + 1 = 2.37;第 7 筆猜 2.94 個、中了 94%,換回 2.76 + 1 = 3.76猜得準但猜得少,輸給猜得多但沒那麼準。

所以要分三層看:接受率量準度,分母是猜了幾個;每輪 target 產出量收益,分母是跑了幾輪;實測加速比才是扣完成本的淨利——每輪 target 產出還得除以這一輪花了多久。

這裡有一個很容易踩到的口徑坑。llama.cpp 的 server log 會印 mean len,vLLM 也能回傳 mean_acceptance_length,兩者都是 1 + 被接受的草稿數 ÷ speculative verification 次數

但本文算的是「每次 target step 拿回幾個 token」,分母包含 n-gram 找不到候選、只能普通 decode 的那些輪;官方數字只看真的產生過草稿的輪。**一個在問「有猜的時候拿回多少」,另一個在問「整段生成平均拿回多少」。**對 n-gram 來說兩者可能差很多——拿不同工具的數字互相比之前,先看分母。

https://ithelp.ithome.com.tw/upload/images/20260831/20183550spgb1dxpzI.png
圖 1:同一台機器、同一顆模型的八筆紀錄,橫軸是工具印給你的數字。每輪 target 產出 = (iterations + accepted) ÷ iterations;那個 +1 是主模型額外確定的 token——有草稿被拒絕時是 correction token,全數接受時是 bonus token,這一輪根本沒有草稿時就是普通 target token。八個點不足以宣稱任何函數關係,所以不畫趨勢線。

同樣幾乎全中,為什麼一個 6.43×、一個只有 1.12×

我固定 target、prompt、輸出長度、sampling 與硬體,只替換 drafter,讓兩條路線對打。

target 是 Llama-3.1-8B Q4_K_M。一邊是 ngram-mod,不用第二個權重檔,從已生成的序列裡撈候選,維護一張約 16 MB 的雜湊表;另一邊是 draft-simple 配一顆 Llama-3.2-1B Q4_K_M,每猜一個 token 就跑一次它的 forward(這裡把它當 generic small-model drafter 的成本對照,不代表專門訓練的 draft model/head 的表現)。

drafter 接受率 每輪 target 產出 加速比
ngram-mod--spec-default 即為此) 1.000 14.22 6.43×
draft-simple + 1B(--spec-draft-n-max 7 0.998 7.88 1.12×

【T1 M4 Max 128GB・llama.cpp b10488・llama-server-ngl 99 -fa off -c 4096 -np 1n_predict 512temperature 0cache_prompt false・一格一個獨立 process・3 次取中位數。ngram 側用預設的 --spec-ngram-mod-n-match 24 / n-min 48 / n-max 64。同一格五次重跑的全距比達 1.261×,所以這組資料無法把 1.12× 與執行波動分開;6.43× 則遠超波動範圍。】

接受率只差 0.2%,兩邊都幾乎猜什麼中什麼。可是一輪結帳,ngram 拿回 14.22 個 token,1B 只有 7.88;前者主要付的是雜湊表的查找與維護,後者則得逐個 token 跑小模型。這一局,ngram 是一邊拿得多,一邊付得少。

所以帳要這樣記:

加速比 = 每輪 target 產出 × 不開投機時每個 token 的耗時
        ÷ 開了投機之後每一輪的總耗時

分子是收益,分母是成本。--spec-draft-n-max 7 表示每輪最多猜七個——猜滿七個就得付七次 1B forward,提早停下來才會少一些。這裡只能比檔案大小:1B 的 GGUF 是 0.75 GiB、8B 是 4.58 GiB,比例 16.4%。那不等於單次 forward 的耗時比,只說明 drafter 並不免費。

https://ithelp.ithome.com.tw/upload/images/20260831/20183550rwimCVHtIU.png
圖 2:一輪裡的三個量各自算什麼。加速比的分母是 drafter 猜的時間 + 一次 batched verification + dispatch、同步、KV 維護——這篇沒有把三項拆開量。⚠️ 另外,工具印的 mean len 分母只算有草稿的輪,跟圖上這個「每輪 target 產出」不同,兩者不能直接互換。

Day 11 的上界,真的穿過去了

第 11 天我算過 M4 Max 跑 70B 的 plain-decode 上界是 546 ÷ 42.52 ≈ 12.8 tok/s,並且寫「投機有能力打破它,但我沒有任何證據」。

我原本想直接用 70B 補上這筆證據,但同一格四次跑出 7.74~15.83 tok/s,波動整整兩倍,整組只能作廢。所以下面改用同一輪量測穩定得多的 12B——它證明的是那道上界的前提能被打破,不是一份 70B 跑分。

Gemma 4 12B QAT Q4_0 的檔案是 6.976 GB,同一條算式給 78.27 tok/s。

workload 不開投機(tok/s) 佔上界 ngram-mod(tok/s) 佔上界
逐字重複 50.24 64.2% 96.44 123.2%
照文件摘錄 48.63 62.1% 75.50 96.5%
中文散文 50.00 63.9% 76.92 98.3%
寫程式 49.25 62.9% 71.60 91.5%

【同上條件,n_predict 128,每格 3 次取中位數,八格全距比 1.006–1.116×。】

逐字重複那格是帳面上界的 123%。上界沒有壞——它算的是一步讀一次權重,而投機讓一次 target verification 換回好幾個 token。破的是前提,不是物理。

小模型 drafter,預設值也可能讓你變慢

draft-simple 來說,--spec-draft-n-max 決定每輪最多猜幾個,llama.cpp 的預設是 3。

同一顆 8B 配那顆 1B,跑一段中文散文、輸出 128 個 token:0.52×。開了投機,慢了將近一半。接受率只有 0.481,猜十個中不到五個,而每一次猜測不管中不中,1B 的 forward 都已經付出去了。

【同上條件,n_predict 128--spec-draft-n-max 3(llama.cpp 預設),每格 4 次取中位數。這格基準線本身不算穩,四次落在 33.98–50.34 tok/s;但開了投機的四次是 21.04–25.23,兩組區間完全不重疊——就算拿基準線最差的一次去比投機最好的一次,也還是 0.74×。】

但同一格把輸出拉到 512,就只剩 1.02×、接受率升到 0.818。慢一半那件事,綁在 128 token 的窗上。

同一段散文換成 ngram 也一樣:128 個 token 時一個候選都沒產生,512 個才接受 196/400——它要先累積到夠長的重複片段才開始有用。兩件事指向同一條:你量到的「有沒有效」,可能只是你選的輸出長度。

先翻 log,三分鐘就知道值不值得開

想自己試,只需要一台跑得動 llama-server 的機器和現成的 log;不必先下載第二顆模型,--spec-default 就能啟用 ngram-mod

三步:確認這次真的有草稿、算出每輪 target 產出、再跟關掉投機的對照。細節在圖裡。

https://ithelp.ithome.com.tw/upload/images/20260831/20183550frFuiIJ8kg.png
圖 3:三步各一行,可以截圖帶走。N 是產出的 token 總數,accepted 是被接受的草稿數——llama.cpp 給 predicted_ndraft_n_accepted,Ollama 給 iterationsaccepted。第三步要起兩個 process,是因為 --spec-type 是 server 的啟動參數,不是打兩次 API。

圖上沒寫的兩個例外。沒看到 draft_n 不等於沒啟用——也可能是啟用了但 n-gram 一個候選都沒找到,上一節那格就是後者。另外,在我這版 Ollama 上,GGUF runner 印的是 no implementations specified for speculative decoding,MLX runner 才印本文開頭那行 stats。

一個可以自己驗的事:在 llama.cpp b10488、temperature=0、本文那四組 ngram-mod 對照裡,開與不開投機的四組輸出逐字相同,sha256 也一致。

⚠️ 但這四組不能替所有 drafter 背書。標準投機解碼理論上保持 target 的輸出分布,而上游有人回報:量化 target 配 draft-mtp / draft-dspark 時即使 greedy 也會偏離,同一顆量化 target 換成 ngram 則一致【ggml-org/llama.cpp#25618,2026-08 仍 open、尚未確認】。「無損」要綁 drafter 路線、量化格式與 runtime 一起講。

附錄:十種方法,五種不用第二個權重檔

方法 要不要第二個權重檔 候選從哪來
ngram-simple 不要 當前 token history 裡重複出現的片段
ngram-map-k / ngram-map-k4v 不要 同上,不同索引與統計結構
ngram-mod--spec-default 開的就是它) 不要 token history + 一張共用的 hash pool(約 16 MB)
ngram-cache 不要 累積的 n-gram 統計,也可載入外部統計檔
draft-simple 另一顆小模型
draft-eagle3 / draft-dflash 針對 target 訓練的 draft model/head
draft-dspark DFlash backbone + semi-autoregressive Markov head
draft-mtp 看 target 自己有沒有 模型內建的 MTP head

draft-dspark 要一份針對該 target 訓練的 checkpoint,而且官方文件寫明目前只支援 Qwen3 backbone,其他仍在規劃中。

小結

  • **三個數字,三個分母。**接受率看猜中幾成,每輪 target 產出看一次 decode step 拿回幾個,實測加速比才是最後結帳。⚠️ 工具印的 mean len 分母只算有草稿的輪,跟這裡不同。
  • **產出有地板,成本沒有。**ngram 一輪拿得多,整輪付出的時間也明顯比較少;而 1B drafter 即使幾乎全中,也可能沒有實質加速——本文那格量到 1.12×,以這輪的波動根本分不出它有沒有真的加速。
  • **超過 plain-decode 上界不代表物理失效。**投機解碼改掉的是「一次 forward 只產一顆 token」這個前提。

明天 Day 21 換一張帳:一張 12GB 卡要同時養 LLM、embedding、reranker 三張嘴,顯存怎麼一毛一毛分——那本帳裡最容易被漏掉的,是三份沒人記得的 CUDA context。

咱們明天見。


上一篇
Day 19 - 1,792 GB/s 的 5090 對 1,200 GB/s 的 M5 Ultra:為什麼帳面 decode 上界只差 6%?
下一篇
Day 21 - 一張 12GB 卡怎麼替整套 RAG 編預算:LLM、embedding、reranker 的顯存分帳
系列文
不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言