
从去年开始我自己负责一个几十人的技术团队的模型服务部署一个 7B 级别的开源模型上线给内部工具用。第一个版本上线当天就被同事吐槽转圈圈太久了问一句话要等半天才看到第一个字出来。当时我对大模型推理加速的理解还停留在买更好的显卡这种层面后来才陆续把量化、投机采样、PD 分离这些技术一个一个啃下来每次优化都能看到实打实的数字变化。这篇文章不是教科书式的原理综述而是我学习和落地这三项加速技术的实践总结。大模型推理加速、量化、投机采样、PD 分离这几个词在网上一搜一大把但真正要落到自己的服务里中间有一堆文档里不会写的坑。文章适合刚接触模型部署的后端工程师、算法工程师也适合自己折腾本地大模型但卡在速度问题上的玩家读完你至少能知道自己的场景该上哪套方案、怎么测、怎么排查问题。1. 先看懂推理瓶颈算力、访存和生成节奏1.1 两阶段模型Prefill 与 Decode大模型生成文本的过程不是像打字一样一个字符一个字符地蹦出来那么简单。从引擎视角看一次完整的生成被切成两个节奏完全不同的阶段。第一个阶段叫Prefill预填充。你把一长段 prompt 丢给模型模型要把这整段输入的每个 token 都过一遍神经网络算出每个位置的中间状态然后把这些状态缓存下来。这个阶段的特点是并行度极高。因为同一个输入序列里的 token 之间互不依赖至少在 Attention 的 mask 范围内可以一起算GPU 可以一次性把所有 token 一起算完。所以 Prefill 阶段更像是一次批处理输入越长单个请求在这个阶段花的时间就越长但这个阶段里 GPU 的利用率通常是比较好看的。第二个阶段叫Decode解码。模型开始真正一个 token 一个 token 地往外吐。第 N1 个 token 的计算依赖于前面 N 个 token 的完整状态所以这个过程天然是串行的。GPU 每算完一个 token才知道下一个该算什么。这个阶段的特点是单次计算量很小但必须来回反复执行。你让模型生成 500 个 tokenDecode 阶段就要串行执行 500 次前向计算。这两个阶段的差异是后面所有加速技术的出发点。我举个例子就像做一桌菜。Prefill 是集中备料把菜都洗好切好一次搞定Decode 是炒菜必须一道一道炒前面那道不上桌后面那道就没法开火。1.2 为什么 GPU 很忙却很慢很多人第一次部署大模型时会发现GPU 利用率看起来并不低但吐字速度就是上不去。这就要引入一个关键概念访存带宽Memory Bandwidth瓶颈。GPU 干活分两步先把数据从显存搬到计算单元然后计算单元算完再把结果写回显存。对于 Decode 阶段来说每一步生成只需要算很少的浮点运算——大约 2 倍参数量 × 处理预算——但这个过程中模型的所有权重比如 7B 的模型有 70 亿个参数都要被从显存里读一遍。算得少、搬得多整个阶段就变成了访存密集型任务。GPU 的计算单元经常在等着数据从显存送过来利用率高不代表计算满了而是搬数据的通道忙疯了。KV Cache 的出现又加重了这个问题。每一轮生成都要把之前所有 token 的 Key 和 Value 状态缓存下来序列越长KV Cache 越大每一步 Decode 要读取的缓存数据也越多。这就是为什么上下文窗口从 2K 拉到 32K 之后生成速度会肉眼可见地下降——因为你每一次生成新 token都要去检索一遍过去所有的缓存。理解了这个加速的本质就清晰了。要么减少单次需要搬动的数据量量化就是这个思路要么减少串行次数把多次搬动变成一次搬动投机采样就是这个思路要么让不同性格的任务不要互相排队拖累PD 分离就是这个思路。1.3 三种加速技术的分工定位给这套知识框架先打个底。量化、投机采样、PD 分离在推理链路上管的环节完全不同量化管的是模型本身。把模型从 FP16 变成 INT8/INT4体重减半甚至减到四分之一访存量同步下降直接提升 Decode 速度和单卡能承担的并发数。投机采样管的是生成节奏。用一个更小更快的模型先猜出接下来的若干个 token再由大模型一次性并行验证把串行的 Decode 变成半并行。PD 分离管的是资源调度。把 Prefill 和 Decode 拆到不同的计算实例上去跑避免高延迟的 Prefill 拖累低延迟的 Decode同时让每种实例的显存和算力配置更精准。这三件事不冲突甚至可以叠加。接下来我按顺序拆开讲。2. 量化把模型的体重降下来2.1 量化在改什么从 FP16 到 INT8/INT4量化这个词听起来高大上本质就是用更少的比特数去表示模型权重和中间激活值。原本一个权重参数用 FP1616 位浮点数占 2 字节存储和计算现在改用 INT88 位整数占 1 字节或者 INT44 位整数占 0.5 字节。模型体积直接变成原来的 1/2 或者 1/4访存量同步下降推理速度和并发能力自然就上来。但你不能简单地把浮点数截断成整数。一个大模型里权重的值分布通常是某个范围内的浮点数比如 -1.5 到 1.5 之间而 INT8 只能表示 -128 到 127 的整数。你需要做一次映射找到一组浮点数范围把它等比缩放到整数范围内。这就是量化的两个核心参数——缩放因子Scale和零点偏置Zero Point。伪代码是这样的# 量化浮点 - 整数 scale (r_max - r_min) / (q_max - q_min) zero_point q_min - round(r_min / scale) q round(r / scale) zero_point # 反量化整数 - 浮点 r_dequantized (q - zero_point) * scale这个公式看着简单难点在于你怎么确定 r_min 和 r_max。实际模型里的权重分布是有长尾的如果按最大最小值来定范围大部分精度的粒度都会被几个极端值浪费掉如果按百分位数来定又可能让极端值被截断。不同量化算法的主要差异本质上就是如何选择范围和如何在有限比特里保住重要信息的博弈。在实际部署时我会优先告诉你一个经验判断INT8 量化是无损感的INT4 量化是可感知的FP8 是折中方案。后面我会展开说怎么验证。2.2 主流方案怎么选PTQ、GPTQ、AWQ 与 QAT量化方案按是否需要训练分成两大类。第一类叫PTQPost-Training Quantization训练后量化模型训练完之后直接做转换不动任何参数。第二类叫QATQuantization-Aware Training量化感知训练在训练过程中就模拟量化的误差让模型学会抵抗精度损失。QAT 效果最好但成本高一般开源社区用得少绝大多数部署场景用的都是 PTQ 或者 PTQ 的改进算法。PTQ 家族里目前最核心的是这么几个GPTQ基于二阶误差补偿的逐层量化方案。它的做法是把权重矩阵按列分组每量化一组就修正一次误差让后面的组弥补前面组的损失。GPTQ 对 7B-70B 这种规模的模型普遍有效INT4 精度损失可以控制得很小。AWQActivation-aware Weight Quantization激活感知量化思路很朴素——不是所有权重都同等重要。它先统计激活值的分布找出对模型输出影响最大的少量通道这几条通道在量化时保留更高精度其他通道正常量化。AWQ 的优势是不需要重训练速度快配合 INT4 组量化效果很好。SmoothQuant主要解决 INT8 激活量化的痛点。注意力机制里的激活值经常有明显的大值峰SmoothQuant 把激活值的尖峰平滑掉转移一部分难度到权重里从而让激活也可以用 INT8 跑。这里我给一个很实用的选型建议如果你只是想让模型跑起来优先试 INT8 的简单 PTQ如果上 INT4直接选 GPTQ 或者 AWQ 的现成版本。社区里已经有很多量化好的权重可以直接下载不用自己做量化除非你有非常特殊的模型结构。2.3 量化后的精度与速度怎么评测才不会翻车精度评测是我见过最容易被拍脑袋的一个环节。很多同学量化完模型手动画两个测试用例看着输出还像样就敢上线了。结果一上线用户的复杂提问全都变了味。我的做法是分三层验证第一层看懂语言建模损失的变化。用困惑度PerplexityPPL来衡量。拿一批领域相关的文本分别跑 FP16 模型和量化模型对比 PPL 数字。PPL 涨了 5% 以内通常是无感的涨超过 15%你就要注意了。第二层跑任务集评测。通用能力用 MMLU、C-Eval、GSM8K 这类数据集如果你是垂直领域一定要自己攒一批真实业务问题找两三个人盲测输出质量。这一层才是真正决定能不能上线的依据。第三层关注失败案例的模式。如果量化后的模型写代码时频繁出现变量名拼错、中英文标点混乱这种低级错误多半是量化粒度太粗或者某类算子的范围估算错了。这类问题不是多测几个用例能发现的你需要看失败案例是不是集中在某个结构附近——比如像 MoE 模型里的 router 层、长文本的 Attention 层、以及带特殊 Token 的分词器附近。速度收益方面给你一个可预期的参考7B 模型 FP16 权重占 14GB 左右INT8 到 7GBINT4 到 4GB 上下。访存受限的 Decode 阶段速度提升和模型体积下降近乎线性。也就是说 INT4 的 7B 模型Decode 速度看着能比 FPGA 快 2 倍以上。但这只是单流场景真实服务器上还有一个更关键的收益——显存省下来了就能塞更多并发请求batch size 上去之后 GPU 的算力才真正被用起来吞吐量提升往往比单流速度提升更夸张。2.4 实际部署中的量化经验踩过一次最大的坑是把所有层统一量化成同一种格式。后来才发现现代模型的很多层根本不值得量化。比如Embedding 层一般是查表操作参数量大但计算少把它压成 INT4 对显存有贡献但没必要压得太狠。Norm 层 / RoPE 编码这类层通常还是跑浮点强行量化容易爆精度问题。Attention 的后半段很多量化方案会保留 Attention 里的部分操作用 FP16因为它们对精度太敏感。所以我做量化配置时习惯先看推理引擎的量化配置模板不同引擎会提供哪些算子量化、哪些保留的开关先跑默认配置再针对失败案例去调整。现在的开源推理引擎如 vLLM、TensorRT-LLM、llama.cpp 对量化支持已经非常成熟你不需要自己去写量化逻辑重点是选对格式、看懂引擎日志里对量化层和保留层的报告。另外一个小技巧量化之后的模型最好做一次SMOKE TEST冒烟测试用你线上见过最长的一批 prompt 跑一遍确认没有显存超限、算子不支持、数值溢出这三类问题再放到灰度环境。3. 投机采样让小模型当侦察兵3.1 核心思想草稿加验证量化解决的是每一步生成更快的问题但 Decode 的串行本质还在每吐一个 token就要完整跑一次前向。投机采样想干掉的就是这个串行。它找了个取巧的路子既然大模型每生成一步太慢那我先用一个非常小的模型或者一个简单的查表机制快速生成接下来的 K 个候选 token然后再让大模型一次性验证这 K 个 token 能不能接受。如果小模型猜得准大模型一次前向就相当于生成了多个 token平均速度就提上来了。这个方案为什么理论上是成立的因为在 Decode 阶段大模型的计算能力是冗余的——瓶颈在访存不在算力。你让大模型一次读进 K 个 token 并行验证计算量增加了但访存里模型权重只被读一遍额外成本是有限可控的。用一个又小又快的小模型去做廉价推测再用大模型去做质量把关两者分工。3.2 一次投机采样的完整流程我举一个具体例子K3。假设大模型正在生成一句话当前已经生成到人工智能正。草稿阶段小模型从人工智能正出发快速连续生成 3 个候选 token比如改变、世界、。这 3 个 token 是串行生成的但因为小模型很小速度快到几乎可以忽略。验证阶段大模型一次性接收人工智能正改变世界这个扩展后的序列并行计算每个位置的输出分布一次性验证这 3 个候选 token 分别出现在对应位置的概率。接受/拒绝每个候选 token 如果被大模型接受就保留一旦遇到拒绝就退回到被拒绝的那个 token用它重新生成并把后续的草稿全部丢弃。这里有个关键细节验证不是简单的相等就行而是按照大模型的概率分布做随机接受Stochastic Acceptance。每一个草稿 token只要大模型认为它概率足够高就接受如果概率低但也不是零有可能按某种概率碰巧接受。这样才能保证最终生成分布和直接用大模型生成是几乎一致的不会因为投机采样让生成质量系统性变差。从工程结果来看如果小模型和大模型分布足够接近接受率能做到 0.7-0.8那么理论上平均每一步能生成 2 个以上的 tokenDecode 的速度就有接近翻倍的潜力。注意我说的是潜力实际能不能兑现取决于后续说的工程细节。3.3 工程实现里的关键细节草稿模型的选择是第一个关键决策点。最理想的情况是用同源的小模型——比如同一个基座模型剪出来的小版本。同源模型的 token 分布和大模型高度接近接受率最高加速效果最好。如果没有同源小模型退而求其次选一个同样分词器Tokenizer的小模型也凑合但如果分词器都不一样草稿 token 压根没法对齐整个方案基本废了。第二个关键点是K 值的设置。K 越大单次验证的并行度越高但如果草稿质量不够好后面 K 个 token 大概率在前面几个就被拒绝了前面小模型的生成成本就白花了。所以 K 值不是一个拍脑袋的常量应该根据你实测的接受率去调。我见过最实用的经验法则是K 期望加速倍数 / 接受率你先设 K3 跑一版看接受率如果接受率在 0.8 以上把 K 提到 4 或 5 通常会更好如果低于 0.6K 建议保持 3 或者干脆别用投机采样。第三个关键点是采样策略的一致性。草稿小模型生成时要使用和验证端一致的采样参数temperature、top-p否则分布对不上接受率会暴跌。很多文档不会提这一点但实操中遇到投机采样反而变慢的情况八成是这里出了问题。现在的 vLLM、TensorRT-LLM 都已经内置了投机采样支持不需要自己实现草稿-验证逻辑。你只需要配置草稿模型路径、K 值、接受率阈值这些参数即可。自己实现这套逻辑需要处理蛮多边界情况比如生成结束符怎么办、要不要限制最少长度不建议从零开始造轮子。3.4 收益怎么估算不是所有场景都适合投机采样不是银弹。我后面做了几个不同业务的对比压测总结出来几条规律适合投机采样的场景生成型任务输出长度长比如 500 token 以上加速收益可以积累。你的服务单请求吞吐不是主要指标而是单用户延迟——就是说用户就一个请求在那等没别的并发来分摊算力。草稿模型够小跑一轮草稿的开销能控制在验证开销的 20% 以内。不适合投机采样的场景短输出任务比如分类、抽取、标题生成。总共就生成几十个 token草稿-验证的固定开销占比太高反而更慢。批量高并发服务。当 batch 已经很大时GPU 的算力已经被充分利用了投机采样引入的额外计算并不能换来并行化收益因为瓶颈已经从访存转移到了算力。这种场景下投机采样经常是负优化。草稿模型不够好接受率低。接受率低于 0.5 时投机采样的平均收益通常打不过额外开销。我提供一个最简单的验算方式做一次 A/B 测试用 100 个真实请求跑 FP16 基线然后开投机采样对比 TPS每秒生成 token 数。投机采样的正确打开方式是锦上添花不是雪中送炭——如果基线本来就很差比如吞吐只有个位数先找基线的瓶颈显存不够、并发太低、卡太老不要急着上投机采样。4. PD 分离把两个阶段拆开各干各的4.1 Prefill 与 Decode 的性格冲突前面说了 Prefill 和 Decode 是两种不同性格的任务现在说说它们混在一起有多别扭。Prefill 是计算密集的它要处理整个输入序列算力需求高但它的响应时间主要取决于输入长度用户从发起请求到看到第一个 tokenTTFTTime To First Token基本就是这个阶段决定的。Decode 是访存密集的单步计算量小但要不间断执行它决定了用户看到后续 token 的节奏TPOTTime Per Output Token输出越长这部分占比越大。问题是GPU 在一台机器上是固定的。如果同一个 GPU 又要跑 Prefill 又要跑 Decode两个任务会互相干扰。长上下文请求的 Prefill 会占住一大块显存和算力导致正在进行的 Decode 请求被挤得变慢Decode 请求虽然算力占得不多但访存通道一直被它占着Premfil 的计算也快不起来。这种现象在系统里叫CPU/GPU 互锁Interference最典型的表现就是某个长文档请求一进来其他所有请求的生成速度集体掉一半。另一个隐藏问题是显存分配。Prefill 阶段需要的临时显存和 KV Cache 分配策略完全不同如果两者混在一起系统只能按最大值预留显存造成大量浪费。长上下文功能需求越强这个问题越严重。4.2 PD 分离的架构与工作方式PD 分离Prefill-Decode Decoupling的逻辑很简单不要让他们住一个屋檐下。把整个推理集群拆成两种角色的实例Prefill 实例简称 P 实例专门接收新请求处理输入序列生成完整的 KV Cache。Decode 实例简称 D 实例专门接收已算好的 KV Cache并继续逐 token 生成输出。请求的流经路径变成用户请求先到 P 实例P 实例算完 KV Cache 后把这个 Cache 通过网络传给 D 实例D 实例负责后续的生成。P 实例的任务完成后就可以立即接收新的 Prefill 请求D 实例则稳定地把自己手头的生成任务跑完。这个架构里最关键的组件是KV Cache 的传输和共享。KV Cache 本质上是张量数据在分布式系统里它可能很大序列长、层数多的时候几个 GB 都很正常所以有了张量传输和分布式共享缓存的说法。工程实现上有两种做法一种是通过高速网络把 KV Cache 拷贝到 D 实例另一种是做一个分布式 KV Cache 存储服务P 实例和 D 实例共享同一份缓存只是各自访问不同的部分。后者现在是主流因为避免了反复拷贝显存数据的开销。4.3 上了 PD 分离之后指标和资源怎么变PD 分离带来最直观的变化是两类指标独立变好TTFT首 token 延迟因为 P 实例不会再被 Decode 任务拖累新请求的处理速度更快长输入的 TTFT 能稳定下降。如果配合在线/离线分离的资源池策略效果更明显。TPOT单 token 生成延迟和吞吐量D 实例专注跑 Decode批处理调度更干净不再有 Prefill 突发请求来抢夺资源生成节奏稳定批吞吐量能提高不少。我自己的实测记录同规格 8 卡 A100、并发 64、混合长短请求混跑模式下长请求的 TTFT 平均要 12 秒生成吞吐大约 800 token/s切了 PD 分离后同样请求的 TTFT 降到 5-6 秒吞吐提升到 1300 token/s 左右。这还是在网络传输 KV Cache 开销没完全优化的前提下。公开社区里做更长上下文场景的收益比我这个更明显因为那些场景 Prefill 和 Decode 的冲突更严重。不过 PD 分离增加了系统的复杂度。第一P 实例和 D 实例的算力配比需要根据你的请求画像动态调整——输入长但输出短的场景P 实例要多配输入短但输出长的场景D 实例要多配。第二你的推理引擎得支持 PD 分离拆分的 API 和 KV Cache 传输协议目前 vLLM、SGLang 这些主流引擎都有对应能力但版本之间稳定性差异挺大上生产前务必压测。4.4 与量化、投机采样叠加的实践思路三项技术不互斥我后来是实实在在叠加过的。叠加后的逻辑是P 实例主要吃算力对模型权重访存量不是最敏感量化权重会让 P 实例的显存占用降低间接提高 P 实例能承载的并发但算力瓶颈不变收益有限。D 实例是访存密集的量化收益在这里最大。我的做法是 D 实例用 INT4 或 INT8 权重的量化模型同时保持 KV Cache 用高精度缓存量化 KV Cache 会伤精度谨慎起见先不动。D 实例上跑投机采样效果也很好。因为 D 实例是访存受限的草稿模型引入的额外算力很少并行验证的收益能兑现。实测中量化 投机采样在 D 实例上的叠加效果基本等于两个技术单独收益的乘积的 80% 左右算是不错了。当然不难想象这三项全上之后系统的部署和调试复杂度也是指数级上升的。我的建议是把它们当模块来看——先每个单独验证、量化收益再组合。不要一上来就全开。5. 落地选型与问题排查5.1 按业务场景选加速方案我总结了一张很实用的选型表你直接按你的场景对号入座就行业务画像推荐方案理由单 GPU 本地部署模型太大装不下量化INT4/INT8降低显存门槛是第一优先级内部 API 服务请求并发高、长短混合量化 PD 分离稳定吞吐避免长请求拖垮整体代码生成/写作助手输出长、对延迟敏感量化 投机采样长输出场景收益最稳定超长文档问答输入经常 10K tokenPD 分离优先Prefill 冲突是最大瓶颈预算有限、旧 GPU 也要上线量化 投机采样两者都不需要额外显卡资源一个核心原则先量化再观察瓶颈再决定要不要上投机采样和 PD 分离。很多团队一上来就上 PD 分离最后发现显存比预期多花了 30%是因为他们的请求画像根本没那么极端纯粹是量化 连续批处理就能解决 80% 的问题。5.2 常见问题与排查技巧实录我把实操中几个高频问题整理成速查表每个都是踩过坑的问题现象排查思路解决的技巧量化后模型输出明显变差先跑 PPL 对比再分模块测关闭量化层的开关对比优先恢复 Attention 相关算子的精度检查是不是 Embedding 被误量化了投机采样开启后反而更慢看日志里单次验证的平均接受长度降低 K 值换草稿模型检查算的分布是否一致确认是否是高并发 batch 场景显存不够模型加载就 OOM用nvidia-smi看占用检查是否有权重和 KV Cache 双重叠放开投机采样和 PD 分离时草稿模型和部分实例会额外占显存需要分别配置显存预算PD 分离后总吞吐没提升看 P/D 实例的利用率是否失衡调整 P/D 实例配比检查 KV Cache 网络的传输是不是成了新瓶颈生成速度波动大时快时慢大概率是 Prefill 和 Decode 又混到一起了检查调度策略是不是开启了连续批处理之外还混了长请求把 Prefill 和 Decode 的队列分别限流还有一个经常被忽略的坑加载模型时的预热问题。量化模型和投机采样模型首次推理时会做算子级别的初始化第一次请求慢到怀疑人生很正常。上线前一定要用几个 dummy 请求把模型焐热了再接流量否则你会在监控面板上看到第一个请求的延迟高到离谱。5.3 性能基准怎么测才靠谱没有准确数字的优化都是耍流氓。我跑评测时统一看四个指标缺一不可TTFT第一个 token 出现的时间反映用户感知响应快慢。TPS每秒生成 token 数单请求维度反映每一问等多久才答完。吞吐量Throughput整个服务每秒能处理多少 token反映系统成本效率。并发下的稳定性同一组并发请求反复跑 3 遍看方差而不是只看平均值。评测集不要自己随便拍脑袋写 20 个句子。我的习惯是从线上真实请求里捞一批日志去重自动拼出 50-100 条覆盖不同长度、不同领域的问题再固定生成参数temperature0.7、max_tokens512在同样的 GPU 和并发环境下做 A/B。你跑完四组指标就知道某项加速手段到底值不值得上。另外强烈建议把你优化前后的 KV Cache 峰值显存也记录下来。很多加速手段量化、PD 分离的真实收益并不是变快而是省显存省下的显存再转成并发。只看速度不看显存你会漏掉一大块优化空间。这个领域最近演进很快比如投机采样有了自推测不需要额外小模型的变体PD 分离也在往分布式 KV Cache 共享的方向走。但底层逻辑始终是这三条少搬数据、少串行、让资源调度更合理。不管工具怎么变先吃透原理再上手调参通常不会出错。