从排队到闲置:聊天服务为何浪费GPU
假设你运营一个在线聊天机器人服务,用户输入问题后,模型逐步生成回答。你租用了昂贵的GPU,却发现压力测试时GPU利用率只有35%左右。用户抱怨响应慢,但GPU明明有空闲算力。问题往往不在模型本身,而在调度方式。
传统深度学习推理中,批处理(batching)把多个输入打包成一个批次,一次性前向传播,输出大小固定。但大语言模型(LLM)生成文本是迭代式的:每个token依赖之前生成的token,输出长度不固定。一个聊天请求可能15个token就回答完毕,同一批次中的代码生成请求却需要800个token。静态批处理(static batching)把一批请求作为一个整体,直到所有序列都生成完毕才释放资源。短请求完成后,它的计算槽位和显存仍然被占用,只能填充(padding)等待长请求结束。这导致GPU利用率只有30%~60%,大量算力浪费在等待上。
动态批处理(dynamic batching)通过时间窗口(例如50ms)收集请求,减少了准入延迟,但一旦窗口关闭,批次仍作为一个整体运行,直到最后一个序列完成。问题没有根本解决。
迭代级调度:每次前向传播重新决策
连续批处理(continuous batching),又称迭代级调度(iteration-level scheduling)或飞行中批处理(in-flight batching),将调度决策从请求粒度细化到迭代粒度。调度器在模型的每次前向传播时运行一次,而不是每个请求一次。
具体流程如下:
- 扫描当前批次,找出已生成结束符(EOS)的序列。
- 释放这些序列的KV缓存块。
- 从等待队列中取出新请求,数量受显存和最大批大小限制。
- 将所有活动序列拼接成一个复合批次。
- 执行一次前向传播,每个序列生成下一个token。
- 重复上述过程。
关键在拼接步骤。静态批处理要求序列填充到相同长度,因为矩阵运算需要统一形状。连续批处理则构造一个带注意力掩码的“超级序列”,防止请求之间互相关注。没有填充,每个GPU的FLOP都用于处理真实token。这种拼接方式与FlashAttention的变长算子(variable-length kernel)自然集成,即使序列长度不同,也能在单个GPU算子调用中处理所有序列。
下图展示了连续批处理中批次构成的动态变化:
flowchart TD
A[请求到达] --> B{队列非空?}
B -- 是 --> C[调度器扫描运行中批次]
B -- 否 --> D[等待新请求]
C --> E{有序列完成?}
E -- 是 --> F[释放KV缓存块]
E -- 否 --> G[检查显存和最大批大小]
F --> G
G --> H{有可用槽位?}
H -- 是 --> I[从队列取出新请求]
H -- 否 --> J[保持当前批次]
I --> K[拼接所有活动序列]
J --> K
K --> L[执行一次前向传播]
L --> M[每个序列生成下一个token]
M --> C
图中,每次前向传播后,调度器重新检查批次,完成序列立即被替换,新请求在下次迭代前加入。
预填充与生成:两种计算模式的调度挑战
连续批处理并非没有代价。LLM推理分为预填充(prefill)和生成(decode)两个阶段。预填充阶段处理整个输入提示,计算密集且并行度高;生成阶段逐token产生输出,内存带宽受限。两者计算模式不同,难以在同一批次中高效混合。
如果预填充请求和生成请求混在一起,预填充会占用大量计算资源,拖慢生成速度;而生成请求又会让预填充等待。连续批处理框架通常通过超参数管理这种混合,例如等待已服务比(waiting_served_ratio)控制等待队列中预填充请求与生成请求的比例。
以聊天场景为例,用户发送新消息时,系统需要先处理预填充,然后进入生成。如果新请求的预填充与正在生成的请求混批,可能增加生成请求的延迟。因此,调度器需要权衡:是优先让生成中的请求尽快完成,还是让新请求尽快开始。
内存管理:PagedAttention与连续批处理的协同
连续批处理解决了何时调度请求,但请求的KV缓存存储同样关键。KV缓存是LLM推理中存储历史token注意力键值的内存,每个请求的KV缓存大小随生成动态增长。
在PagedAttention出现之前,框架为每个请求预分配一块连续GPU显存,大小基于最大可能输出长度。这导致60%~80%的显存浪费:过度保留的槽位、对齐间隙,以及大多数序列不会用完最大分配空间。
PagedAttention(vLLM, SOSP 2023)将操作系统的虚拟内存分页模型应用于KV缓存管理。KV缓存被划分为固定大小的块(vLLM默认每块16个token),按需动态分配,而非预先分配。块表将每个序列的逻辑块映射到物理GPU显存位置,块不需要连续。显存浪费降至4%以下,仅浪费每个序列最后一个未填满的块。
PagedAttention还支持物理块在序列间共享,通过写时复制(copy-on-write)语义。束搜索的分支、并行采样以及共享相同系统提示词的请求,在分歧前可以引用相同的物理KV块。对于束搜索,这可以减少高达55%的开销,吞吐量比非共享分配提高2.2倍。
SGLang通过RadixAttention进一步扩展:用基数树在不同请求间维护KV缓存,实现自动前缀重用。共享系统提示词、少样本示例或RAG上下文的请求可以重用彼此已缓存的KV块,无需重新计算。在大量前缀共享的工作负载中,推理速度可提升5倍。
在聊天服务中,用户通常共享相同的系统提示词(例如“你是一个友好的助手”),PagedAttention和RadixAttention可以显著减少重复计算。
延迟指标:TTFT与TPOT的权衡
连续批处理对延迟的影响需要区分两个指标:
- TTFT(Time To First Token):从请求到达到生成第一个token的时间。
- TPOT(Time Per Output Token):生成每个输出token的平均时间。
静态批处理中,新请求必须等待当前批次完成才能加入,TTFT可能很高。连续批处理允许新请求在下次迭代时加入,TTFT显著降低,尤其在中低负载下。
然而,TPOT可能受影响。当批次中混入预填充请求时,生成请求的计算资源被抢占,TPOT可能上升。调度器需要控制预填充与生成的比例,平衡TTFT和TPOT。
High-Flyer的实验(基于A100 GPU,OPT-13B模型)显示,在QPS=1时,连续批处理在所有百分位数上延迟都优于静态批处理;QPS=4时,优势缩小,因为系统饱和后,新请求立即注入的机会减少。
吞吐与延迟的权衡:参数与策略
连续批处理的核心参数包括最大批大小、队列策略和预填充/生成比例。
- 最大批大小:增大批大小可以提高吞吐,但增加每个请求的TPOT,因为共享计算资源。
- 队列策略:FIFO(先进先出)简单公平,但长序列可能阻塞短序列。优先级队列可以优先处理短请求,降低TTFT,但可能饿死长请求。
- 预填充/生成比例:控制预填充请求与生成请求的混合程度,影响TTFT和TPOT的平衡。
下表对比了不同调度策略的适用场景:
| 策略 | 吞吐 | TTFT | TPOT | 适用场景 |
|---|---|---|---|---|
| 静态批处理 | 低 | 高 | 稳定 | 同质输出,离线批量 |
| 动态批处理 | 中 | 中 | 中 | 中等方差,低并发 |
| 连续批处理(无内存优化) | 高 | 低 | 可能波动 | 高方差,在线聊天 |
| 连续批处理 + PagedAttention | 很高 | 低 | 较低 | 高并发,长序列,共享前缀 |
在聊天场景中,输出长度方差大,连续批处理优势明显。但如果所有请求都生成恰好50个token,优势几乎为零。
失效模式:何时连续批处理会退化
连续批处理并非万能。以下场景中,其收益可能减弱甚至产生负面影响:
- 低QPS:每秒个位数请求时,所有方法表现相近,调度开销可能超过收益。
- 同质输出:离线批量推理,序列长度预先已知且统一,静态批处理更简单且具有竞争力。
- 高并发饱和:系统满负载时,新请求无法立即注入,延迟上升,连续批处理优势缩小。
- 内存碎片化:块大小固定可能导致碎片化,例如块大小16个token,序列生成17个token时,第二个块只使用1个token,浪费15个token空间。
- 队头阻塞:基于内存的队头阻塞,当新请求需要大量KV缓存而显存不足时,可能阻塞后续请求。
实际部署中,需要监控GPU利用率、TTFT、TPOT、排队长度、KV缓存命中率等指标。当GPU利用率长期低于50%时,考虑调整批大小或队列策略;当TTFT过高时,检查预填充与生成比例。
替代方案与适用边界
与连续批处理相比,其他优化方法各有侧重。
- 模型量化(如AutoGPTQ):减少模型显存占用,间接增大批大小,但可能损失精度。
- FlashAttention:优化注意力计算的内存IO,与连续批处理正交,可叠加使用。
- 投机解码(speculative decoding):用小模型草稿加速大模型生成,减少解码步数,但与批处理调度独立。
连续批处理最适合高方差、高并发的在线服务,如聊天、Agent、交互式助手。对于离线批量推理,静态批处理可能更简单。
仍未解决的问题
连续批处理虽已广泛应用,但调度问题尚未完全解决。如何动态调整最大批大小以适应负载变化?如何在不同租户间公平分配GPU资源?预填充与生成的混合比例如何自适应优化?这些问题仍需要进一步研究。
对于工程实践,建议从监控开始:记录TTFT、TPOT、GPU利用率、队列长度,分析瓶颈是调度还是内存。然后逐步调整参数,观察延迟分布的变化。连续批处理不是一劳永逸的解决方案,而是需要持续调优的系统设计。