上篇把GPT类OSS模型的私有化部署流程跑通后后台问得最多的问题出奇一致服务是起来了输出速度却让人血压拉满。单条请求的流式吐出像老式打印机batch一批就OOMGPU利用率只有个位数手里显卡不差跑起来却跟CPU差不多。如果你也卡在这一步这篇就是为你准备的。上篇解决“能跑”这篇解决“跑得快”。我会从推理性能的计算模型讲起拆解链路中的每一处瓶颈再给出我实际验证过的组合优化方案——连续批处理、分页注意力、权重量化、投机采样、多卡张量并行依次叠加最终把单机吞吐从几十tokens/s拉到几千甚至上万tokens/s。整个过程按实操顺序展开每一步都会给出预期收益和参数建议方便直接复现。先强调一个概念标题里的“100倍”不是单次请求延迟降低100倍而是真实服务场景下多用户并发请求的整体推理吞吐量提升100倍。这两个指标差别巨大后面我会用具体数据说明。如果你是想优化单条请求的响应速度重点看第3章的投机采样和第5章的并发配置如果你关心的是系统整体能力请从第2章开始完整走一遍。1. 先搞清楚推理性能的三个瓶颈1.1 卡在显存带宽而不是算力很多人以为大模型推理慢是GPU算力不够。实测下来大多数场景的瓶颈在显存带宽。为什么这么说看GPT类模型生成token的过程就明白自回归解码每一步只生成一个token然后把新token拼进序列再做一次完整前向计算。这个计算中权重矩阵和KV Cache要从显存搬到计算单元数据搬运量极大实际浮点运算量却很小。对7B模型FP16精度一次前向要读取约14GB权重但只做了很少的矩阵乘法。GPU在这期间大部分时间处于“等数据”状态算力空转。显存带宽决定了这一步的上限换更高算力的卡不一定有用。生活化类比算力是厨房的灶台显存带宽是传菜通道。菜权重从仓库显存一盘盘搬到灶台计算单元需要时间。灶台再快通道拥堵也白搭。优化方向自然就明确少搬、搬精、尽量一次搬多份。1.2 静态批处理让GPU空转第二个瓶颈是调度方式。直接把基础框架实现套成服务用的是静态批处理把一批请求捆绑在一起等这一批里最慢的请求生成完毕整批释放。在线场景请求到达时间错开先来的请求要等后来的请求计算单元在等待期间闲置。Batch里还要预留最坏情况的显存导致batch size上不去吞吐自然上不去。第三个瓶颈是显存碎片。KV Cache是连续分配的临时缓冲静态分配容易碎片化显存明明还有余量却找不到足够大的连续块新请求进不来。并发低时是小问题并发一高直接封顶。1.3 我的优化顺序与预期收益把瓶颈拆开优化顺序也就定下来了。第一步连续批处理加分页注意力解决调度和碎片这一步通常带来10到20倍吞吐提升第二步权重量化减少带宽搬运量再翻2到4倍第三步KV Cache量化和投机采样降低显存占用、减少生成步数延迟降一半以上第四步多卡扩展解决单卡算力上限。整体叠加100倍不是口号是保守估计。顺序不能乱。先解决调度再谈量化存在逻辑关系量化减少的是“每个token搬运权重的字节数”但如果GPU本身一直在空转省下来的带宽也没活干。把批处理做起来之后GPU忙着带宽减少才直接转化为吞吐。2. 连续批处理与分页注意力吞吐量翻倍的第一步2.1 连续批处理的原理与配置传统服务框架的静态批处理类似“包桌上菜”一桌客人一个batch必须全部吃完才能翻台。连续批处理是“翻台模式”哪个客人放下筷子立刻收拾新客人随时入座。每一步解码时系统动态决定哪些请求继续生成、哪些已完成释放、哪些新请求加入。GPU在每一轮都满负荷干活没有等待。实现在主流推理框架中已经是标配。核心参数是max_num_seqs和max_num_batched_tokens。前者限制同一时刻处理的请求数量也就是batch大小后者限制每个调度周期内喂给GPU的token总数防止单步计算时间过长导致延迟抖动。我习惯从max_num_seqs128、max_num_batched_tokens4096起步然后观察两个指标GPU利用率上不去就加大max_num_seqs单步延迟变大就减小max_num_batched_tokens。这是一个反复试探的过程每台机器的最优值不一样。另一个容易忽略的点是连续批处理对请求到达模式很敏感。如果请求全是同时到达的突发流量它的优势会被冲淡如果请求是错峰到达的常态流量优势会非常明显。所以做压力测试时不要只测“满并发同时发请求”要用接近真实生产的到达模型去测否则会低估这个特性的价值。2.2 分页注意力消灭显存碎片连续批处理解开了调度束缚显存物理限制还在。KV Cache随生成token数动态增长每个请求要为32层模型保存大量K/V向量。如果每个请求预分配一段连续显存当多个请求长度差异大时碎片化严重大量显存被浪费。分页注意力把KV Cache切分成固定大小的块block每个块的物理地址不要求连续逻辑上通过页表映射。请求结束时立即释放块新请求动态申请。这个思路和操作系统虚拟内存几乎一模一样显存利用率可接近100%。配置上只需要关注块大小block size我常用128或256。块太小页表管理开销大块太大内部碎片多128对大多数场景都是均衡点。2.3 一段可以直接用的启动配置以下是在某主流开源推理框架中开启连续批处理与分页注意力的示例。请把模型路径替换成你自己的python -m xxx_engine.serve \ --model /data/models/your-7b-model \ --tokenizer /data/models/your-7b-model \ --tensor-parallel-size 1 \ --max-num-seqs 128 \ --max-num-batched-tokens 4096 \ --gpu-memory-utilization 0.90 \ --use-v2-block-manager \ --block-size 128--gpu-memory-utilization 0.90表示允许引擎用90%显存做模型权重和KV Cache缓存。生产环境如果请求最大长度很大建议降到0.85给突发长请求留缓冲。这套配置在实际项目中把单卡吞吐从约100 tokens/s单请求串行拉到2000 tokens/s以上batch从1涨到几十GPU利用率从个位数涨到70%到90%。这是整个100倍优化链条里收益最猛的一步。3. 权重量化与KV Cache优化把带宽用到极致3.1 先算清楚KV Cache吃了多少显存保持连续批处理满负荷运行下一个卡点是显存和带宽。先算KV Cache的账以7B模型为例32层、隐藏维度4096、使用分组查询注意力GQA且KV头数为8、每头维度128每个token每层需要保存2K和V乘8乘128等于2048个数值。FP16下每数值2字节单层每token约4KB32层约128KB。一个2048 token的请求KV Cache约256MB。听起来不多20个并发就是5GB单卡直接压力山大。长序列场景更恐怖。上下文从2048涨到8192KV Cache涨4倍。这解释了为什么很多部署在长上下文中batch只能降到个位数。KV Cache是并发上限的最大敌人优化它等于直接提升吞吐。3.2 KV Cache量化与GQA带来的红利缓解KV Cache的经典手法是GQA。相比传统多头注意力MHAGQA让多个查询头共享一组K/V头把KV Cache体积降为原来的1/N。如果模型本身是MHA结构优先考虑换用GQA变体这是结构层面的根本减负。在GQA基础上KV Cache还能做INT8或FP8量化。K/V矩阵数值分布相对稳定量化后精度损失很小显存占用直接减半。很多框架支持kv-cache-dtype参数开启后实测质量下降感知不到perplexity增幅小于1%并发上限却明显提高。优先做KV Cache量化因为它几乎无损、收益直接。实际调参时我会关注两个联动参数KV Cache量化精度和块大小。量化后单块占用的显存变小块大小可以适当调大减少页表管理开销。3.3 权重量化给带宽减负解码阶段的“显存带宽饥饿”主要来自权重矩阵的反复读取。把FP16权重变成INT8或INT4每次读取的数据量减半甚至减到四分之一。对同一个矩阵乘法来说计算量几乎不变但数据搬运时间大幅缩短。实际测试INT8让吞吐提升约2倍INT4再加约1.5到1.8倍。注意INT4的收益需要在连续批处理已经跑满的情况下才明显因为只有GPU在忙省下的带宽才被利用起来。量化操作本身要谨慎先跑INT8验证质量不掉点再上INT4。大多数框架支持按层指定量化精度输出层和注意力层的敏感度最高可以保留FP16中间层用INT4均衡质量与速度。我见过一个项目在4比特下质量整体还行但特定的代码生成任务崩溃最后就是靠“输出层回退FP16”修好的。这个细节很值得记下来。4. 多GPU并行与负载调度的正确姿势4.1 张量并行把一个模型切到多张卡当单卡显存放不下模型或者单卡算力成为瓶颈就需要多卡并行。大模型推理最常用的是张量并行。原理很简单把每一层的权重矩阵按维度切成多份每张GPU只存一部分前向计算时各自算一部分最后通过聚合通信汇总结果。张量并行在2到8卡范围内扩展性很好对Transformer结构的矩阵乘法尤其自然。配置方式是设置tensor-parallel-size参数例如两张卡设2。需要提醒的是张量并行对GPU间通信带宽非常敏感。用高速互联的卡2卡扩展到接近1.9倍如果只走普通总线通信开销会吃掉不少收益可能只有1.4到1.5倍。生产环境强烈建议用支持高速互联的GPU组合否则容易“加了卡反而更慢”。4.2 数据并行多副本换吞吐如果单卡能放下模型但并发请求太多更划算的做法是数据并行每张卡部署一个完整模型副本前面加负载均衡。这相当于把服务水平扩展吞吐量随卡数线性增加几乎没有通信开销。数据并行和张量并行可以组合比如2组张量并行乘2组数据并行适合模型较大、并发也高的场景。大多数推理框架支持同时指定tensor-parallel-size和数据并行副本数。我实际项目里常用“4卡等于2乘2”的组合2卡拆模型、2卡横向扩展比纯4卡张量并行更灵活单请求延迟略高但整体吞吐更高。4.3 流水线并行为什么不推荐流水线并行是按层切分GPU1算前16层GPU2算后16层层间传输中间结果。它的问题在于流水线气泡前一层没算完时后一层只能闲置。在线推理场景中气泡导致的延迟浪费很致命。一般来说除非显存实在装不下否则优先选张量并行而不是流水线并行。如果模型大到必须用流水线也要配合足够大的batch才能把气泡填上这更适合离线批量场景。5. 实操全流程从基线到百倍吞吐的完整操作5.1 第一步先测基线别信感觉优化前必须有基线数据否则后面每一步值不值得都说不清。我建议做三组测试单请求测试固定输入长度和输出长度测单请求延迟并发测试发20个并发请求测整体吞吐长序列测试输入2048加输出2048测显存占用。基线测试的记录格式建议记GPU型号、精度、batch、平均延迟、吞吐tokens/s、显存峰值。这些数据后面每一步优化后都要重测对比才直观。没有基线就优化的等于闭眼开车。5.2 第二步到第五步逐项叠加优化第二步启用连续批处理和分页注意力预期吞吐提升10到20倍。参考第2章的配置样例。开启后先跑通流程不要急着上量化确认GPU利用率上来了再往下走。第三步权重量化。先尝试INT8验证质量没有明显回落后再上INT4。实测INT8的加速倍率约1.8到2.2倍。量化后一定重新跑一遍离线评测集别只看速度不看质量。如果掉点明显回退到INT8或按层混合精度。第四步KV Cache量化和投机采样。这一步把并发上限再推高一截同时显著降低单请求延迟。KV Cache量化按第3章参数开投机采样的草稿模型选择参考下文。第五步多卡扩展。模型能单卡放就先用数据并行加并发模型放不下才上张量并行。按tensor-parallel-size2起步逐步加。每加一张卡都要重新测一次吞吐确认扩展效率没有崩。5.3 投机采样实操草稿模型与参数选择投机采样这块值得单独说。草稿模型一般选择比目标模型小一两个数量级的同族模型比如目标7B草稿选0.5B。配置类似--speculative-model /data/models/your-0.5b-draft \ --num-speculative-tokens 4num-speculative-tokens是每次预判几个token。太小加速有限太大接受率下降、浪费算力。我实测过4是一个甜点值8在部分模型上反而更慢。注意草稿模型本身不要量化保持FP16精度有助于提高接受率。我把草稿模型量化后接受率掉到40%整体收益反而变差。另外短请求场景比如只生成几十个token投机采样的收益会被草稿模型的前向开销吃掉这种场景可以考虑关闭。5.4 最终性能对比数据说话以7B模型为例在单卡环境下用我上面说的组合一套有代表性的数据如下配置阶段吞吐tokens/s平均token延迟ms/token备注基线无批处理、FP16、单请求30-8040-60GPU利用率低于10%加连续批处理分页注意力1500-250020-30并发32-64加INT8权重KV Cache量化3000-500018-25显存占用显著下降加投机采样3500-55008-12短序列收益小加2卡张量并行6500-90005-8需要高速互联从这个表能清楚看到100倍主要来自前两阶段的核心突破后面是锦上添花。这也是为什么我反复强调先解决调度和显存再谈量化和并行。数据来自我项目中的实际运行记录硬件不同会有浮动但数量级是稳的。6. 常见问题与排查技巧实录6.1 为什么开了连续批处理反而OOM这是出现频率最高的问题。原因通常是gpu-memory-utilization设置太高或者max_num_seqs与KV Cache大小不匹配。解决路径先把gpu-memory-utilization降到0.80把max_num_seqs减半确认请求的最大长度限制max-model-len是否合理它决定了每个请求KV Cache的上限。如果还OOM看是不是被草稿模型的KV Cache挤占了。投机采样时草稿模型也要显存别忽略。6.2 吞吐卡在某个数值上不去我遇到过max_num_seqs从128加到256吞吐纹丝不动的情况。用分析工具看瓶颈在显存带宽而不是算力。这种时候加并发没用改做量化或减小max_num_batched_tokens更有效。另一个常见原因是prefill处理输入阶段和decoding生成阶段在同一个GPU上互相干扰。如果长输入请求多prefill会周期性占满计算资源导致decoding抖动。解法是区分prefill和decode的调度优先级部分框架已经有相关开关或者把两类请求拆到不同实例上。6.3 投机采样加速不明显甚至变慢排查顺序先看草稿模型的接受率。如果低于50%大概率是草稿模型和目标模型差异太大。换一个更同源的草稿模型或者降低num-speculative-tokens。还要确认请求是否太短短序列上投机采样的前置成本收不回直接禁用这个功能就好。另外注意温度参数采样温度太高会让草稿模型的预判更难被接受投机采样在低温场景下效果更好。6.4 量化后生成质量异常权重量化后的质量波动主要集中在长尾生成场景。我的经验是用校准集重新跑一遍离线评测对比量化前后的困惑度或任务指标如果掉点超过1%考虑退回INT8或对敏感层如输出层、注意力层不做量化。大多数框架支持按层指定量化精度值得细调。注意校准集的选择要和你的真实业务场景接近用通用语料校准的模型在专业领域可能掉点更多。6.5 多卡并行时负载不均衡张量并行最常见的坑是某些卡显存占用明显高于其他卡这通常是因为激活值和KV Cache的分布不均也可能是框架版本的已知问题。排查方法看日志里每张卡的显存占用再检查是否有残留进程占用显存。框架层面可以尝试更细粒度的切分策略但这类问题多数是版本性的优先更新框架版本。最后分享几个我在实际项目中反复验证的心得。第一优化顺序不能乱批处理是地基量化是放大器投机采样是加速器。跳过批处理直接量化收益会大打折扣。第二不要照抄任何人的“标准参数”显存、带宽、卡间互联都不一样同样的参数在不同机器上可能差出一个数量级多跑基准测试让数据做决定。第三思维要跳出“单请求延迟”的框架服务化部署比拼的是整个系统的吞吐别为了单个请求的极致延迟牺牲整体并发能力。想清楚这三点你的私有化部署性能不会差到哪里去。
阅读完成 · 觉得有帮助?