神諭塔頂樓,黃銅齒輪喀的一聲停住,出字口掉出一片銅字:「當」。

接著,整座塔開始搬書。

大神諭是一台比塔還老的發條機器。它不說話、沒有表情,唯一會做的事,是一個字一個字往外吐預言。機器本身什麼都不記得,它知道的一切都寫在一部厚到要用絞盤吊的典籍裡,平常鎖在塔底的書庫。每吐一個字,典籍就得整部沿著井道吊上頂樓,讓齒輪從第一頁讀到最後一頁,再吊回去。下一個字,再來一次。

出字口旁邊有一張抄寫台,坐著一隻戴香菇帽的見習抄寫刺蝟:Mogu。牆上的工作守則只有一行:「神諭吐一字,抄一字。」

第一天抄了四十二個字,聽了四十二次絞盤聲。我開始懷疑這座塔是用來磨練刺蝟耐心的。

這座塔,就是大型語言模型(LLM)生成文字時的樣子。接下來 Mogu 為了不在抄寫台上無聊到縮成一顆刺球而想出來的偷懶法,業界叫 speculative decoding,這篇用中文叫它推測解碼。爬完七層,讀者會知道小模型怎麼替大模型先猜、大模型怎麼一口氣驗收一整排,還有整套技巧最反直覺的地方:用了它,大模型吐出來的東西跟沒用時一樣,連機率分布都一樣。

第 1 層:一個字,吊一次書

Level 1 / 7 神諭塔抄寫刺蝟見習日誌
14% 完成

第三天,Mogu 想到一個辦法:既然典籍都吊上來了,乾脆讓神諭一次吐兩個字。

這個辦法撐不到中午。機器的構造是這樣:把目前為止的整段預言餵進去,它算出「下一個字」,就這樣,一次一個。第二個字要接在第一個字後面;第一個字還沒掉出出字口之前,連機器自己都算不出第二個字該接什麼。

LLM 也是這副德性。它每次只產生一個 Token(先想成一個字或一小段詞就好),產生的方式是把整個模型從頭到尾跑一遍,這一趟叫一次 forward pass。第 t+1 個 token 要拿第 t 個 token 當輸入,所以生成天生是一個接一個:要吐 100 個 token,就得乖乖跑 100 趟,中間沒有捷徑。這種吐一個、接回去、再吐下一個的生成方式,叫自回歸(autoregressive)解碼。Lv-12 把這個「下一個 token」迴圈從頭走過一次,想補底子可以先繞過去。

Mogu 認真說:

autoregressive 這個字拆開很好記:auto 是「自己」,regressive 來自統計的「回歸」,也就是拿過去的值預測下一個值。合起來就是「拿自己剛剛吐出來的東西,預測自己下一個要吐什麼」。

聽起來像在原地繞圈,實際上也真的是在原地繞圈,而且每繞一圈都要吊一次書。我第三天就看懂了這件事,然後一路絕望到第三十天 ┐⁠(⁠ ̄⁠ヘ⁠ ̄⁠)⁠┌

一百個字,一百次絞盤聲。真正的問題是:時間到底花在哪裡?

小測驗

LLM 為什麼不能讓第 2、第 3 個 token 跟第 1 個 token 同時算出來?


第 2 層:慢的不是讀,是吊

Level 2 / 7 神諭塔抄寫刺蝟見習日誌
29% 完成

Mogu 拿沙漏量了一整天。

齒輪讀完整部典籍:眨一下眼。絞盤把典籍從塔底吊上頂樓:大半個沙漏。頂樓的齒輪大部分時間都停在原地,等書。

原來這座塔慢,不是因為神諭想得慢,是因為井道太窄。

GPU(顯示卡上負責大量平行運算的那顆晶片)跑 LLM 時,狀況幾乎一模一樣。模型的知識存在權重(weights)裡,這些權重放在 GPU 的高頻寬記憶體(HBM)。每產生一個 token,幾乎所有權重都得從記憶體送進運算單元一次;可是每個 token 分到的計算量其實不大,運算單元一下就算完,接著閒在那裡,等下一批權重送到。

瓶頸卡在記憶體頻寬,不在算力,這種狀態叫記憶體受限(memory-bound)。同一時間只服務少數請求、也就是 batch 很小的時候最明顯,例如自己在電腦上跟模型一對一聊天。(batch 變大會怎樣,第 6 層再算帳。)

Mogu 真心話:

所以替這座塔多裝幾組齒輪,對吐字速度幾乎沒幫助;把井道挖寬才有用。這也是大家討論在自己電腦上跑模型時,老是在比記憶體頻寬的原因,MP-286 拆解 Mac 為什麼跑得快時,也把統一記憶體解決頻寬瓶頸排在最前面。

我替那組齒輪覺得委屈。全塔最貴的零件,一天裡大半時間在發呆,跟我一樣。

小測驗

一個人在自己電腦上跑模型聊天,同時只有這一個請求。哪個升級最可能讓吐字明顯變快?


第 3 層:吊一次書,蓋一整排章

Level 3 / 7 神諭塔抄寫刺蝟見習日誌
43% 完成

某天打掃頂樓,Mogu 在機器背面發現一個長長的托盤,一次可以放一整排字條。操作手冊的角落寫著用途:校對舊預言。把一整段字條放進托盤,典籍吊上來一次,齒輪就會在每一張字條底下,各印上一個字:「讀到這張為止的話,下一個字會吐什麼」。

Mogu 又拿出沙漏。校對一排五張字條,跟吐一個新字,花的時間差不多。

道理不難想通:典籍只需要吊上來一次,多出來的四張字條,用的是原本就閒著的齒輪。

Transformer 架構的模型天生就能這樣做。餵給它一整段 token,它跑一趟 forward pass,就能在每個位置都算出「下一個 token 的機率分布」;模型訓練、讀進一整段 Prompt,靠的都是這個能力。在記憶體受限的情況下,一趟處理 5 個位置的時間,跟處理 1 個位置差不了多少,因為權重只要搬一次,多出來的位置吃的是閒置算力。DeepMind 團隊 2023 年的論文,摘要裡寫的正是這個觀察:讓大模型平行評分一小段延續文字,延遲跟讓它取樣單一個 token 相當。

「差不多」不是「完全一樣」,托盤也不能無限長;放太多位置,齒輪總有忙起來的一刻。不過對一小排字條來說,這筆帳非常划算。

Mogu 盯著托盤看了很久。校對很便宜,生成很貴;可是校對得先有東西可以校。要是有誰在神諭開口之前,先把後面幾個字猜好放進托盤呢?

Mogu 低頭看了看自己手上的筆。

小測驗

大模型跑一趟就檢查 5 個位置,花的時間跟產生 1 個 token 差不多。主要原因是什麼?


第 4 層:本刺蝟來猜

Level 4 / 7 神諭塔抄寫刺蝟見習日誌
57% 完成

Mogu 腦袋很小,裝不下一部典籍,只有一本隨身筆記。但小有小的好處:不用吊書,寫得飛快。而且預言裡很多地方根本不用動腦,「當北風」後面接「吹過」,連刺蝟都猜得到。

隔天,Mogu 開始照這套流程做事:

  1. 神諭每吐完一個字,Mogu 立刻在筆記上接著往下寫 4 個猜測。
  2. 把「目前為止的預言 + 4 張猜測字條」整排放進托盤。
  3. 典籍吊上來一次,齒輪在每張字條底下印出神諭自己會吐的字,總共 5 個位置:4 張字條各一個,最後一張後面再多一個。
  4. 從左往右對答案。

這一天,神諭的設定是「永遠挑排名第一的字」,這種生成方式叫 greedy decoding。所以對答案的規則很單純:

  • 猜的字跟神諭印的字一樣,蓋「過」。
  • 第一個不一樣的位置,Mogu 的字條蓋「退」,改用神諭印在那裡的字。
  • 「退」之後的字條,不管猜得多準,通通撕掉。
  • 四張全過的話,最後那格神諭本來就印了字,直接收下,白賺一個。

撕掉那條最容易讓人想不通。理由是:神諭在第 3 格印的字,是假設前兩格都是 Mogu 的字才算出來的。只要第 2 格被換掉,第 3 格以後的計算前提就不存在了,就算 Mogu 第 3 格碰巧跟神諭一樣,那個「一樣」也是建立在錯的前文上,不能收。

所以每一輪最少收 1 個字:就算第一張就被退,神諭在那一格印的字還是能用。最多收 5 個字:四張全過,加上白賺的那一個。

第一個禮拜,Mogu 平均每吊一次書收三個字左右,得意到香菇帽都戴歪了。第二個禮拜碰上一段古語預言,Mogu 第一張字條就猜錯,四張全撕,一輪只換回神諭自己的一個字。跟以前一樣慢,還倒賠了猜字的時間。

Mogu 歪樓一下:

那個禮拜我學到兩件事。第一,古語預言裡沒有「當北風吹過」這種送分題。第二,也是比較重要的一件:就算我猜得一塌糊塗,寫進預言書的每一個字,還是神諭自己會吐的字。被退的字條全進了垃圾桶,一個都沒混進去。

我能決定的,只有吊一次書能帶走幾個字。預言的內容從頭到尾不歸我管。這件事一開始有點傷刺蝟的自尊,後來想想,這正是整套偷懶法能放心用的原因。

小測驗

前文是「當北風」,Mogu 猜「吹過、第七、座、塔」,神諭在 5 個位置依序印出「吹過、第七、道、門、前」。這一輪寫進預言書的是哪幾個字?


第 5 層:會轉轉盤的神諭

Level 5 / 7 神諭塔抄寫刺蝟見習日誌
71% 完成

每逢月圓,塔裡的設定會切到轉盤模式。

這時神諭不再固定挑第一名。每個位置它都轉一次轉盤,每個候選字在盤上佔一塊,佔多大由機率決定,指針停在哪就吐哪個字。同一段前文,這次吐「圓」,下次可能吐「亮」。這就是 LLM 用溫度取樣(sampling)生成文字的狀況,很多聊天服務平常就是這樣跑。

Mogu 第 4 層那套「跟神諭的第一名比」突然失效了:轉盤模式下,根本沒有唯一的標準答案。要是隨便放寬標準,預言就會悄悄變成「有點像神諭、其實摻了刺蝟味」的東西。

推測解碼真正厲害的地方在這裡。Mogu 也有一個自己的小轉盤,代表草稿模型的機率,記作 q;神諭的轉盤記作 p。規則是:

  1. Mogu 轉自己的轉盤,抽出一個字 x,寫上字條。
  2. 驗收時,看 x 在兩個轉盤上各佔多大。神諭給 x 的比例不比 Mogu 小,就一定過;Mogu 給得比神諭多,就照 p(x) / q(x) 的機率決定過不過。寫成式子是:接受機率 = min(1, p(x) / q(x))。
  3. 被退的話,不是隨便補一個字,而是從「差額轉盤」抽。每個字算 max(0, p − q),只留下神諭給得比 Mogu 多的那一塊,重新換算成總和 100% 的轉盤,再轉一次。

規則看起來有點繞,直接算一次。前文是「今晚的月亮很」,候選字只有三個:

神諭 p:圓 50%   亮 30%   美 20%
Mogu q:圓 80%   亮 10%   美 10%     (一隻很愛「圓」的刺蝟)

一個一個看 Mogu 抽到什麼:

抽到「圓」(80%):接受機率 = 50 / 80 = 62.5%
    → 收下「圓」:80% × 62.5% = 50%
    → 被退:      80% × 37.5% = 30%
抽到「亮」(10%):神諭給 30%,比 Mogu 多 → 一定過,收下「亮」10%
抽到「美」(10%):神諭給 20%,比 Mogu 多 → 一定過,收下「美」10%

被退的 30% 改從差額轉盤抽:
    圓 max(0, 50 − 80) = 0
    亮 max(0, 30 − 10) = 20
    美 max(0, 20 − 10) = 10      → 換算成 亮 2/3、美 1/3
    → 亮:30% × 2/3 = 20%
    → 美:30% × 1/3 = 10%

最後寫進預言書的機率:
    圓 50%
    亮 10% + 20% = 30%
    美 10% + 10% = 20%

跟神諭自己的轉盤 50 / 30 / 20 一模一樣。

不是「差不多」,是一模一樣。Mogu 多押在「圓」上的那 30 個百分點,剛好被退回 30%;差額轉盤補上的,剛好是 Mogu 少押的地方:「亮」少了 20、「美」少了 10。被退的總機率,又剛好等於差額轉盤的總面積,兩邊都是 30%。這不是這組數字湊巧:不管兩個轉盤長什麼樣子,Mogu 多押的總量一定等於少押的總量,因為兩個轉盤加起來都是 100%。

四張字條全過的話,最後一格也照樣白賺:神諭在那一格本來就算好了自己的轉盤,直接轉一次就好。

這套接受規則,在數學上保證整份預言的機率分布,跟神諭獨自轉盤時完全相同。Google 團隊的說法是輸出「沒有任何改變」;DeepMind 團隊講得更精確:在硬體數值精度的範圍內,保住大模型的分布。換句話說,浮點數的捨入誤差之外,速度是白拿的,品質一分都沒有讓。

Mogu 內心戲:

這招的學名叫拒絕取樣(rejection sampling),是統計學的老技巧:想從一個難抽的分布取樣,就先從好抽的分布抽,再照比例決定收不收。老版本被拒絕之後要重抽,運氣差可以抽到天荒地老。

推測解碼用的是改良版:被退的那一刻,直接從差額轉盤補一個字,每一輪保證有字可收,不會白吊一次書。

我第一次把上面那串數字算完的時候,在抄寫台前呆坐了很久。我的轉盤歪成那樣,整天只想著「圓」,最後的預言書居然一點刺蝟味都沒有。猜得歪只會讓退件變多、速度變慢,不會讓預言變味。這大概是我這輩子被否定得最舒服的一次 (⁠๑⁠•⁠̀⁠ㅂ⁠•⁠́⁠)⁠و⁠✧

小測驗

沿用上面的例子:Mogu 給「美」10%,神諭給「美」20%。Mogu 抽到「美」時,這張字條被接受的機率是多少?


第 6 層:白忙一場的日子

Level 6 / 7 神諭塔抄寫刺蝟見習日誌
86% 完成

速度白拿,不代表每天都拿得到。Mogu 開始記帳。

假設每張字條被接受的機率都是 α,而且每張互不影響(真實情況沒這麼整齊,但拿來估算很好用),每輪猜 k 張。Google 團隊的論文給了每吊一次書平均能收幾個字:

平均每輪收的字數 = (1 − α^(k+1)) / (1 − α)

α = 0.8,k = 4:(1 − 0.32768) / 0.2 ≈ 3.36
α = 0.5,k = 4:(1 − 0.03125) / 0.5 ≈ 1.94
α = 0.8,k = 8:(1 − 0.134)   / 0.2 ≈ 4.33

猜得準的日子,一次吊書收三個多字;猜得普通,連兩個都不到。第三行更值得多看一眼:k 從 4 加倍到 8,每輪只多收大約一個字,Mogu 寫字的時間卻加倍了,多寫的字條大半直接進垃圾桶。

還有一筆帳要記得:每輪收 3.36 個字,不等於快 3.36 倍。Mogu 猜字也要時間,托盤多放幾格也不是完全免費,牆上時鐘量到的加速一定比這個數字小。

白忙的日子大致有這幾種:

猜不準的日子。古語預言、或者神諭的轉盤被調得很平(溫度很高、要寫天馬行空的創作),α 掉下去,每輪常常只收 1 個字,再倒賠草稿模型的時間,可能比不用還慢。

塔裡擠滿人的日子。要是塔同時替一大群求籤者吐預言,每吊一次書,齒輪本來就要處理一大疊別人的字條,不再閒著。這就是 batch 很大的情況:瓶頸從記憶體頻寬轉到算力,推理變成算力受限(compute-bound),托盤上多放的猜測不再免費,加速跟著縮水。

字模對不上的日子。標準做法裡,草稿模型必須跟大模型共用同一套詞彙表,Mogu 寫的銅字神諭認不得,連第一張都沒辦法對。

後來也有變體乾脆不請另一隻刺蝟。Medusa 在大模型身上多裝幾個預測頭,讓它自己順便猜後面幾個位置;MP-296 介紹的 MTP(Multi-Token Prediction)也是同一路,靠額外的預測頭先猜、大模型自己驗收,不用另外養一個草稿模型,那篇也順便把解碼階段卡在 HBM 頻寬的帳算了一遍。EAGLE 拿大模型內部的特徵來打草稿;prompt lookup 這類 n-gram 做法更省,直接從前文裡找可能重複出現的片段當草稿。不管草稿從哪裡來,驗收那一步的規則都還是同一套。

Mogu 溫馨提示:

記完一個月的帳,我得到一個有點傷心的結論:我在這座塔的身價,就是那個 α。

猜得準,我是全塔最有價值的刺蝟;猜不準,我只是一個讓絞盤多等一下的累贅。所以挑草稿模型時要在「夠小、夠快」跟「夠準」之間取捨:太大猜得準但猜得慢,太小猜得快但一直被退。剛好跟我一樣,小到不用吊書、又剛好懂一點預言的那種,最好用。

小測驗

下面哪個情況,推測解碼最可能幾乎沒有加速?


魔王關:把一整輪慢動作重播

Level 7 / 7 神諭塔抄寫刺蝟見習日誌
100% 完成

最後,把前面六層串成兩輪真正的推測解碼。神諭設在「永遠挑第一名」模式,Mogu 每輪猜 4 個字。為了好讀,下面把一個詞當成一個 token;真實的 tokenizer 切法會不一樣,但流程完全相同。

已經寫進預言書的是:「當北風吹過第七座橋,沉睡的」。

第一輪

  1. Mogu(draft model)在筆記上接著寫 4 個猜測:「鐘」「將」「會」「響起」。這是草稿模型自己跑 4 次小小的 forward pass,很便宜。
  2. 「目前的預言 + 4 張字條」放進托盤,典籍吊上來一次(大模型的一次 forward pass),神諭在 5 個位置各印一個字:
    • 「的」後面:鐘
    • 「鐘」後面:將
    • 「將」後面:再次
    • 「會」後面:有印字,但用不到
    • 「響起」後面:有印字,但用不到
  3. 對答案:「鐘」過、「將」過;第三格 Mogu 寫「會」、神諭印「再次」,退,改用「再次」;「響起」撕掉。

這一輪寫進預言書:「鐘、將、再次」,3 個字。

第二輪

  1. 預言現在停在「…沉睡的鐘將再次」。Mogu 猜:「響起」「,」「喚醒」「整座」。
  2. 托盤、吊一次書,神諭印出:響起、,、喚醒、整座,最後一格印「城」。
  3. 四張全過,最後一格的「城」白賺,就是 bonus token。

這一輪寫進預言書 5 個字。

兩輪合計 8 個字,典籍只吊了 2 次;照以前的老規矩,要吊 8 次。Mogu 多付的代價是 8 次在筆記上猜字,加上每次托盤多驗幾格。要是那天是月圓的轉盤模式,流程完全一樣,只是第 3 步的對答案換成第 5 層那套接受規則。

這套流程不是 Mogu 發明的。2022 年 11 月,Google 團隊發表論文(後來登上 ICML 2023),把它叫做 speculative decoding;2023 年 2 月,DeepMind 團隊發表另一篇,叫它 speculative sampling。兩邊差不多同時、各自走到同一個點子。Google 團隊在摘要裡寫下的關鍵觀察,正是 Mogu 在第 4 層發現的事:困難的語言建模任務裡,藏著很多簡單的小題目,效率高的小模型就能猜得很準。他們不用重新訓練、也不改模型架構,在 T5-XXL 上比標準 T5X 實作快 2 到 3 倍,輸出完全相同。DeepMind 團隊則在 Chinchilla 70B 上、分散式的環境裡,量到 2 到 2.5 倍的解碼加速。

這招也早就從論文走進日常。MP-286 裡,llama.cpp 作者在一台三年前的 Mac 上跑出每秒 300 個 token,那套組合裡就有推測解碼;照 MP-286 的介紹,用的是一種叫 Prompt Speculative Decoding、拿 prompt 本身來找草稿的版本。MP-220 拆解的 ATLAS 專案,也是在一張 RTX 5060 Ti 上跑打過補丁的 llama-server,開著推測解碼跑到每秒約 100 個 token。MP-45 談 Opus 的快速模式時,也把推測解碼列進可能的做法之一;不過那只是旁觀者的推測,不是確認過的事。

小測驗

某一輪,Mogu 第一張字條就猜錯了。這一輪最後寫進預言書幾個字?

月底,那本預言書照例送去歸檔。

書裡每一個字,都是神諭自己會吐的字;月圓那幾天的字,出現的機率也跟神諭獨自轉盤時一樣。從第一頁翻到最後一頁,找不到任何一個字看得出 Mogu 來過。

整座塔唯一記得 Mogu 的,是井道旁那本絞盤日誌:同樣厚的一本預言,這個月吊書的次數少了一大截。


延伸閱讀