目录

Disaggregated LLM Serving:从 PD 分离到 Attention 卸载

过去两年,LLM Serving 的核心矛盾越来越清晰:模型越大、上下文越长,请求之间越难靠“放到同一批里一起算”来解决效率问题。真正麻烦的是,不同阶段、不同模块对硬件的需求并不一样。Prefill 想要算力,Decode 想要显存带宽,MoE 的 Attention 和 Expert 又有各自完全不同的瓶颈。

所谓 Disaggregated LLM Serving(解耦式大模型推理),本质上就是把这些资源需求不同的部分拆开,让它们在不同 GPU 池、不同并行策略,甚至不同存储层级上独立扩展。本文按演进路径梳理几类代表系统:Continuous Batching、Chunked Prefill、DistServe、Mooncake、MegaScale-Infer 和 Adrenaline。

1. 从自回归生成开始

LLM 推理通常分为两个阶段:

  1. Prefill(预填充):一次性处理输入 Prompt 的所有 token,并生成第一个输出 token。由于 Prompt 内 token 可以并行计算,Prefill 通常是 compute-bound(计算密集型),主要影响 TTFT(Time To First Token)。
  2. Decoding(解码):之后每次只生成一个 token。每一步都要读取历史 KV Cache,因此 Decoding 通常是 memory-bandwidth-bound(显存带宽密集型),主要影响 TPOT(Time Per Output Token)或 ITL(Inter-Token Latency)。
Autoregressive Generation
LLM 推理的两个阶段:Prefill 并行处理 Prompt,Decoding 逐 token 自回归生成

这张图可以从两个维度读:

  1. 横向看 token 依赖:Prefill 阶段一次性吃进完整 Prompt,Prompt 内 token 可以并行进入模型;Decoding 阶段每一步只能生成一个新 token,下一个 token 必须等待上一个 token 的输出。
  2. 纵向看 KV Cache 变化:Prefill 会为所有输入 token 写入 KV Cache;Decoding 每生成一个 token,既要读取已有 KV Cache,又要把新 token 的 KV 追加进去。上下文越长,单步 Decode 读取的历史状态越多。
  3. 对应到性能指标:Prefill 决定用户多久看到第一个 token,也就是 TTFT;Decoding 决定后续 token 是否流畅输出,也就是 TPOT/ITL。

这个差异决定了后续所有系统优化的方向:如果把 Prefill 和 Decoding 强行塞进同一套 GPU 配置里,系统迟早会在 TTFT、TPOT、吞吐量和资源利用率之间反复妥协。

2. 单体架构里的调度优化

在真正把系统拆开之前,早期工作主要尝试在单体架构里改进 batching。

2.1 Continuous Batching:消除请求级等待

传统 static batching 会被 batch 中最长的请求拖住。只要一个请求还没结束,整个 batch 的资源槽位就难以释放。Orca 提出的 Continuous Batching 把调度粒度降到 token iteration:每一轮生成后,完成的请求立刻退出,新请求立刻补位。

Continuous Batching
Continuous Batching:在 token iteration 级别调度请求,避免等待最长请求结束

这张图左半部分的 ①②③④ 是 Orca 的调度闭环:

  1. ① 从 Request Pool 取状态:Scheduler 会观察当前所有活跃请求的进度。图中 $x_1$ 已经有较长历史上下文,当前要从 $x_{14}$ 继续生成 $x_{15}$;$x_2$ 已经生成到 $x_{22}$,当前要继续生成 $x_{23}$。因此 $x_1,x_2$ 是正在 Decode 的老请求。相对地,$x_3$、$x_4$ 刚进入系统,Request Pool 里只有 Prompt token(如 $x_{31},x_{32}$ 和 $x_{41},x_{42},x_{43}$),所以它们处在 Prefill 阶段。
  2. ② 选择本轮 iteration 的请求:Scheduler 把 $x_1,x_2,x_3,x_4$ 放进同一轮执行。这里的细节很重要:$x_1,x_2$ 只各自需要一个 query token 做 Decode,而 $x_3,x_4$ 需要把多个 Prompt token 一起送进模型做 Prefill。也就是说,Continuous Batching 的一个 iteration 里可以同时混有 Decode token 和 Prefill token。
  3. ③ Execution Engine 只执行一轮:Execution Engine 不会一直跑到某个请求结束,而是执行一次模型 iteration。对 $x_1,x_2$ 来说,这轮会基于各自历史 KV Cache 生成下一个 token;对 $x_3,x_4$ 来说,这轮会处理 Prompt 并建立对应的 KV Cache。
  4. ④ 返回本轮结果:Engine 返回 $x_{15},x_{23},x_{33},x_{44}$ 这类新生成 token 或阶段结果,Scheduler 再更新 Request Pool。如果某个请求遇到 EOS,就可以立刻释放 slot;如果 $x_3,x_4$ 的 Prefill 完成,它们后续也会转入 Decode 队列。

右半部分解释的是这一轮 iteration 在模型内部如何执行。不同请求的序列长度不一样,QKV Linear 之后会先 Split 成不同请求对应的 attention 子任务,例如图中的 Attn $x_1$、Attn $x_2$、Attn $x_3$、Attn $x_4$。Attention K/V Manager 负责为每个请求找到自己的历史 KV Cache;各个 attention 子任务完成后再 Merge 回统一的 batch,继续经过 Attn Out Linear 和后续层。Orca 的关键就是把“请求级 batch”变成“iteration 级 batch”:每一轮只推进一步,但每一轮都可以重新组合请求。

这样做的关键收益是减少 padding 和空转,但它也意味着长 Prefill 会和正在 Decode 的请求共用同一次 iteration,因此阶段干扰仍然存在。

它解决的是 batch 内部的“空座位”问题,但没有解决阶段冲突。一个长 Prompt 的 Prefill 仍然可能插入到 Decode 队列中,让正在生成的请求等待,从而制造 TPOT 抖动。

2.2 Chunked Prefill:把长 Prefill 切小

Sarathi-Serve 的 Chunked Prefill 进一步把长 Prompt 的 Prefill 切成多个 chunk,让 Prefill chunk 和 Decode token 在同一轮 iteration 中混合执行。这样做可以把一次巨大的计算尖峰摊平,降低 Decoding 的 latency spike。

Chunked Prefill
Chunked Prefill:将长 Prompt 的 Prefill 拆成较小 chunk,平滑混合批处理负载
Chunked Prefill Details
Sarathi-Serve 的 chunk 拆分与 piggybacking 机制

这两张图表达的是同一个思想:不要让一个长 Prompt 的 Prefill 一次性占满 GPU。Sarathi-Serve 会把长 Prompt 切成多个 chunk,每个 iteration 只处理其中一小段,并把这个 Prefill chunk 和其他请求的 Decode token 拼在一起执行。这样 GPU 每轮看到的 workload 更均匀,Decode 请求不会因为某个长 Prompt 突然加入而等待一个巨大的 Prefill block。

代价也在图里隐含着:被切开的请求必须等所有 chunk 都算完才能拿到第一个输出 token,所以它自己的 TTFT 会变长。Chunked Prefill 是“平滑延迟尖峰”的调度技术,还不是彻底的资源解耦。

Chunked Prefill 是一个很实用的折中:它降低了长 Prompt 对 Decode 的阻塞,但代价是该请求自身 TTFT 会被拉长。更重要的是,Prefill 和 Decoding 仍然共享同一组 GPU 和同一套并行策略。阶段之间的根本资源错配仍然存在。

3. 为什么需要 PD 分离?

Prefill-Decoding Disaggregation(PD 分离) 的动机来自两个问题:阶段干扰,以及资源和并行策略耦合。

3.1 阶段干扰:TTFT 和 TPOT 相互拖累

在 colocated serving 中,Prefill 和 Decoding 会被混在同一个 batch 里执行。长 Prefill 会阻塞 Decode token,造成 TPOT 抖动;Decode 请求也会占用 batch slot,增加 Prefill 排队时间和 TTFT。

Prefill-Decoding Interference
Prefill-Decoding 干扰:混合执行时,长 Prefill 和 Decode token 会相互拖慢

这张图里蓝色小块可以理解为 Decode token,绿色长块是新请求的 Prefill。问题出在绿色块插入之后,同一时间窗口内其他请求的 Decode step 被迫拉长,图上方标出的 “longer decoding time” 对应 TPOT 变差;同时,Prefill 自己也不是独占 GPU 运行,而是夹在 Decode workload 中,图下方的 “longer prefill time” 对应 TTFT 变差。换句话说,混合执行并不是“互相利用空闲”,而经常是互相污染关键路径。

对于交互式聊天、代码补全、摘要生成这类应用,TTFT 和 TPOT 往往都有明确 SLO。只优化其中一个指标并不够,系统必须同时满足两者。

3.2 资源耦合:同一套 GPU 配置无法同时适配两个阶段

Prefill 更像大矩阵计算,倾向于通过 tensor parallelism 把计算摊到多卡上,从而压低 TTFT。Decoding 每次只生成一个 token,更容易被 KV Cache 读写和显存带宽卡住。过大的 tensor parallelism 还会把 All-Reduce 放到每个 token 的关键路径上,收益递减明显。

Parallelism Strategy Coupling
不同并行策略会改变推理延迟与资源占用;混合部署迫使 Prefill 和 Decoding 共享同一套策略

这张图展示的是并行策略本身的差异。No parallelism 代表单卡完整跑模型;Tensor Parallelism(TP)把同一层的矩阵计算切到多卡上,适合加速大矩阵计算;Pipeline Parallelism(PP)把不同层放到不同卡上,适合扩展模型和 KV Cache 容量。Prefill 更看重快速完成大块矩阵计算,Decode 更看重单步通信开销、显存容量和 batch 聚合能力。混合部署的问题是:两阶段只能共享一套 TP/PP 配置,很难同时最优。

在混合部署里,资源数量和并行策略必须同时服务 Prefill 与 Decoding 两个阶段,因此配置决策会被更紧的 SLO 或更重的阶段牵引。某些 workload 下,为了压低 Decode 的 TPOT,系统可能增加 GPU 或调整并行策略,结果 Prefill 的 TTFT 远低于 SLO,形成过度配置;反过来,如果配置主要按 Prefill 的 TTFT 目标收敛,Decode 侧又可能因为可承载 batch size、KV Cache 容量或显存带宽不足而先触碰 TPOT SLO。

Parallelism Coupling
并行策略耦合会导致过度配置:为了满足一个阶段的 SLO,被迫浪费另一个阶段的资源

这张图的纵轴同时放了 TTFT 和 TPOT 两个指标,红色虚线是 SLO。左侧单 GPU 情况下,某些请求的 TPOT 已经低不过 SLO;右侧通过增加 GPU 可以把 TPOT 压下去,但 TTFT 也被一起压得过低。对用户来说,低于 SLO 很多的 TTFT 并不会产生额外价值,但系统已经为它支付了更多 GPU 成本。这就是 DistServe 所说的 resource and parallelism coupling。

PD 分离的基本思路因此很直接:把 Prefill 和 Decoding 放到不同 GPU 池中,让它们分别选择资源规模和并行策略。

4. DistServe:以 Goodput 为目标的 PD 分离

DistServe 是 PD 分离的代表系统之一。它把 Prefill worker 和 Decode worker 物理拆开:请求先进入 Prefill 池完成 Prompt 计算,再把生成的 KV Cache 传给 Decode 池继续自回归生成。

DistServe Architecture
DistServe 架构:Router 将请求分发到 Prefill worker,再将 KV Cache 传输给 Decode worker

这张架构图里有三条主线。第一,Controller/Router 接收请求后先选择 Prefill Instance,因为新请求必须先把 Prompt 编码成 KV Cache。第二,Prefill Instance 和 Decoding Instance 都保存完整 LLM Model,但它们的并行策略和资源规模可以不同。第三,Prefill 结束后真正跨节点传输的是 KV Cache,而不是原始 Prompt 或完整中间激活;这个 KV Cache Transfer 是 PD 分离的主要额外开销,也是后面 placement 策略要优化的对象。

DistServe 的关键不只是“拆开”,而是把优化目标定义成 Goodput(有效吞吐量):在同时满足 TTFT 和 TPOT SLO 的前提下,系统能承载的最大请求速率。这个指标比单纯 throughput 更贴近在线服务,因为超时请求即使被处理完,也不能算有效服务能力。

DistServe Goodput
Goodput:在 TTFT 和 TPOT 都满足 SLO 时,系统能维持的最大请求速率

Goodput 图要看每条曲线和虚线 SLO 的交点。Colocate 场景下,Prefill 和 Decode 混在一起,系统最大 goodput 由更早撞到 SLO 的阶段决定;图中 Decode 侧大约 1.6 rps/GPU 就成为瓶颈。Disaggregate 场景下,Prefill-only 和 Decode-only 可以分别扩展,例如用 2P1D 的比例让 Prefill 侧和 Decode 侧的能力更接近,最终每 GPU goodput 提升到约 3.3 rps/GPU。这个例子说明,分离的收益不是单个阶段更快,而是两阶段的 SLO 约束可以被分别满足。

拆开之后,Prefill 可以采用更激进的 tensor parallelism 来压低 TTFT;Decode 可以采用更适合 KV Cache 容量和带宽的 parallelism,把 batch size 做大以提升吞吐。DistServe 还会根据集群拓扑进行 bandwidth-aware placement,尽量把 KV Cache 传输放在高速链路上。

因此,Prefill 节点和 Decode 节点的比例不是固定的。更合理的做法是从 workload 和 SLO 反推:长 Prompt、短输出的场景通常更吃 Prefill;短 Prompt、长输出的场景通常更吃 Decode;TTFT SLO 越紧,Prefill 侧越需要更强的计算能力;TPOT SLO 越紧,Decode 侧越需要更大的 KV Cache 容量、显存带宽和可承载 batch size。DistServe 的思路是枚举 Prefill/Decode 的 GPU 数量和并行策略,分别估计它们能满足的 TTFT/TPOT 曲线,再选择 per-GPU goodput 最高的组合。例如图中的 2P1D 并不是通用比例,而是在该 workload 和 SLO 下让 Prefill 与 Decode 能力更匹配的一个结果。

DistServe Placement
DistServe 的带宽感知放置:根据节点亲和性和网络带宽降低 KV Cache 传输开销

这张图解释为什么 PD 分离不能只看 GPU 数量,还要看网络拓扑。左侧是高亲和场景:Prefill 和 Decode 尽量放在同一节点或同一 NVSwitch 域内,让 KV Cache 通过 NVLink/NVSwitch 传输。右侧是跨节点场景:如果 Prefill 和 Decode 必须跨机器,DistServe 会根据带宽把模型层、head 或 worker 放到更合适的位置,减少低带宽链路上的 KV Cache 传输。这里的放置策略直接决定“分离带来的通信成本”会不会超过“分离带来的调度收益”。

论文报告 DistServe 在严格延迟约束下可服务最多 7.4 倍 的请求,或满足 12.6 倍 更严格的 SLO,并保证 90% 以上请求满足延迟约束。

DistServe 的代价也很明确:KV Cache 必须跨 worker 传输。如果网络带宽不足,通信开销会吞掉分离收益;另外,Prefill 池和 Decode 池的资源利用率会出现新的不均衡,例如 Prefill 节点显存空闲,而 Decode 节点算力空闲。

5. Mooncake:把 KVCache 变成系统中心

长上下文场景把 PD 分离推向了下一步:瓶颈不再只是“算在哪里”,还包括 KV Cache 存在哪里、如何复用、如何迁移

Moonshot AI 的 Mooncake 是 Kimi 背后的 KVCache-centric serving 架构。它同样分离 Prefill 和 Decoding,但进一步把 GPU 集群中未充分利用的 CPU、DRAM、SSD 和 NIC/RDMA 资源组织成分布式 KVCache 池。

Mooncake Architecture
Mooncake 架构:围绕分布式 KVCache 池组织 Prefill、Decode、Transfer Engine 和 Conductor

Mooncake 架构图可以分成三层看。左侧 Conductor 包含 cache-aware prefill scheduler、KVCache balance scheduler 和 load-balance decoding scheduler,说明它不是简单轮询,而是同时调度缓存命中、缓存均衡和 Decode 负载。中间绿色虚线区域是分布式 KVCache Pool,它把 CPU/DRAM/SSD 也纳入缓存体系,而不只依赖 GPU VRAM。右侧还明确写了两个优化目标:Prefill 侧最大化 cache reuse 并满足 TTFT SLO,Decode 侧最大化 throughput 并满足 TBT/TPOT SLO。

Mooncake 的调度核心是 Conductor。它维护全局 KVCache 视图,在请求到来时综合考虑缓存命中、Prefill 队列、Decode 负载和传输成本,尽量把请求路由到最能复用历史上下文的位置。

Mooncake Scheduling
Mooncake 的 KVCache-centric 调度:优先复用已有 KVCache,并在 Prefill 与 Decode 之间流式传输

这张 workflow 图把一次请求拆成四步。s1 是 Prefix KVCache Reuse:如果历史上下文已经缓存,Prefill 实例先复用 prefix cache。s2 是 Incremental Prefill:只对新增部分做 Prefill,并在 GPU 和 CPU 之间按 layer 进行 load/store,尽量把 KVCache 搬运隐藏在计算中。s3 是 KVCache Transfer:把完整或增量 KVCache 传给 Decode 实例。s4 是 Decoding:Decode 侧异步加载 full KVCache,同时继续生成 token,避免 GPU 因等待 KVCache 而空闲。

Mooncake 的价值在长文本工作负载中尤其明显:论文报告在真实 trace 下有效请求容量提升 59% 到 498%,生产系统运行在数千节点上,每天处理超过 1000 亿 token。它的复杂度也随之上升:跨层级存储的延迟差异、KVCache 迁移、热点复制和过载保护都需要被全局调度器纳入决策。

6. MegaScale-Infer:从阶段分离到模块分离

PD 分离解决的是 Prefill 和 Decoding 的阶段差异。但 MoE 模型普及后,Decoding 阶段内部又出现了新的资源错配:Attention 和 FFN/Expert 的瓶颈并不相同。

6.1 动机:MoE Decoding 中的利用率错配

MegaScale-Infer 的动机来自一个看似反直觉的现象:MoE 减少了每个 token 激活的参数量和 FLOPs,但在真实 serving 中不一定降低单位计算成本。原因是 GPU 是否高效,不只取决于总 FLOPs,还取决于这些 FLOPs 能否形成足够大的 batch 和高效的矩阵乘。

在 decoding 阶段,Attention 需要为每个请求读取各自的历史 KV Cache;不同请求的 KV Cache 不能像模型权重那样被多个 token 共享,因此 Attention 往往更偏 memory-bound,GPU 利用率不高。FFN 则不同:它主要读取模型权重,同一组 FFN 权重可以被 batch 内所有 token 复用;batch size 增大时,FFN 的 GEMM 变大,更容易把 GPU 算力吃满。

MegaScale-Infer Motivation
MegaScale-Infer Figure 1:decoding 阶段 Attention 与 FFN 的 GPU 利用率随 batch size 的变化;MoE FFN 因稀疏路由更难跑满

三幅子图分别对应 dense model、普通 MoE 和 MegaScale-Infer。蓝线是 Attention,红线是 FFN。Dense model 每层只有一个 FFN,所有 token 都会经过它;在显存容量和响应延迟允许的最大 batch size 内,FFN 通常已经能形成足够大的 GEMM,因此红线较快接近高利用率。

MoE 改变了这一点。这里要区分 全局 batch size每个 Expert 的有效 batch size:前者是一次 decode iteration 中系统处理的请求数,后者是经过 Gate 路由后真正落到某个 Expert 上的 token 数。由于每个 token 只选择 top-k 个 Expert,而 Expert 总数通常很多,每个 Expert 分到的 token 可能只有全局 batch 的一小部分;在一些大规模 MoE 配置中,甚至可能少于四分之一或低一个数量级。这样 FFN 的 GEMM 变小,MoE FFN 就可能从 compute-intensive 退化成 memory-intensive,造成图中普通 MoE 的红线爬升缓慢。

MegaScale-Infer 的解法是把 Attention 和 Expert 拆开独立扩展。Attention 模块通过 data parallelism 复制成多个 replica,分别处理不同请求;Expert 模块独立成 expert parallel group,接收来自多个 Attention replica 的 token。这样做的关键效果是 把多个 Attention 副本上的请求汇聚到 Expert 侧:单个 Expert 看到的 token 更多,FFN 更容易回到 compute-intensive 的高利用率区间。图中的虚线表达的就是这种变化。

关键并不是“MoE 一定慢”,而是 Attention 和 FFN 需要不同的扩展方式。如果把两者绑在同一组 GPU 上,系统很难同时让 Attention、Expert 权重访问和通信成本都处在高效区间。MegaScale-Infer 的思路是把 Attention replica 和 Expert parallel group 拆开,让多个 Attention replica 的 token 汇聚到 Expert 侧,增大 Expert 的有效 batch。

6.2 背景:MoE Layer 与 Expert Parallelism

在 MoE layer 中,Attention 是 dense 的,所有 token 都要参与计算;FFN 则变成稀疏 Expert,token 只会被 Gate 路由到少数专家。传统 expert parallelism 会把不同 Expert 分散到不同 GPU 或节点上,并通过 All-to-All 交换 token。

MoE and Expert Parallelism
MegaScale-Infer Figure 2:MoE layer 和 Expert Parallelism;Gate 将 token 路由到不同 Expert,并通过 All-to-All 分发和聚合 token

上半部分是单个 MoE layer:token 先经过 Attention,再由 Gate 选择 Expert,最后把 Expert 输出聚合送到下一层。下半部分是 Expert Parallelism:不同 Expert 分散在不同设备上,token A 和 B 可能被路由到不同专家,因此需要 All-to-All 把 token 发到对应 Expert,再把结果发回来。这个 All-to-All 是 MoE 扩展的基础,但也会在 batch 太小或路由太稀疏时放大通信和调度开销。

Expert Parallelism 的重点是顺着 MoE 的稀疏路由来组织计算:不同 Expert 放在不同设备上,token 通过 Gate 被发送到被选中的 Expert,再把结果聚合回来。它的问题也正来自这种稀疏性:每个 Expert 只看到全局 batch 的一部分 token;当路由后落到单个 Expert 的 token 太少时,FFN 很难形成足够大的 GEMM,GPU 利用率就会下降。

6.3 AE 分离:Attention Node 与 Expert Node 独立扩展

MegaScale-Infer 将 Decoding 阶段内部的 Attention 和 FFN/Expert 进一步拆开,形成 Attention-Expert Disaggregation(AE 分离)。Attention 节点保存 Attention 参数和 KV Cache,可通过 data parallelism 复制多个 attention replica;Expert 节点保存专家参数,形成 expert parallel group。多个 Attention replica 的 token 可以汇聚到 Expert 节点,从而增大每个 Expert 的有效 batch size。

MegaScale-Infer Architecture
MegaScale-Infer Figure 3:runtime 架构;Attention Node 与 Expert Node 分离,并通过 M2N/N2M 通信连接

在 MegaScale-Infer 的 runtime 架构中,左侧 Attention Node 有 M 个 replica,每个节点保存 Attention 参数和 KV Cache,并在节点内使用 Tensor Parallel;右侧 Expert Node 组成 N 路 Expert Parallel,每个节点保存一部分 Expert 参数。中间的 M2N/N2M 通信表示 token hidden states 在 Attention 侧和 Expert 侧之间双向流动,底部的 Ping-Pong Pipeline Parallel 说明系统不是等一整个 batch 完成后再传输,而是按 micro-batch 流水化推进。

这种拆分带来三个直接收益:

  1. 独立扩展:Attention 和 Expert 可以按各自瓶颈配置 GPU 数量。
  2. 异构部署:Attention 可使用更适合 KV Cache 和 dense 计算的节点,Expert 可使用更适合承载大量权重的节点。
  3. 更大的 Expert 有效 batch:来自多个 Attention replica 的 token 汇聚到专家侧,让 Expert 更接近 compute-bound。

Attention Node 和 Expert Node 的比例同样不能写死。Attention 侧主要受 KV Cache 容量、显存带宽和 decode latency 约束;Expert 侧主要受每个 Expert 的有效 batch size、FFN 计算时间和路由负载均衡约束。Attention replica 太少,KV Cache 访问和 Attention 计算会成为瓶颈;Expert 节点太少,FFN 计算会排队;Expert 节点太多,每个 Expert 分到的 token 又会变少,GEMM 变小,反而降低利用率。MegaScale-Infer 因此用性能模型搜索部署方案,让 Attention micro-batch 时间和 Expert micro-batch 时间尽量接近,并让 A2E/E2A 通信能被流水线中的计算覆盖。

为了降低分离带来的通信开销,MegaScale-Infer 使用定制的 M2N 通信库,减少 GPU 到 CPU 拷贝、通信组初始化和全局同步开销。

6.4 Ping-Pong Pipeline:用微批次隐藏通信

AE 分离也有明显代价:token 需要在 Attention 节点和 Expert 节点之间频繁往返。MegaScale-Infer 因此引入 Ping-Pong Pipeline Parallelism:纵向四条 lane 分别是 Attention、A2E 传输、Expert、E2A 传输;横轴是时间;每个小块表示某个 layer 上的某个 micro-batch。

Ping-Pong Pipeline Parallelism
MegaScale-Infer Figure 4:Ping-Pong Pipeline 时序图;micro-batch 在 Attention、A2E、Expert、E2A 四个阶段交错执行

其中 $m=4$ 表示 batch 被切成 4 个 micro-batch,$L=2$ 表示示例展示 2 个 layer。每个格子里的 $i_k$ 表示第 $k$ 层的第 $i$ 个 micro-batch。红色斜线标的是跨层依赖:同一个 micro-batch 必须完成上一层的 E2A 返回,才能进入下一层 Attention。图中 $T_a$、$T_e$、$T_c$ 分别近似代表 Attention 计算、Expert 计算和通信时间;整体耗时被写成与 $\max(T_a, T_e)$ 和 $T_c$ 相关,意思是只要流水线足够饱满,系统希望用较慢的计算阶段覆盖另一侧计算和通信。

这种时序安排的目标是用 Attention 或 Expert 计算去覆盖 A2E/E2A 通信,减少分离架构带来的等待。论文报告 MegaScale-Infer 相对先进 MoE serving 系统可获得最高 1.90 倍 的单卡吞吐提升。但它对网络延迟和抖动非常敏感,一旦 M2N/N2M 通信不能稳定跟上,pipeline bubble 就会直接损失吞吐。

7. Adrenaline:把 Decode Attention 卸载给 Prefill

PD 分离之后,系统又暴露出新的利用率问题:Prefill 节点算力忙但显存容量和带宽可能空闲,Decode 节点显存和带宽吃紧但算力单元又常常空闲。Adrenaline 的出发点就是利用这种互补性。

Resource Utilization in PD Disaggregation
Adrenaline 动机:PD 分离后,Prefill 阶段计算利用率高但显存带宽利用率低,Decoding 阶段则相反

这张图是 Adrenaline 的动机来源。左侧 Prefill phase 中,prompt length 增大时 compute utilization 能保持较高,但 memory bandwidth utilization 很低,说明 Prefill 实例还有显存带宽和容量余量。右侧 Decoding phase 中,batch size 增大时 memory bandwidth utilization 很高,但 compute utilization 较低,说明 Decode 实例被 KV Cache 访问拖住。Adrenaline 正是利用这种互补:把 Decode 中最吃显存带宽的 Attention 交给 Prefill/Attention Executor 侧处理。

Adrenaline 的核心想法是:把 Decoding 阶段中最吃 KV Cache 读写的 Attention 计算,部分卸载到 Prefill 节点上执行。Decode 节点保留较轻的 Q projection 和 FFN,把 Query 发送给 Prefill 节点;Prefill 节点读取自己承载的 KV Cache,完成 Attention 计算后把结果返回 Decode 节点。

PD Disaggregation vs Adrenaline
Adrenaline 与普通 PD 分离的对比:Decode batch 从 M 扩展到 M+N,并将部分 Attention 计算卸载到 Prefill 实例

对比图左侧是普通 PD 分离:Prefill 实例只做 Prefill,Decode 实例自己完成 QKV projection、Attention、O projection 和 FFN,Decode batch size 是 $M$。右侧是 Adrenaline:Decode 实例仍然做 QKV projection,但其中一部分请求的 Attention(batch $N$)被卸载到 Prefill 实例;Decode 实例腾出的显存和带宽可以容纳更大的 batch,即 $M+N$。红色箭头代表跨实例传输的 Query/Attention 输出,传输量远小于完整 KV Cache,但它发生在每个 Decode step 的关键路径上。

Low-latency Attention Offloading
低延迟 Attention Offloading 执行流:Decode 节点发送 QKV 相关数据,Attention Executor 完成卸载计算并返回结果

这张执行流图进一步说明 offloading 怎么落到一次 Decode step。上半部分没有 offloading 时,Decode 实例内部顺序执行 Schedule、QKV、Attention、O、FFN。下半部分引入 Attention Executor 后,Decode 实例先为被卸载请求分配 block,再把 qkv 相关小张量发给 Attention Executor;Executor 使用自己持有或可访问的 KV Cache 计算 Attention,并把输出返回给 Decode 实例继续 O projection 和 FFN。关键点是:跨节点传输的是相对小的 Query/输出,而不是每步搬运完整 KV Cache。

图中的 ①②③ 对应 Adrenaline 为降低同步开销做的三处关键优化:

  1. ① 提前处理元数据和内存管理:当 Proxy 判断某个请求的 Attention 需要卸载时,会提前把这个请求作为 hint 发给 Attention Executor,让它初始化 prompt length 等运行时元数据;KV cache block 的分配和回收也尽量提前完成。这样 Init Meta.Alloc Block 不会卡在每个 decode step 的关键路径上。
  2. ② 聚合发送 QKV 输入:Adrenaline 不把零散的 $q$、$k$、$v$ 张量分别发送,而是把 Attention Executor 需要的输入聚合成一次 qkv 传输,减少跨实例通信次数和启动开销。
  3. ③ 重叠远端与本地 Attention:Decode 实例并不是把所有 Attention 都卸载出去,仍有一部分本地 Attention 需要执行。调度器会尽量让 Attention Executor 上的远端 Attention kernel 与 Decode 实例里的本地 Attention kernel 同时运行;远端算完后只把 out 返回给 Decode 实例继续后面的 O projection 和 FFN。

这三个标号共同服务于同一个目标:把 offloading 带来的元数据准备、输入传输和远端计算尽量移出或隐藏在关键路径之外。否则每一层都多一点同步等待,最终会直接放大 TPOT。

为了让这个机制不拖慢每个 token 的关键路径,Adrenaline 需要三类技术:

  1. 低延迟解码同步:降低跨节点 Attention 对 TPOT 的影响。
  2. 资源高效的 Prefill 协同:在 Prefill 节点上同时安排自身 Prefill 和外来的 Decode Attention 任务。
  3. 负载感知卸载调度:只在收益大于通信成本时选择卸载,并保证请求 SLO。

论文报告 Adrenaline 可让 Prefill 实例的显存容量利用率提升 2.28 倍、显存带宽利用率提升 2.07 倍,Decode 实例计算利用率最高提升 1.67 倍,整体推理吞吐提升 1.68 倍

Adrenaline E2E Performance
Adrenaline 的端到端效果示例:在相同请求率下改善 TTFT/TPOT,并提升输出吞吐

这组曲线展示了 ShareGPT + Llama-2 7B 场景下的端到端效果。横轴是请求率,四个子图分别看平均 TTFT、平均 TPOT、P99 TPOT 和输出吞吐。随着请求率升高,vLLM 的 TTFT 和 TPOT 更早出现陡增,而 Adrenaline 通过释放 Decode 侧 KV Cache 压力,可以在更高请求率下维持较低延迟,并让输出吞吐继续上升。需要注意的是,图中收益依赖 offloading ratio、模型大小、网络和 workload 分布,并不意味着所有部署都能无条件获得同样提升。

它的限制同样明显:每个 Decode step 都可能引入跨节点通信。网络延迟、同步开销和调度误判都会直接进入 TPOT 的关键路径。因此 Adrenaline 更像是在高质量互联和精细调度基础上的进一步压榨,而不是所有集群都能无脑采用的通用方案。

8. 总结:解耦粒度越来越细

LLM Serving 的演进可以看成不断识别并拆开资源错配的过程:

  1. Continuous Batching / Chunked Prefill:仍在单体架构内,通过更细粒度调度减少空转和延迟尖峰。
  2. PD Disaggregation(DistServe、Mooncake):把 Prefill 和 Decoding 拆开,分别优化 TTFT、TPOT、KVCache 传输与复用。
  3. AE Disaggregation(MegaScale-Infer):在 MoE decoding 内部继续拆分 Attention 和 Expert,解决稀疏 Expert 的低利用率问题。
  4. Attention Offloading(Adrenaline):在 PD 分离后进一步跨阶段调配 Attention 计算和 KV Cache 访问,利用 Prefill/Decode 节点的资源互补。

这些系统的共同主题不是“拆得越细越好”,而是只有当两个部分的资源需求、扩展方式或 SLO 目标确实不同,并且通信成本可以被隐藏或摊薄时,解耦才会带来净收益。未来更长上下文、更大 MoE 模型和更复杂的 agent workload,会继续推动 serving 架构向更细粒度、更动态的方向演进。

参考资料