
一张A100的算力接近千T级别可你在上面跑一个7B模型每秒生成的token数常常只有二三十个。更离谱的是打开nvidia-smi看GPU利用率很多时候连20%都没到。这个现象我一开始怎么都想不通明明算力这么强为什么我花钱买的显卡在“摸鱼”直到我开始系统学习大模型推理的加速技术把量化、投机采样、PD分离这三样东西彻底搞明白才意识到大模型推理慢问题的根源压根不在“算力不够”而是“数据搬运的速度跟不上”。这篇文章是我把这块重新啃了一遍之后的学习笔记加实测经验适合正在做大模型部署、调优推理引擎、或者被线上token/s指标折磨的工程师。我会尽量把每个技术背后的“为什么”讲明白再给出可以直接上手的配置思路和避坑经验。1. 先搞清楚推理慢在哪权重搬运比矩阵乘法更花钱很多人在优化大模型推理的时候第一反应是“模型太大、计算量太大”于是拼命加GPU、堆算力。但真实情况恰恰相反。在推理的decode阶段GPU的算力利用率经常低得可怜瓶颈根本不在FLOPs而在显存带宽——也就是GPU从HBM里面把权重搬到计算单元的速度。1.1 从prefill到decode两种负载的“性格”完全不同大模型在线推理的核心流程拆开看其实只有两个阶段。第一个阶段是prefill预填充用户把一整段prompt丢进来模型一次性并行处理所有输入token计算每个位置上的隐藏状态和KV cache。这个阶段是典型的计算密集型输入token越多矩阵乘法越大GPU的tensor core能跑得满满当当。第二个阶段是decode解码模型每生成一个token都要把这个token拼回输入序列然后重新跑一次前向传播。注意这次前向传播只为了算“下一个token”的概率分布而参与计算的序列长度比prefill阶段长得多。也就是说decode阶段每前向一次都要把整个模型的权重从显存搬到计算单元里过一遍但真正要算的新数据只有一个位置。所以decode阶段本质上是带宽密集型负载。GPU算力再强大部分时间都花在“等数据从显存送到寄存器”上了。1.2 算一笔账光重复读权重就要花掉7毫秒我们用7B模型、FP16精度来算一笔账。7B参数乘以每个参数2字节权重一共是14GB。A100 80G的HBM带宽大约是2TB/s。那么光是把这14GB权重完整读一遍就需要14GB / 2TB/s ≈ 7ms这意味着即便GPU完全不计算decode阶段每生成一个token光“读权重”这个动作就已经花了7毫秒。实测里7B模型在A100上大概每秒生成20到40个token算下来每token耗时25到50毫秒权重搬运占了大头。反过来看prefill为什么快因为prefill一次性算几百上千个token同样一次读权重的开销被摊到了很多token头上计算密度高GPU利用率自然就上去了。这个认知是整个推理加速的基石**decode阶段谁能让“每次前向要读的权重字节数”变少谁就能直接提升token/s谁能让“串行前向的次数”变少谁也能提升token/s。**量化和投机采样恰好分别击中这两个点。2. 量化提速的底层逻辑把一次前向要读的字节数砍到原来的四分之一量化的思路说白了很简单模型权重本来是用FP16存储的每个参数占2字节。如果能用INT8存就变成1字节用INT4存就是0.5字节。7B模型的权重从14GB降到3.5GB同样2TB/s的带宽读一遍权重只需要1.75ms。decode的token/s理论上能翻近一倍甚至更多。2.1 量化的基本公式与三种粒度量化的本质是把连续的高精度浮点数映射到离散的低精度整数。最常见的是对称量化r_quant round(r_float / scale)其中scale是缩放因子通常由权重或激活值的绝对最大值决定。更完整的形式还会带上zero_point做零点对齐用于非对称量化。关键在scale怎么算、按什么粒度算。常见的粒度有三档粒度说明精度损失实现复杂度per-tensor整个矩阵共用一个scale最大最低per-channel每个输出通道一个scale中等中per-group每128个元素一组一个scale较小较高现在主流的GPTQ、AWQ基本都做到了per-group粒度。group越小量化误差越小但也需要更多的元数据存储和计算开销所以实际中128是一个常用的折中。2.2 权重量化与激活量化难度差了一个量级这里有个新手最容易踩的坑把“量化”当成一锤子买卖。权重量化只动模型的参数不动计算过程中的激活值。推理时依然用FP16算激活只是把权重反量化回来再算。这种做法实现简单不需要重新训练用一小部分校准数据就能搞定GPTQ和AWQ都属于这一类。激活量化要复杂得多。它要求计算过程中的中间激活也变成INT8或INT4这样才能真正用到低精度矩阵乘法的加速tensor core。问题在于激活值的分布远不如权重稳定尤其在attention层会出现明显的离群值直接量化会把信息打没。SmoothQuant这类方法的思路是先把激活里的离群值“平滑”到权重侧再统一量化实现W8A8。从实际收益看如果只做权重量化W8A16或W4A16主要省的是显存和带宽如果做权重激活量化W8A8才能吃到低精度矩阵乘法带来的计算加速。2.3 我踩过的坑敏感层、校准数据与评估方法我最早自己用GPTQ量化一个7B模型跑通用对话感觉还行但一跑代码生成和数学推理输出质量明显下滑。排查下来有两个原因。第一校准数据集和业务场景偏差太大。GPTQ需要一小批文本去统计权重误差补偿如果校准数据是维基百科/新闻而业务是代码量化会把代码相关的分布压坏。后来换成业务相关的代码语料做校准质量立刻回来不少。第二敏感层没有保护。attention输出投影和FFN的某些层对量化特别敏感强行压到4bit会放大误差。现在很多方案支持混合精度比如“大部分层用INT4、少数敏感层保留FP16”。我实测在代码生成任务上只保护最后2层质量损失就明显减小速度损失却很小。所以量化的正确打开方式不是“无脑全量化”而是先跑一份业务baseline量化后再跑同样的用例逐层对比找出质量崩塌的层再决定哪些层要豁免。3. 投机采样让大模型从“逐个写”变成“批改作业”量化解决的是“每次前向读多少字节”的问题。投机采样解决的则是另一个问题“能不能减少串行前向的次数”。大模型decode的痛点在于每生成一个token都必须完整跑一次前向而前向是串行的第N个token没算出来之前第N1个token根本没法开始。那有没有可能一次前向算出好几个token从数学上讲做不到精确并行但可以“猜”。3.1 整体流程拆解草稿、验证、接受与拒绝投机采样的流程分四步。第一步用一个很小的草稿模型draft model快速生成K个token的候选序列。草稿模型一般只有几亿到十几亿参数每步生成只要几毫秒。第二步把草稿序列拼接进原有上下文交给大模型target model做一次并行前向。注意这里是一次前向同时算出K个位置的logits等价于一次prefill而不是K次decode。第三步逐个位置做接受/拒绝判定。规则大概是如果大模型对草稿token的置信度比草稿模型更高就直接接受如果更低则以一定概率接受拒绝后从大模型的分布里重新采样一个token补上。第四步把接受的部分当作正式输出然后重新从草稿模型开始下一轮。关键点在于一次大模型前向如果运气好能“顺带确认”多个草稿token这就把原本K次串行decode压缩成了一步并行前向加K步草稿前向。3.2 接受率与期望收益一个简单的公式投机采样能不能加速完全取决于草稿模型和大模型分布的一致程度数学上用一个接受率α来表示。如果每个位置接受概率都是α那一次大模型前向期望接受的token数是E 1 α α² ... α^K (1 - α^(K 1)) / (1 - α)我拿一个常见配置算一笔账。假设target模型每步40ms草稿模型每步5ms草稿长度K6。接受率α期望接受token数一轮总耗时草稿6步大模型1步等效每token耗时对比基线40ms0.4约1.666×5 40 70ms约42ms无加速甚至更慢0.6约2.4370ms约29ms约1.4倍0.8约3.7570ms约19ms约2.1倍0.9约5.2270ms约13ms约3倍这个表说明一个残酷事实**投机采样不是无脑加速接受率上不去反而可能拖慢。**α低于0.5时草稿模型的成本甚至抵消不了收益。所以草稿模型的质量才是整个方案的关键变量。3.3 草稿模型选型与参数对齐的实操建议草稿模型怎么选呢有一个必要条件草稿模型和target模型的tokenizer必须一致否则产生的是乱码token接受率直接崩盘。比较经典的搭配是Llama-2 7B配一个68M的草稿模型社区里跑下来的效果普遍不错。现在的趋势是用EAGLE这类方法直接复用target模型倒数第二层的hidden state来训练草稿头接受率能比随机小模型高不少。还有一个特别容易被忽略的细节采样参数必须严格对齐。草稿模型和大模型推理时的temperature、top_p、top_k、repetition penalty都要保持一致差别一丁点都会让分布偏差变大接受率骤降。我一开始只对齐了temperature忘对齐top_p结果接受率掉了十几个百分点排查半天才发现。vLLM里开投机采样很简单大致是这个形式vllm serve meta-llama/Llama-2-7b-chat-hf \ --speculative-model JackFram/llama-68m \ --num-speculative-tokens 6不同vLLM版本参数名可能略有差异但核心就三个草稿模型路径、草稿token数K、是否开启。K一般取4到8太大会让草稿环节拖长太小又吃不到并行红利。4. PD分离把Prefill和Decode拆到两拨GPU上各干各的量化解决带宽投机采样解决串行PD分离解决的是更上层的问题两种负载混在一起互相拖累。4.1 为什么混在一起会互相拖累传统的GPU推理引擎里prefill和decode请求是在同一批GPU上混跑调度的。问题很明显。第一prefill和decode的“性格”完全不同一个是计算密集型一个是带宽密集型。把它们放在同一张卡上无论调度策略怎么设计总有一方在受罪。prefill大请求进来时会抢走大量显存带宽和SM资源正在跑的decode请求就会被卡住用户的TPOT单token生成时间瞬间飙升流式输出的体验变得一卡一卡的。第二长prompt的prefill会长时间独占GPU。比如一个10000 token的prompt进到引擎prefill可能持续几百毫秒甚至更久期间其他用户的decode都在排队。这就是为什么线上服务偶尔会出现“所有人一起卡顿”的灵异现象。4.2 分离后带来的三个直接收益PD分离的做法是把prefill阶段和decode阶段放到不同的GPU池子上。prefill节点拿到prompt算完KV cache把KV cache和中间结果传给decode节点decode节点接着从KV cache开始逐token生成。这件事有三个直接收益。一是两个池子可以独立扩缩容。prefill节点可以用A100/H100这种算力怪物decode节点可以用带宽充裕的卡或者根据在线并发量动态调整decode节点数量。二是用户侧的流式体验更稳定。decode节点不再被突如其来的prefill打断TPOT的抖动大幅度减小输出速度变得平稳。三是每类节点都可以做针对性优化。prefill节点基本无需投机采样把张量并行做满就行decode节点则可以叠加量化和投机采样把带宽瓶颈压到极致。有意思的是PD分离做得越彻底KV cache的“跨节点搬家”就越重要。一个8B模型、GQA策略下每个token的KV cache大约128KB到512KB。4K上下文长度的请求KV cache就能到几百MB甚至上GB级别。要在节点间传输这么大块的数据没有高速网络和高效的KV cache序列化格式纯属白折腾。4.3 落地需要付出什么KV cache传输与调度复杂度PD分离不是免费的午餐它把原本在单机内部解决的问题推到了集群层面。KV cache传输就是最大的新开销。现在的开源方案里有把KV cache统一成标准序列化格式来绕开模型结构差异的比如NVIDIA的NIXL也有直接在框架层做管理的比如vLLM加LMCache的组合或者Moonshot开源的Mooncake方案。这些工具的思路都是把KV cache当成一等公民来管理缓存、压缩、异步搬运、按请求路由。另外prefill节点和decode节点之间需要一套调度和队列逻辑。请求要先到prefill池算完KV cache再被路由到decode池的某张卡上。这个路由如果做得糙比如只按轮询很容易出现部分decode节点排队、部分闲置的情况需要按显存余量、token进度做细粒度调度。对于没有大流量的团队我的建议是先别直接上全套PD分离而是用chunked prefill分块预填充做轻量替代把一个长prompt切成多个块依次处理避免单个长prefill长期独占GPU。效果上没有完全分离那么强但架构改动小得多。5. 三件套怎么组合我的选型优先级与实测参考很多人学完这三个技术会问那我到底该上哪个我的答案是先看业务到底痛在哪再决定优先级。5.1 三个技术各自的“攻击面”用一张表把三个技术的机制和适用场景理清楚技术主要解决的瓶颈核心机制典型收益主要代价量化显存带宽压力降低每次前向读取权重的字节数token/s提升、显存占用下降、吞吐量提升精度损失需要校准和保护敏感层投机采样串行decode次数用草稿模型大模型一次并行验证减少前向次数TPOT显著下降单用户体验提升需要额外草稿模型接受率不稳定时可能负优化PD分离混跑时的资源争抢prefill和decode分池调度、独立扩缩容吞吐量提升、TPOT抖动减小KV cache跨节点传输、架构复杂度高这三个技术不是互斥的而是打在三个不同的瓶颈上。量化降低单次前向的成本投机采样减少前向的次数PD分离让整个引擎的调度不再互相拖累。5.2 不同场景的优先级建议如果你是在单卡上做个人部署或小团队内部服务优先级很明确先把量化跑通。INT4量化之后7B模型在单卡上的显存占用从14GB左右降到4GB到5GB很多之前跑不动的场景直接就跑起来了速度也有实打实的提升。之后再评估上不上投机采样前提是你找得到tokenizer兼容的草稿模型并且能接受额外的调度复杂度。如果你是在做企业级在线服务流量大、并发高、对TPOT稳定性有要求那PD分离反而是第一个该考虑的。它决定了整个系统的底座能不能水平扩展。底座稳定之后再在decode节点上叠加量化和投机采样把每一路请求的速度榨到极限。5.3 我的最终推荐先量化再采样最后做架构我个人的调优顺序是固定的先量化再投机采样最后才做PD分离。原因很简单量化的收益最确定只要精度损失可控基本是稳赚不赔投机采样的收益取决于接受率必须实测验证但配置成本低可以快速试PD分离收益上限最高但涉及整个集群的改造应该放在最后动刀。每一步做完都要用三个指标验证TTFT首token延迟、TPOT每token生成时间、整体吞吐量。如果TTFT高问题大概率在prefill侧优先考虑chunked prefill或PD分离如果TPOT高优先考虑量化和投机采样如果单路都正常但整体吞吐上不去那就是调度和资源池的问题了。另外想多说一句学习上的体会。大模型推理加速这门课最忌讳的是上来就背各种框架参数。只要把“decode阶段带宽瓶颈”这条主线吃透量化、投机采样、PD分离甚至后面那些新出的加速手段本质上都是在围绕这条主线做文章要么少读点数据要么少跑几趟要么把路分开走。搞懂这一点看到任何新的加速方案你都能很快判断出它到底在解决哪个环节的什么问题。