☰
大模型推理优化三板斧:量化、投机采样与PD分离实战
2026/10/2 10:17:59 网站建设 项目流程

做在线推理服务的同学,估计都有一个共同的痛点:模型越换越大,卡越买越贵,可首字延迟(TTFT)和吞吐就是不达标。追根到底,问题出在三个层面——模型权重太大搬不动、自回归生成太慢算不快、推理请求挤在一起谁也跑不动。当前生产环境里,解决这三个问题最核心的三板斧就是量化、投机采样、PD分离。

这篇文章不是做纯理论科普,而是我从读 vLLM 源码、手把手在 nano-vllm 里跑推理功能验证的过程中,把这三种技术从原理、选型到调参踩坑完整串起来的实操笔记。适合正被推理性能折磨的朋友,也适合想系统性搞懂大模型部署优化、本地部署大模型时蹲在瓶颈上不知道从哪下手的人。我会把每个技术“解决了什么问题、怎么落地、坑在哪”都交代清楚,保证你照着做一遍就能把概念真正变成手感。

1. 为什么是“量化、投机采样、PD分离”这一套组合

1.1 三项技术分别解决哪一类瓶颈

很多人一上来就到处查“推理加速方案”,然后被一堆名词砸晕。我的建议是先退一步,把在线推理的瓶颈拆成三类,再对症下药。

  • 权重显存与带宽瓶颈:模型参数动不动几十上百GB,单卡放不下、批量上不去,计算单元被数据搬运拖死。这是量化的主场。
  • 单请求生成延迟瓶颈:自回归解码一次只能吐一个 token,算力再强也发挥不出来。这是投机采样的主场。
  • 多请求资源争抢瓶颈:一个长请求的 prefill 阶段霸占 GPU,把其他请求的 decode 全堵死,尾延迟惨不忍睹。这是 PD 分离和 chunked prefill 的主场。

这三者不是“三选一”的关系,而是层层递进的。我发现很多团队踩坑的根源就是只做其中一项,比如量化完发现延迟没降到预期,就误以为量化没用,其实是 decode 的串行瓶颈没解决。下面这张表可以帮你在做方案时快速对上号:

技术针对瓶颈核心手段典型效果
量化权重大、显存贵、带宽吃紧低比特存储与计算显存占用减半、推理速度提升 20%~60%
投机采样decode 阶段串行逐 token 生成小模型草稿 + 大模型并行验证生成阶段延迟降低 1.5~2.5 倍
PD 分离prefill 与 decode 互相干扰切分或分离调度尾延迟与吞吐稳定性显著改善

1.2 为什么选 nano-vllm 作为学习主线

完整版 vLLM 的源码量非常大,调度、显存管理、算子融合、分布式通信混在一起,新人进去很容易迷路。nano-vllm 是一个精简实现,把“prefill + decode 采样循环、KV Cache 管理器、量化加载、投机采样入口”这些核心路径提炼到了一个能在一个周末读完的代码量级。它的价值不在于性能可以替代生产版 vLLM,而在于它是唯一不会骗人的教材:所有纸面上的概念,在这里都能找到对应的代码逻辑,改一行就能看到效果变化。

我把量化、投机采样、PD 分离这三块知识都放在 nano-vllm 的框架里去验证,这样学到的不是孤立的“名词解释”,而是一条完整的推理链路的认知。后面每一章的实操片段,你也都可以在 nano-vllm 里找到对应入口,跑一遍、断点看一眼,理解立刻翻倍。

2. 量化:把“精度换速度”这件事做扎实

2.1 量化的底层逻辑:从 FP16 到 INT8/INT4

先说最基础的计算模型。未量化的模型权重通常是 FP16 或 BF16 存储,每个权重占用 2 字节。一个 70B 模型光权重就要 140GB,单张 A100 80G 放不下。如果量化到 INT8,每个权重只要 1 字节,权重体积直接减半;量化到 INT4(用 int4 或 fp4 存储),则可以压到约 35GB,单卡就能塞进去。这是量化最直观的收益:模型装得下,批量才上得去。

第二个收益在计算侧。现代 GPU 的 Tensor Core 对 INT8 和 FP16 的算力支持是分开计算功耗的,以 A100 为例,INT8 算力约为 FP16 的两倍。同样的矩阵乘法,用 INT8 计算不仅在搬数据时省带宽,在真正做 GEMM 时也更快。所以量化不是单纯“省显存”,而是在存储、访存、计算三个环节同时受益。

量化背后统一的数学表达是对称或非对称线性映射:

r ≈ scale × (q - zero_point)

其中r是原始浮点值,q是量化后的整数,scale是缩放因子,zero_point是零点偏移。平时听说的对称量化就是zero_point = 0,非对称量化多一个零点修正,通常用于激活值分布不均匀的情况。

这里必须说清楚一个原则:在推理服务里,量化几乎永远是 PTQ(训练后量化),不要轻易去碰 QAT(量化感知训练)。PTQ 的套路是拿几百条有代表性的数据喂给模型,统计权重和激活的分布,然后算出 scale 和 zero_point,整个过程只要几分钟到几十分钟。QAT 则需要重新训练,成本高、周期长,除非你要做 3bit 以下那种极端量化,否则优先级非常低。

2.2 主流量化方法与参数选择

常听到的 GPTQ、AWQ、SmoothQuant 到底选哪个?我的选择逻辑很简单:先看你的量化位宽和计算模式。

GPTQ是目前最普及的权重量化方法。它利用模型权重矩阵的二阶 Hessian 信息做逐层量化补偿,把量化误差摊到未量化的权重里。GPTQ 的特点是强在“权重压缩”,适合 W4A16(4bit 权重、16bit 激活)这类方案,好处是无脑、好用、社区支撑多。代价是它对校准集比较敏感,校准集特征与实际任务分布差别很大时,量化后可能在某些任务上出现明显的精度滑坡。

AWQ针对“不是所有通道的重要性都一样”这一点做文章。它基于激活值分布去识别哪些 channel 对精度更重要,那些 channel 保留更高精度,其余通道才做低比特量化。AWQ 平时给我的感觉是比 GPTQ 更稳一点,尤其在 4bit 场景下,同样的量化位宽,AWQ 的侧翻任务(比如代码、数学)掉点更少。

SmoothQuant解决的则是激活值的离群点问题。大模型激活里经常会有少数几个特别大的值,导致激活量化难度大。SmoothQuant 的做法是把这个“尖峰”通过一个缩放系数转移到权重上,让激活变得平滑可量化,从而支持 W8A8 这类量化模式。

我整理了一个选型速查表,你在实际部署时可以直接对号入座:

方法推荐位宽主打优势适用的激活模式坑点
GPTQW4A16 / W3A16压缩率高、生态好激活保持 FP16校准集不匹配会掉点
AWQW4A16 / W4A8精度稳定,更抗分布偏移激活保持 FP16 或低比特转换步骤多一步
SmoothQuantW8A8全面 int8 计算,速度上限高激活也量化需要算子配套,否则收益有限
GGUF 家族2bit~8bit 可选本地部署友好,CPU/GPU 混合跑激活通常 FP16与 vLLM 原生不通用,走 llama.cpp 生态

以我实测过的 Qwen 系列 27B 这类模型为例,W4A16 的 AWQ 量化版本在显存占用上大约比 BF16 版本降低 55%~60%,在足量并发下吞吐提升约 40%~70%,而通用问答任务的得分下降通常控制在 1~3 个点之内。这个收益在线上是完全划算的。

2.3 nano-vllm / vLLM 中的量化落地实操

在 nano-vllm 里学习量化,我推荐从“加载一个已量化模型”入手,先把链路走通,再回头看量化本身的算法实现。用 vLLM 加载 AWQ 或 GPTQ 模型,命令行里只需要指定量化参数:

# 启动一个 AWQ 4bit 模型的推理服务 python -m vllm.entrypoints.openai.api_server \ --model /models/qwen-27b-awq \ --quantization awq \ --dtype float16 \ --gpu-memory-utilization 0.85 \ --max-model-len 4096

如果模型是用 AutoAWQ 导出的 4bit 版本,--quantization一般填awq;如果是 GPTQ 导出的,填gptq。如果你的量化模型文件是带有quantize_config.json的 HF 格式,vLLM 有时也能自动识别量化方式,但我还是建议显式指定参数,免得加载时静默回退到 FP16,那样你会发现显存根本没降下来。

在 Python API 里加载也是同理:

from vllm import LLM, SamplingParams llm = LLM( model="/models/qwen-27b-awq", quantization="awq", dtype="float16", tensor_parallel_size=2, ) outputs = llm.generate( ["量子纠缠的通俗解释是什么?"], SamplingParams(max_tokens=256), )

走完加载链路之后,建议你做一个“三层精度体检”:

  1. 通用 ppl 检查:跑一段校准集,对比量化前后模型在验证集上的困惑度。PPL 暴涨超过 0.5~1.0,说明校准集没选对,重新换数据。
  2. 关键任务抽查:选 20~50 条和你业务最相关的 prompt,量化前后逐条对比输出。重点看代码、数学这种对精确性敏感的任务。
  3. 输出稳定性测试:同一 prompt 跑 5 次,看量化后是否出现偶发乱码或重复回答。出现这种情况,优先检查scale和zero_point是否有 NaN,以及加载时dtype是否和配置文件一致。

再补充一个很多人忽略的点:KV Cache 也要量化。长文本场景下,KV Cache 往往比权重更占显存。vLLM 支持把 KV Cache 缓存成 FP8:

python -m vllm.entrypoints.openai.api_server \ --model /models/qwen-27b-awq \ --quantization awq \ --kv-cache-dtype fp8 \ --dtype float16

KV Cache 量化之后,同样显存下能塞进更多并发请求,吞吐收益显著。但注意:如果你在窗口长度特别大(比如 32K 以上)的任务上使用,需要自己测一下精度表现。我在实际测试时发现 FP8 KV Cache 在绝大多数场景误差可忽略,但在“多轮精确引用前排文本”的任务上偶尔会丢失细节,这种任务建议先保守关闭。

3. 投机采样:用算力换延迟的一种“作弊”技巧

3.1 自回归解码的瓶颈:为什么一个 token 一个 token 地蹦

先想一个问题:为什么大模型生成速度这么慢?本质原因是生成过程的自回归特性——模型要生成第 N 个 token,必须等前 N-1 个 token 全部生成完毕。虽然 GPU 单次推理一个 batch 的耗时并不算长,但每个 token 都要完整跑一遍权重矩阵乘。这个大矩阵乘的宽度只有1 × hidden,数据量小,根本喂不满 GPU 的计算单元,反而是把整个模型权重的几百 GB 数据从 HBM 搬到 SRAM 的过程成了主要耗时。decode 阶段是访存密集型,不是计算密集型。

这就是投机采样能起作用的前提:你的 GPU 在 decode 时有大量算力闲着,闲着也是闲着,不如拿来多干点活。

3.2 投机采样的核心逻辑与数学保证

投机采样(Speculative Decoding)的思路特别直白:小模型草稿,大模型验证。过程拆开是四步:

  1. 用一个比目标模型小得多的草稿模型(比如 0.5B~1B)先生成未来 K 个候选 token;
  2. 把 K 个候选 token 拼接起来,一次性丢给目标大模型;
  3. 大模型并行计算这几个 token 的概率分布,并逐位置判断:小模型猜对了几个?
  4. 从第一个被拒绝(没猜对)的位置开始,大模型用自己的分布重新采样,然后把这段结果作为正式输出。

这里最关键的是数学保证。投机采样使用的是“拒绝采样”框架,只要草稿模型的概率分布q(x)满足对所有 token 都有q(x) > 0(严格来说是被目标分布p(x)覆盖),那么通过接受-拒绝机制采样出来的最终分布,与目标模型自己的采样分布是完全一致的。换句话说,它不会改变模型生成的风格和内容分布,是一个无偏加速。

它最多能省多少时间呢?经验公式是:

期望加速比 = num_speculative_tokens × 平均接受率

如果一次生成 5 个草稿 token,平均接受率 0.6,那么每个 step 能多推进5 × 0.6 = 3个有效 token,生成阶段理论加速约 3 倍。不过实际还要扣除草稿模型本身的生成时间,所以端到端加速一般在 1.5x~2.5x 之间。

3.3 实操配置与调参心得

nano-vllm 里跑投机采样,核心是先搞清楚“谁生成草稿,谁做验证”这两个角色。vLLM 命令行配置大概长这样:

python -m vllm.entrypoints.openai.api_server \ --model /models/qwen-27b \ --speculative-model /models/qwen-0.5b \ --num-speculative-tokens 5 \ --speculative-draft-tensor-parallel-size 1 \ --max-model-len 4096

四个关键参数里,我觉得最需要解释的是--num-speculative-tokens。它表示草稿模型每次生成几个候选 token。这个值不是越大越好。我的实际经验是:

  • 草稿 token 太少(比如 2~3):可批量验证的 token 数少,收益不明显。
  • 草稿 token 太多(比如 10+):越往后位置的准确率急剧下降,大部分 token 被拒绝,而草稿模型生成它们的时间却被白白浪费,甚至可能比不开启投机采样更慢。
  • 比较稳的起点是 5~6:在大多数模型上,5~6 个 token 能在“草稿生成成本”和“验证接受率”之间取到平衡点。

接受率是另一个核心指标。怎么看接受率?vLLM 的 metrics 里会暴露类似speculative_acceptance_rate的指标,如果你在业务里看不到,也可以在代码里加个 counter 自己统计:总共验证了多少个 token、接受了多少个。接受率低于 0.3~0.4 时,我建议优先考虑两件事:草稿模型是不是和目标模型差距太大(换大一号的草稿模型),以及草稿模型的 vocabulary 是否与目标模型完全一致。

有个搭配技巧值得记下来:草稿模型不一定要单独下载一个完整的 0.5B 模型。如果你的目标模型本身有多个大小版本(比如 Qwen 系列就有 0.5B/1.8B/7B/27B/72B 同源的模型),直接用同系列的小版本做草稿是兼容性最好、效果最稳的选择。跨系列的草稿模型经常在 tokenizer 映射上出问题,表现为生成的候选 token 被大量拒绝。

还要提一个限制:投机采样默认只支持纯采样(sampling)和 greedy 解码,不支持 beam search,也不支持和 LoRA 动态切换同时使用。如果你线上策略里同时开了 beam search,投机采样这个提速通道会被自动绕过,此时你去压测会发现速度不升反降,别慌,先确认参数是不是真的生效了。

4. PD 分离:把排队这件小事做得更优雅

4.1 prefill 与 decode 为什么不能愉快地混在一起

在任何一个推理请求里,模型的执行过程可以分成两个阶段:

  • prefill(预填充):处理输入 Prompt 的所有 token,并生成第一个输出 token。这个阶段是并行处理大量 token,计算密集,GPU 利用率高,耗时和输入长度几乎线性相关。
  • decode(解码):一个 token 一个 token 地自回归生成输出。这个阶段是访存密集,GPU 利用率低,但它的时延直接决定了用户“敲一个字符要等多久”。

这两个阶段放在同一个 GPU 上处理,表面看很自然,实际是灾难。想象一个场景:一个用户提交了一个 5000 token 的长文档分析任务,prefill 需要跑好几秒;与此同时,十几个交互式请求正在 decode,每个请求期望 100ms 内吐一个 token。GPU 调度器如果按先来后到的顺序执行,长 prefill 会把后面所有 decode 的“及时性”全部打穿。也就是说,长请求的 prefill 是用户尾部延迟爆表的头号元凶。

4.2 两条落地路径:chunked prefill 与分离式部署

针对 prefill 和 decode 互相打架的问题,行业里有两类解法。

第一类是chunked prefill(预填充切分),也是 vLLM 默认支持的开箱方案。核心思路是:把长 prefill 切成一小块一小块,穿插到 decode 的间隙里去执行。比如一个 5000 token 的 prefill 被切成 5 段,每段 1000 token,GPU 先跑一段 prefill,再跑几个 decode step,再回来跑下一段 prefill。这样 decode 请求不会被长 prefill 堵死,首 token 延迟保持在可接受范围内。

vLLM 里开启方式非常轻量:

python -m vllm.entrypoints.openai.api_server \ --model /models/qwen-27b \ --enable-chunked-prefill \ --max-num-batched-tokens 4096

--max-num-batched-tokens决定了每个调度批次最多放多少个 token。这个值设得越小,prefill 被切得越碎,decode 的响应越及时,但吞吐会下降;设得大,吞吐高,但 prefill 容易重新变成“长块”。我的习惯是先设 4096,再用压测脚本逐步往下调,找到吞吐和 p95 延迟的平衡点。

第二类是PD 分离(Prefill-Decode 分离部署),这是更激进的路线。简单说就是把 prefill 和 decode 分布到不同的 GPU 实例上,prefill 实例负责处理所有输入 prompt 并生成第一个 token,然后把中间状态(主要是 KV Cache)传给 decode 实例,decode 实例再负责后续的自回归生成。这种架构下,prefill 实例可以专门优化高吞吐的批量计算,decode 实例可以专心地保证低延迟。

这类架构的代表是 Mooncake、DistServe 等项目的设计思路,它们把“计算形状不匹配”这个矛盾用物理隔离的方式解决。好处非常明显:不同形状的任务互不干扰,p99 延迟稳定到个位数毫秒级波动。代价是系统复杂度显著上升——你需要自己做 KV Cache 的跨节点传输、生命周期管理和实例伸缩策略,传统负载均衡那套玩不转,必须理解“传缓存”和“传请求”之间的本质区别。

4.3 收益对比与选型建议

两条路线针对的是不同量级的问题,我建议按业务规模选型:

维度chunked prefillPD 分离部署
适用规模单机多卡、中小规模在线服务大规模生产集群、SLA 苛刻
部署复杂度低,vLLM 原生参数高,需自研调度与 KV 传输
解决核心问题prefill 阻塞 decode 的排队抖动两类任务完全物理隔离
吞吐收益中,适合仍共卡部署高,两类实例各自调参到最优
尾延迟稳定性改善明显可以做到非常稳
主要成本无额外硬件开销需要额外的 KV 传输网络和实例副本

我的建议是:如果你只是单卡或者双卡部署,先用 chunked prefill 把长 prompt 的阻塞问题解决掉,这是性价比最高的第一步。只有当你的 qps 已经高到单实例瓶颈明显、且业务对 p99/p999 延迟有强约束时,才值得去啃 PD 分离这种架构。

在 nano-vllm 里看 PD 分离的实现时,你可以重点关注 KV Cache 管理器在两个实例间是如何被传递和重建的。有一个非常容易踩的坑:KV Cache 传输的延迟如果大于单机 decode 的生成延迟,这个方案就完全失去意义。所以 PD 分离的前提一定是节点间通信延迟足够低,或者你的业务允许“KV 先到、后续节点再复用”的异步方式,否则看起来高大上的架构,压测时反而比单机还慢。

5. 常见问题与排查实录

把量化、投机采样、PD 分离这三块叠在一起,遇到的问题往往是交叉出现的。下面是我整理的高频排查速查表,每一条都是我在实际测试中踩过的,不是纸上谈兵。

问题现象可能原因排查与解决方案
量化后输出质量崩了校准集分布与业务不匹配换 200~500 条业务真实 prompt 重新校准
量化后加载失败或精度异常dtype 或量化配置与文件不符显式指定--quantization和--dtype,检查quantize_config.json
KV Cache 显存爆炸未启用 KV 量化或窗口过长开启--kv-cache-dtype fp8,调--max-model-len
投机采样打开后反而变慢草稿 token 数过大或草稿模型太弱按住--num-speculative-tokens 5起步,换同源草稿模型
开启 chunked prefill 后吞吐下降token 批次切得太碎调大--max-num-batched-tokens并重新压测
PD 分离后 p99 仍然很高KV 传输延迟盖过了收益检查 NIC/RDMA 延迟,或改用共享显存方案

5.1 量化后精度滑坡,这锅谁来背?

当量化后模型输出明显变差时,我第一个检查的永远是校准集。用通用 C4 或 WikiText 校准出来的 4bit 模型,直接拿到代码生成和数学任务上,很容易掉点。正确做法是从你的线上日志里抽一批真实 prompt,混合一些失败 case,做成 300 条左右的校准集,重新跑一次量化,通常会救回大部分精度。

第二个检查点是权重异常。量化后权重里如果出现大量 NaN 或者 scale 巨大,会导致输出灾难性错误。这种情况多发生在混合精度加载的时候——模型文件是按 FP16 保存的,但启动服务时传了--dtype float16之外的参数。保持 dtype 一致是成本最低的修复手段。

5.2 投机采样加速失效,如何判断是不是草稿模型的锅

如果你开启投机采样后,吞吐反而低于直接生成,先别怀疑这个技术。用 vLLM 的 metrics 看接受率,如果接受率低于 0.3,大概率是草稿模型选得不好。换草稿模型时,优先换同系列小模型,因为 tokenizer 和分布都更接近。

还有一个小坑藏在--speculative-draft-tensor-parallel-size里。当草稿模型很小,而你把它也做了张量并行时,通信开销可能超过它省下的时间。草稿模型通常用单卡跑就够了,不要轻易给它开 2 卡以上的并行度。

5.3 压测环境下的“假吞吐”陷阱

最后提醒一个在压测时容易误判的点。很多人开了量化或者投机采样后,拿单并发去测延迟,发现提升不明显,就得出结论说技术无效。这是错的。这三种技术本质上对“高并发下的整体吞吐和尾延迟”收益最大。单并发测试时量化只省了显存,投机采样的草稿生成开销占比太高,而 PD 分离在低 qps 时甚至可能因为多一跳网络而变慢。所以测试时请一定带足并发,至少模拟 16~32 个并发请求,对比 p50、p95、p999 三个分位,才能看到真实差距。

6. 最后分享一点个人经验

如果你现在正想系统性学习大模型推理加速,我建议的学习顺序是:先把 nano-vllm 跑起来,在单卡上完成 1~2 个模型的量化加载,感受“显存余量变大”这个直观变化;再用投机采样把同一个模型的生成延迟压下去,记录接受率指标并对比加速比;最后模拟一批长短混杂的请求,加上 chunked prefill 看 p95 是否回归平稳。这三个实验做完,你对推理链路的理解会比看十篇理论文章都深。

我个人实际踩过最深的坑,是同时把量化、投机采样、chunked prefill 全打开,结果系统之间相互干扰,问题定位变得非常困难。所以强烈建议你每次只开一个变量,用数据说话,逐个验证收益。先把量化做扎实,把显存盘活;再用投机采样压低生成延迟;最后才用 PD 分离去解决多用户并发下的稳定性。每一步的数据都记录下来,这套组合拳打到后面,你就能清楚地知道哪一项技术替你省了多少时间,而不是稀里糊涂地“全开了再说”。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询