Day 15 的 Continuous Batching 解決了「完成一條,就補進一條」。
但還有另一種卡頓:一條很長的新 prompt 進來,Prefill 一次要處理幾千個 tokens;正在 decode 的 requests 雖然只各需要一個 token,卻得等這個長 step 結束。
Chunked Prefill 的想法很單純:不要一次跑完整條 prompt,把它切成多個 scheduler steps。
先說清楚:今天是概念與 source tracing,沒有執行 benchmark。
Prefill 會平行處理 prompt 裡的所有 tokens,通常是 compute-heavy;Decode 則每條 request 一次只前進一個 token。
當一條長 prompt 和許多正在串流輸出的 requests 共用 GPU 時,如果 Scheduler 把完整 prompt 一次排進去:
長 Prefill step 變很久
→ Decode requests 這一步無法前進
→ 使用者看到下一個 token 的間隔變長
Chunked Prefill 會把 prompt 切成連續片段:
P[0:chunk] → P[chunk:2×chunk] → P[2×chunk:3×chunk] → ...
每一段算完後,KV Cache 都保留下來。下一段能直接讀取已算好的 prefix,不是從頭重算。
假設每個 step 的 token budget 是 16。目前有六條 decode requests,各自需要一個 token;新來的 P 有 40-token prompt。
| Step | Decode | 給 P 的 Prefill chunk | P 已完成 Prefill |
|---|---|---|---|
| 1 | 6 tokens | P[1:10] | 10 / 40 |
| 2 | 6 tokens | P[11:20] | 20 / 40 |
| 3 | 6 tokens | P[21:30] | 30 / 40 |
| 4 | 6 tokens | P[31:40] | 40 / 40 |
這個排程選擇先讓六條 decode 都前進,再把剩下的十個 token 用在 P。P 完成最後一段 Prefill 後,就能進入正常的 generation。
這個表只是在追 token budget,不代表四個 steps 的實際時間相同,也不是 benchmark。
如果沒有 chunking,單一 iteration 的 token 上限小於 prompt 長度時,系統甚至無法在該上限內完整排入這條 prompt。vLLM 的設定檢查也明確指出:沒有 Chunked Prefill 時,過小的 max_num_batched_tokens 會限制可接受的最大 sequence 長度。
V1 Scheduler 為每條 request 記錄已計算的 tokens。一次排程時,它先算出還欠多少:
num_new_tokens
= 尚未計算的 prompt / output tokens
接著把這個數字限制在本 step 的可用預算內:
可排入 tokens
= min(還欠的 tokens, 剩餘 token budget, 其他限制)
若設定了 long_prefill_token_threshold,它還能為長 prompt 加上每條 request 的上限。Scheduler 會為這一段配置 KV Cache slots,讓 request 留在 running 狀態;下一輪從新的 num_computed_tokens 繼續。
所以 Chunked Prefill 切的不是字串,也不是把模型重跑好幾遍;切的是每一輪要執行的 token work。
vLLM V1 的 unified scheduler 用 {request_id: num_tokens} 表示每個 request 在這個 step 的工作量。這讓 partial prefill 和 decode 可以共享同一個 token budget。
| 設定 | 它限制什麼 | 拉高後的直覺 |
|---|---|---|
max_num_batched_tokens |
單一 iteration 可處理的總 token 數 | Prefill chunk 可更大,但單一步驟也可能更久 |
long_prefill_token_threshold |
長 prompt 單條 request 的 cap;0 不啟用此 cap |
更大代表更少 chunks;更小代表更常讓出預算 |
max_num_seqs |
單一 iteration 可容納的 sequences 數 | 可同時維持更多 decode requests,但仍受 KV Cache 限制 |
沒有一組數字在所有 workload 都最好。短 prompt、長 context、輸出很長、低併發與高併發,會導向不同選擇。
較大的 chunk
→ 長 prompt 較快完成 Prefill
→ 該 request 的 TTFT 可能更好
→ 其他 requests 的 inter-token latency 可能變差
較小的 chunk
→ Decode 較不容易被長 Prefill 卡住
→ tail ITL 通常較容易受保護
→ 長 prompt 要經過更多 scheduler steps,TTFT 可能變差
因此 Chunked Prefill 不是「一定更快」,而是把原本一大段不可中斷的 Prefill work,變成 Scheduler 可以穿插安排的 work units。
Sarathi/Sarathi-Serve 將這個觀點做得更明確:在維持 decode 前進的同時,讓 prefill chunks 填入剩餘工作量。
Continuous Batching:哪條 request 可以進入下一個 batch?
Chunked Prefill:一條長 request 在這個 batch 可以吃多少 tokens?
兩者合在一起,Scheduler 才能面對真實流量:新 prompt 不斷到達,舊 request 又必須持續 streaming。
下一篇看另一個減少 Prefill 的方法:如果兩個 requests 前面一段 prompt 相同,Prefix Caching 能不能連 Prefill 都少算?