上一篇我們知道,LLM 並不是一次把整句答案吐出來。
它會不斷做同一件事:
看目前的內容 → 猜下一個 Token → 再繼續猜
但這裡其實少講了一件很重要的事。
模型第一次看到你的 Prompt,和後面開始一個 Token、一個 Token 往外吐,做的事情其實不太一樣。
這兩個階段分別叫做:
Prefill 和 Decode
假設我們問:
請幫我整理這篇 5000 字的文章,並列出三個重點。
LLM 不會看到第一個字就直接開始回答。
它得先把整段 Prompt 處理過一遍,理解前面到底講了什麼。
這個階段就是:
Prefill
流程可以先想成:
整段 Prompt → Prefill → 建立上下文資訊
你可以把它想成考試。
老師丟給你一大篇閱讀測驗,你至少得先把題目看完,才知道等等要回答什麼。
Prompt 越長,要看的東西自然也越多。
所以有時候我們丟一大段文件給模型,會發現:
答案還沒開始跑,前面就先卡了一下。
這段等待時間,很大一部分就跟 Prefill 有關。
Prompt 處理完之後,模型終於可以開始回答了。
接下來就進入:
Decode
例如模型先產生:
台灣
接著再產生:
最高
然後:
的
再來:
山
所以 Decode 大概是:
Decode → Token → Decode → Token → Decode → Token → ...
這個階段跟 Prefill 最大的差別,就是它很難一次把後面的 Token 全部算完。
因為下一個 Token 是什麼,要看前一個 Token 產生了什麼。
有點像寫作文。
你不可能還沒決定上一句要寫什麼,就先把後面十句全部寫好。
所以 Decode 天生就比較像:
一個一個慢慢往前走。
這時候就會出現一個很大的問題。
假設 Prompt 已經有 5000 個 Token。
模型產生第 5001 個 Token 時,如果又把前面 5000 個 Token 全部重新算一次。
下一個 Token 又重新算一次。
那真的會慢到懷疑人生。
所以 LLM 會把前面算過的一部分結果先保存起來。
這個東西就是:
KV Cache
整個流程就變成:
Prompt → Prefill → KV Cache → Decode → Token → Decode → Token → ...
這樣 Decode 的時候,就不用每次從頭把整段 Prompt 全部重新算過一次。
是不是突然開始有點像「Cache」該做的事情了?
這也是 LLM Inference 很有趣的地方。
Prefill 一次可以處理很多 Prompt Token,會進行大量矩陣運算。
所以通常會比較吃:
GPU 運算能力
但到了 Decode,每次通常只產生少量的新 Token,卻還是需要不斷讀取大量模型權重。
這時候很多情況下,問題反而變成:
記憶體頻寬不夠快。
也就是:
Prefill:算很多東西
Decode:一直搬很多東西
當然實際情況還會受到模型大小、Batch Size、硬體等因素影響,但先記住這個方向就夠了。
關係其實非常大。
如果 Prefill 很慢:
你會等很久,模型才開始回答。
如果 Decode 很慢:
模型開始回答了,但每個 Token 都慢慢擠出來。
所以我們之後在看 LLM 效能時,不能只看一個 Tokens/s 就結束。
因為:
「多久開始回答」
跟
「開始回答後有多快」
其實是兩件不同的事情。
今天先記住這條就好:
Prompt → Prefill → KV Cache → Decode → Token → Decode → Token → ...
Prefill 負責先把 Prompt 處理好。
Decode 則負責後面一個 Token、一個 Token 生成答案。
而接下來我們就會碰到第一個很重要的效能問題:
GPU 明明這麼快,為什麼 Decode 還是可能跑不快?
下一篇,我們就來看看:
到底是 GPU 算不動,還是資料根本來不及送給它?