iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Software Development

LLM infra 學習日記系列 第 9

Prefill 與 Decode 為什麼是兩種不同的 Workload?

  • 分享至 

  • xImage
  •  

上一篇把一個 Transformer Layer 拆成 GEMM、elementwise、reduction 和 data movement。

但同一個 Layer,在 LLM inference 裡其實會跑出兩種完全不同的硬體行為:

Prefill:一次處理整段 Prompt
Decode:每一步只產生一個新 Token

模型權重沒有變,operator 也沒有變。真正改變的是 tensor shape。


先看答案

以下是在一張閒置的 RTX 6000 Ada 上,使用 Llama-3.2-1B、bf16、batch 1、context length 2048 的測量結果:

Prefill Decode
Linear Layer 輸入 [2048, d] [1, d]
Projection arithmetic intensity 768 1
Attention arithmetic intensity 819 4
Roofline 區域 Compute-bound Memory-bound
SM Active 89.9% 17.0%
Tensor Active 64.1% 0.1%
GPU Clock 1015 MHz 2342 MHz

這兩欄不只是「工作量比較大」和「工作量比較小」。

Prefill 和 Decode 落在 Roofline 的兩側,受到不同的硬體上限限制。

https://ithelp.ithome.com.tw/upload/images/20260825/20183542aqSF7eLjLD.png

圖中的橘色圓點是 prefill,藍色方塊是 decode;marker 面積代表該操作佔該階段 GPU time 的比例。Prefill 的主要 GEMMs 位於 ridge point 右側,decode operations 則集中在左側的 memory-bound 區域。


Prefill:把 Prompt Tokens 一次排開

Prefill 會把整段 prompt 一次送進模型。

當 sequence length 是 2048 時,Linear Layer 看到的 GEMM 類似:

[2048, d] × [d, N]

同一份權重可以被 2048 個 token 重複使用,因此搬進 GPU 的每個 byte 可以換到很多次運算。

這就是高 arithmetic intensity。

在這次測量中,projection 的 arithmetic intensity 是 768 FLOP/byte,高於這張卡約 343 FLOP/byte 的 ridge point,因此落在 compute-bound 區域。

GPU metrics 也符合這個結果:89.9% 的 SM 處於 active,Tensor Core activity 達到 64.1%。

Clock 反而降到 1015 MHz,是因為密集的 bf16 matrix multiplication 讓這張 300 W GPU 接近 power limit。這個 clock 數字具有硬體特性,但它和高 Tensor Active 一起說明:prefill 真的在大量計算。


Decode:每一步只有一個 Token

Decode 時,前面的 tokens 已經存進 KV Cache,每一步只需要處理最新的一個 token。

Projection 的 shape 於是變成:

[1, d] × [d, N]

矩陣權重仍然要讀取一次,但只服務一個 token。GEMM 退化成接近 GEMV 的工作,幾乎沒有 weight reuse。

對 bf16、batch 1 的 decode projection,可以粗略得到:

Arithmetic Intensity ≈ Batch Size ≈ 1 FLOP/byte

相對於 343 FLOP/byte 的 ridge point,這顯然落在 memory-bound 區域。

實測也看到 SM Active 只有 17.0%,Tensor Active 更只剩 0.1%。GPU 並不是算不動,而是大部分時間沒有足夠的計算可以餵給 Tensor Core。

這時 clock 可以維持在 2342 MHz,但更高的 clock 也無法解決問題:decode 更需要 memory bandwidth,而不是更多 FLOPs。


Decode Attention 也被 KV Cache 限制

Projection 要反覆讀取模型權重;attention 則要讀取先前累積的 KV Cache。

對這個使用 GQA 4× 的模型,decode attention 的 arithmetic intensity 約為:

AI = 2 × GQA ratio / bytes per element
   = 2 × 4 / 2
   = 4 FLOP/byte

有趣的是,batch size 和 context length 在這個式子裡會互相消掉。

增加 batch 可以提高 projection 的 weight reuse,卻不會讓每條 sequence 共用彼此的 KV Cache。因此 projection 可能隨 batching 逐漸靠近 compute-bound,decode attention 仍然留在 memory-bound。

這也是 MQA、GQA、KV quantization 和 prefix sharing 都在想辦法減少 KV traffic 的原因。


Decode 還有另一個問題:Kernel Launch

上一篇看到,一個 Layer 在 PyTorch SDPA 下仍然會展開成 43 個 kernels。

Prefill 時,每個大型 GEMM 可以跑很久,CPU 發出下一個 kernel 的時間不明顯。

Decode 時,每個 kernel 只做很少的工作,GPU 反而可能跑完後等待 CPU 繼續 enqueue。

這可以用 CUDA Graph 做一個很乾淨的對照:

vLLM Prefill Decode / token
Eager launches 30.74 ms 7.982 ms
CUDA Graph replay 30.97 ms 3.432 ms

兩邊執行相同 kernels、處理相同資料,只差在 kernel launches 是逐一送出,還是從已錄製的 graph replay。

結果是:

Prefill:幾乎沒有差異
Decode:2.33× faster

CUDA Graph 沒有減少 FLOPs,也沒有增加 memory bandwidth。它只是減少 Python、C++ 和 CUDA driver 的 launch overhead。

這個實驗直接說明:decode 不只 memory-bound,也可能 launch-bound。


Decode 的 Memory Floor

Llama-3.2-1B 約有 1.24B parameters。使用 bf16 時,模型權重大約是:

1.24B × 2 bytes = 2.47 GB

RTX 6000 Ada 的 bandwidth 約為 960 GB/s。假設每產生一個 token,權重至少要通過 memory bus 一次:

2.47 GB / 960 GB/s = 2.57 ms/token

也就是理想上限約為 388 tokens/s,而且這還沒有計入 KV Cache、activation、scheduling 和任何其他 overhead。

這次 vLLM + CUDA Graph 實測為 3.43 ms/token,也就是 291 tokens/s,已經達到這個簡化 memory floor 的約 75%。

所以如果只讓 Tensor Core 再快一倍,decode latency 也不會跟著快一倍。權重還是要從 memory 搬進來。

Reference


上一篇
一個 Transformer Layer 在 GPU 上到底跑了什麼?
下一篇
FlashAttention 解決了什麼?從 Tiling 到 Online Softmax
系列文
LLM infra 學習日記19
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言