返回博客

/ AI Infra

[Infra-17] 大模型推理中的长尾:从请求调度到 MoE 慢节点

区分请求级与计算级长尾,梳理 Chunked Prefill、PD 分离、KV Cache 管理、EPLB 和通信重叠的作用与代价,并给出从 P99 指标到 GPU 关键路径的定位方法。

12 minInfra · LLM Inference · Tail Latency · MoE

推理服务的平均吞吐量很好,不代表每个用户都能顺畅地得到回答。少数请求可能等了很久才收到第一个 token,也可能在生成途中突然停顿;在分布式 MoE 推理中,还可能只有一个 GPU 明显慢于其他 GPU,却让整个阶段都等着它完成。

这些现象都与长尾有关,但它们发生在不同层级。讨论优化之前,首先要回答两个问题:谁在变慢,以及谁在等待它?

我更愿意把推理长尾优化理解为:找到慢请求或慢计算任务的来源,减少它们对其他任务的影响,并在吞吐量、成本与服务等级目标(SLO)之间控制 P95/P99 延迟。单纯提高平均吞吐,未必能改善这件事。

1. 先定义:什么样的慢才值得优化#

请求级长尾与计算级长尾#

层级典型现象优先检查的方向
请求级(Request-level)少数请求的首 token、生成过程或端到端延迟很高排队、调度、请求隔离、KV 容量
计算级(Execution-level)某个 GPU、Expert 或通信 rank 明显落后负载均衡、通信、同步、关键路径

两者不是互斥的:计算级的慢节点可能通过同步等待,最终表现为一批请求的 ITL 尖峰。但如果只观察请求 P99,就无法确定瓶颈究竟来自调度还是执行。

假设一组相近输入、输出长度的请求具有如下端到端延迟分布:

分位数延迟(假设性示例)
P50200 ms
P95800 ms
P991,600 ms

这些数字仅用于说明现象,不是任何模型或硬件的实测结果。P99 为 1,600 ms,意味着约 99% 的观测值不超过这一阈值,仍有约 1% 位于更慢的一端。流量足够大时,这部分用户的绝对数量也可能很大。

不过,长请求不自动等于异常请求。生成 10,000 个 token,通常就比生成 100 个 token 更耗时。必须结合输入长度、输出长度、缓存命中情况与业务 SLO,判断某个请求是否真的异常。

TTFT、TPOT 和 ITL 不能混为一谈#

指标关注的问题容易忽略的地方
TTFT:Time To First Token多久能看到第一个 token?可能包含排队、Prefill,以及 PD 分离后的交接开销
TPOT:Time Per Output Token一个请求平均多久生成一个后续 token?请求内部的平均值会掩盖单次卡顿
ITL:Inter-Token Latency相邻输出 token 的间隔有多长?token 级分布与请求级 TPOT 分布不是同一统计口径
E2E Latency完整回答多久结束?强烈依赖输出长度

对于普通逐 token 输出,若总输出数为 Nout>1N_{\mathrm{out}}>1,一个常见定义是:

TPOT=TE2E−TTTFTNout−1\mathrm{TPOT}=\frac{T_{\mathrm{E2E}}-T_{\mathrm{TTFT}}}{N_{\mathrm{out}}-1}

一个请求即使平均 TPOT 不高,也可能出现某次很长的 ITL。投机解码、服务端缓冲或网络分块还会影响客户端实际看到的输出间隔。因此,报告 P99 时要说明统计对象、采集位置和时间窗口,避免将客户端体验与服务端执行指标混用。

2. 请求级长尾:减少请求之间的干扰#

Chunked Prefill:限制一次 Prefill 的工作量#

假设 Decode 原本每轮约需 10 ms,此时一个 32K 输入请求到达,完整 Prefill 需要约 200 ms。若调度器连续执行这段长 Prefill,已有请求的下一次 Decode 就可能被推迟。这里的时长同样只是示意。

Chunked Prefill 将长输入拆成较小的计算块,通过每轮 token budget,限制 Prefill 对 Decode 的干扰:

完整 Prefill:
[Decode] [-------------- Long Prefill --------------] [Decode]
 
分块 Prefill:
[Decode + Chunk 1] [Decode + Chunk 2] ... [Decode + Chunk N]

图中表达的是调度关系,方框宽度不代表实测时间。真实实现可以在一个 forward step 内混合 Decode token 与 Prefill chunk,并不一定严格交替执行。

vLLM V1 的相关调度会优先安排 Decode,再用剩余 token budget 安排 Prefill;大 Prefill 超出预算时会被分块。具体行为和配置应以部署版本为准。vLLM 优化文档

这里的关键是控制每轮干扰,而不是把 chunk 无限缩小。更小的 chunk 可能改善 Decode 间隔,却增加 Prefill 的轮数和开销,影响新请求的 TTFT。需要在真实负载下寻找合适预算。Sarathi-Serve 也围绕分块 Prefill 与混合调度讨论了吞吐和延迟的权衡。

PD 分离:把干扰移出同一个执行实例#

当在线集群规模足够大,可以将 Prefill 和 Decode 放到不同的 GPU 实例上。这样,长 Prefill 不再直接占用 Decode 实例的执行时间。SGLang 的 PD 分离文档 将 Prefill 对 Decode 的干扰,以及 Attention DP worker 间的不均衡,列为采用这一架构的动机。

但隔离并不意味着长尾消失。分离之后,关键路径上会出现 KV Cache 传输与接收侧容量约束:

请求进入 → Prefill 排队与执行 → KV 交接 → Decode 执行 → 完成
                                  ↑
                     传输、接收侧容量与排队都可能成为瓶颈

因此要同时观察 P/D 两侧队列、KV 传输耗时与 Decode 可用 KV 容量,并根据负载调整实例配比。Prefill 很快、Decode 却接不住请求时,扩容 Prefill 不会解决问题。

相关背景可以结合本站的 PD 分离 与 vLLM KV Connector 阅读。

隔离、负载均衡与准入控制#

128K 长上下文请求和 1K 短请求共享资源池时,除了执行时间,还会争抢显存、带宽与调度机会。不同手段对应不同代价:

策略主要作用代价与边界
长短请求分池降低不同长度请求的相互干扰资源池变小,利用率可能下降
SLO-aware Scheduling根据延迟预算与优先级调度需要公平性约束,防止饥饿
动态负载均衡避免请求集中到忙实例请求数不足以准确描述负载
Live Migration将运行中的请求及状态迁移到其他实例搬运 KV 占用带宽,可能引入新停顿
Admission Control防止系统超出可承载容量可能延长外部等待或拒绝请求

Llumnix 是这一方向的代表工作:通过跨实例运行时重调度与请求状态迁移,改善负载不均、资源碎片和隔离问题。

准入控制尤其需要明确测量边界。如果只是把等待移到网关之外,服务端 P99 可能变好,但用户端延迟没有改善。应同时报告成功率、拒绝率和端到端等待时间。

3. 计算级长尾:MoE 中的 Straggler#

假设一个 MoE 阶段使用 8 张 GPU 做 Expert Parallelism,其中 7 张 GPU 在约 3 ms 后完成,最后一张需要 12 ms。如果后续操作依赖所有参与方的结果,那么这个同步阶段的耗时将受最慢参与方约束:

Tstage≈max⁡i=1,…,NTiT_{\mathrm{stage}}\approx\max_{i=1,\ldots,N}T_i

这是对同步阶段的简化描述;实际耗时还包含通信、同步及其重叠关系,不能直接当成整个模型的耗时公式。这个例子中的数字也只是示意。

此时继续优化已经在 3 ms 完成的 GPU,可能几乎不改变阶段总延迟。真正需要解决的是 Straggler:它为什么晚完成,又挡住了哪些依赖?

EPLB:让热点专家的工作分散执行#

MoE routing 会将 token 分配给不同专家,专家负载未必均匀。某些 Hot Expert 收到更多 token,就可能让承载它们的 GPU 成为慢节点。

DeepSeek 开源的 EPLB 根据估计的专家负载,计算冗余专家复制与放置方案,并在适用条件下考虑节点内放置以减少跨节点流量。这与训练阶段让模型学习更均匀的 routing 不是同一层面的机制。

部署侧的直观过程是:

统计 Expert 负载 → 识别热点 → 分配冗余副本 → 调整物理放置
                                             ↓
                                运行时将工作分配给相应副本

副本需要额外显存;负载估计也可能落后于实际热点。因此,除了专家 token 数,还要看负载变化速度、重配置成本和最终阶段耗时。即使长期 routing 分布均匀,小 Batch 下仍可能出现明显的瞬时波动。

All-to-All:均衡计算以后还要看通信#

Expert token 数接近,不代表执行时间就一定接近。Dispatch 和 Combine 还可能受到跨节点流量、网络拥塞、通信启动开销以及参与方到达时间差异的影响。

定位时,应把同一轮的各 rank 时间线对齐,区分“发送方迟到了”和“数据真的传得慢”。只看通信算子的总时长,容易把上游计算不均衡误判成网络问题。

可优先观察每个 rank 的 token 数、收发字节数、Dispatch/Combine 耗时,以及同一轮最慢 rank 的变化。这里的 rank-level P99 应来自多轮观测,不能把单轮 8 个 rank 的耗时误当成稳定的尾部统计。

Micro-batch Pipeline:重叠能隐藏部分等待#

将大 Batch 拆成多个 Micro-batch,可以尝试让一个 Micro-batch 的专家计算与另一个的通信重叠:

Micro-batch A: [Dispatch] [Expert Compute] [Combine]
Micro-batch B:            [Dispatch]      [Expert Compute] [Combine]

这是理想化的依赖示意。收益取决于硬件、通信实现、计算粒度和资源竞争;切得过细还可能降低 kernel 效率。Overlap 可以隐藏一部分等待,但无法消除仍然处在关键路径上的慢任务。

4. KV Cache:显存压力如何变成延迟尖峰#

长尾不一定来自某个 kernel 变慢,也可能来自请求暂时失去继续执行的资源。

随着 Decode 并发与上下文长度增加,KV Cache 占用持续增长。可用空间不足时,引擎可能暂停部分请求,之后通过重计算恢复状态。vLLM 的 Preemption 文档 说明了这类机制及其延迟影响。

KV 压力增大 → 请求被抢占 → 等待资源 → 重计算恢复 → 请求延迟出现尖峰

因此,Batch 越大并不总是越好。调参时应同时考虑运行中的序列数量、每轮 token budget、上下文长度分布和可用 KV 容量。在 vLLM 中,max_num_seqs 和 max_num_batched_tokens 控制不同维度,不能把它们当成同一个“Batch 大小”。

增加 KV 空间可能缓解抢占,但也要给模型权重、激活与其他运行时开销留下余量。判断优化是否有效,应看抢占、排队与请求延迟是否共同改善,而不只是 KV 使用率是否下降。

5. 定位方法:从请求时间线追到关键路径#

我建议先确认用户侧的症状,再逐步下钻到实例、GPU、Expert 和通信层。

观测现象优先排查可验证的优化方向
P99 TTFT 高排队、长 Prefill、KV 交接调度预算、PD 配比、接收侧容量
P99 ITL 高Prefill 干扰、单轮 Batch 过重、同步等待Decode 调度、限制每轮工作量、执行 Profile
并发升高后 P99 突然恶化KV 不足、抢占、队列积压KV 容量管理、准入控制
某些 GPU 经常落后Expert 热点、通信、硬件差异EPLB、拓扑放置、通信诊断
相近长度请求延迟差异很大调度公平性、缓存命中、资源竞争请求隔离、负载均衡、迁移

表中的原因只是排查假设,不能从某一个指标直接推导出结论。例如,TTFT 变高可能只是队列变长,并不代表 Prefill kernel 退化。

先把指标口径固定下来#

对于 vLLM,可以从 Production Metrics 和实际部署实例的 /metrics 入手,检查排队、TTFT、生成间隔、KV 使用率、运行中请求数以及抢占计数。指标名称与可用性会随版本变化,应以运行实例导出的名称和说明为准。

原始样本或直方图应保留必要的模型、实例与负载分组。不要简单平均各实例的 P99:多个分位数的平均值不等于整体分位数。聚合直方图时,还要保证桶边界与观测口径一致。

用两组负载区分问题#

第一组固定输入、输出长度,逐步提高并发,观察 TTFT、ITL、抢占与队列的变化。这样更容易判断系统在哪个容量点开始退化。

第二组使用真实业务的长度分布,分别观察长短请求,检查短请求是否被少量长请求拖慢。仅用固定长度 benchmark,容易漏掉这种干扰。

每次实验还应记录到达率、完成率、错误率、超时与拒绝请求,避免只统计成功返回的请求而低估尾部问题。若负载发生器总是等待上一个请求完成后才发下一个,系统变慢时发压速率也会下降;评估排队长尾时,需要考虑这种闭环负载与真实到达过程的差异。

最后才决定改哪里#

将请求时间线与对应执行轮次关联起来,回答:延迟尖峰是在排队、Prefill、KV 交接,还是某一轮 Decode 中产生?如果出现在 Decode,再看是否有某个 rank、Expert 或通信阶段落后。

一次调整一个主要变量,在相同负载下比较。除了 P99,也要保留吞吐、成本与 SLO 达标率,防止“延迟优化”只是以大幅降低承载能力换来的结果。

6. 更值得研究的是跨层协同#

在大规模 MoE 推理中,请求调度、KV 分配、Attention DP、Expert Parallelism 和 All-to-All 并不是彼此独立的模块。

一个实例的请求数最少,不代表它的 KV 占用最低,也不代表相关 EP 组的专家计算最轻。一个专家放置方案让计算更均衡,也可能增加跨节点通信。局部指标变好,最终 P99 未必变好。

因此,一个值得探索的研究方向是:综合请求长度、KV 压力、专家负载与通信状态,在端到端 SLO 约束下进行调度和放置。这是本文提出的优化方向,而不是已经验证的通用最优方案。评价时应比较满足 SLO 的有效服务能力,并计入迁移与重配置成本。

我会把这些手段归纳成三类:隔离减少相互干扰,均衡减少最慢参与方的拖累,重叠隐藏可以并行的等待。但选择手段之前,必须先找到真正的关键路径。

如果所谓的“长尾”只是少量请求生成特别多 token,长期占据 KV Cache 和调度槽位,那么重点应回到长度感知调度、公平性、抢占和迁移。它与 MoE 专家执行不均衡可以同时存在,却需要分别测量和处理。

延伸阅读#

  1. vLLM:Optimization and Tuning
  2. vLLM:Production Metrics
  3. SGLang:PD Disaggregation
  4. Sarathi-Serve,OSDI 2024
  5. Llumnix,OSDI 2024
  6. DeepSeek EPLB:Expert Parallelism Load Balancer