1. 先搞清楚三个加速技术分别解决什么问题最近在学习大模型推理加速的时候我一直在思考一个问题模型越来越大显存越来越贵但实际部署的时候瓶颈经常不在模型本身的精度而在推理链路上浪费掉的大量算力和显存带宽。量化、投机采样、PD分离这三个词大家应该都听腻了但真正能把它们放在同一个系统里理解、并且能实际跑通的人其实不多。我这次用 nano-vllm 这个轻量级高吞吐推理引擎作为载体把这三个技术逐个拆开、逐个验证收获很大。先说结论性的事实大模型推理的瓶颈不是算力而是显存带宽和显存容量。一个 7B 参数的模型在 FP16 精度下光权重就要占 14GB 显存每次生成一个 token都要把整个权重从 HBM 里读一遍。这时候 GPU 的算力再强也只能等数据搬过来。这就是 Memory-Bound 的含义。量化解决的是“让数据更小、搬得更快”的问题投机采样解决的是“减少搬数据次数”的问题PD分离解决的是“不同阶段对资源的需求不同混在一起互相拖累”的问题。三个技术对应三类不同的瓶颈很多教程把它们混在一起讲反而让人越看越乱。我当时给自己定了一个学习目标不只看概念要用 nano-vllm 在本地真实跑起来分别对比量化前后的吞吐、投机采样前后的解码速度、PD分离前后的大并发表现最后整理出一份能直接用于决策的经验记录。下面的内容就是这份记录的完整整理版适合已经知道 Transformer 大概原理、想真正落地推理加速的工程师参考。2. 量化把模型“压缩”进更小的带宽通道2.1 为什么量化对推理加速这么关键先说一个让人容易忽略的事实在做 GPU 推理时模型的计算量FLOPs和参数的读取量Memory Access是两回事。一个 7B 模型生成一个 token需要做一次完整的 forward此时权重全部要走一遍 HBM。HBM 的带宽一次大概 1-2TB/sFP16 权重 14GB 意味着每一轮解码都要读 14GB 数据算下来一个 token 最少也要 7-14 毫秒。如果这时候模型只有 3.5GBINT8或者 1.75GBINT4同样的带宽能把读取时间砍到四分之一吞吐直接翻上去。这也是量化在很多场景里比稀疏、剪枝更实用的根本原因。剪枝改变了模型结构很多推理框架根本不支持稀疏矩阵的高效算子而量化后的算子INT8 GEMM、INT4 GEMM在显卡上有多年的工程积累能直接跑。从实际效果来看量化带来的加速比上限不是算力利用率而是带宽压缩比FP16 到 INT8 接近 2 倍到 INT4 接近 4 倍。2.2 量化的核心方法与精度控制量化本质上就是把高精度数值映射到低精度。FP16 的数值范围很大直接截断到 INT8 会爆精度所以现在主力量化方案都引入了“缩放因子”。以最简单的 Per-Tensor 对称量化为例给定一个权重矩阵 W我们找它的绝对值最大值然后用这个最大值把整个矩阵线性压缩到 [-127, 127] 的范围。公式就两行scale max_abs(W) / 127W_int8 round(W / scale)反量化回去的时候乘回 scale 就行。这种方法简单直接但坏处是如果某个权重特别大整个矩阵的分辨率都会被它拉低。所以后来有了 Per-Channel 量化——每个 channel 单独算 scale精度明显改善。现在你在 GPTQ、AWQ 这些主流算法里看到的本质上都是在“找到更合适的缩放因子和数值映射方式”。AWQ 的一个关键洞察是不是所有 channel 都同等重要。它通过统计激活值的分布找出那些对量化误差更敏感的 channel用少量代表性数据去校准缩放因子把误差集中在少数权重上。这样在 INT4 下也能保持相对较好的质量。GPTQ 则是从“逐列最优量化”的角度切入用 Hessian 矩阵做误差补偿一层一层地压过去。实际用下来GPTQ 在纯文本生成任务上精度很稳AWQ 对 activation 分布变化更鲁棒两者都能用但要结合自己的任务测试。2.3 nano-vllm 实战INT8 / INT4 量化部署全流程nano-vllm 本身支持直接加载 HuggingFace 上已经量化好的模型也支持把 FP16 模型在线量化。我这次用的是 Qwen 系的 7B 模型试水过程大概是这个路径# 1. 安装 nano-vllm pip install nano-vllm # 2. 用 AWQ 量化一个模型需要校准数据这里用 c4 数据集子集 python -m nano_vllm.quantize \ --model Qwen/Qwen2.5-7B-Instruct \ --quant_method awq \ --bits 4 \ --calib_dataset c4 \ --calib_samples 128 # 3. 启动推理服务 python -m nano_vllm.serve \ --model ./qwen2.5-7b-awq-w4 \ --tensor_parallel_size 1 \ --max_model_len 8192 \ --gpu_memory_utilization 0.85跑起来之后我重点测了两个指标首 token 延迟TTFT和生成速度tokens/s。在单张 A100 上FP16 的 7B 大概跑出 40-50 tokens/s而 INT4 版本能到 90-110 tokens/s这个差距就是带宽压缩带来的直接回报。显存占用上也差了很多FP16 吃掉 14GB 权重加 KV cache而 INT4 的权重只有 4GB 左右能腾出更多显存放 KV cache支持更长的上下文。这里有个非常关键的操作细节量化之后一定要做“精度验证”不要光看 ppl 或者 loss 指标变化。我实测下来用lm-evaluation-harness跑几个标准任务如 GSM8K、MMLU比看 ppl 更可靠。因为有些量化模型 ppl 只掉了 0.1但实际数学推理能力已经垮了。原因在于 ppl 是全局平均而数学推理要求的是 token 级的精确概率个别位置的概率扰动就足以导致错题。2.4 量化部署的三个坑第一个坑是层误差累积。量化误差每一层都会引入但随着网络加深误差会累积放大。GPTQ 里的 Hessian 补偿就是针对这个做的。手动做逐层量化时建议按顺序逐层量化、逐层回放校准数据不要一次性全压完再校准。第二个坑是KV Cache 要不要量化。很多人只量化权重KV cache 仍是 FP16这在大 batch 和长上下文场景下会成为新的显存大头。实测 8K 上下文、batch 32 时KV cache 的显存能占 30-40%。现在有一些方案把 KV cache 用 INT8 存储、FP16 计算显存省很多但注意做量化时不要对 KV cache 做“静态校准”——KV 分布随输入动态变化很大静态校准误差大要用动态 scale 方案。第三个坑是量化格式和硬件算子匹配。例如 A100/H100 对 FP8 支持很好而 INT4 要靠反量化后走 FP16 计算实际加速比取决于算子的实现质量。买卡之前先查一下支持的格式再决定模型量化方案不然可能出现“量化了但没变快”的尴尬情况。3. 投机采样让“大模型少跑几步”3.1 投机采样的核心思路量化是低成本让单次前向变快但 decode 阶段每生成一个 token 还是要完整跑一次大模型 forward这种串行模式本质上是带宽的低效利用。投机采样Speculative Decoding的思路很反直觉先用一个很小的草稿模型快速猜出接下来 k 个 token再由大模型一次性验证这 k 个 token。如果小模型猜得准大模型的 k 次串行 decode 就被替换成了 1 次并行前向验算吞吐直接倍增。这个思路的本质是利用“大模型显存带宽受限、而小模型权重小访存快”这一剪刀差。比如你用 1B 模型当草稿猜 4 个 token大模型验证的时候实际上只跑了一次 forward就能“让”出 4 个 token 的位子。一次 forward 验证 k 个 token策略是“贪婪接受 拒绝采样修正”。大模型给出这 k 个新 token 的分布如果某个位置的最高概率 token 和草稿预测一致就接受不一致的地方就从大模型的分布里重新采样并截断——这在数学上保持了和原模型完全一致的分布所以投机采样是“无损”的。3.2 投机采样为什么“投机”这里要解释为什么不是所有场景都能加速。投机采样的收益完全取决于草稿模型的“接受率”。如果草稿预测的 k 个 token 被大模型接受的比例高大模型一次 forward 就能稳定产出 k 个 token如果草稿质量太差每个位置都被大模型拒绝那就白白多跑了一次草稿前向反而更慢。我实际测过一个 1B 草稿 7B 目标模型的组合在对话类任务里接受率大约 60%-70%选择 k4 时平均每个 decode step 能产出约 2.2 个 token吞吐提升大概 70%-80%。但如果任务换成代码生成、专业术语密集的文档小模型猜得东倒西歪接受率降到 30%收益立刻缩水到 20% 以下。关键变量是接受率而不是草稿模型的参数量。3.3 nano-vllm 开启投机采样nano-vllm 对投机采样的支持很直接一个参数就能跑起来python -m nano_vllm.serve \ --model Qwen/Qwen2.5-7B-Instruct \ --speculative_config {draft_model: Qwen/Qwen2.5-1.5B-Instruct, num_speculative_tokens: 4} \ --gpu_memory_utilization 0.85跑起来之后观察日志中的spec_acceptance指标这个值直接决定收益。然后剩下的工作就是调 k 值。k 太小验证开销占比高k 太大草稿模型一次要跑 4-8 个 token容易在后半段大量被拒绝。经验做法是先在 4 这个值上测接受率接受率超过 70% 就往上加到 6-8低于 40% 就缩回 2-3。另外一个很重要的点是草稿模型和目标模型要共享词表。如果词表不对齐草稿模型输出的 token id 和目标模型完全对不上投机采样就无法工作。目前 nano-vllm 的实现是要求两个模型用同一个 tokenizer 和词表所以跨模型的投机采样比如 7B 用 Qwen 词表、草稿用 Llama 词表是跑不了的。选择草稿模型时优先看同系列、同词表的较小版本。3.4 投机采样和量化叠加效果投机采样和量化是两回事前者减“步数”后者减“每步时间”两者理论上可以叠加。我实测了 INT4 量化目标模型 草稿模型组合吞吐比单独量化再叠加约 40%-50%比单独投机采样再叠加约 30%-40%。但因为量化后的目标模型权重很小一次验证 forward 也很快导致草稿模型的相对开销变大所以叠加收益不是绝对的“乘法效应”还是要看具体模型组合和场景。要注意的是投机采样在多用户并发时会影响吞吐的稳定性。因为在一个 batch 里不同请求的接受率不一样无法保证每个请求都贴着“k1 个 token 对齐”的最优路径走。nano-vllm 的实现是让每个请求独立做投机然后用动态调度器平衡 batch 内的负载调大 max_num_seqs 后整体吞吐会上去但单请求延迟会有轻微抖动。能接受这个波动的话投机采样是一个高性价比、无精度损失的优化方向。4. PD 分离把 Prefill 和 Decode 拆开跑4.1 Prefill 和 Decode 为什么不能好好做朋友自回归解码的核心循环是先 Prefill处理整段输入生成 KV cache 和首个 token然后循环 Decode逐个生成后续 token。这两个阶段的硬件需求完全不一样。Prefill 处理的是几百甚至几千个输入 token计算量巨大是 compute-boundDecode 阶段一次只生成一个 token主要开销是把模型权重和 KV cache 从显存搬到计算单元是 memory-bound。混在一起跑有个隐蔽问题prefill 请求会“插队”到 decode 队列里抢占正在做 decode 的算力导致那些已经生成了一半的请求延迟暴涨。最典型的场景是同时有“一个很长的问题”prefill 耗时 40ms和 100 个“短回复”每个 decode 5-10ms。长 prefill 一旦卡进来所有短请求的 TTFT 和 token 间延迟都会恶化。这种“长尾效应”在大并发场景下尤其致命。4.2 PD 分离的架构和收益PD 分离就是把 prefill 和 decode 拆到不同的 GPU 甚至不同的机器上Prefill 节点负责接收长输入、计算并缓存 KV cacheDecode 节点只做自回归解码。两批机器各干各的互不抢占。代价是多了 KV cache 传输的一跳网络开销。所以这个方案在大规模部署里收益远大于成本但小规模单机场景里如果强行拆机反而会因为网络传输 KV cache 的耗时抵消掉并行收益。收益可以用一个简单模型估算。prefill 阶段 GPU 利用率高decode 阶段显存利用率高但算力闲置。如果混跑浪费的是各自的互补空闲。分开后prefill 节点可以同时服务多个长输入decode 节点专注批量小步快速推进。实测在 2 张 A100 的 nano-vllm 集群配置下混跑吞吐大约 180 tokens/sPD 分离后能达到 260 tokens/s 左右同时首 token 延迟降了约 35%。4.3 nano-vllm 分布式部署与配置nano-vllm 支持通过 ray 做 multi-node 部署PD 分离的配置主要是启动两个不同的 worker 组一组跑 prefill一组跑 decode。示例配置如下# Node 1: prefill worker python -m nano_vllm.serve \ --model Qwen/Qwen2.5-14B-Instruct \ --worker_type prefill \ --tensor_parallel_size 1 \ --scheduler_config {max_prefill_length: 8192, prefill_batch_size: 16} # Node 2: decode worker python -m nano_vllm.serve \ --model Qwen/Qwen2.5-14B-Instruct \ --worker_type decode \ --scheduler_config {max_num_seqs: 64, decode_batch_size: 32}这里max_prefill_length和max_num_seqs是关键参数。prefill worker 不要塞太多并发不然后面的 decode worker 会断粮。我试过的合理比例是 prefill 并发不超过 decode 的 1/3否则 PV 队列会出现大量排队TTFT 反而恶化。另外KV cache 的传输不要走普通 TCP建议走 RDMA 或者 NVLink 桥接的机器否则网络带宽会成为新的瓶颈。4.4 什么时候不该上 PD 分离PD 分离不是银弹。如果你的模型很小1B-3B、单卡就能跑满、请求量不高拆开两套节点只会增加运维复杂度和 KV cache 传输开销。我建议用下面这个判断表负载特征是否建议 PD 分离说明输入平均长度 500 tokenQPS 低不建议混跑足够拆了白白增加一跳网络输入很长2000 token并发高强烈建议长 prefill 对 decode 干扰极大输出很长1000 token并发中等建议倾斜 prefill 专用节点decode 饱和时 prefill 穿插影响小单机多卡 NVLink 互联建议拆分到卡而不是机器减少跨机 KV 传输划算这个表帮我在做架构决策时省了很多纠结。核心原则就一条看你的系统里是 prefill 抢 decode 的时间多还是 decode 的空闲等待多——前者拆后者不拆。5. 常见问题与排查技巧实录5.1 量化后为什么生成速度不升反降这种情况多半不是量化本身有问题而是算子没吃满。如果你只改了模型精度但推理框架不认识对应的量化格式它会先把 INT4 反量化成 FP16再走普通 FP16 算子速度自然没提升。排查思路很简单跑一遍nano_vllm.benchmark看 kernels 名字如果日志里出现quant_gemm或者awq_gemm说明走到量化算子如果全是gemm说明没有命中专用 kernel。另一个可能是量化格式和显卡不匹配。比如在 V100 上用 INT8 算子虽然理论可行但 V100 对 INT8 的原生支持远不如 A100/Turing 以后架构。换卡或者试试 FP16 是否反而更快能帮你快速判断瓶颈到底在算子还是带宽。5.2 投机采样在 nano-vllm 里始终没有加速先看两个参数spec_acceptance和avg_spec_tokens。如果接受率大于 50% 但吞吐没涨大概率是 batch 太小导致草稿模型的额外开销没有被均摊掉。这时候把max_num_seqs从 8 调到 32吞吐立刻会不一样。如果接受率很低说明草稿模型质量不行换更大的草稿模型或者调整num_speculative_tokens到 2-3 试试。还有一个小坑如果 target 模型的max_model_len和草稿模型不一致nano-vllm 会隐式地截断到两者最小值导致长输出被腰斩。启动时把两个模型的max_model_len都显式设成一样的值能避免这种 hidden error。5.3 PD 分离后 TTFT 反而更高了TTFT 变高通常有三个原因。第一是 prefill worker 没跑满两个节点上的负载不均衡decode worker 干等。第二是 KV cache 传输走的是千兆以太网带宽不够传输耗时比本地 prefill 还长。第三是调度策略太保守prefill_batch_size设得太小。最常见的是第二个原因建议至少 25Gbps 以上网络预算宽裕就上 RDMA。如果条件受限可以把 prefill 和 decode 放在同一台机器的不同卡上用 NVLink 桥接能省掉这层跨机传输。5.4 KV Cache 显存不够和量化冲突怎么处理很多人同时做 KV cache 量化和 PD 分离结果显存还是爆了。这是因为 KV cache 显存估算公式和权重不同它是按batch_size * seq_len * num_layers * 2 * hidden_size * dtype_size算的。以 7B 模型 32 层、layer 维度 4096 为例FP16 下每 token 大约 0.5MB8K 上下文 × batch 32 大约就是 128GB单卡根本放不下。KV cache 量化成 INT8 能砍一半但别同时开很大的 speculative tokens因为草稿模型也会产生自己的 KV cache。没事看一眼kv_cache_usage日志超过 90% 说明该压缩上下文或减少 batch 了。5.5 量化加投机加 PD 分离三个一起上的顺序如果三个都要上建议按这个顺序排查先量化保底跑通推理再上投机采样调接受率最后做 PD 分离做系统级调优。反过来容易出问题——一旦 PD 分离了网络传输里 KV cache 本来就是量化过的再叠加投机采样的草稿模型 KV 传输问题排查会变得非常复杂。我在实际验证中也是按这个顺序分步上线的每一步都看着指标确认收益再叠加下一步这样出了问题能快速定位是哪层引入的。6. 最后的实操笔记这次用 nano-vllm 完整跑下来最大的感受是加速技术的核心永远是对齐资源和瓶颈不是堆参数。量化省的是显存带宽效果立竿见影但要注意算子匹配和精度验证。投机采样省的是解码步数收益看接受率需要耐心测试草稿模型和 k 值的配合。PD 分离省的是调度冲突在大规模并发下收益明显但小规模上反而增加成本。每次只改一个变量用同样的 benchmark 脚本测 TTFT、TPOT、吞吐三个指标再决定要不要保留这个改动。这套方法论比直接抄任何配置都好用——因为同样的配置在不同显卡、不同任务、不同并发模型上可能就是完全不同的结果。我个人现在的默认方案是单卡小模型用 INT4 量化 投机采样大规模服务用 INT8 权重 PD 分离 动态 batch。每个方案上线前我都会用一个小脚本先做精度回归确保量化误差和投机采样的采样偏差没有破坏模型能力。这套流程跑熟了之后大模型推理提速就是一个纯粹的工程优化问题每次都能看到明确的数字回报。