第22章:进阶推理优化
上一章我们探讨了 KV Cache 和 Continuous Batching 等基础推理优化手段。但在实际生产环境中,工程师们面临的挑战远不止于此:如何在保持输出质量的同时将延迟压缩到极限?如何让一块消费级 GPU 运行起 70B 的大模型?如何把计算资源分配得更精准?
本章深入四个进阶方向:投机采样、Prefill/Decode 分离、模型量化,以及主流推理框架的横向对比。
22.1 投机采样(Speculative Decoding)
核心矛盾:精度与速度不可兼得?
大模型(如 70B)输出质量高,但自回归逐 token 生成极慢——每生成一个 token 都要做一次完整的前向传播。小模型(如 7B)速度快,但输出质量较差。
一个自然的问题:能否让小模型先"猜"一批 token,再让大模型"验",从而用一次大模型前向传播换来多个 token?
这就是投机采样(Speculative Decoding)的核心思想,由 Google 和 DeepMind 分别于 2022-2023 年独立提出。
草稿模型(Draft Model)
小模型被称为草稿模型(Draft Model)。它以自回归方式快速生成 个候选 token,形成一条"草稿序列":
草稿模型的生成过程与普通解码完全相同,只是模型参数量小得多,速度快 5-10 倍。
目标模型(Target Model)与并行验证
关键洞察在于:大模型可以对一段序列进行并行的前向传播——输入 个位置,同时得到 个位置的概率分布,时间复杂度与处理单个 token 相当(受益于 Transformer 的并行性)。
目标模型(Target Model,即大模型)接收完整序列,对草稿的每个位置 计算其概率分布 。
验证机制:接受-拒绝采样
对草稿序列中每个 token ,执行以下判断:
- 若接受(accept):保留该 token,继续验证下一个
- 若拒绝(reject):从调整后的分布中重新采样一个 token,并丢弃后续所有草稿 token
重采样使用的修正分布为:
:::info 为什么这个机制保证了输出分布等价? 可以证明,经过上述接受-拒绝过程,最终生成序列的概率分布与单独使用目标模型(大模型)完全一致。投机采样是无损加速——不牺牲任何输出质量。 :::
加速原理
假设草稿 token 的平均接受率为 ,草稿序列长度为 ,则每次大模型前向传播平均产生的有效 token 数为:
当 , 时,期望接受数约为 3.36 个 token——相比原来每次只生成 1 个,吞吐量提升约 3 倍。
实际工程中,投机采样在 Llama 系列上可实现 2-3× 吞吐量提升,延迟降低尤为明显。
自投机采样(Self-Speculative Decoding)
一个更优雅的变体:不需要单独的小模型,而是用同一大模型的早期层作为草稿模型。
具体做法是让模型在某个中间层(如第 16 层)提前输出一个 token 概率分布,作为草稿;完整的 32 层输出作为目标分布进行验证。这样无需维护额外模型,部署更简单。
22.2 Prefill / Decode 分离(PD Disaggregation)
两种截然不同的计算模式
一次 LLM 推理分为两个阶段:
| 阶段 | 输入 | 计算特征 | 瓶颈 |
|---|---|---|---|
| Prefill | 整个 prompt(可能数千 token) | 矩阵乘法密集,GPU 算力满载 | 计算(Compute-bound) |
| Decode | 单个 token(逐步生成) | 频繁读写 KV Cache,计算量小 | 内存带宽(Memory-bound) |
当两者混跑在同一个 GPU 上时,问题来了:
- 一个大 Prefill 请求会独占 GPU 数秒,导致其他 Decode 请求被"卡住",出现长尾延迟(P99 延迟飙升)
- Prefill 和 Decode 的最优批处理策略完全不同,无法同时满足
:::warning 干扰效应(Interference) 这不是小问题。在长 prompt 场景(如 RAG 检索增强、文档摘要),一次 Prefill 可能耗时超过 5 秒,此时所有 Decode 请求全部阻塞,用户看到的是长时间"没有响应"。 :::
PD 分离架构
解决方案是将 Prefill 和 Decode 部署在不同的 GPU 集群上:
用户请求
│
▼
┌─────────────┐ KV Cache ┌─────────────┐
│ Prefill 集群 │ ─────────────── ► │ Decode 集群 │
│ (算力优化) │ 高速网络传输 │ (带宽优化) │
└─────────────┘ └─────────────┘
- Prefill 节点:计算整个 prompt 的 KV Cache,使用大 batch 和高算力 GPU(如 H100)
- Decode 节点:接收 KV Cache,逐 token 生成,使用内存带宽更高的 GPU(如 A100)
- KV Cache 传输:通过 NVLink、InfiniBand 或 RDMA 高速网络传输,通常在毫秒级完成
代表实现
Mooncake(月之暗面,2024):将 KV Cache 传输延迟与 Decode 计算重叠(overlap),通过"以传代算"减少 Decode 等待时间。同时设计了分布式 KV Cache 池,支持跨节点共享前缀缓存。
DistServe(2024):系统性分析了 PD 分离的资源配比问题,提出根据实际请求负载动态调整 Prefill:Decode 节点比例。
:::tip 效果 PD 分离在长 prompt 场景下(prompt 长度 > 1000 token),P99 延迟可降低 40-60%,首 token 时间(TTFT, Time To First Token)和生成吞吐量同时改善。 :::
22.3 模型量化(Model Quantization)
为什么需要量化:显存的铁幕
一个直接的计算:FP16(16 位浮点数)格式下,模型权重每个参数占 2 字节。7B 模型有 70 亿参数,仅权重就需要 14GB 显存,加上 KV Cache 和激活值,往往需要 24GB 以上的 GPU。
量化(Quantization)将权重从高精度降到低精度:
| 格式 | 每参数字节 | 7B 模型权重大小 | 备注 |
|---|---|---|---|
| FP32 | 4 bytes | 28 GB | 训练常用 |
| FP16 / BF16 | 2 bytes | 14 GB | 推理标准 |
| INT8 | 1 byte | 7 GB | 精度几乎无损 |
| INT4 | 0.5 bytes | 3.5 GB | 轻微质量下降 |
| INT2 | 0.25 bytes | 1.75 GB | 质量损失较大 |
INT4 量化可让 7B 模型在单张 4GB 显存的 GPU 上运行——这使消费级硬件成为可能。
权重量化 vs 激活量化
权重量化(Weight Quantization):对模型权重矩阵做量化。权重在推理时不变,可以离线精确校准,难度较低。
激活量化(Activation Quantization):对前向传播中的中间激活值做量化。激活值随输入动态变化,存在异常值(outliers),量化难度大得多。
大多数实用方案选择仅做权重量化(Weight-Only Quantization),计算时将权重反量化回 FP16 再做矩阵乘法,或使用混合精度内核直接完成量化矩阵乘法。
GPTQ(2022):基于 Hessian 的逐层量化
GPTQ 的核心思想是逐层最小化量化误差。对于某一层权重矩阵 ,找到量化后的 ,使得:
其中 是该层的输入激活(使用少量校准数据计算)。
GPTQ 利用 Hessian 矩阵 来决定量化顺序和误差补偿:量化某个权重后,将其误差"传播"给同行的其他权重(Optimal Brain Compression 思想)。
这样在 INT4 精度下,Llama-2 70B 的困惑度(Perplexity)相比 FP16 仅上升约 0.1-0.3,几乎可以忽略。
AWQ(2023):保护重要权重
AWQ(Activation-aware Weight Quantization)的洞察是:不同权重对输出质量的重要性差异极大——与激活值幅度相关性高的权重(即"重要权重")一旦量化误差过大,对输出质量影响是灾难性的,仅占权重总数的 0.1-1%。
AWQ 的策略:
- 通过校准数据统计每个权重通道对应的激活幅度
- 对重要权重(高激活幅度对应的权重)进行缩放保护:量化前将权重除以 ,从而在量化步长固定时,重要权重获得更高的相对精度
- 其余权重正常激进量化到 INT4
AWQ 无需 GPU 反向传播,量化速度比 GPTQ 快约 10 倍,且在多数基准上精度更高。它是目前 llama.cpp 和 vLLM 生产部署中最常用的量化方案之一。
22.4 主流推理框架对比
推理框架的选择对系统性能影响巨大。当前主流框架各有侧重,适合不同场景。
vLLM
核心技术:PagedAttention + Continuous Batching(详见第 21 章)
- 优势:吞吐量业界最高,支持几乎所有主流开源模型,社区活跃,API 兼容 OpenAI
- 局限:多轮对话场景中前缀缓存(Prefix Caching)命中率有限;冷启动较慢
- 适用场景:高并发在线推理服务、API 网关
# vLLM 典型用法
from vllm import LLM, SamplingParams
llm = LLM(model="meta-llama/Llama-3-8B-Instruct")
outputs = llm.generate(["Hello, my name is"], SamplingParams(max_tokens=50))
TGI(Text Generation Inference)
开发方:Hugging Face
- 优势:工程成熟,与 HuggingFace Hub 生态深度集成,Docker 部署极简,支持 Tensor Parallelism
- 局限:吞吐量略低于 vLLM,定制化扩展难度较大
- 适用场景:快速原型验证,HuggingFace 生态用户,需要稳定企业支持的场景
SGLang
核心技术:RadixAttention(基数树前缀缓存)
RadixAttention 将 KV Cache 组织为一棵基数树(Radix Tree),不同请求共享相同前缀的 KV 只需计算一次。对于以下场景命中率极高:
- System prompt 固定的多轮对话(所有用户共享同一 system prompt 的 KV)
- Few-shot 样例固定的批量推理(所有样本共享 demonstration 部分)
- RAG 场景中重复出现的文档片段
System Prompt(共享) ──┐
├── 用户A的对话
├── 用户B的对话
└── 用户C的对话
- 优势:共享前缀场景下 TTFT 降低 50-80%,结构化输出(JSON mode)支持出色
- 适用场景:Agent 框架、多轮对话服务、批量推理
llama.cpp
核心技术:纯 C++ 实现,GGUF 格式量化权重
- 优势:可在 CPU 上运行,支持 Apple Silicon (Metal)、NVIDIA (CUDA)、AMD (ROCm) 等多种硬件;INT4/INT2 量化生态最完善;内存占用极低
- 局限:吞吐量远低于 GPU 专用框架,不适合高并发服务
- 适用场景:本地开发测试、边缘设备、无 GPU 环境、个人电脑运行大模型
选型建议
| 场景 | 推荐框架 | 核心理由 |
|---|---|---|
| 高并发 API 服务(无共享前缀) | vLLM | 吞吐量最优,生态完善 |
| 多轮对话 / Agent(固定 system prompt) | SGLang | RadixAttention 大幅降低重复计算 |
| 快速部署,HuggingFace 模型 | TGI | 一键 Docker,集成度高 |
| 本地运行 / 边缘设备 / 无 GPU | llama.cpp | 跨平台,CPU 友好 |
| 需要 PD 分离的超大规模集群 | vLLM + Mooncake | 生产级分布式推理 |
本章小结
| 技术 | 核心思想 | 典型收益 | 代价 |
|---|---|---|---|
| 投机采样(Speculative Decoding) | 小模型猜,大模型验 | 延迟降低 2-3× | 需要维护草稿模型 |
| PD 分离(PD Disaggregation) | Prefill 和 Decode 专机专用 | P99 延迟降低 40-60% | 需要高速网络互联 |
| GPTQ 量化 | Hessian 引导的逐层误差最小化 | INT4 精度损失 < 1% | 量化耗时较长 |
| AWQ 量化 | 保护高激活相关权重 | INT4 精度更高,速度更快 | 需要少量校准数据 |
| vLLM | PagedAttention + 连续批处理 | 吞吐量最优 | 前缀场景不占优 |
| SGLang | RadixAttention 前缀树复用 | 共享前缀场景延迟极低 | 非共享场景收益有限 |
| llama.cpp | C++ CPU 推理 + GGUF 量化 | 零 GPU 运行大模型 | 吞吐量低 |
推理优化的本质是在延迟、吞吐量、显存三个维度之间寻找帕累托最优解。没有万能银弹,只有根据场景特征做出的工程权衡。
到目前为止,我们讨论的都是如何更快、更省地运行一个已有的模型。但如果你的业务场景高度特殊,通用的预训练模型无法满足需求,又不想从头训练——这就引出了下一章的主题:高效微调(Fine-tuning),以及如何用极少的参数更新让大模型适配特定领域。