昨天 vLLM Engine Core 的主迴圈:
schedule → execute → update
關鍵是這個迴圈只推進一個 engine step,不會把整批 requests 一次跑到結束。
因此每一步結束後,Scheduler 都能重新決定下一步的 batch。這就是 Continuous Batching,也常被稱為 iteration-level scheduling 或 in-flight batching。
先說清楚:今天只有概念與 source tracing,沒有執行 benchmark。
一般的 batching 會先收集一批輸入,再一起執行。但 LLM decode 和影像分類不同:每條 request 要產生幾個 tokens,事前並不知道。
如果 batch 成員從開始到結束都固定:
短 request 先完成 → 留下一個不能補人的空位
新 request 抵達 → 必須等待整個 batch 結束
GPU 仍在跑最長的 request,但 batch 已經愈來愈小。
Dynamic batching 有時只是「多等幾毫秒,把同時抵達的 requests 湊成一批」;如果 batch 啟動後仍不能換人,問題並沒有消失。
假設同時最多容納兩條 sequences,忽略 Prefill 成本;每格代表一條 request 完成一次 decode iteration:
| Request | 抵達時間 | 需要的 decode iterations |
|---|---|---|
| A | Step 1 前 | 4 |
| B | Step 1 前 | 2 |
| C | Step 1 後 | 2 |
| Step | Slot 1 | Slot 2 |
|---|---|---|
| 1 | A | B |
| 2 | A | B 完成 |
| 3 | A | 空 |
| 4 | A 完成 | 空 |
| 5 | C | 空 |
| 6 | C 完成 | 空 |
C 必須等 A 所在的 batch 結束,六個 steps 中出現四個空 slots。
| Step | Slot 1 | Slot 2 |
|---|---|---|
| 1 | A | B |
| 2 | A | B 完成 |
| 3 | A | C |
| 4 | A 完成 | C 完成 |
B 完成後,C 在下一個 step 補進來。相同八次有效 decode iterations,在這個簡化例子裡由六個 steps 降為四個。
這只是排程示意,不是 latency benchmark。真實 step 時間還會受 batch 內 token 數、context length、Prefill、硬體與 kernel 影響。
在目前的 vLLM V1 Scheduler 中,requests 主要位於 waiting 或 running queue。一次 step 可以簡化成:
1. 先檢查 running requests 下一步需要計算多少 tokens
2. 在 token budget 內配置新的 KV Cache slots
3. 若 sequence 容量與 budget 還有空間,再從 waiting queue 加入 requests
4. 執行這一輪 model forward
5. 更新輸出;完成的 request 退出並釋放資源
6. 下一個 step 重新排程
因此「batch」不是一個從頭到尾不變的 tensor 群組,而是 Scheduler 為這個 step 產生的工作集合。
目前 V1 是 unified scheduler。它不必先把 request 永久標成 Prefill 或 Decode,而是用類似下面的資料描述這一步:
{request_id: num_tokens_to_compute}
一般 decode request 通常前進一個 token;Prefill request 則可能一次排入許多 tokens。兩者能出現在同一套 token budget 裡。
Continuous Batching 仍受幾個上限約束:
max_num_seqs:單一 iteration 最多處理多少 sequences。max_num_batched_tokens:單一 iteration 最多處理多少 tokens。所以新 request 抵達後只是取得「下一步可以被考慮」的機會;資源不足時仍然要等待,甚至可能觸發 preemption。
這也解釋了 PagedAttention 和 Continuous Batching 為什麼常一起出現:前者讓每條 request 的 KV Cache 可以用 blocks 動態增長與釋放,後者才能更靈活地讓 batch 成員進出。
Continuous Batching 的主要收益是減少空 slots,讓 GPU 在有併發流量時維持較大的有效 batch,通常有利於 throughput,也能縮短新 request 等待下一批的時間。
但它也會帶來干擾:如果 Scheduler 把一個很長的 Prefill 一次塞進正在 Decode 的 batch,這個 step 會變久,其他 requests 的 inter-token latency 也可能上升。
所以真正的問題不只是在 batch 中途補人,還要決定:
一條很長的 prompt,應不應該一次把全部 tokens 排進去?
這就是明天的 Chunked Prefill。
Static Batching
固定 batch 成員,最慢的 request 決定何時換下一批
Continuous Batching
每個 iteration 重新排程,完成一條就能補進一條
它沒有改變 autoregressive generation 一次產生下一個 token 的本質;它改變的是 Scheduler 與 execution engine 互動的粒度:
request-level → iteration-level
而在 vLLM 裡,這個機制必須和 token budget、KV Cache blocks、Prefill 與 Decode 一起考慮。