4、动态批处理工作原理:请求调度、批处理窗口、内存管理机制
动态批处理(Dynamic Batching)是 TensorRT-LLM 在服务端提升吞吐量的核心机制。与静态批处理在请求到达前就固定 batch 大小不同,动态批处理允许请求在 执行间隙 被动态插入或移出当前迭代。理解其底层工作原理,是后续调优(如 max_batch_size、max_num_tokens、调度策略)的前提。本节从请求调度、批处理窗口和内存管理三个维度拆解其运行机制。
4.1 请求调度:从队列到执行器的路径
TensorRT-LLM 的调度器(Scheduler)维护一个 等待队列(Waiting Queue) 和一个 执行中的批处理槽位(Active Slots)。每个请求进入系统后,会经历以下状态流转:
- 接收:HTTP/gRPC 前端将请求封装为内部任务,放入等待队列。
- 调度决策:在每个解码迭代步(iteration)开始前,调度器检查当前批处理是否还有空闲容量(容量由
max_batch_size和max_num_tokens共同约束)。 - 插入:若容量允许,调度器从等待队列中取出请求,为其分配 KV Cache 空间,并加入当前迭代的输入张量。
- 执行:GPU 执行一次前向传播,生成所有序列的下一个 token。
- 完成/退出:若序列生成
<EOS>或达到max_new_tokens,则释放其 KV Cache 槽位,并将结果返回客户端。
关键调度参数(在 config.json 或 executor 中设置):
{
"max_batch_size": 64, // 最大并发序列数
"max_num_tokens": 8192, // 所有序列 token 总数上限(含输入+输出)
"scheduler_policy": "max_utilization" // 或 "guaranteed_no_evict"
}
调度策略直接影响吞吐与延迟的平衡。max_utilization 会尽量填满每个迭代的 token 预算,适合高吞吐场景;guaranteed_no_evict 则保证已开始的请求不被抢占,适合对延迟抖动敏感的服务。
4.2 批处理窗口:连续 token 预算与动态插入
传统静态批处理中,一个 batch 的所有序列必须同时开始、同时结束,导致大量 padding 浪费。TensorRT-LLM 采用 连续批处理(Continuous Batching) 窗口模型,其核心思想是:每个解码步只处理当前所有活跃序列的一个 token,但允许序列在不同时间点加入或离开。
批处理窗口的容量由 max_num_tokens 决定,它表示一次前向传播中所有序列的 token 总数上限。例如,若窗口为 8192 tokens,当前有 32 个序列,每个序列平均长度为 200 tokens(输入+已生成),则已占用 6400 tokens,剩余 1792 tokens 可容纳新请求(假设新请求输入长度为 100 tokens,则可插入约 17 个新序列)。
这种窗口机制避免了 padding 浪费,但也引入了 碎片化 风险:若新请求的输入 token 数较大,而剩余窗口不足以容纳,则即使 batch 未满也无法插入。因此,调优时需关注请求长度分布,必要时设置 max_input_len 上限或使用 truncation 策略。
4.3 内存管理机制:KV Cache 的分页与共享
KV Cache 是动态批处理中最关键的内存消费者。TensorRT-LLM 借鉴操作系统虚拟内存的分页思想,将 KV Cache 划分为固定大小的 逻辑块(blocks),每个块存储固定数量 token 的 K/V 向量。每个序列维护一张 块表(block table),记录其逻辑块到物理 GPU 显存块的映射。
| 概念 | 说明 | 调优影响 |
|---|---|---|
| KV Cache 块大小 | 每个块存储的 token 数(如 64 tokens/block) | 块越小,内存利用率越高,但块表开销增大 |
| 最大块数 | 由 max_num_tokens 和块大小决定 |
决定可并发序列的总长度上限 |
| 块分配策略 | 首次适配(First Fit)或最佳适配(Best Fit) | 影响碎片化程度和分配延迟 |
| 跨序列共享 | 相同前缀(如 system prompt)可共享物理块 | 显著降低长 prompt 场景的内存占用 |
在内存分配时,调度器会为每个新请求预留其 最大可能长度 的 KV Cache 空间(基于 max_new_tokens),但实际只按需逐步分配物理块。这种 按需分配 机制使得内存利用率远高于静态预留。然而,若请求的 max_new_tokens 设置过大,会导致调度器过度预留逻辑块,压缩其他请求的插入机会。
调优建议:对于输入长度差异大的场景,启用 enable_prefix_caching 可让相同 prompt 前缀的请求共享 KV Cache 物理块,实测可减少 30%~50% 的显存占用,同时提升命中缓存的请求的调度速度。
4.4 案例:混合负载下的调度行为
假设某在线服务同时处理两类请求:短问答(输入 50 tokens,输出 100 tokens)和 长文档摘要(输入 2000 tokens,输出 500 tokens)。配置如下:
max_batch_size = 32
max_num_tokens = 8192
kv_cache_free_gpu_memory_fraction = 0.8
当 20 个短问答请求正在执行时,每个请求占用约 150 tokens 的窗口,共 3000 tokens。此时一个长文档摘要请求到达,需要 2500 tokens 的窗口。调度器检查剩余容量(8192 - 3000 = 5192 tokens),足以容纳,于是将其插入。但若此时已有 30 个短问答请求(占用 4500 tokens),剩余 3692 tokens,仍可容纳该长请求。若短问答请求数量达到 32 个(batch 已满),则长请求必须等待,即使窗口仍有剩余 token 空间——因为 max_batch_size 限制了并发序列数。
常见误区:误以为 max_batch_size 越大越好。实际上,当 batch 过大时,每个序列的生成延迟会因 GPU 计算资源竞争而上升。若你的服务对 TTFT(首 token 延迟)敏感,应适当调低 max_batch_size,并依赖 max_num_tokens 来控制单次前向的计算量。
4.5 复盘:调度机制中的典型问题与排查
在实际部署中,动态批处理可能出现以下异常行为,需要结合监控指标定位:
- 吞吐量上不去但 GPU 利用率低:检查等待队列长度。若队列为空,说明请求到达率不足;若队列积压但 GPU 空闲,则可能是
max_batch_size或max_num_tokens设置过小,或调度策略过于保守。 - 显存溢出(OOM):查看 KV Cache 分配统计。若
kv_cache_used接近上限,而max_num_tokens未触顶,可能是块碎片化严重。尝试减小块大小(如从 128 降至 64)或启用kv_cache_reuse。 - 个别请求延迟极高:可能是长请求占用了大量窗口,导致短请求被阻塞。此时可考虑设置
max_input_len上限,或启用priority调度策略,让短请求优先执行。
理解上述机制后,你便能针对具体负载特征,合理配置 max_batch_size、max_num_tokens 与 KV Cache 参数,使动态批处理在吞吐和延迟之间达到最佳平衡。