目标:系统掌握大语言模型推理加速与资源优化的核心技术,理解量化、KV Cache 优化、投机解码、批处理策略等如何协同解决推理中的显存带宽、计算延迟、吞吐量三大瓶颈。
前置要求:了解 Transformer 架构的基本概念(自注意力、前馈网络)和自回归生成的基本流程。阅读过本系列前两篇会更有帮助,但不是必须的。
本系列博文从三个维度系统拆解了大语言模型的全生命周期。第一篇建立了模型架构与训练的理论地基——注意力机制、损失函数、优化器、正则化;第二篇聚焦工程实践——预训练数据工程、分布式训练、混合精度、显存优化。本篇将视角转向模型训练完成后:如何让一个数十亿参数的模型在用户提问时快速响应、在有限硬件上高效运行、在各种设备上灵活部署?
推理阶段的核心挑战可以归结为一个矛盾:模型越大越聪明,但推理越慢越费资源。本文将围绕显存带宽、计算延迟、吞吐量三大瓶颈,系统讲解推理优化的关键技术。
1. 推理的挑战与本质
1.1 自回归生成的串行特性
大语言模型的推理过程是自回归的:每次只能生成一个 token,而每个 token 的生成都依赖前面所有已生成的 token。这意味着生成 100 个 token 就需要串行执行 100 次前向传播。
类比:抄写员。想象一个抄写员在抄书,他每次只能写一个字,而且必须先看清楚前面写了什么才能写下一个字。即使他写字很快,但 100 个字就要看 100 次前面的内容,这就是推理延迟的根本来源。
1.2 推理的两个阶段
大语言模型的推理可以分为两个截然不同的阶段:
预填充(Prefill)阶段:一次性处理用户的整个 prompt(输入文本),计算所有 token 的 Key 和 Value 并缓存起来。
- 特点:计算密集。需要处理大量 token 的矩阵乘法,GPU 的计算单元被充分利用。
- 类比:老师一次性阅读学生的整篇作文。
解码(Decode)阶段:从 prompt 的最后一个 token 开始,逐个自回归地生成回复 token。
- 特点:访存密集。每生成一个 token,都需要读取整个模型的权重和完整的 KV Cache,但实际计算量很小(只计算一个 token)。
- 类比:老师逐字批改——每改一个字都要翻阅整本参考书(模型权重)和学生的全部前文(KV Cache),但实际只是写一个批注。
1.3 瓶颈分析:访存带宽是关键
解码阶段的计算强度(FLOPs / 字节数)极低。以一个 7B 参数模型为例:
- 每生成一个 token,需要读取约 14GB 的模型权重(BF16 格式)。
- 但实际浮点运算量只有约 14 GFLOPs。
- A100 GPU 的计算能力是 312 TFLOPs,显存带宽是 2TB/s。
- 读取 14GB 需要 7ms,计算 14GFLOPs 只需 0.045ms。
- 瓶颈在显存读取,而非计算!
这意味着,推理优化的核心思路是减少数据搬运量:减少需要读取的模型大小(量化)、减少需要读取的 KV Cache 大小(KV Cache 优化)、减少需要执行的推理步数(投机解码)。
2. 模型量化——把"高精度字典"变薄
2.1 量化的基本概念
量化是将模型的权重和激活值从高精度浮点数(FP32/FP16)用更低位宽的整数(如 INT8、INT4)表示的技术。
类比:简笔画代替高清照片。一张 4K 照片占 10MB,但简笔画只占 10KB——虽然细节有所损失,但"主要内容"完全保留。量化就是用更少的比特来"画"模型的权重。
核心好处
- 显存减少:INT4 量化后,7B 模型的权重从 14GB(BF16)降到约 3.5GB。
- 访存加速:读取的数据量减少,直接缓解显存带宽瓶颈。
- 计算加速:INT8/INT4 整数运算在专用硬件(如 Tensor Core)上比浮点运算更快。
2.2 量化的数学基础
量化的核心公式:
$$ q = \text{round}\left(\frac{x}{s}\right) + z $$其中:
- $x$ 是原始浮点数值
- $s$ 是缩放因子(scale),控制浮点数到整数的映射范围
- $z$ 是零点(zero point),对齐浮点数中的 0 到整数中的某个值
- $q$ 是量化后的整数值
反量化(将整数恢复为浮点近似值):
$$ \hat{x} = s \cdot (q - z) $$对称量化:假设 $z = 0$,浮点数的 0 对应整数的 0,公式简化为 $q = \text{round}(x/s)$。
非对称量化:保留 $z$,适用于分布不对称的情况(如 ReLU 激活值只有正值)。
2.3 PTQ vs QAT
| 方法 | 全称 | 思路 | 适用场景 |
|---|---|---|---|
| PTQ | 训练后量化 | 用少量校准数据统计权重/激活分布,直接量化 | LLM 的主流选择 |
| QAT | 量化感知训练 | 在训练时模拟量化误差,让模型学会适应 | 需要从头训练,成本高 |
对于大语言模型,PTQ 是主流——因为从头训练一个大模型的成本太高,而 PTQ 只需要一小批校准数据(几百到几千条样本)就能完成量化。
2.4 权重量化与激活量化的区别
这两个概念经常被混淆,但它们解决的是不同的问题:
- 权重量化:只将模型权重从 FP16 量化为 INT4/INT8,存储时节省显存,但计算时仍需反量化为 FP16 执行矩阵乘法。节省显存,但不一定加速计算。
- 激活量化:将激活值(矩阵乘法的输入)也量化为整数。这样矩阵乘法可以完全在整数计算单元上执行。同时节省显存和加速计算。
激活量化的难点在于:激活值的分布比权重更不规则,容易出现异常值(outlier)——少数极大或极小的值,导致量化精度崩溃。
2.5 主流量化方法
GPTQ——权重量化的经典
GPTQ 基于近似海森矩阵信息,逐层对权重进行量化。核心思想是:在量化某个权重时,通过调整同层其他未量化权重来补偿量化误差,使整体输出误差最小化。
- 输入:FP16 模型 + 少量校准数据(128~256 条)
- 输出:INT4 量化模型
- 特点:速度快(几分钟即可完成一个 7B 模型的量化),效果好,是权重量化的标准方法
AWQ(激活感知权重量化)
AWQ 的核心洞察:不是所有权重同等重要——那些对应激活值较大的权重通道更关键,量化时应该给予更多保护。
方法:观察校准数据的激活分布,找到"关键通道",对这些通道的权重使用更高精度(或做缩放后再量化),其余通道正常量化。
- 优势:在极低比特(如 INT3/INT4)下比 GPTQ 更好地保留精度。
SmoothQuant——实现 W8A8
SmoothQuant 解决的是激活量化的难题。核心技巧是"平滑转移":
$$ Y = (X \cdot \text{diag}(s)^{-1}) \cdot (\text{diag}(s) \cdot W) = \hat{X} \cdot \hat{W} $$通过引入平滑因子 $s$,将激活中的异常值"转移到"权重上。因为权重的分布更规则、更容易量化,所以转移后再做 W8A8 量化效果很好。
类比:均匀搅拌。激活值中有些"特别突出"的异常值,就像果汁里有几块没搅开的果肉。SmoothQuant 的做法是把果肉搅匀(平滑转移),让整杯果汁的浓度更均匀,这样用统一的容器(量化精度)就能准确量取。
LLM.int8()(bitsandbytes)
另一种思路:对异常值特殊处理。
- 找出激活中维度最大的 0.1% 的特征通道(异常值通道)。
- 这些通道保持 FP16 精度。
- 其余 99.9% 的通道正常量化为 INT8。
- 通过混合精度矩阵乘法实现加速。
2.6 量化的精度损失与恢复
量化不可避免地会引入精度损失,但程度因任务而异:
- 知识密集型任务(如事实问答):对量化更敏感,因为数值微小的变化可能导致输出不同的知识。
- 生成质量(如流畅度、连贯性):通常影响较小,INT4 量化后仍能生成流畅文本。
- 数学/编程推理:对精度较敏感,低比特量化可能导致推理错误。
恢复手段:在量化后的模型上做少量微调(如 QLoRA),可以部分恢复精度损失。
3. KV Cache 优化——给注意力记忆"瘦身"
3.1 KV Cache 原理回顾
在第一篇中我们介绍了因果注意力和 KV Cache 的基本概念。在解码阶段,每生成一个新 token,注意力需要访问所有历史 token 的 Key 和 Value。为了避免重复计算,这些 K、V 向量被缓存下来,即 KV Cache。
3.2 KV Cache 的显存压力
KV Cache 的显存公式:
$$ \text{KV Cache 大小} = 2 \times L \times n_{\text{heads}} \times d_{\text{head}} \times \text{seq\_len} \times \text{batch\_size} \times \text{bytes\_per\_param} $$以 LLaMA-2 70B 为例(80 层,64 头,维度 128,BF16):
$$ 2 \times 80 \times 64 \times 128 \times 4096 \times 2 \text{ bytes} \approx 10.7 \text{ GB per sequence} $$如果 batch size 为 32,KV Cache 就需要 342 GB——远超模型权重本身!
KV Cache 已经成为长序列推理中最大的显存瓶颈。
3.3 多查询注意力(MQA)与分组查询注意力(GQA)
MQA(Multi-Query Attention)
在标准的多头注意力中,每个头都有自己独立的 Q、K、V 投影。MQA 的做法是:所有头共享同一组 K 和 V,只有 Q 保持独立。
$$ \text{MQA}: \quad Q_1, Q_2, \ldots, Q_h \quad \text{共享同一个} \quad K, V $$效果:KV Cache 的大小从 $h \times d_{\text{head}}$ 缩减为 $1 \times d_{\text{head}}$,压缩了 $h$ 倍。
类比:共享笔记。在标准多头注意力中,每个"专家"(头)都记自己的笔记(K、V);MQA 中所有专家共享一份笔记,虽然可能有信息损失,但存储成本大幅降低。
GQA(Grouped-Query Attention)
MQA 的极端共享可能导致表达力下降。GQA 是折中方案:将 $h$ 个头分成 $g$ 组,每组共享一组 K、V。
$$ \text{GQA}: \quad \underbrace{Q_1, \ldots, Q_{h/g}}_{\text{共享 } K_1, V_1}, \quad \underbrace{Q_{h/g+1}, \ldots, Q_{2h/g}}_{\text{共享 } K_2, V_2}, \quad \ldots $$当 $g = 1$ 时退化为 MQA;当 $g = h$ 时退化为标准 MHA。LLaMA-2 70B 和 LLaMA-3 使用 $g = 8$ 的 GQA,在效率和性能间取得了良好平衡。
3.4 KV Cache 的量化与驱逐
KV Cache 量化:KV Cache 本身也可以量化存储。例如,将 K、V 从 BF16 量化为 INT8 甚至 INT4,显存进一步压缩 2~4 倍。由于 K、V 的分布通常比激活值更稳定,量化精度损失较小。
滑动窗口注意力(Sliding Window Attention):只保留最近 $W$ 个 token 的 KV Cache,丢弃更早的。这在 Mistral 等模型中被使用。
驱逐/丢弃策略
- H2O(Heavy-Hitter Oracle):根据注意力分数,保留"高注意力"的 token 的 KV,丢弃"低注意力"的。
- StreamingLLM:发现注意力分布中第一个 token(“注意力汇聚点”)和最近的窗口最重要,保留这两部分即可。
3.5 PagedAttention——vLLM 的核心创新
传统 KV Cache 管理的一个严重问题是内存碎片化:每个请求需要连续的显存空间存储 KV Cache,但请求的长度不同,导致显存中充满碎片——就像停车场中大小不一的空位,虽然总面积够用但停不进大车。
PagedAttention 借鉴了操作系统的虚拟内存分页机制:
- 将 KV Cache 分成固定大小的页(Page),每页存储固定数量 token 的 K、V。
- 不同请求的 KV Cache 页可以不连续存储在物理显存中。
- 通过页表(Page Table) 维护逻辑到物理的映射。
类比:虚拟内存。就像操作系统中,每个程序以为自己有连续的内存空间,但实际物理内存是分散的——PagedAttention 让每个请求以为自己的 KV Cache 是连续的,但实际显存是分页管理的。
效果
- 显存利用率:从约 60%~70% 提升到 95% 以上(几乎消除了碎片)。
- 吞吐量:可以在同样的显存中服务更多的并发请求,吞吐量提升 2~4 倍。
4. 投机解码——用小模型给大模型"打草稿"
4.1 动机
自回归生成的串行特性是推理延迟的根本瓶颈。如果能一次验证多个 token,就能大幅减少前向传播的次数。
类比:老师批改作文。逐字批改很慢(标准自回归)。如果学生先自己写一份草稿(草稿模型),老师只需通读一遍确认哪些字写对了(并行验证),不对的字再改——这样老师的工作量大幅减少。
4.2 原理
投机解码(Speculative Decoding)的核心流程:
- 草稿阶段:使用一个轻量级的"草稿模型"(Draft Model),快速自回归生成 $K$ 个候选 token:$\tilde{x}_1, \tilde{x}_2, \ldots, \tilde{x}_K$。
- 验证阶段:将这 $K$ 个候选 token 一次性输入大模型(Target Model),大模型并行计算每个位置的条件概率。
- 接受/拒绝:从第一个 token 开始,按顺序检查:
- 如果大模型对该 token 的概率不低于草稿模型的预测概率,接受。
- 如果不满足,用拒绝采样决定是否接受,或者从修正后的分布中重新采样。
- 结果:每轮至少前进 1 步(即使所有草稿都被拒绝),最多前进 $K + 1$ 步。
关键性质:通过精心设计的接受/拒绝条件,投机解码保证生成的分布与直接用大模型生成完全一致,是无损加速。
4.3 接受条件的数学描述
对于草稿 token $\tilde{x}$ 在位置 $t$,大模型概率为 $p(x_t = \tilde{x})$,草稿模型概率为 $q(x_t = \tilde{x})$:
$$ \text{接受概率} = \min\left(1, \frac{p(\tilde{x})}{q(\tilde{x})}\right) $$- 当 $p \geq q$ 时(大模型比草稿模型更"确定"这个 token),概率为 1,一定接受。
- 当 $p < q$ 时(草稿模型过于自信),以 $p/q$ 的概率接受。
如果被拒绝,从修正分布 $\max(0, p(x) - q(x))$ 归一化后重新采样。
4.4 草稿模型的选择
| 方案 | 描述 | 优缺点 |
|---|---|---|
| 独立小模型 | 使用同架构的小模型(如 7B 给 70B 打草稿) | 简单,但需要额外加载模型 |
| 提前退出(Self-Speculative) | 大模型的中间层输出直接作为草稿 | 无需额外模型,但草稿质量可能较低 |
| Medusa | 在大模型上添加多个预测头,同时预测未来多个位置 | 无需草稿模型,训练额外的预测头 |
| Lookahead Decoding | 利用 Jacobi 迭代的思想,并行猜测多个位置 | 无需草稿模型,但接受率较低 |
4.5 令牌树与分支验证
更激进的策略是令牌树(Token Tree):草稿模型不是生成一条线性的候选序列,而是生成一棵树——在每个位置保留多个候选 token,形成多条分支。
大模型对整棵树做一次并行验证,沿着概率最高的路径选择接受的 token。这提高了"命中率"(至少有一条分支被接受的概率更高),代价是需要更大的计算量。
4.6 实际加速效果
投机解码的加速比取决于:
- 草稿模型与大模型的一致性:一致性越高(接受率越高),加速越大。
- 草稿模型的速度:草稿模型越快,每次"打草稿"的时间越短。
- 实际经验:2~3 倍的加速是常见的,对于某些任务(如代码生成)可达 4 倍以上。
5. 批处理策略与吞吐优化
5.1 静态批处理(Static Batching)
最简单的方案:等待一批请求凑齐后一起推理。但问题是——短请求完成后必须等待最长的请求结束才能处理下一批,GPU 在等待期间处于空闲状态。
类比:班车。班车必须等所有乘客到齐才能发车——即使有人只坐一站就下车,也得等最后一站的乘客到达。
5.2 连续批处理(Continuous Batching)
连续批处理(也叫 In-flight Batching)是推理服务的关键优化:当一个请求完成生成后,立即插入新的请求到当前批次中,无需等待整批结束。
flowchart LR
subgraph 静态批处理
R1["请求1: 3 tokens"] --> W1["等待最长请求"]
R2["请求2: 10 tokens"] --> W1
R3["请求3: 5 tokens"] --> W1
W1 --> N1["开始下一批"]
end
subgraph 连续批处理
R4["请求1: 3 tokens"] --> D1["完成后立即插入新请求"]
R5["请求2: 10 tokens"]
R6["请求3: 5 tokens"]
D1 --> R7["请求4: 即刻开始"]
end
效果:GPU 利用率从 50%~70% 提升到 90%+,吞吐量显著提升。vLLM、TensorRT-LLM 等框架都支持连续批处理。
5.3 Prefill 与 Decode 的混合调度
Prefill 和 Decode 的计算特征截然不同:
| 特性 | Prefill | Decode |
|---|---|---|
| 计算模式 | 计算密集 | 访存密集 |
| 并行度 | 高(处理整个 prompt) | 低(只处理 1 个 token) |
| 延迟要求 | 低(用户在等待首次输出) | 中(已开始流式输出) |
如果不加区分地混合调度,Prefill 的突发大量计算可能阻塞正在 Decode 的请求,导致延迟抖动。
解决方案包括优先级调度(优先处理 Prefill 以降低首 token 延迟)和分块 Prefill(将长 prompt 的 Prefill 分成多个小块穿插执行)。
5.4 分离式架构
更进一步的优化是将 Prefill 和 Decode 分离部署到不同的 GPU 上:
- Prefill 节点:配置高计算能力的 GPU,专门处理 prompt 的预填充。
- Decode 节点:配置高显存带宽的 GPU,专门处理自回归解码。
这样每个节点可以针对各自的瓶颈做专门优化,整体效率更高。
6. 推理框架与部署架构
6.1 主流推理框架对比
| 框架 | 核心特性 | 适用场景 | 语言 |
|---|---|---|---|
| vLLM | PagedAttention、连续批处理 | 高吞吐在线服务 | Python |
| TensorRT-LLM | C++ 编译优化、极致性能 | 对延迟要求极高的生产环境 | C++/Python |
| llama.cpp | 纯 CPU 推理、GGUF 量化格式 | 本地/边缘设备部署 | C/C++ |
| HuggingFace TGI | 开箱即用、与 HF 生态集成 | 快速原型和中等规模服务 | Rust/Python |
| SGLang | RadixAttention、结构化生成优化 | 复杂推理管线 | Python |
6.2 推理服务化
生产环境中的推理服务需要关注:
- RESTful API:提供标准的 HTTP 接口,兼容 OpenAI API 格式。
- 流式输出(Streaming):通过 Server-Sent Events(SSE)逐 token 返回生成结果,降低用户感知延迟。
- 负载均衡:将请求分发到多个推理实例,避免单点过载。
- 自动扩缩容:根据请求量动态调整 GPU 实例数量,平衡成本和性能。
6.3 边缘部署
将大语言模型部署到端侧设备(手机、浏览器、嵌入式设备)是一个快速发展的方向:
llama.cpp:Georgi Gerganov 开发的纯 C/C++ 推理引擎,支持 GGUF 量化格式,可以在 CPU 上高效运行。通过 Metal(macOS)、CUDA 等后端利用 GPU 加速。
MLC LLM:将模型编译为各种硬件的原生代码(CUDA、Metal、WebGPU),支持在浏览器中运行。
端侧的特殊挑战
- 内存极有限(手机通常 6~12GB 共享内存)。
- 功耗敏感,不能长时间满载。
- 通常使用 4-bit 甚至更激进的量化。
7. 端侧推理与硬件加速
7.1 硬件适配
NPU(神经网络处理单元):手机 SoC 中的专用 AI 加速器(如 Apple Neural Engine、高通 Hexagon),针对低比特整数运算优化。
Apple Silicon 的统一内存优势:M 系列芯片的 CPU 和 GPU 共享同一块物理内存,模型不需要在 CPU 内存和 GPU 显存之间拷贝——这对大模型推理是一个显著优势,因为显存带宽不再是瓶颈。
7.2 内存卸载(Offloading)
当模型太大装不进 GPU 显存时,可以将部分层卸载到 CPU 内存甚至 SSD:
- CPU Offloading:将不常用的层(如 FFN)放到 CPU 内存,需要时再加载到 GPU。
- SSD Offloading:利用 NVMe SSD 的高带宽(~7GB/s),将部分参数存储在 SSD 上。
类比:书架与书桌。GPU 显存是书桌(容量小但取用快),CPU 内存是书架(容量大但要走过去拿),SSD 是仓库(容量最大但取用最慢)。聪明的做法是把最常用的书放在桌上,不常用的放回书架。
代价:卸载引入了额外的数据传输延迟,推理速度会明显下降。但在"跑不起来"和"跑得慢"之间,“跑得慢"是更好的选择。
7.3 模型剪枝与蒸馏
模型剪枝(Pruning):移除模型中不重要的参数或结构。
- 非结构化剪枝:将单个权重置零,形成稀疏矩阵。需要专门的稀疏计算硬件才能加速。
- 结构化剪枝:移除整个注意力头、FFN 神经元或层。可以直接减小模型大小,通用性更强。
知识蒸馏(Knowledge Distillation):用大模型(教师模型)的输出来训练小模型(学生模型),让小模型学习大模型的"知识”。
- 常见做法:用 GPT-4 的输出训练 7B 模型,让 7B 模型接近 GPT-4 的能力。
- 蒸馏后的小模型天然适合推理——更小、更快、更容易部署。
8. 全文总结——推理优化的技术全景
flowchart TD
B1["显存带宽瓶颈"] --> Q["模型量化(GPTQ/AWQ/SmoothQuant)"]
B1 --> KV["KV Cache 优化(GQA/PagedAttention)"]
B1 --> SD["投机解码(减少推理步数)"]
B2["计算延迟瓶颈"] --> SD
B2 --> MP["批处理策略(连续批处理)"]
B2 --> SP["Prefill/Decode 分离"]
B3["吞吐量瓶颈"] --> MP
B3 --> KV
B3 --> Q
Q --> D["高效推理服务"]
KV --> D
SD --> D
MP --> D
D --> E1["云端部署(vLLM/TRT-LLM)"]
D --> E2["边缘部署(llama.cpp/MLC LLM)"]
量化(INT4/INT8)减少模型权重的存储和搬运量,直接缓解显存带宽瓶颈。
KV Cache 优化(GQA、PagedAttention、驱逐策略)减少推理过程中最大的显存消耗者,提升并发能力。
投机解码通过小模型打草稿 + 大模型并行验证,减少自回归推理的串行步数,降低延迟。
批处理策略(连续批处理、Prefill/Decode 分离)提升 GPU 利用率和吞吐量。
推理框架(vLLM、TensorRT-LLM、llama.cpp)将上述技术集成到开箱即用的工具中。
这些技术并非相互独立——在实际部署中,它们通常组合使用:一个典型的生产级推理服务可能同时使用 AWQ INT4 量化 + GQA + PagedAttention + 连续批处理 + 投机解码,将推理延迟降低 5-10 倍,吞吐量提升 3-5 倍。
系列导航:
- 第一篇:从因果注意力到正则化——架构设计、损失函数、优化器、正则化
- 第二篇:工程实践核心技术——预训练、SFT/RLHF/DPO、分布式训练、混合精度
- 第三篇(本文):高效推理与轻量部署——量化、KV Cache 优化、投机解码、推理框架