资讯详情

资讯详情

大模型推理Prefill与Decode分离:KV Cache优化与参数调优实战

1. 从一次线上推理抖动说起为什么Prefill和Decode必须拆开看前段时间帮一个团队排查他们自研推理服务的延迟毛刺问题现象很典型P99延迟时不时飙到两三秒但平均延迟看着还行GPU利用率却始终上不去卡在40%上下晃悠。他们一开始怀疑是显存不够、模型太大甚至动了换卡换机器的念头。我让他们把请求按阶段拆开打点结果一目了然——问题根本不在算力而在于Prefill和Decode这两个阶段的资源争抢。这个场景其实在推理系统里非常普遍。大模型推理从计算特征上天然分成两个阶段**Prefill预填充**负责把用户输入的整段prompt一次性喂进模型算出所有输入token的KV并生成第一个输出token**Decode解码**则是自回归地一个token一个token往外吐。前者是计算密集型矩阵乘法规模大、并行度高能把GPU的算力吃满后者是访存密集型每生成一个token都要把整个KV Cache读一遍算力利用率极低但显存带宽压力巨大。把这两个阶段混在一个批次里跑就像让短跑运动员和马拉松选手同场竞技——Prefill的长序列会拖慢Decode的响应Decode的频繁小批次又会让Prefill的算力利用率下降。所以现在主流的推理引擎无论是哪家的方案核心优化方向都是PD分离Prefill-Decode Disaggregation加上KV Cache精细化管理。这篇笔记就把我这两年在几个项目里踩过的坑、调过的参数、验证过的方案系统地梳理一遍。适合读这篇的人正在做推理服务部署、被延迟和吞吐折磨的工程师想搞明白KV Cache到底怎么占显存、怎么优化的同学以及准备做PD分离架构选型的技术负责人。我会尽量把原理讲透同时给出可以直接抄的参数和排查思路。2. Prefill与Decode的本质差异先搞清楚在优化什么2.1 两个阶段的计算特征对比要调优先得把这两个阶段的脾气摸清楚。我用一个具体的例子来说明假设模型是70B参数、FP16精度输入prompt长度2048输出长度512batch size为1。Prefill阶段要处理2048个token每个token都要和所有历史token做注意力计算。这个阶段的矩阵乘法维度大GPU的Tensor Core能跑满计算时间大致和序列长度的平方成正比注意力部分加上和序列长度成正比的部分FFN等。实测下来70B模型处理2048长度prompt在单张高端卡上大概几百毫秒。Decode阶段每次只处理1个新token但它需要读取之前所有token的KV Cache。这时候计算量很小瓶颈在显存带宽——要把几十GB的KV Cache从显存里读出来参与注意力计算。每生成一个token的时间基本恒定主要取决于KV Cache的大小和显存带宽。维度PrefillDecode计算特征计算密集型访存密集型并行度高整段并行低逐token串行算力利用率高可达70%低常低于10%显存带宽压力中等极高延迟敏感度影响首token时间TTFT影响每token时间TPOT批次策略大batch提升吞吐小batch降低延迟这张表是我自己实测总结的不同模型和硬件会有差异但趋势是一致的。理解了这个差异后面所有的优化手段就都有了解释的根基。2.2 为什么混跑会导致互相拖累很多人会问既然Prefill算力利用率高、Decode算力利用率低那把它们放一起不正好让Decode填空、Prefill吃满吗理论上很美实际很骨感。问题出在调度粒度上。当一个批次里同时有Prefill请求和Decode请求时调度器必须等整个批次的所有请求都完成才能进入下一步。Prefill请求处理时间长Decode请求就得干等而Decode请求每步都要执行Prefill请求又被迫频繁插入。结果就是两边的延迟都被拉高而且GPU的流水线频繁被打断利用率反而下降。更麻烦的是显存碎片。Prefill阶段需要为长prompt分配大块连续的KV CacheDecode阶段则是持续的小块增长。混在一起时显存分配器很难高效管理容易出现明明有空间却分配不出来的碎片问题。我见过一个案例显存利用率显示还有15GB空闲但就是无法接收新请求最后发现是碎片导致的。提示如果你的服务同时有长prompt和长输出需求混跑几乎必然导致延迟抖动。这时候优先考虑PD分离而不是去调batch size。2.3 PD分离的核心思路PD分离的本质是让专业的资源干专业的事。把Prefill和Decode放到不同的实例组上各自用最适合的批次策略和并行配置。Prefill实例组可以配大batch、高并行度追求吞吐Decode实例组配小batch、优化显存带宽追求低延迟。两者之间通过KV Cache的传输来衔接——Prefill算完KV后把KV传给Decode实例Decode接着往下生成。这个架构的代价是KV传输开销。KV Cache动辄几十GB跨实例传输需要高速网络。所以PD分离是否划算取决于你的网络带宽和请求特征。如果prompt很短、输出也很短传输开销可能超过收益如果是长prompt长输出的场景收益非常明显。3. KV Cache的显存账本算清楚才能调得动3.1 KV Cache到底占多少显存这是最基础也最容易被忽略的问题。很多人部署时发现显存不够第一反应是模型太大其实KV Cache可能才是大头。KV Cache的大小计算公式KV Cache大小 2 × 层数 × 注意力头数 × 头维度 × 序列长度 × batch_size × 精度字节数以70B模型为例假设80层、64个注意力头、头维度128、FP16精度2字节单个token的KV Cache 2 × 80 × 64 × 128 × 2 2.6 MB如果序列长度2048、batch size 16那么KV Cache总量 2.6MB × 2048 × 16 ≈ 85 GB这个数字相当惊人。模型权重本身FP16也就140GB左右KV Cache在长序列大batch下能占到显存的很大比例。所以KV Cache优化不是锦上添花而是决定服务能不能跑起来的关键。3.2 PagedAttention为什么有效传统KV Cache分配是连续的每个请求预留最大序列长度的空间。这导致两个问题一是内部碎片实际用了500长度却占了2048的空间二是外部碎片请求来来去去后显存被切得七零八落。PagedAttention的思路借鉴了操作系统的虚拟内存分页。把KV Cache切成固定大小的block比如16个token一块用页表来管理逻辑块到物理块的映射。这样请求的KV Cache不需要连续按需分配block碎片问题基本消除。实测下来PagedAttention能把显存利用率从60%左右提升到90%以上意味着同样的显存能支撑更大的batch或更长的序列。这是目前几乎所有主流推理引擎的标配。3.3 量化KV Cache的收益与代价KV Cache量化是另一个大杀器。把FP16的KV降到INT8甚至INT4显存直接减半或减到四分之一。但这里有个坑KV Cache量化对精度的影响比权重量化更敏感。因为KV会参与注意力计算量化误差会被放大。我实测过INT8量化在大多数场景下精度损失可接受困惑度上升不到1%但INT4就需要谨慎某些任务上会出现明显的质量下降。量化方案显存节省精度影响适用场景FP16基准无精度要求极高INT850%轻微大多数生产场景INT475%明显显存极度受限、可接受质量下降FP850%轻微支持FP8的硬件注意KV Cache量化要和权重量化分开评估。有些团队权重用了INT4KV还用FP16结果显存还是不够。两个都要看。4. PD分离架构的落地细节从设计到部署4.1 实例配比怎么定PD分离后Prefill实例和Decode实例的数量配比是个关键决策。配比失衡会导致一方成为瓶颈。我的经验算法是这样的先测出单个Prefill实例的吞吐每秒能处理多少prompt token和单个Decode实例的吞吐每秒能生成多少output token。然后根据业务的输入输出比例来算。假设业务平均输入2048 token、输出512 token比例4:1。如果单Prefill实例每秒处理10000 prompt token单Decode实例每秒生成2000 output token那么处理一个请求Prefill需要0.2秒Decode需要0.256秒。为了平衡Prefill和Decode实例数比大约是1:1.3。实际部署时还要留余量因为流量是波动的。我一般会按峰值流量的1.5倍来配置然后通过自动扩缩容来应对日常波动。4.2 KV Cache传输的优化PD分离后Prefill算完的KV要传给Decode。这个传输如果没优化好会成为新的瓶颈。传输优化的几个方向压缩传输KV在传输前做量化压缩到Decode端再解压。INT8压缩能把传输量减半网络压力大幅降低。分层传输不是等所有层的KV都算完才传而是算完一层传一层流水线化。这样Decode能更早开始工作降低首token延迟。就近调度调度器尽量把同一个请求的Prefill和Decode分配到网络距离近的实例上减少传输延迟。我实测过不做任何优化的情况下70B模型2048长度prompt的KV传输要几百毫秒比Prefill本身还慢。加上压缩和分层传输后能压到几十毫秒基本可接受。4.3 调度器的设计要点PD分离的调度器比单体架构复杂得多要同时考虑Prefill队列、Decode队列、KV传输队列的状态。核心逻辑是请求进来先进Prefill队列Prefill完成后KV进入传输队列传输完成后进入Decode队列。调度器要保证各队列不积压同时尽量让同一个请求的Prefill和Decode在时间上衔接紧密。一个容易忽略的点是超时处理。如果KV传输失败或Decode实例挂了请求要有重试或降级机制。我在一个项目里就遇到过Decode实例OOM导致请求卡死的情况后来加了超时重试才稳定。5. 参数调优实战那些文档不会告诉你的细节5.1 batch size不是越大越好很多人以为batch size越大吞吐越高在Decode阶段这不一定成立。Decode阶段每个token都要读一遍KV Cachebatch size增大意味着KV Cache读取量线性增长。当batch size超过某个阈值后显存带宽成为瓶颈吞吐不再提升反而因为单批次处理时间变长导致延迟上升。我实测的经验值是Decode阶段的batch size控制在8到32之间比较合适具体取决于模型大小和显存带宽。可以通过压测找到拐点——吞吐不再随batch size增长的那个点。Prefill阶段则相反batch size可以开得比较大因为计算密集大batch能充分利用算力。但也要注意显存限制Prefill的KV Cache是瞬时分配的batch太大会OOM。5.2 块大小block size的选择PagedAttention的block size是个需要调的参数。block太小页表开销大管理复杂block太大内部碎片增加。常见的选择是16或32。我一般先用16如果发现页表管理开销明显比如block数量过多导致调度变慢再调到32。这个参数对性能的影响不是特别大但值得花十分钟测一下。5.3 抢占与重计算的权衡当显存不够时推理引擎需要决定是抢占把某些请求的KV换出到CPU内存还是重计算丢弃KV需要时重新算。抢占的代价是PCIe传输重计算的代价是算力。一般来说如果CPU内存充足且PCIe带宽高抢占更划算如果算力有富余重计算更简单。我的建议是优先用抢占但要设置好换出阈值。不要等到显存完全满了才换出留10%到20%的余量避免频繁换入换出导致的抖动。6. 常见问题排查速查表调优过程中遇到的问题五花八门我把高频问题整理成表方便快速定位。现象可能原因排查方向解决思路TTFT高但TPOT正常Prefill排队或算力不足看Prefill队列长度和GPU利用率增加Prefill实例或优化batchTPOT高但TTFT正常Decode显存带宽瓶颈看显存带宽利用率和batch size减小batch或量化KV Cache延迟周期性抖动显存碎片或换入换出看显存分配日志调block size或预留余量吞吐上不去批次策略不合理看各阶段利用率PD分离或调batchOOM但显存显示有空闲碎片或预留不足看最大连续空闲块重启服务或调分配策略KV传输慢网络带宽或未压缩看传输耗时占比开启压缩和分层传输这张表是我从多次排查中总结的基本覆盖了80%的常见问题。遇到新问题时先对照这张表缩小范围再深入分析。实操心得排查延迟问题一定要分阶段打点。TTFT首token时间和TPOT每token时间要分开监控否则根本定位不到是Prefill还是Decode的问题。我见过太多团队只监控端到端延迟结果排查时两眼一抹黑。7. 几个容易踩的坑和我的应对经验第一个坑是盲目追求PD分离。PD分离不是银弹它引入了KV传输开销和架构复杂度。如果业务是短prompt短输出或者流量很小单体架构反而更简单高效。我一般建议先压测单体架构确认瓶颈确实在PD争抢上再考虑分离。第二个坑是KV Cache量化一刀切。不同层对量化的敏感度不一样有些层量化后精度掉得厉害。可以做分层量化敏感层保持FP16其他层量化。这个需要一些实验但收益明显。第三个坑是忽略CPU内存。KV Cache换出到CPU内存时如果CPU内存不够会触发swap性能直接崩掉。部署时要确保CPU内存至少能容纳峰值时换出的KV量。第四个坑是调度器没有背压机制。请求大量涌入时如果调度器无限制接收会导致队列爆炸、延迟飙升。要有背压队列满了就拒绝或降级保护系统稳定。最后分享一个我常用的压测方法用不同长度的prompt和输出组合画一张二维的性能热力图。横轴prompt长度纵轴输出长度颜色表示延迟或吞吐。这张图能直观看出系统在什么区间表现好、什么区间会崩对容量规划和参数调优非常有帮助。这套东西我在几个项目里反复验证过从单体到PD分离从FP16到INT8 KV每一步都有明确的收益和代价。核心还是那句话先搞清楚在优化什么再动手调参数。盲目调参不如先把账算清楚。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →