TT Lab
开始
学习 学习路径 课程

LLM 服务

静态批处理丢掉的东西

在 TT Lab 中继续学习

一句话总结

静态批处理会让其余槽位一直闲置,直到批次中最长的请求结束。连续批处理则会立即用新请求填补已经结束的槽位。两者的吞吐量差距可达 2~5 倍。

为什么需要了解这些

对于图像分类等任务,批处理很简单。输入大小相同,处理时间也相同,因此收集 32 个输入后一次执行即可。

LLM 生成则不同。每个请求的输出长度都不一样:有的请求只生成 10 个令牌,有的则生成 1,000 个。若通过静态批处理把 32 个请求组成一批,即使其中 31 个在生成 10 个令牌后已经结束,这 31 个槽位也会一直空闲,直到那个需要 1,000 个令牌的请求完成。GPU 占用了可供 32 个请求使用的资源,却只在处理 1 个请求。

工作原理

连续批处理(continuous batching,也称 in-flight batching)不会把批次视为固定的一组请求,而是把它看作动态的槽位集合。每次 decode 迭代时,调度器都会检查:如果有序列已经结束,就把它从槽位移除;如果等待队列中还有请求,就将其放入这个槽位。

这样,GPU 就能始终维持接近最大规模的批次。基准测试报告显示,相较静态批处理,吞吐量可以提升 2~5 倍。

这里有两个可调参数。max_num_seqs 是同时处理的序列数量上限,max_num_batched_tokens 是单次迭代所处理令牌数的上限。前者受 KV 缓存内存限制,后者受计算能力限制。

其中存在取舍。提高并发度会增加吞吐量,但也会延长单个请求的延迟。当 KV 缓存不足时,调度器会抢占(preempt)部分序列,把它们移出,稍后再放回来。此时要么丢弃该序列的 KV 缓存并重新计算,要么将其交换到 CPU,两者成本都很高。如果频繁发生抢占,吞吐量反而会下降。

另一个候选方案是前缀缓存。多个请求以同一系统提示词开头时,可以复用这一部分的 KV 缓存。收益取决于公共前缀长度、复用率、具体实现和内存策略;如果每次提示词都完全不同,效果几乎为零。必须在真实请求分布下同时测量缓存命中率与 TTFT。

生产现场会看到什么

观察队列深度与尾部 TTFT 之间的关系,可以建立运营直觉。当流入量接近处理能力时,等待队列会增长,高百分位延迟会比平均值更早恶化。应根据产品用户体验和流量规模,选择 p95 或 p99 等尾部百分位作为 SLO 指标。

确定目标时也有先后顺序。首先根据产品和工作负载设定 TTFT SLO(本实验为了计算使用 200ms 作为示例),再找出满足该目标的最大并发度,最后用这一并发度下的吞吐量作为容量规划依据。如果一开始只追求吞吐量最大化,就很容易错过尾部延迟 SLO。

KV 缓存才是真正的极限

如果问是什么限制了连续批处理的并发度,通常会有人回答计算能力;但实际中最先耗尽的几乎总是内存。正在生成的序列必须保存截至当前所有令牌的键和值,其大小会与令牌数成正比持续增长。 单个请求增长到 4,000 个令牌时,其缓存也会膨胀到初始大小的数倍。

因此,计算批量大小的过程如下:模型权重占用后剩余的内存,就是可用于缓存的空间;将其除以单个请求平均占用的缓存大小,即可得到能同时容纳的序列数量。但这里不能只看平均值,而要看较长的一端。 即使绝大多数请求很短,只要少数长请求占用了缓存,可用槽位就会相应减少。

缓存不足时,调度器会抢占并移出序列,这是运营中最危险的时刻。被移出的序列稍后重新进入时,必须从头重新计算,因此已经做过的工作会再做一次。负载只是略微增加,吞吐量却突然跌落的断崖就出现在这里。需要观察三个指标。

指标 表达的含义 不良信号
KV 缓存使用率 内存余量 长时间停留在 90% 附近
抢占次数 被撤销的工作量 只要不为 0,就已经超过极限
等待队列长度 已接收却无法处理的数量 持续增长时必须限制流入

应对方法分为三类:限制每个请求的最大输出长度,避免缓存无限增长;把 max_num_seqs 降低到不会发生抢占的值;如果仍然不足,就增加实例。容忍抢占并维持高并发度,几乎总是得不偿失。 表面看起来接收了更多请求,实际上却在重复执行同一计算。

还需要注意一点。把长请求和短请求混合在同一个实例中,长请求占用缓存期间,短请求的延迟也会一并恶化。把交互式响应与具有批处理性质的长文本生成分配到不同实例,并不是浪费资源,而是保护尾部延迟最可靠的方法。

下一次实验要做什么

分别通过模拟实现静态批处理和连续批处理调度器,并比较吞吐量。应用并发上限,观察队列深度与 p99 等待时间的关系,分离并建模 prefill 和 decode 的成本,然后探索满足本实验示例 SLO——TTFT 200ms——的最大并发度。