大模型推理慢,几乎是每个做过本地部署的人都会遇到的坎。模型参数一大,生成一个字要好几百毫秒,交互体验基本没法看。这两年社区里讨论最多的加速手段,无非就是量化、投机采样和 PD 分离这三大件,但很多人只是听过名字,不知道它们各自打的是什么瓶颈。我最近把 vLLM 的简化教学版 nano-vllm 从头过了一遍,对照源码重新理解了这几个功能,才发现之前不少理解是错的——量化不只是把数字变小,投机采样也不是单纯多跑一个模型,PD 分离更不是简单拆成两段。这篇文章就按我自己的学习路径,把这三个加速技术的原理、配置、坑点挨个拆开讲,尽量让没有分布式环境、只有单卡的朋友也能看明白。
1. 为什么推理加速成了刚需:先从一次"卡死"说起
1.1 大模型推理的两个阶段,瓶颈完全不一样
先看一个大模型推理请求的完整过程。输入一串文字进来,模型先做 Prefill(预填充),把整段输入一次性处理完,生成第一个 token 的 hidden state;然后进入 Decode 阶段,一个 token 一个 token 地往外冒。前者像"一次性读完一本书并记笔记",后者像"问一句答一句",两者的资源需求完全不同。
这个阶段差异直接决定加速方向。Prefill 是典型的计算密集:提示词有几千个 token 时,要在 GPU 上跑一次很大的矩阵乘法,算力是瓶颈,GPU 的 FLOPs 越快越好。Decode 则是访存密集:每次只要生成一个 token,但因为模型要读取全部权重和 KV Cache,算得不多,数据搬来搬去却不少,这时候显存带宽反而成了瓶颈。
你会发现一个反直觉的现象:在同样的算力下,模型越大,Decode 越慢,不是因为它算不过来,而是因为每次都要把所有权重从显存搬一遍。70B 的 FP16 权重大约 140GB,就算 A100 的带宽堆到 2TB/s,光搬权重就得几十毫秒,这还是一个 token 的时间。
1.2 三个加速技术分别打在哪个瓶颈上
量化、投机采样、PD 分离,正好分别对应上面说的三个问题:量化把权重和 KV Cache 的字节数砍掉,让访存更快;投机采样让 Decode 阶段从"一步一读"变成"一步验证多个 token",减少访存次数;PD 分离则是把计算密集的 Prefill 和访存密集的 Decode 拆到不同节点,让 GPU 不干重复的活。
| 加速技术 | 主要打击瓶颈 | 典型收益 | 最适合的场景 |
|---|---|---|---|
| 量化 | Decode 阶段的显存带宽 | 体积减半或减到四分之一,速度提升 1.5~3 倍 | 显存紧张、本地部署、成本敏感 |
| 投机采样 | Decode 的串行生成延迟 | 解码速度提升 1.5~2.5 倍 | 在线交互、长文本生成 |
| PD 分离 | Prefill/Decode 互相干扰 | 吞吐和延迟抖动改善 | 高并发在线服务、多实例部署 |
这里先建立一个基本判断:不同场景的加速手段优先级完全不同。我就是因为一开始没搞清这个,才在实际部署里走了一堆弯路。
2. 量化:用精度换速度的账,算清楚才敢动手
2.1 量化到底在做什么
量化这件事,本质上就是把 FP16 的权重变成 INT8 或 INT4,相当于把一张高精度照片压成低精度图片。存储压力小了,读取速度快了,但会有画质损失。核心是找到合适的缩放因子(scale)和零点(zero point),把浮点范围映射到整数范围。
量化粒度非常重要。per-tensor 用一套 scale 描述整个张量,实现简单,但精度损失大;per-channel 每个通道一套 scale,更贴近真实分布;per-group(比如 128 个元素一组)是目前主流语言模型用得最多的,配合 GPTQ、AWQ 这类算法,可以在 4bit 精度下把困惑度损失控制在很小范围。
注意,这里的"量化"是大模型推理里的数值量化,跟金融领域说的"量化交易"完全是两回事。我之前看到不少朋友把这两件事混在一起搜资料,真买错书就尴尬了。本文说的量化,只涉及模型数值精度压缩。
2.2 我实测走过的部署路径:GGUF 还是 GPTQ/AWQ
本地部署时,比较稳妥的一条路径是直接下载 GGUF 量化版,比如 qwen-image-2.1 这类多模态模型的 GGUF 量化版,用 llama.cpp 跑,或者新版 vLLM 也支持导入 GGUF。GPTQ 和 AWQ 则更适合需要批量加载到 GPU、配合 vLLM 这类推理框架使用的场景。
拿 70B 模型举例,FP16 权重体积约 140GB。INT8 压到约 70GB,INT4 压到约 35GB。显存占用下来了,Decode 阶段每次搬权重的数据量也下来了,这就是加速的主要来源。
| 精度 | 权重体积 | 相对显存 | 一般速度表现 | 精度损失 |
|---|---|---|---|---|
| FP16 | 140GB | 基准 | 基准 | 无 |
| INT8 | 70GB | 约一半 | 快 1.5 倍上下 | 较小 |
| INT4 | 35GB | 约四分之一 | 快 2 倍左右 | 合理优化后较小 |
很多人以为 INT8 算起来更快,其实大多数场景下计算速度提升有限,真正的收益在访存变小。如果 GPU 支持 INT8 张量核心(比如 Ada 架构),那计算环节也能确实提速,但核心逻辑仍然没变:量化省的是内存带宽。
2.3 在 nano-vllm 里看量化的落地方式
nano-vllm 是把 vLLM 关键路径抽出来的教学版项目,非常适合用来观察量化在推理框架里到底发生在哪一层。你会在里面看到,量化并不是简单地在加载模型时调用model.half(),而是权重加载之后要做反量化(dequantize),或者直接用支持低精度输入的算子。
我的建议是去跟一遍 weight loader 的代码,你会理解一个关键点:量化模型的加载流程比普通模型多了"反量化映射"这一步。scale和zero_point要跟着权重一起加载,如果只换了权重文件、没换配置,模型跑起来大概率会乱码。这也是很多本地部署教程没讲清楚、新手最容易翻车的地方。
2.4 踩过的坑:维度不匹配和校准数据的问题
第一个坑是多模态模型量化后出现维度不匹配。前段时间社区里有人反馈 minimax h3 量化版出现 clip5120 与 4096 不匹配的问题。这类问题常见于 vision-language 模型:视觉编码器输出的 token 长度和文本模型期望的最大长度冲突,量化后在 forward 阶段报形状错误。解决办法通常是修改配置文件,把 encoder 端和 text 端的长度对齐,或者绕开 vision 分支直接跑文本任务。遇到这种问题先别怀疑量化算法,先去看 shape 的来源。
第二个坑是量化校准数据可能"泄露未来信息"。做 PTQ(训练后量化)时,如果我们对因果模型做 per-token 动态量化,却把整句话的统计信息拿来归一化当前 token,就会让量化后的模型出现奇怪的偏差。验证时用常规 prompt 一切正常,一上线遇到长尾输入就变差。这就是因为校准过程用到了模型在真实推理时看不到的"未来信息"。
第三个坑是别贪心。INT4 的显存很诱人,但如果数据集比较偏门、校准没做好,生成质量会肉眼可见地下降。我自己一般从 INT8 起步,跑通正确性再压到 INT4,每一步都留一份对比结果,方便定位问题。
3. 投机采样:让草稿模型先跑一步的"猜答案"机制
3.1 核心直觉:一次验证一串 token,而不是一次生成一个
Decode 阶段最大的痛点是串行,一个 token 一个 token 地生成,每一步都要等前一步。投机采样的思路很直接:先让一个又小又快的模型生成一串候选 token,再把这一串 token 一起交给大模型验证。大模型一次前向就能并行确认多个 token,等于把串行延迟摊掉了。
打个比方:自己做数学题比较慢,不如先让一个机灵点的初中生把步骤写出来,你再快速检查一遍。检查一整页草稿和从头做一整页题,时间完全不同。这里的"初中生"就是草稿模型,"你"就是目标大模型。
3.2 草稿模型怎么选:同源小模型还是 Ngram
草稿模型可以是同系列的小模型,比如用 Qwen2.5-0.5B 配 Qwen2.5-7B。同源小模型和大模型的知识结构接近,接受率高,生成的草稿更"靠谱",但它会额外占显存。另一种是 Ngram 采样,直接从历史 token 里截取候选,不依赖额外模型,省显存,但接受率普遍一般。
在 vLLM 里,投机采样通过speculative_config配置,指定草稿模型路径和草稿长度(num_speculative_tokens)。草稿长度一般选 4~8。太短了并行验证的优势不明显,太长了接受率会下降,超出合理范围的候选 token 等于白算。关键是草稿模型必须比目标模型快 5~10 倍以上,否则整体收益会被拖没。
3.3 拒绝采样为什么能保证输出不错乱
很多人担心:草稿模型质量差,让大模型验证之后,会不会把错的 token 也输出出去?不会。投机采样靠拒绝采样机制纠偏。目标大模型会对每个候选 token 算出它真实的概率分布,如果草稿模型猜的 token 落在可接受范围内,就接受;如果不在,就回退到目标大模型自己的采样结果作为当前输出。
这个机制保证了投机采样的最终输出在数学上等价于直接跑目标大模型。草稿模型只是帮你"加速试探",不会改变真实分布的走向。换句话说,投机采样不是降低质量换速度,而是把大模型的能力"预演"了一遍,再用小模型降低预演成本。
3.4 配置要点和调参经验
以下数据是说明性的,不同模型、不同任务的实际表现会有差异,但趋势值得参考。
| 草稿长度 | 接受率 | 实际加速 | 备注 |
|---|---|---|---|
| 2 | 高,约 0.8 | 1.5x | 保守但稳 |
| 4 | 中,约 0.6 | 2.0x | 常见选择 |
| 8 | 低,约 0.4 | 1.6x | 长度太长反而浪费计算 |
我自己调参时发现,采样温度对接受率影响很大。温度越低,草稿模型和目标模型的分布越集中,接受率越高;温度太高,输出五花八门,草稿模型猜中的概率就明显下降。所以投机采样更适合确定性比较强的任务。如果生成任务要求高随机性,收益会打折。
还有一点容易被忽略:草稿模型也会吃显存。如果你已经把模型压到 INT4 才勉强放进显存,然后再塞一个草稿模型,可能反而把显存挤爆。这种情况下一个更务实的方案是先用量化腾出空间,再加投机采样。
4. PD 分离:把排队和干活拆开的资源调度思路
4.1 Prefill 和 Decode 的"体质"完全不同
Prefill 一次处理大量 token,算力可以跑满,但时间一般只有几十到几百毫秒;Decode 每个 token 都要读一遍权重和 KV Cache,耗时和 token 数线性增长。对多用户并发来说,GPU 一会儿在大量计算,一会儿又在搬数据,整体利用率上不去,还容易出现排队。
更麻烦的是两者混在一起会互相干扰。一个长提示词的 Prefill 会阻塞正在 Decode 的小请求;反过来,Decode 串行时间太长,也会拖住后面等着 Prefill 的请求。延迟抖动就是从这里来的。
4.2 PD 分离到底分开了什么
PD 分离(Prefill-Decode Disaggregation,也叫解耦式预填充/解码)把 Prefill 和 Decode 分别放到不同的 GPU 实例上。Prefill 实例接收请求,一次性算出完整的 KV Cache,然后把 KV Cache 传给 Decode 实例;Decode 实例只负责逐个生成 token。这样每个实例的负载类型都很纯粹,GPU 利用率更高,还能单独扩容。Prefill 算力不够就加 Prefill 节点,Decode 带宽不够就加 Decode 节点。
这个思路最大的价值,是让在线服务的首 token 时延和总时延都更容易控制。你不再需要为极少数超大 prompt 去堆整机的算力,只需要把"计算密集"的活集中在一起调度。
4.3 单机单卡也能体会 PD 分离的调度思路
没有分布式环境也能体会 PD 分离在调度层面的内核。看 nano-vllm 里的 scheduler,你会发现它本质上就是在区分 prefill 请求和 decode 请求。批量调度时不是完全混跑,而是尽可能分阶段处理,避免频繁在计算密集和访存密集之间切换。这就是 PD 分离在单机上的雏形。
真要在单机多卡上玩得更彻底,可以参考 vLLM 里的 disaggregated 部署示例,把 prefill worker 和 decode worker 分别绑定不同的卡,中间通过 KV Cache 传输接口连接。不过 KV 传输的通信量不小,本地高速互联(比如 NVLink)时收益明显,如果跨了慢速网络,传输开销可能把优化收益吃光。
4.4 需要接受的取舍
PD 分离的成本是架构复杂度。你需要处理 KV Cache 如何切分、如何序列化传输、如何对齐,还要解决调度上的感知问题。对单卡部署的人来说,PD 分离暂时体会不到——卡只有一块,Prefill 和 Decode 注定在同一块上跑。但对云上多实例的团队,这已经是目前最值得关注的吞吐优化方向。
一句话总结我的体会:PD 分离不是让单个请求变快,而是让"一堆请求"整体变稳、变快。它优化的是系统的调度效率,而不是单次推理的计算效率。
5. 三个加速手段怎么搭配,以及我实测后的取舍
5.1 建议的搭配组合
量化和投机采样经常可以一起用。量化先把权重字节砍掉,腾出显存,草稿模型才有地方放;草稿模型也可以用同一个量化精度加载,减少额外开销。PD 分离属于系统级优化,通常应该在前两个手段吃透之后、确认瓶颈在排队延迟时才上。
| 你的情况 | 建议 |
|---|---|
| 显存不够,模型都放不进去 | 先上 INT4/GGUF 量化 |
| 显存够,但单请求生成太慢 | 量化加投机采样 |
| 多用户并发,延迟抖动明显 | 优先考虑 PD 分离思路,同时用量化降负载 |
5.2 一个综合压测的参考过程
我做过一个说明性测试:70B 模型,多卡 A100,先用 FP16 跑 128 并发,首 token 延迟大约 800ms,吞吐不高。换 INT8 量化后,显存压力降下来,吞吐大约翻了一倍。接着加一个 0.5B 草稿模型做投机采样,长文本生成的 tokens/s 又明显提升。如果再把 Prefill 和 Decode 拆到两组机器上,P99 延迟的稳定性会好很多。
具体数字每台机器都不一样,但趋势是一致的:量化最先带来显存收益,投机采样带来单请求串行延迟收益,PD 分离带来并发稳定性收益。先量力而行,再逐级叠加,而不是一上来就三件套全上,那样根本分不清瓶颈在哪里。
5.3 不建议偷懒的做法
- 不要三个加速手段一次性全开,调优时会失去参照系。
- 不要在没做 profiling 的情况下盲目调参数。vLLM 自带的 benchmark 脚本、
nvidia-smi的显存带宽观察、nsight compute 都值得用起来。 - 不要以为量化后的模型输出和原模型完全一致。做质量回归测试,至少准备一组标准 prompt 对比前后结果。
- 不要忽略草稿模型带来的额外显存,也不要忽略 PD 分离跨节点时的 KV Cache 传输带宽。
最后分享一个我自己的体会。我在接触这三个加速手段之前,总觉得"加速"等于"跑得更快",真正动手之后才发现,大部分收益其实来自"知道瓶颈在哪,然后绕开它"。量化绕开的是内存带宽,投机采样绕开的是串行等待,PD 分离绕开的是负载混杂。建议每个想深入的朋友,拿 nano-vllm 跑一遍代码,自己在 scheduler 和 weight loader 里打断点看几行,比泛泛读十篇论文都管用。踩过坑之后你会意识到,推理加速这件事,理解瓶颈比堆 GPU 更省预算。