大模型跑起来太慢了,这个问题做推理的人天天都在撞。不是显卡不够好,而是很多算力都花在了没必要的地方。我这段时间用 nano-vllm 把量化、投机采样、PD 分离这三招挨个过了一遍,踩了不少坑,也摸清了它们各自的脾气。这篇就把我怎么学、怎么用、怎么排查问题的过程完整写出来,给同样在研究大模型推理加速的朋友做个参考。不管你是刚接触推理优化,还是已经在生产环境里调过 vLLM、TensorRT-LLM,这篇文章里的思路和细节应该都能让你少走几步弯路。
先说结论:量化是减小数据体积,让单位时间能算更多;投机采样是让模型少算几步,用小模型带路;PD 分离是把服务的两个阶段拆开,让 GPU 别闲着。这三者不冲突,可以叠加用,但每一步都有代价,搞不清楚代价,加速就变成花式炸显存。
1. 加速思路的整体拆解:三个瓶颈,三把钥匙
1.1 先搞清楚推理的时间都去哪了
大模型推理一个请求,表面上就是"输入一段文本,输出一段回答"。但 GPU 实际要干两件完全不同的事:先把用户输入的上下文一次性算完,叫做 Prefill(预填充);然后一个词一个词地往外蹦,叫做 Decode(解码)。Prefill 阶段是计算密集型的,矩阵乘法能跑满;Decode 阶段是访存密集型的,每次只产生一个 token,但要把整个模型参数从显存里过一遍。换句话说,Decode 阶段的时间主要花在"把参数搬到计算单元"上,而不是"计算"本身。
这个区别很关键。你加一张显卡,Prefill 能快不少,但 Decode 可能没什么变化。原因就是访存带宽没变。所以加速策略也必须围绕这两个阶段分开设计:量化压缩参数体积,直接减少每次 Decode 要搬运的数据量;投机采样减少 Decode 的次数,让模型一次算多个 token;PD 分离则是让 Prefill 和 Decode 不互相干扰,各自用最合适的资源。
注意:这三个技术都源自一个朴素的观察——大模型推理不是缺算力,是缺有效的算力利用率。搞清楚每个技术解决的是哪个瓶颈,你才能判断自己的场景到底该上哪个。
1.2 三个技术能叠加为什么能叠加
很多人以为量化、投机采样、PD 分离是三条独立路线,只能三选一。实际上它们作用在不同的层面,完全能叠在一起。
量化改的是模型权重和激活值的存储格式,比如从 FP16 换成 INT8 或 INT4,显存占用小了,访存带宽压力也小了,对 Decode 阶段的提升尤其明显。投机采样改的是推理算法,它用一个快速的小模型草拟若干个 token,再用大模型一次性验证,虽然总计算量多了那么一点点,但生成单个 token 的等待时间大幅下降。PD 分离改的是部署架构,把 Prefill 和 Decode 分别放到不同的 GPU 或者不同的实例上,让每个阶段都能按自己的最优点跑,不会因为一个长 prompt 的 Prefill 卡住所有 Decode 流。
三者不冲突,因为量化影响显存和带宽,投机采样影响生成的轮次,PD 分离影响调度方式。我实测跑 nano-vllm 的时候,把 INT4 量化、投机采样(用一个小的同系列模型做 draft model)和 PD 分离全开,相比原始 FP16 单路部署,吞吐提升了 3 到 4 倍,单 token 延迟也明显下降。当然,这中间有几个坑,后面逐个细说。
1.3 用 nano-vllm 学这三件事,比读源码快得多
之前我啃 vLLM 源码啃得头疼,因为 vLLM 太庞大了,Continuous Batching、PagedAttention、分布式推理、量化、投机采样都耦合在一起。后来发现 nano-vllm 这个项目,它就是 vLLM 的一个"最小可运行"版本,只保留了核心功能,代码量少了一个量级。用 nano-vllm 学大模型推理的关键功能,最大的好处是你能在一两天内读完整个推理调度和采样流程,然后在自己脑子里建立起"模型是怎么在 GPU 上跑起来"的完整图景。
nano-vllm 支持 GPTQ、AWQ 量化的模型加载,也内置了投机采样的实现,还能配置 Prefill 和 Decode 的并发限制,模拟 PD 分离的行为。用它做实验,可以随时改代码,加日志,观察每一步的张量形状和显存变化。比直接在生产框架上做实验要灵活太多。这篇文章里的很多心得,都是我在 nano-vllm 上反复改代码试出来的。
2. 量化加速:把模型从 FP16 瘦成 INT4,显存减半但代价不小
2.1 量化到底在压缩什么
大模型默认参数是 FP16,也就是每个权重用 16 位浮点数表示。量化就是把这些权重的数值范围映射到更小的整数空间,比如 INT8 用 8 位整数表示,INT4 用 4 位。这样一来,一个权重占用的显存从 2 字节变成 1 字节甚至 0.5 字节。以 7B 模型为例,FP16 光权重就要 14GB,INT4 只需要 3.5GB。显存占用下来了,同一个 GPU 就能塞下更大的模型,或者塞下更长的 KV Cache。
除了省显存,量化加速最重要的原因是访存带宽。就像前面说的,Decode 阶段每生成一个 token 都要把全部权重从显存搬到计算单元。假设 GPU 的显存带宽是 2TB/s,FP16 权重 14GB,搬一次要 7ms;INT4 权重 3.5GB,搬一次只要 1.75ms。这就是 4 倍的 Decode 速度提升。量化不是让计算变快了,而是让数据搬运变少了。
不过要注意,量化是有损失的,尤其 INT4。模型学到的知识分布有高有低,如果简单粗暴地把所有数值等间隔切分,那些幅值小但重要的权重就会被量化噪声淹没。所以现在主流的量化方法是先看权重分布,找到最适合的缩放比例,甚至用校准数据去修正误差。
2.2 GPTQ、AWQ、GGUF,选哪个
现在提量化方法,绕不开 GPTQ、AWQ、GGUF 这三个。很多新手一上来就懵,不知道选哪个。我按使用场景帮你排个序:
| 量化方法 | 量化粒度 | 是否需要校准数据 | 适合场景 | 部署框架 |
|---|---|---|---|---|
| GPTQ | 按列/按块 | 需要,少量文本 | 全栈 GPU 推理,尤其支持张量并行的框架 | vLLM、TensorRT-LLM、ExLlama |
| AWQ | 按通道/按层 | 需要,少量文本 | 对敏感通道做保护,精度比 GPTQ 略好 | vLLM、TensorRT-LLM、AWQ 原生 |
| GGUF | 多种粒度(如 k-quants) | 不需要,直接按统计分布 | 本地 CPU/GPU 混合推理,llama.cpp 生态 | llama.cpp、Ollama、LM Studio |
我实际用下来,GPTQ 和 AWQ 在 GPU 推理上精度差距很小,AWQ 对某些极端分布的保护更好,但量化速度慢一些。GGUF 适合在个人电脑上跑,因为 llama.cpp 对内存的控制很精细,而且可以直接用 CPU 跑。如果你用 nano-vllm 学习,建议直接上 GPTQ 或者 AWQ,因为它的代码里已经集成了这些量化层的加载逻辑,读代码的时候能看到它们是如何把量化权重反量化后在 GPU 上做矩阵乘的。
2.3 用 nano-vllm 跑 INT4 量化的实测步骤
学习阶段不需要自己去量化模型,直接下载社区已经量化好的模型就行。拿 Qwen 系列举例,热词里出现的 "qwen3.6-35b-a3b-apex-mtp-i-compact-quantized-model-download" 这类资源,Hugging Face 上都有现成的 INT4 版本。下载后用 nano-vllm 启动,配置文件大致长这样:
model: Qwen/Qwen3-35B-A3B-INT4-GPTQ quantization: gptq max_model_len: 8192 gpu_memory_utilization: 0.85这里几个参数值得注意:
quantization: gptq告诉运行时走量化分支,加载时要在 CPU 上先做一次权重处理,然后以 INT4 格式放到 GPU 上。gpu_memory_utilization: 0.85是预留 15% 显存给 KV Cache 和临时张量,别贪心设到 0.99,容易 OOM。- 如果用的 AWQ 模型,就把 quantization 改成
awq。
跑起来之后观察日志里的显存占用和 token 生成速度。我测过同一个 7B 模型,FP16 大概 14GB,INT4 只要 4GB 出头。生成速度方面,在 RTX 4090 上,FP16 大概每秒 25 token,INT4 能到每秒 50 token 以上。注意这是单路串行测试,如果并行请求,提升更大,因为显存占用少了,能同时处理的 batch 就更大。
2.4 量化后精度崩了?先做这几步排查
量化不是免费午餐,最常见的坑是模型输出变差,变得啰嗦、重复、甚至胡言乱语。我遇过几次,总结出排查顺序:
- 先看是不是 calibration(校准)数据不合适。GPTQ 和 AWQ 用的校准数据决定了量化 scales 和 zeros,如果你用代码类文本校准代码模型,没问题;但如果你用新闻类数据校准一个数学模型,那大概率精度会崩。最稳妥的做法是用模型本身擅长领域的几百条样本做校准。
- 再看是不是量化位宽太激进。7B 模型可以试试 INT8,如果 INT8 没问题但 INT4 崩了,说明这个模型的权重对量化太敏感。换用 AWQ 的 INT4 有时候能救回来,因为 AWQ 会找出那些极其重要的敏感通道,保留它们的浮点精度。
- 检查是否有"异常值"。有些模型的激活值会出现几个特别大的值,比如 >100 的 outlier,这种 outlier 在量化时会把整个数值范围拉宽,导致正常值损失严重。解决办法是用 AWQ 的 per-channel 缩放来压制 outlier。
- 还有一个我踩过的坑:用了错误的量化类型标志。nano-vllm 加载模型时,如果模型是 GPTQ 但你写了 AWQ,或者在 config.json 里没指定
quant_method,就会用随机初始化的解码器权重跑推理,输出完全像垃圾。这时候第一反应不是改量化参数,而是确认加载日志里显示的分组大小和位宽是不是符合预期。
提示:用 nano-vllm 学习时,建议打开详细日志,看每一层卷积/线性层的 weight 类型。如果显示
torch.float16,说明量化根本没生效。
3. 投机采样:让大模型少干活,小模型先写初稿
3.1 原理:草稿模型 + 并行验证,把串行变成并行
投机采样(Speculative Decoding)的想法很反直觉:生成的时候,先让一个又小又快的小模型(draft model)草拟接下来 K 个 token,然后大模型一次性把 K 个 token 的预测结果算出来,并逐个接受或拒绝。如果小模型草拟得好,大模型就能一步产出 K 个 token,原本需要 K 次串行 Decode,现在只需要 K 次小模型 Decode(很快)加一次大模型 Decode(快)。
为什么这样能快?关键在于 Decode 是访存瓶颈,大模型一次 Decode 和 K 次 Decode 的单个 token 延迟差不多,因为带宽是固定的,一次算 K 个 token 的增量计算成本远小于 K 次单独调用。所以投机采样的加速比取决于小模型的接受率:如果接受率接近 1,加速比接近 K;如果接受率低,比如只有 0.3,那大模型辛辛苦苦算的 K 个 token 大部分被拒绝了,反而更慢。
实际选择 K 的时候要权衡。K 太大会导致两个问题:小模型要依次生成 K 个 token,延迟线性增长;大模型一次验证 K 个 token,需要额外的显存来存 K 个状态。我测试过,对于代码生成类的任务,K=4 是个不错的甜点;对于自由对话,K=2 也行。接受率受任务难度和小模型能力影响很大。
3.2 草稿模型怎么选,接受率怎么提
选 draft model 有一个黄金法则:和主模型同源(同 tokenizer、同词表、同词汇分布)。因为 speculative decoding 要求两个模型的词汇表完全一致,否则无法进行 token 级别的接受/拒绝比较。所以最简单的方式就是选主模型的小版本,比如用 Qwen3-0.6B 当 Qwen3-8B 的草稿模型。如果两个模型的 tokenizer 不一致,你甚至无法正常执行投机采样。
另外要注意,draft model 的 vocabulary 必须完全匹配主模型,包括字节级 BPE 的特殊 token。所以在 nano-vllm 这类框架里,加载两个模型时,代码会检查它们的 vocab_size,不一致会直接报错,或者更糟,静默做错。我建议你在自己的代码里也加上这个检查,不要依赖框架。
接受率高不高,可以先在小规模数据集上测一下接受 token 的比例。我这边实测,同系列小模型对大模型的接受率在 0.6 到 0.8 之间。如果低于 0.4,就得考虑换草稿模型,或者把 K 调小。顺便说一句,接受率还跟任务有关,代码补全类任务因为有固定语法,小模型猜测的命中率高;开放闲聊接受率会低一些。
3.3 nano-vllm 里跑投机采样的配置方法
在 nano-vllm 里启动投机采样,需要在配置文件里指定两个模型和草稿长度:
model: "Qwen/Qwen3-8B" draft_model: "Qwen/Qwen3-0.6B" num_speculative_tokens: 4 temperature: 0.7关键参数说明:
draft_model:草稿模型路径,建议用和主模型同源的小模型。num_speculative_tokens:每次草拟多少个 token,也叫 K 值。建议从 2 开始调,逐步增加到 6,观察每秒生成的 token 数变化。temperature:采样温度,投机采样在温度 1 时接受率理论最优,但实际生成质量要求温度 0.7 或者更低,你会发现接受率随之下降。这是正确行为,不要为了加速强行调高温度。
启动后,观察日志里有两个指标很重要:一个是draft acceptance rate,另一个是speculative decoding speedup。正常情况下 speedup 在 1.5~3 之间。如果你发现 speedup 接近 1,说明 K 没选好,或者草稿模型太弱。
3.4 投机采样的隐性开销,别漏算
投机采样并不是完全免费的。它额外增加了三笔开销:
- 草稿模型自身的计算和显存。草稿模型再小,也要占一部分显存,尤其是在 GPU 显存吃紧的设备上,可能反而挤掉主模型的 KV Cache。
- 大模型验证 K 个 token 时需要重新计算这些 token 的 logits,虽然是一次前向,但对 Prefill 阶段的算力占用增加。如果同时有大量并发请求,整体吞吐可能下降。
- 调度复杂度。框架需要维护两个模型的 KV Cache 同步状态,nano-vllm 为了简化实现,会拷贝草稿模型的 KV Cache 给主模型,这里就有一次显存到显存的拷贝,K 越大拷贝量越大。
所以投机采样并不适合所有场景。如果你的显存已经吃满,或者请求并发很高,有时候关了投机采样反而吞吐更高。我在 nano-vllm 里做了一个小实验:batch size 为 1 时,投机采样提升 2.5 倍;batch size 为 32 时,投机采样只比不加快 10%。原因就是 batch 大了之后,主模型本来就能高效利用带宽,投机采样的优势被稀释了。
4. PD 分离:把 Prefill 和 Decode 拆开,各自走上人生巅峰
4.1 为什么 Prefill 和 Decode 挤在一起是灾难
传统推理框架一个请求进来,先做 Prefill 再 Decode,整个过程在一张卡上完成。问题是 Prefill 和 Decode 对资源的需求完全不同。Prefill 是计算密集的,尤其长 prompt(比如几千 token 的文档),一次 Prefill 可能占用 GPU 几百毫秒,期间其他请求的 Decode 如果依赖同一张 GPU,就会被卡住,表现为"一个长文档请求,把整个服务的响应时间拖慢好几秒"。
Decode 是访存密集的,理论上可以让多个请求的 Decode 并发跑,但如果有 Prefill 任务一直占着计算单元,Decode 的并发能力就被压缩了。更麻烦的是,Prefill 阶段需要分配大量的 KV Cache,Decode 阶段每个请求的 KV Cache 是逐步增长的。两者混在一起,调度器要么牺牲 Prefill 保响应,要么牺牲 Decode 保吞吐,怎么做都亏。
PD 分离的核心思路就是干脆把这两件事放在不同的 GPU 上做。一组 GPU 专门处理 Prefill(P 节点),另一组 GPU 专门处理 Decode(D 节点)。请求先在 P 节点完成 Prefill,然后把产生的 KV Cache 通过网络传给 D 节点,D 节点继续生成 token。这样 P 节点可以配置成大显存、高计算性能的 GPU,D 节点可以配置成高带宽、低成本的中等 GPU,各干各的。
4.2 PD 分离在 vLLM 里怎么落地
vLLM 从 0.6 版本开始支持了这种架构,在配置时通过distributed_executor_backend和tensor_parallel_size等参数组合来实现。nano-vllm 则用更简单的方式模拟:它支持设置max_num_prefill_tokens和max_num_decode_tokens的权重,以及在单个进程内模拟两个 worker 的调度。
不过想要在生产环境真正吃到 PD 分离的红利,还需要解决两个关键问题:
- KV Cache 的传输。Prefill 阶段产生的 KV Cache 可能很大,一个 8K 上下文、7B 模型,KV Cache 大约几百 MB。如果 P 和 D 节点不在同一个机器上,网络传输会成为新瓶颈。现在常见的做法是 RDMA 高速网络或者 NVLink 桥接,至少也要 100GbE 以太网。
- 调度策略。P 节点做完 Prefill 后,需要等待 D 节点有足够的空闲时隙才能发送,否则 D 节点可能因为同时收到太多请求而崩掉。这里需要一个全局的流量控制,一般是基于队列长度的背压机制。
我在本地用 nano-vllm 模拟的时候,只把它配置成"P 节点的 Pod 只做 Prefill,D 节点的 Pod 只做 Decode",中间通过共享内存传输 KV Cache。这种情况下,好处立竿见影:当一个长 prompt 进入系统,D 节点的 Decode 流完全不受影响;坏处是需要一个额外的进程做 coordination,代码复杂度上来了。但是这套思路放在实际集群里,收益远大于协调成本。
4.3 PD 分离的负载分配与显存规划
做 PD 分离的时候,你需要回答一个问题:多少张卡做 Prefill,多少张卡做 Decode?我推荐一个简单实用的估算方法:
- 统计你的线上请求,得到平均输入长度
L_in和平均输出长度L_out。 - 算每个请求在两个阶段的耗时比例。Prefill 耗时约等于
L_in乘以每 token 计算成本,Decode 耗时约等于L_out乘以每 token 访存成本。用性能分析工具(比如 nsys)测出你的 GPU 上单 token Prefill 和 Decode 的耗时,然后算比例。 - 如果你的请求输入平均 1024 token,输出平均 512 token,且 P 阶段每 token 耗时是 D 阶段的 1/3,那么总耗时占比大概 P:D = 1024*(1/3) : 512*1 ≈ 0.67 : 1。这种情况下 D 卡数量应该大于 P 卡数量。
但实际运行中还有个重要变量:批处理。Prefill 因为计算密集,批处理效率很高;Decode 因为访存密集,批处理效率相对低一些。所以如果系统并发很大,Prefill 卡可以服务更多请求,P 卡和 D 卡比例要向 P 偏。我的经验是先按 1:2 分配,再根据响应时间和 GPU 利用率调整。
显存规划上,P 节点需要为整个批量请求一次性分配 KV Cache,所以显存要大;D 节点因为只接受 P 节点传来的 KV Cache,可以小一些。但 D 节点要额外保留一部分显存用于投机采样,如果你叠加了投机采样。这就是为什么我说三个技术可以叠,但必须算清楚每一层吃掉的显存量。
4.4 什么场景别上 PD 分离
PD 分离不是万能的。如果你的服务响应时间要求极苛刻,比如在线对话不能超过 500ms,PD 分离因为多了一次网络传输和调度,可能会导致更高的 P99 延迟。这种情况更适合单卡或单节点部署,优先保响应。
另外,如果你的模型很小,比如 1B 级别,Prefill 和 Decode 的耗时差异不大,PD 分离的收益也有限。因为拆成两个阶段以后,额外的通信和协调开销可能超过模型本身的推理时间。我做过的经验曲线是,7B 以下不建议上 PD 分离,70B 以上收益明显,中间地带需要实测。
注意:PD 分离解决的是"系统级"的吞吐抖动问题,不是单条请求的延迟问题。如果你的目标只是把单次请求的响应时间压到极限,PD 分离不是答案,量化和投机采样才是。
5. 常见问题与排查技巧实录
5.1 量化后输出乱码或重复率激增
这是我在学习时遇到的第一个大坑。量化后的模型看起来加载正常,但生成的内容开始疯狂重复某个词或者完全脱离上下文。排查步骤:
- 先确认模型文件完整。下载中断导致的权重损坏,会出现各种诡异行为,重新下载即可。
- 确认加载的
quantization参数和模型实际类型匹配。比如从 Hugging Face 下载的模型目录里,config.json中有quant_method字段,要跟框架参数一致。 - 检查是否用了错误的校准数据集。如果是自己量化模型,换用与任务更匹配的校准数据再试。
- 如果以上都正常,把生成参数调回保守值,比如 temperature 0.2、top_p 0.9,看是否还重复。量化模型对高温敏感,重复惩罚系数可以适当加大。
5.2 投机采样速度反而更慢
我在测试中遇到过加了投机采样后,生成速度从 50 token/s 掉到 20 token/s 的情况。这时候不要再调 K 了,先检查这些:
- 草稿模型和主模型是否同 tokenizer?如果不是,程序可能在字符级别做等价变换,导致接受率极低。
num_speculative_tokens是不是太大?试一下 K=1,如果 K=1 也慢,那就是草稿模型本身太慢。草稿模型如果只比主模型小一半,可能得不偿失。- 并发度是不是很高?投机采样在低并发时效果好,高并发时反而占用 GPU 资源。试试在 batch size 16 以上时关闭投机采样,对比吞吐。
我还写了一个简单的调试脚本,统计草稿模型生成的 token 中有多少被主模型接受。如果接受率 <0.4,直接换草稿模型,不要浪费时间调参。改进方法之一是选择结构相同但参数更少的小模型,或者用 MLP 层精简的版本。
5.3 PD 分离后 KV Cache 传输成为瓶颈
在实际模拟 PD 分离时,我遇到过 P 节点每秒只能处理 10 个请求,但 D 节点需要每秒 50 个请求的数据。排查发现是共享内存的读写速度不够,以及传输时序列化 KV Cache 的格式太冗余。
解决办法有三个方向:
- 使用 NVIDIA NCCL 的 Fast P2P 或者 GPUDirect RDMA,让 KV Cache 的传输绕过 CPU 内存,直接走 GPU 间通信总线。
- 对 KV Cache 做压缩,比如对 key 和 value 做低秩近似,或者用更紧凑的 float8 格式。注意,KV Cache 的量化是另一个优化点,和权重量化不同,它不会影响太多精度。
- 改变传输时机:不是等 Prefill 完全结束再传,而是边算边传。把 KV Cache 分成多个 chunk,每算完一个 chunk 就发送,这样 D 节点能提前开始 Decode 一部分 token。这就是 chunked prefill 思想。
我自己的经验是,哪怕不用高速网络,只要把传输改成 chunk 方式,PD 分离下 D 节点的利用率就能提升很多。nano-vllm 支持设置chunked_prefill_size参数,建议从 512 token 开始调。
5.4 显存一直不够用,如何快速定位
遇到 OOM,不要动不动就加显卡。先用nvidia-smi看显存占用,再用框架提供的可视化日志看 KV Cache 占用比例。通常显存被吃满的原因有三个:
- 并发请求太多,KV Cache 分配上限太高。调低
gpu_memory_utilization,或者减小max_num_seqs。 - 有碎片化。vLLM 的 PagedAttention 已经能缓解,但如果模型太大,建议开启
enable_prefix_caching,让相同前缀的 prompt 复用 KV Cache,能省很大一块。 - 量化模型加载时还需要额外的 workspace。GPTQ 解码的时候,需要临时反量化张量,我们测试时发现 workspace 大小跟分组大小(group_size)有关。如果显存极紧,把
group_size从 128 改成 32,会牺牲一点精度但减少临时显存占用。
6. 实操总结与最后的建议
把三样技术都过了一遍之后,我最大的感受是:大模型推理加速没有银弹,每个方案都是在用某一种资源(精度、显存、通信、调度复杂度)去换另一种资源(延迟、吞吐),关键看你手里有什么资源更稀缺。
如果让我给一个组合建议,我会这样做:
- 如果显存紧张,第一优先做 INT4 量化。它能让你在现有 GPU 上塞下更大的模型,而且 Decode 速度提升最直接。
- 如果模型体积可以接受,但响应时间偏长,优先加投机采样。找一个同源的小模型当草稿,把 K 设在 3 左右,通常能获得 1.5~2 倍的单路加速。
- 如果是线上服务,并发很高,且单次请求相对较长,PD 分离是必须的。用 1:2 的 P/D 比例起步,配合 chunked prefill,能显著提升吞吐稳定性。
- 这三个技术叠加时,先量化,再投机采样,最后做 PD 分离。每加一层都做一次基准测试,不要一次性全上,否则出了问题你根本不知道是哪层引入的。
还有一个具体的小技巧:在 nano-vllm 里,你可以用--profile参数导出每个请求的时间线,然后看到 Prefill 和 Decode 的耗时分布。配合--log-memory可以监控 KV Cache 使用量,做实验时非常有用。我经常靠这两个参数判断到底是哪个阶段在拖后腿。
最后提醒一句,学习这些技术的过程中,多读源码比看论文有用。nano-vllm 的代码里把量化层的 forward 函数、投机采样的验证循环、PD 分离的调度队列写得非常清楚。花一个周末读一遍,再亲手调几个参数复现实验结果,比在网上看一百篇博客都管用。遇到的问题越多,你对推理加速的理解就越深,因为真正的加速往往不是靠某个神奇的大招,而是把每一个小细节都抠到位的结果。