3、长序列推理瓶颈:内存瓶颈、计算瓶颈、延迟与吞吐量权衡

当输入序列从 2K 扩展到 32K、128K 甚至 1M 时,推理系统的性能特征会发生根本性变化。短序列场景下,模型的前向计算时间占据绝对主导;而长序列场景下,KV Cache 的显存占用、注意力计算的二次复杂度、以及由此引发的调度开销成为新的瓶颈。理解这三类瓶颈及其相互关系,是设计长序列推理加速方案的前提。

3.1 内存瓶颈:KV Cache 的“显存黑洞”

自回归解码过程中,模型需要缓存历史 token 的 Key 和 Value 张量,用于后续位置的注意力计算。对于 batch 大小为 B、序列长度为 L、注意力头数为 H、每个头的维度为 D、精度为 P 字节的模型,KV Cache 的显存占用为:

KV_Cache_Size = 2 × B × L × H × D × P

以 Llama-2-7B 为例(H=32,D=128,FP16),单条 32K 序列的 KV Cache 占用为:

2 × 1 × 32768 × 32 × 128 × 2 = 512 MB

这已经超过了模型权重本身(约 14 GB 中的 3.6%),而实际推理中 batch 通常大于 1。当 batch=8 时,仅 KV Cache 就需要 4 GB 显存。更严重的是,KV Cache 是动态增长的——每生成一个 token,所有序列的 KV Cache 都会同步增长,导致显存碎片化和频繁的显存分配/释放。

关键认知:长序列推理中,KV Cache 的显存占用随序列长度线性增长,且与 batch 大小相乘。当 batch 较大时,KV Cache 会迅速挤占模型权重和激活值的显存空间,导致 OOM(Out of Memory)或被迫降低 batch 大小,进而降低吞吐量。

下图展示了长序列推理中显存分配的结构与瓶颈位置:

长序列推理显存分配与瓶颈示意(batch=4, seq_len=32K) GPU 总显存(例如 80 GB) 模型权重 ~14 GB KV Cache(动态增长) 4 × 512 MB = 2 GB(seq=32K) ← 随生成 token 数持续增长 激活值 ~3 GB 剩余可用 ~61 GB ▲ 主要瓶颈:KV Cache 线性增长 + 碎片化 短序列(seq=2K) 权重 KV 激活 剩余空间充足 长序列(seq=32K, batch=8) 权重 KV Cache(4 GB) 激活 不足 KV Cache 随序列长度增长趋势 OOM 风险区 2K 16K 64K 128K+ 显存占用

图中可见,KV Cache 在长序列场景下占据的显存比例急剧上升,且其动态增长特性导致显存碎片化。实际推理中,vLLM 通过 PagedAttention 将 KV Cache 划分为固定大小的块(block),按需分配,从而减少碎片化并提高显存利用率。但即便如此,当序列长度超过一定阈值后,KV Cache 仍会成为限制 batch 大小的首要因素。

3.2 计算瓶颈:注意力机制的二次复杂度

标准多头注意力(MHA)的计算量为 O(L²),其中 L 为序列长度。对于每个 query 位置,需要与所有 key 位置计算点积。当 L=32K 时,单层单头的注意力矩阵大小为 32K × 32K = 10 亿个元素,计算量巨大。

具体而言,注意力计算分为三个阶段:

  1. QK^T 矩阵乘法:计算 query 与所有 key 的相似度,计算量为 2 × L² × D。
  2. Softmax 归一化:对每行进行 softmax,涉及指数运算和求和,计算量为 O(L²)。
  3. 加权求和(PV):用 softmax 结果对 value 加权求和,计算量为 2 × L² × D。

以 Llama-2-7B 的 32 层、32 头为例,单条 32K 序列的注意力计算量约为:

每层每头:2 × (32768)² × 128 ≈ 2.75 × 10¹¹ FLOPs
全模型:2.75 × 10¹¹ × 32 层 × 32 头 ≈ 2.8 × 10¹⁴ FLOPs

这相当于短序列(2K)计算量的 256 倍。更关键的是,注意力计算是访存密集型——需要反复读取 KV Cache,而 KV Cache 远大于片上缓存,导致大量时间花在显存带宽等待上。

优化方向:针对注意力二次复杂度,业界提出了多种近似方案,包括稀疏注意力(如 Longformer、BigBird)、线性注意力(如 Performer)、以及分组查询注意力(GQA)和多查询注意力(MQA)。其中 GQA 在保持质量的同时显著减少 KV Cache 体积,已被 Llama-2/3 等主流模型采用。

3.3 延迟与吞吐量权衡:调度策略的核心矛盾

长序列推理中,延迟(Latency)和吞吐量(Throughput)之间存在根本性矛盾。延迟要求单条请求尽快完成,而吞吐量要求单位时间内处理尽可能多的请求。两者对资源的需求方向相反:

指标 定义 长序列下的挑战 优化手段
延迟 单条请求从发起到返回的时间 长序列导致单步解码时间变长,且 KV Cache 访问延迟高 减少 batch、使用投机解码、优化注意力实现
吞吐量 单位时间处理的 token 数或请求数 KV Cache 占用大,batch 受限,GPU 利用率不足 增大 batch、PagedAttention、continuous batching
TTFT 首 token 生成时间(Time To First Token) 预填充阶段需处理全部输入,计算量大 并行预填充、chunked prefill
TPOT 每个输出 token 的平均生成时间 解码阶段受 KV Cache 访存带宽限制 KV Cache 量化、GQA、优化显存布局

vLLM 的 continuous batching 机制在请求级别动态调整 batch:当一个序列生成完毕,立即插入新请求,而不是等待整个 batch 完成。这种方式在长序列场景下尤为重要——不同请求的序列长度差异巨大,静态 batch 会导致大量 GPU 空闲等待。

3.4 案例:32K 上下文下的性能对比

以下是一个实际测试场景:使用 Llama-2-7B(GQA,8 KV 头),在 A100-80G 上分别测试短序列(2K)和长序列(32K)的推理性能。

# 测试配置
model = "meta-llama/Llama-2-7b-chat-hf"
max_seq_len = 32768
batch_size = 4
input_len = 2048
output_len = 512

# 短序列(2K)结果
TTFT: 0.42s | TPOT: 28ms/token | 吞吐量: 142 tokens/s

# 长序列(32K)结果
TTFT: 6.8s  | TPOT: 96ms/token | 吞吐量: 41 tokens/s

可以看到,长序列的 TTFT 增加了 16 倍,TPOT 增加了 3.4 倍,吞吐量下降了 71%。其中 TTFT 的剧增主要来自预填充阶段的计算量二次增长,而 TPOT 的恶化则源于 KV Cache 访存压力增大。

常见误区:许多开发者认为增加 batch 就能提升吞吐量,但在长序列场景下,KV Cache 的显存占用会迅速耗尽显存。例如上述配置中,batch=4 时 KV Cache 已占用 2 GB,若增加到 batch=16,KV Cache 将占用 8 GB,加上权重和激活值,显存压力显著增大。实际中需要根据序列长度动态调整 batch,而不是盲目增大。

3.5 误区与复盘:三个典型错误

误区一:只关注 FLOPs,忽略访存开销。长序列推理中,注意力计算的访存比(FLOPs/Byte)很低,GPU 计算单元大量空闲等待数据加载。单纯优化计算量(如使用稀疏注意力)而不优化访存模式,收益有限。

误区二:将短序列的优化经验直接套用到长序列。例如,短序列中增大 batch 通常能线性提升吞吐量,但长序列中 KV Cache 的显存占用使 batch 无法无限增大。需要重新评估 batch 上限。

误区三:忽略预填充与解码阶段的差异。预填充阶段是计算密集型,适合大 batch 并行;解码阶段是访存密集型,batch 过大反而增加 KV Cache 访存压力。vLLM 的 chunked prefill 将长输入拆分为多个 chunk,交错执行预填充和解码,以平衡两阶段的计算特性。