LLM推理优化的核心
1. 核心矛盾:混合部署的性能瓶颈
LLM的推理过程包含两大阶段:Prefill(提示处理)和Decode(Token生成)。两者在计算特性上存在根本差异:
Prefill 阶段:一次性处理整个输入Prompt,生成初始的KV Cache。这是一个计算密集型任务,序列长度越大,计算开销越高,耗时也越长。
Decode 阶段:逐个生成输出Token,每次仅处理一个Token,但需要访问完整的KV Cache。这是一个内存密集型任务,对延迟极其敏感。
当这两个阶段在同一设备上混合执行时,长时间运行的Prefill任务会频繁“抢占”计算资源,严重干扰并阻塞了对延迟敏感的Decode任务。这导致整个系统的Token生成速度(吞吐量)大幅下降,并产生不稳定的服务延迟(卡顿感)。
2. 解决方案:计算资源的物理分离与专业化
为解决上述瓶颈,业界采用了一种“解耦”或“分离”的架构,将Prefill和Decode任务分配到物理上独立的GPU集群(节点)上运行。
Prefill 节点:专门负责高效处理批量传入的请求,最大化并行计算能力以提升Prefill任务的吞吐量。
Decode 节点:专门负责执行低延迟的Token生成,通过维持稳定的并发请求数,实现最高的Decode吞吐量。
这种架构的核心思想是让不同特性的计算任务在为其优化的硬件环境中运行,避免资源争抢。其典型的部署配比是一个Prefill节点服务于多个Decode节点(例如1:3),实现了计算资源的有效利用。
3. 技术关键:高速、低延迟的KV Cache传输
解耦架构的成败,关键在于能否在Prefill和Decode节点之间高效、低延迟地传输核心状态——KV Cache。
通过名为KV Messenger的内部组件,利用RDMA等高性能网络技术(如 InfiniBand ConnectX 或 AWS EFA)进行传输。RDMA允许数据从一个节点的GPU内存直接写入另一个节点的GPU内存,绕过了CPU,延迟极低。
传输优化:
流水线式传输:无需等待整个Prefill计算完成,一旦模型中某一层的KV Cache生成,传输便立即启动,与后续层的计算并行进行,最大限度地隐藏了网络延迟。
内存布局优化 (HND):改变KV Cache在内存中的默认布局(从NHD到HND),确保数据在分片场景下也能连续存储,从而可以使用单次RDMA写操作完成整块传输,极大减少了网络交互开销。
4. 架构的得失与实际效果
核心收益:
大幅提升吞吐量:如文中案例所示,DeepSeek-R1模型在解耦后,吞吐量从混合部署难以超过50 Tokens/秒提升至90 Tokens/秒以上。
延迟更稳定:Decode节点不再被Prefill中断,服务延迟变得更加可预测。
主要代价:
TTFT增加:由于引入了KV Cache的网络传输耗时,用户感知到的首次响应会变慢(案例中约增加100毫秒)。
复杂性:该架构需要处理复杂的多GPU分片(TP)场景下的KV Cache同步、拆分与重组问题,对系统实现提出了更高要求。
总结
Prefill与Decode解耦架构,通过将计算任务专业化并部署于独立物理资源,再以RDMA等高速网络技术解决关键的KV Cache状态传输问题,是当前业界公认的、能够突破LLM推理性能瓶颈、实现高吞吐量服务的核心技术范式。它以牺牲一定的首Token延迟为代价,换取了系统整体服务能力和稳定性的巨大提升。
Prefill和Decode分离到不同设备上可加速LLM推理并提升性能
为了从输入提示中生成输出Token,大型语言模型(LLM)推理被分为两个阶段:Prefill和Decode。Prefill处理输入Token,填充KV Cache,随后进入Decode阶段逐个生成Token。虽然单个Decode步骤通常只需数十毫秒,但Prefill耗时明显更长。如果它们在相同设备上运行,混合Prefill和Decode会降低Decode性能。本文探讨一种已确立的解决方案,即解耦Prefill和Decode,将它们运行在独立设备上,以最大化Prefill吞吐量和Decode延迟。
Prefill与Decode性能对比
在典型的LLM服务引擎中,批处理调度器会在每个模型执行步骤中选择要处理的请求。当在单个设备或节点上运行时,Prefill和Decode请求会被一起批处理。注意力机制的成本会随着序列长度累积,对于Prefill和Decode而言,都与KV Cache中的条目长度(kv_len)成比例增长。Decode请求通常仅前向传递一个Token(qo_len=1),由于其他层独立处理序列中的Token,因此通过这些层时的成本最低。而Prefill请求则前向传递数千或数万个Token,由于qo_len较大,通过密集层时的成本显著。
前向传递的延迟受通过密集层处理的独立Token数量(qo_len)影响更大,而不是受注意力机制期间从KV Cache中检索的Token数量(kv_len)影响。注意力机制可并行化处理请求数量和kv_len(与序列长度成比例),从而实现良好利用率。Prefill属于计算密集型:由于qo_len较高,GEMM内核可沿M维度分配足够块,以充分利用现代GPU的计算能力。Decode属于内存密集型:由于批次大小通常较低,沿M的输入数量通常较小,仅够一个块。虽然Split-K GEMM内核可为低Token批次大小提升SM利用率,但缓存和矩阵乘法单元通常仍未充分利用。
当混合运行时,包含Prefill请求的批次会增加前向传递延迟,从而负面影响整个实例的Decode吞吐量。虽然将Prefill请求与Decode请求混合或采用分块Prefill可略微改善Decode性能,但要维持足够的Prefill吞吐量来处理足够多的请求,从而最大化Decode吞吐量,是很困难的。对于大型模型,在典型输出长度下,为了维持Decode的大批次大小,必须足够频繁地执行Prefill,但这会显著降低平均延迟并导致输出卡顿。
这些问题可通过使用独立节点集来执行Prefill和Decode予以解决。通过将一个Prefill节点与多个Decode节点关联,可以为Prefill调度足够多的请求以最大化吞吐量,并在Decode节点上维持足够多的并发请求以最大化其Decode吞吐量。Prefill节点填充KV Cache,随后传输到Decode节点。由于Decode节点不再需要中断来执行Prefill,延迟变得更具确定性,因为活跃请求的kv_len增长对整体影响远小于之前。其代价是首个Token生成时间(TTFT)增加,因为KV Cache的网络传输可能需要数十至数百毫秒。
KV Messenger
在Perplexity,我们的解耦Prefill和Decode实现围绕KV Messenger构建,它与LLM引擎交互,通过网络协调从Prefill节点到Decode节点的KV Cache传输。在Prefill侧,Messenger从Decode节点接收请求,将其交给批处理调度器,并跟踪前向传递执行,以尽可能低延迟地分发KV Cache。在Decode侧,在分配不可驱逐页后,Messenger会阻塞请求调度至Decode,直到收到KV Cache和Decode上下文传输完成的通知。
解耦Prefill需要高吞吐量、低延迟连接,因此我们的实现针对RDMA进行优化,支持EFA和ConnectX网络接口控制器(NIC)。KV Messenger基于libfabric构建,使用我们的fabric-lib包装器提供更高层次的低延迟抽象,覆盖RDMA原语,实现高效页和元数据传输,以及低延迟信令。在后台,fabric-lib协调GPU及其直接连接的NIC,将数据从Prefill节点复制到Decode节点。
接收请求后,Prefill节点分配对应的源KV页集,并使用本地引擎调度请求进行Prefill。为最小化延迟,传输不必等待前向传递完成;相反,一旦模型完成向KV Cache追加单个层的KV Cache条目,KV页复制即启动。由于Prefill请求可分块,批处理调度器在执行前会通知KV Messenger当前调度的块。为支持CUDA图同时跟踪层,Messenger维护专用线程,轮询注意力输出投影后递增的计数器。该计数器仅在分片环境中的领导节点上维护:尽管KV Cache条目在追加后、注意力前有效,但输出投影跨rank归约,从而隐式同步它们。一旦观察到计数器变化,Messenger就会收到通知,并调用fabric-lib启动层传输。
最后一个块的传输完成后,任何额外元数据也会被复制:推测解码或MTP需要将logits和隐藏状态移动到Decode。这些复制也通过RDMA执行,到达预分配缓冲区。
所有最后一个块的待处理传输完成后,Prefill节点释放KV页并完成请求。Decode节点并未被显式通知;相反,它使用即时计数器跟踪完成的操作数量。Prefill侧的RDMA操作数量与传输的页数成比例。一旦已知页和上下文复制操作数完成,fabric-lib即调用KV Messenger,指示请求已准备好进行Decode。Messenger随后释放任何上下文,并将请求交给LLM引擎。
分片KV Cache传输
如果Prefill和Decode依赖张量并行(TP)并相同分片或复制KV Cache,单个传输引擎会协调多个设备发送和接收所有副本的页。为了在模型执行器跨多个设备和进程复制的情况下,仍能使用单个Messenger和传输引擎,我们使用cuMem和cuMemImportFromShareableHandle分配支持KV Cache的设备内存,并将其映射到主进程。传输引擎会检查节点拓扑,以找到NIC和最近NUMA节点中的CPU,用于每个KV Cache切片的传输。
如果源和目标是相同分片的,传输过程简单,因为设备和页之间存在一对一映射。在此情况下,分片隐式地帮助了传输延迟:通过使用更多GPU,可以利用更多关联的NIC,从而接近满带宽利用率。然而,如果存在不匹配,传输引擎必须根据源和目标切片比率来拆分或重构页。
如果Prefill跨更多设备拆分KV Cache,它会通过从Prefill设备发送对应的半部,在Decode上重构完整页。如果Decode有更多分片,它将从多个源接收页。Decode需要知晓Prefill的分片方案,以计算预期接收的RDMA写操作数。如果涉及复制,Prefill会将设备分组为在其内复制完整KV Cache的副本集。目标副本集会被随机分配到源集之一,以使用所有可用设备启动RDMA写。
分片传输需要对KV Cache进行略微调整。默认情况下,FlashInfer依赖NHD布局,在页内按头排序Token。由于缓存最可能沿注意力头数进行分片,这会在头内创建不连续性。RDMA传输不隐式支持跨步写,需要每个头一个操作来执行传输。相反,为减少与libfabric的交互次数,我们使用HND布局组织KV Cache,将头维度置于Token数之前。这确保了连续性,允许单个写操作复制页。
推测解码
推测解码需要对解耦Prefill-Decode进行略微调整。在我们的实现中,Prefill节点不允许采样Token。由于Perplexity的Sonar模型支持结构化输出,我们不想在Prefill和Decode之间同步模式处理器实现的复杂性。在MTP和推测解码机制中,预填充草稿模型至最后一个Token涉及从目标模型采样Token。
为规避这些问题,Prefill不包括输入序列的最后一个Token。相反,它传输Prefill中最后一个Token前的隐藏状态或logits,并在Decode的下一步中将其视为Decode Token。虽然这略微增加了延迟,因为Prefill后必须执行完整的Decode步骤才能发出第一个Token,但实现复杂性大大降低。
解耦部署
我们已部署或实验了多种解耦配置,使用不同的模型,支持生产流量或内部评估工作负载。根据模型大小和注意力机制,我们为Prefill和Decode节点选择合适的分片方案,以最佳利用GPU。
DeepSeek-R1
对于DeepSeek,我们考虑张量并行(TP)和数据并行(DP)部署。如先前博客所述,TP部署提供更好的延迟,但代价是较低的吞吐量,需要更多GPU服务重负载。DP部署随负载扩展性更好,但由于设备间或节点间的通信成本,其峰值吞吐量较低。
DeepSeek依赖多头潜伏注意力(Multi-Head Latent Attention),它会压缩KV Cache。由于所有KV头都压缩为单个潜伏向量,TP无法对KV Cache进行分片,而必须在所有rank上复制潜伏向量。分片发生在解压缩之后,因为每个rank可从相同的潜伏表示中提取不同的头。因此,Prefill和Decode的分片之间,所有KV Cache分片均相同。
在节点内TP设置中,Prefill和Decode是相同分片的。传输从所有rank分发,以充分利用所有可用的NIC。然而,在DP部署中,TP rank大小较低或每个DP rank被分配到单个GPU时,任何持有KV Cache复制副本的Prefill设备均可分发它。为平衡所有可用NIC上的请求,我们随机选择GPU和NIC,从Prefill向Decode发送KV Cache。
在混合Prefill-Decode中,我们的R1部署难以持续超过50 TPS,原因是数百毫秒的频繁Prefill中断。相反,通过分离Prefill,我们为每个请求的TTFT增加了约100ms的罚时,但单个Prefill节点可在3个Decode节点上维持一致批次大小,并在每个Decode节点约1 QPS负载下提供超过90 TPS的吞吐量。在数据并行部署中,TPS略低于50,但实例可处理每个rank 1 QPS的负载,一个节点有8个rank。
Qwen3-Coder
此480B模型使用分组查询注意力(GQA),因此注意力易于分片,并可在不牺牲KV Cache内存的情况下受益于张量并行。因此,我们可将模型跨8个GPU分片用于Prefill和Decode,将约3个Decode节点与单个Prefill节点配对。由于注意力分片,我们依赖HND KV Cache布局来分片Prefill和Decode KV Cache,将Prefill rank与Decode rank配对,并充分利用所有NIC并行传输切片。
来源:https://research.perplexity.ai/articles/disaggregated-prefill-and-decode
更多交流,可加本人微信
(请附中文姓名/公司/关注领域)

