推論就是把模型往前跑一遍
訓練是用資料打造模型的昂貴一次性工程;推論(inference)則是你每次真正使用它時發生的事:你交給模型一段文字,它回給你更多文字。大型語言模型推論是日常的主力——同一份訓練好的權重會被往前跑上數十億次,世界上每一則對話、每一次自動完成、每一個 API 呼叫都是一次推論。
本質上模型只做一件小事:預測下一個詞元。在已知前文的情況下,它替每一個可能的下一個詞元算出機率,挑一個、接上去、再重複。一段 500 字的回答,大約就是 700 個這種步驟首尾相接而成。
下一個詞元預測的核心:模型把隱藏狀態轉換成所有可能下一個詞元的機率分佈,再從中取樣一個。
第一階段:預填充一次讀完整段提示
當你的提示送進來時,模型會在一次平行運算中處理整段提示,這就是預填充(prefill)。因為提示的每個詞元一開始就都到齊了,GPU 可以把它們合成一個大型矩陣乘法一起算——極有效率,也正是 GPU 的強項。預填充與解碼之分是你最該內化的一個分界。
圖示:文字被切分為詞元 id,再映射為嵌入向量。
預填充是算力受限(compute-bound)的:面對 4,000 個提示詞元,GPU 忙著做真正的算術,核心全速運轉。預填充花的時間,就是使用者感受到的首詞元時間(time to first token,TTFT)——從按下 Enter 到看見第一個字出現之間的那段停頓。
第二階段:解碼一次寫一個詞元
提示消化完後,生成就開始了。解碼(decode)每跑一次前向就產出一個詞元:預測一個詞元、接上去、把略長一點的序列再餵回去、預測下一個。你無法跳著算——第 50 個詞元依賴第 49 個——所以解碼天生是循序的。
圖示:自迴歸的下一個詞元生成迴圈,每次前向附加一個詞元。
tokens = prefill(prompt) # one big parallel pass over the prompt
while not finished:
logits = model.step(tokens) # one forward pass -> scores for next token
nxt = sample(logits) # pick a token (greedy or sampling)
tokens.append(nxt) # grow the sequence by one, then repeat解碼是記憶體受限(memory-bound)而非算力受限:每一步只碰一個新詞元,GPU 的運算單元大多在發呆,等著把模型權重從記憶體串流進來。這種不對稱——肥胖的平行預填充、瘦長的循序解碼——幾乎解釋了你日後會遇到的每一種服務最佳化。
使用者真正感受到的兩個數字
兩個指標描述了這段體驗。TTFT是出現任何東西前的等待(由預填充主導);每秒詞元數(tokens per second,TPS)是之後文字串流出來的速度(由解碼主導)。反應靈敏的助理要同時做到低 TTFT 與高 TPS;這是兩個不同的問題,要用兩種不同的辦法解。
為什麼這對後面一切都重要
記住一句話:預填充是算力受限且平行的;解碼是記憶體受限且循序的。KV 快取存在是為了讓解碼可以忍受,批次處理存在是為了在解碼期間填滿閒置的運算單元,而吞吐量與延遲的取捨全都活在這兩個階段之間的縫隙裡。下一篇我們就打開解碼會撞上的那道記憶體高牆。