外观
什么是推理加速
一句话定义:推理加速(Inference Acceleration)是把训练好的模型权重变成生产可服务、能在延迟与成本约束下吞吐真实流量的系统的全部工程——训练解决"模型会不会",推理解决"模型能不能用得上、用得起、用得稳"。
你写过的最"推理"的代码可能是一行 model.generate(inputs)。拆开看这行代码:它不是在算梯度,而是用一组已经固定下来的参数,对输入做一次前向计算,得到输出。整个推理工程学科的起点,就是这行代码背后的张力:训练算的是 FLOPs 多少,推理算的是访存多贵、batching 多紧、用户等多久。一行 model.generate 在 HuggingFace Transformers 上跑 Llama-2-70B,单请求吞吐约 20 tokens/s;同样的权重,在 vLLM 上能跑到 2000+ tokens/s 的聚合吞吐——100 倍差距不在模型,而在系统。
一、定义:从三个层面理解
1. 实用层:做什么
推理系统接收输入(prompt/图像/特征),产出输出(token/标签/分数),但它的独特之处在于它是一项"在线服务":
训练: 数据 + 标签 ──→ 模型权重(离线、批处理、追求总吞吐)
推理: 模型权重 + 输入 ──→ 输出(在线、并发、追求单请求延迟 × 总吞吐)训练是"在实验室里把模型教会",推理是"把毕业的模型送上岗"。凡是"模型已经训好、要让它对真实流量服务"的场景——聊天 API、搜索排序、推荐召回、图像识别、语音转写——都是推理工程的主场。推理工程师的日常不是调损失函数,而是和延迟、显存、吞吐、 batching、量化打交道。
2. 学术层:从计算图视角的定义
从计算图角度看,推理是在计算图上执行一次前向传播(forward pass),不记录梯度、不更新参数:
给定训练好的参数 θ* 和输入 x,推理是计算 y = f(x; θ*) 的过程。其中 f 是模型定义,θ* 是已固定的权重张量集合,x 是新输入。整个过程的算力可以精确刻画为 FLOPs = 2 × P × S(P 为参数量,S 为序列长度,对 Transformer 而言),且对同一 (θ*, x) 结果可复现。
这个定义的力量在于它给出了推理优化的三个可量化目标:
| 目标 | 含义 | 衡量 |
|---|---|---|
| 延迟 (Latency) | 单请求从输入到首 token / 末 token 的时间 | TTFT、TPOT、E2E latency(见延迟与吞吐) |
| 吞吐 (Throughput) | 单位时间处理的请求数或 token 数 | tokens/s、QPS、requests/min |
| 显存占用 (Memory) | 模型权重 + 激活 + KV cache 占用的显存 | GB(见访存与带宽) |
三者构成推理不可能三角——延迟低、吞吐高、显存省通常不可兼得,所有优化都是在这三者之间做权衡。详见Roofline 模型。
3. 数学层:访存瓶颈视角
从数学上看,推理优化的核心问题不是"算得快",而是"喂得饱":
一个 Transformer 解码步的算术强度约为
2 × batch × seq_len × hidden_dim(FLOPs)/2 × batch × seq_len × hidden_dim × bytes_per_param(访存字节)。当 batch=1、bytes_per_param=2(FP16)时,算术强度约为 1 FLOP/byte——远低于 GPU 的 roofline 拐点(A100 约 100 FLOPs/byte),因此单请求 LLM 推理是**访存瓶颈(memory-bound)**的。
这个结论是推理工程最重要的一个事实:LLM 单请求解码不是在算,而是在等显存把权重和 KV cache 搬到 SM。所有 LLM 推理优化——batching、量化、KV cache 压缩、算子融合——本质上都是在抬高算术强度。详见访存与带宽与Roofline 模型。
为什么推理"难"?
训练难的根源是算力大、收敛难;推理难的根源是算力分布极不均匀——同一个模型,单请求解码时算术强度 ~1(访存瓶颈),prefill 阶段算术强度 ~100(算力瓶颈),批量推理时又回到算力瓶颈。一套系统要同时把三种 workload 都喂饱,这是推理工程比训练工程更"碎"的根本原因。
二、推理 vs 训练:算力分布、访存特征、batching 差异
推理和训练看似都是"跑神经网络前向",但工程上几乎是两个领域。最关键的三个差异:
1. 算力分布:训练密集均匀,推理稀疏不均
训练一个 epoch 是"对每个样本算一次前向 + 一次反向",反向的算力约是前向的 2 倍,且所有样本同等对待——算力分布均匀且密集,GPU 利用率可以稳定压到 50-60% MFU。
推理完全相反。LLM 推理分两个阶段(详见推理 vs 训练 vs 微调):
- Prefill 阶段(处理 prompt):算力密集,batch=1 时算术强度也高,GPU 利用率能到 60%+;
- Decode 阶段(生成 token):每个步只算一个 token,batch=1 时算术强度 ~1,GPU 利用率常常不到 5%。
所以同一个 Llama-2-70B 模型,prefill 一次 1k token prompt 的延迟可能只有 200ms,但 decode 200 个 token 要花 4 秒——decode 才是延迟大头,而 decode 几乎在等显存。
2. 访存特征:训练复用权重,推理每次重读
训练时一个 batch 内权重只读一次,算力/访存比由 batch size 抬高;推理 decode 阶段每个 token 都要把全部权重读一遍——70B 模型 FP16 是 140GB,每生成一个 token 就要从 HBM 搬一次 140GB 到 SM,A100 80GB 的 2TB/s 带宽下,单个 token 的访存下限就是 70ms。这就是为什么单请求 LLM 推理的 token/s 上限大致等于 HBM 带宽 / 模型大小——和算力几乎无关。
3. Batching 差异:训练静态,推理连续
训练的 batch size 在一个 step 内是固定的(static batching),可以开到几百上千;推理的请求是用户随时来、长度各不相同的——如果用静态 batching,要么等凑齐 batch(延迟爆炸),要么强制截断(体验崩溃)。这就是 vLLM 的 PagedAttention 与 continuous batching 解决的核心问题:让请求动态加入和退出 batch,把"长度不一的 N 个请求"塞进同一 GPU 一起算,把 decode 的算术强度从 ~1 抬到 ~50+。
完整的训练 vs 推理对照见推理 vs 训练 vs 微调。
三、四大优化层次(模型层 / 算子层 / 图层 / 系统层)
推理优化不是某一招,而是一棵四层的优化树。任何"推理加速"手段都可以归到这四层之一:
text
┌─────────────────────────────────────────────────────────────┐
│ 系统层 │ batching · scheduling · KV cache · 路由 · 多模型 │
│ │ 代表:vLLM continuous batching、PagedAttention │
├─────────┼───────────────────────────────────────────────────┤
│ 图层 │ 算子融合 · 常量折叠 · 死代码消除 · 内存规划 │
│ │ 代表:TensorRT graph fusion、ONNX 图优化 │
├─────────┼───────────────────────────────────────────────────┤
│ 算子层 │ FlashAttention · 手写 CUDA kernel · 算子融合 │
│ │ 代表:FlashAttention-2/3、FlashInfer │
├─────────┼───────────────────────────────────────────────────┤
│ 模型层 │ 量化 · 剪枝 · 蒸馏 · 稀疏化 │
│ │ 代表:GPTQ/AWQ INT4、SmoothQuant、模型蒸馏 │
└─────────┴───────────────────────────────────────────────────┘
▲ 上层收益大但门槛高,下层收益直接但需要硬件知识 ▼
优化顺序应自下而上,但实战中常四层叠加模型层:把模型本身变小
- 量化(Quantization):FP16 → INT8/INT4/FP8,直接把权重和访存量砍 2-4 倍,是性价比最高的优化。详见模型量化基础、权重 only 与混合精度。
- 剪枝(Pruning):去掉不重要的权重/通道/头,结构化剪枝能真省算力,非结构化需要稀疏核支持。详见剪枝。
- 蒸馏(Distillation):用大模型教小模型,让小模型逼近大模型质量。详见知识蒸馏。
算子层:把单个算子写快
- FlashAttention:把 attention 的中间矩阵分块到 SRAM 计算,避免 HBM 来回读写——单算子 2-4x 加速,是 LLM 推理的标配。详见算子融合。
- 手写 CUDA/Triton kernel:针对自家模型的形状特制核,vLLM、SGLang、FlashInfer 都大量使用。
- 算子融合:把多个相邻算子合并成一个 kernel,减少 kernel launch 与访存。
图层:把整张计算图重排
- 算子融合(图级):在图层级识别可融合模式(Conv+BN+ReLU、MatMul+Add+GELU),由 TensorRT、ONNX Runtime 自动完成。
- 常量折叠:编译期把常量计算掉(如
2 × 3 × x折成6 × x)。 - 内存规划:重排算子顺序、复用激活显存,把峰值显存压下来。详见图优化。
系统层:把多请求一起喂饱
- Continuous batching:让长短不一的请求动态进出 batch,是 vLLM/TGI/SGLang 的核心机制。
- KV cache 管理:PagedAttention 把 KV cache 像虚拟内存一样分页管理,显存利用率从 20% 拉到 90%+。
- 投机解码(Speculative Decoding):用小模型/草稿模型先猜几个 token,大模型并行验证,2-6x 加速。详见投机解码案例。
- 多模型路由:简单请求走小模型,复杂请求走大模型,整体成本下降。详见模型服务。
四层叠加才是一线生产
真实生产系统不是"选某一层",而是四层叠加。例如一个典型的 Llama-3-70B 服务:底层 H100 FP8(硬件层)→ AWQ INT4 量化(模型层)→ FlashAttention-3 + FlashInfer(算子层)→ TensorRT-LLM 图融合(图层)→ continuous batching + PagedAttention + EAGLE-3 投机解码(系统层)。四层叠加后,单 H100 节点能从 HuggingFace 的 ~20 tokens/s 推到 ~3000 tokens/s——150 倍差距全在工程。
四、一个最小推理系统:手写 forward + batched forward + KV cache
剥掉 vLLM 和 transformers 的所有封装,一个能跑的 LLM 推理循环只需要 60 行 Python。这一节我们手写一遍,看清推理工程的三个层次:单请求 forward、batched forward、带 KV cache 的自回归生成。
1. 单次前向:最朴素的推理
python
import torch
import torch.nn.functional as F
# 假设 model 是一个已加载好权重的 Transformer(任何框架都可以)
model = load_model("llama-7b", dtype=torch.float16, device="cuda")
input_ids = torch.tensor([[1, 1500, 2500, 980]], device="cuda") # 一个 prompt
# 最朴素的推理:一次 forward,拿 logits,argmax 出下一个 token
with torch.no_grad():
logits = model(input_ids) # [1, seq_len, vocab_size]
next_token = logits[0, -1].argmax(-1) # 取最后一个位置的 logits
print(next_token) # 这就是一个 greedy decode 步这一步什么都没优化,但它已经暴露了核心问题:每次 forward 都要重新算整个序列的 attention,第 2 个 token 时算了前 1 个,第 3 个 token 时又算了前 2 个——O(N²) 重复计算。对 1000 token 的 prompt 生成 200 个 token,相当于算了 1000 + 1001 + ... + 1200 ≈ 220k 次 attention,而真正需要的新计算只有 200 × (1000 + 200) ≈ 240k——重复率接近一半。
2. 批处理:把多个请求一起算
python
# 三个请求,长度不同,需要 padding 到等长
prompts = [
[1, 1500, 2500, 980], # 长度 4
[1, 980, 2333, 880, 1024, 55], # 长度 6
[1, 733], # 长度 2
]
max_len = max(len(p) for p in prompts)
# 左 padding(attention mask 让模型看不见 pad token)
input_ids = torch.full((3, max_len), pad_id, device="cuda")
attention_mask = torch.zeros(3, max_len, device="cuda")
for i, p in enumerate(prompts):
input_ids[i, max_len - len(p):] = p
attention_mask[i, max_len - len(p):] = 1
with torch.no_grad():
logits = model(input_ids, attention_mask=attention_mask)
next_tokens = logits[:, -1, :].argmax(-1) # 三个请求各拿各的 next tokenbatch=3 时算术强度提升 3 倍——但代价是 padding 浪费、且必须等最慢请求算完。这就是 static batching 的痛点,也是 continuous batching 要解决的。
3. KV cache:避免重复计算
python
# 关键:让模型把每层的 K、V 缓存下来,下一步只算新 token
past_key_values = None
generated = list(prompts[0]) # 复制第一个 prompt 作为已生成序列
for _ in range(20): # 生成 20 个 token
input_ids = torch.tensor([[generated[-1]]], device="cuda") # 只喂新 token
with torch.no_grad():
out = model(input_ids, past_key_values=past_key_values, use_cache=True)
logits = out.logits[:, -1, :]
past_key_values = out.past_key_values # 累积 KV cache
next_token = logits.argmax(-1).item()
generated.append(next_token)加了 KV cache 后,每步只算 1 个 token 的前向——算力下降 O(seq_len) 倍,但访存变成每步读整个 KV cache。这是 LLM 推理的最关键数据结构,PagedAttention 就是在它上面做分页管理。
真正动手跑一遍
上面三段代码在任意一个有 GPU 的环境都能跑。建议用 transformers 的 LlamaForCausalLM 试一遍:依次加上 use_cache=False/True、batch=1/4/16,记录每一步的 tokens/s 和显存峰值——你会发现 KV cache 是 LLM 推理的命脉,batch size 是吞吐的命脉。完整的实战流程见从零构建推理服务。
五、为什么推理优化"有效":三个实证刻度
"推理优化能加速"不是宣传口号,是可以用公开数据检验的事实。三个里程碑式的实证:
1. TensorRT vs 原生 PyTorch:2-4x
TensorRT 是 NVIDIA 的推理引擎,对同一模型做图融合 + 算子融合 + 精度优化。在 ResNet-50 上,TensorRT INT8 相比 PyTorch FP32 通常 2-4x 吞吐提升;在 BERT-large 上,TensorRT 相比 PyTorch FP16 约 3x 吞吐提升。这个加速不需要改模型、不需要换硬件,纯工程优化。详见TensorRT 案例。
2. vLLM PagedAttention:10-20x
2023 年 6 月,UC Berkeley 的 vLLM 论文《Efficient Memory Management for LLM Serving with PagedAttention》发布,核心是把 KV cache 像操作系统的虚拟内存一样分页管理,把显存利用率从 ~20% 拉到 ~90%,同时支持 continuous batching。实测在 Llama-2-70B 上,vLLM 相比 HuggingFace Transformers(FP16)的吞吐提升约 14-24x——这是 LLM 推理工程化的引爆点。详见vLLM 与 PagedAttention。
3. EAGLE-3 投机解码:2-6x
2024-2025 年,投机解码(speculative decoding)从理论走向工程。EAGLE 系列用一个小型"草稿头"猜几个 token,大模型并行验证,验证通过的 token 一次性接收。EAGLE-3 在 Llama-3-70B 上实测 2-6x 加速,且不改变输出分布——这是一个"既要又要"的优化。详见投机解码案例。
三个刻度叠加起来:原生 PyTorch (1x) → TensorRT (3x) → vLLM (30x) → + EAGLE-3 (90x)。90 倍差距全在工程,模型权重一字未改——这就是推理工程作为独立学科存在的理由。
一个诚实的反面
推理优化的收益是有条件的:量化在敏感模型上会掉点(尤其是 INT4 权重 only 对激活异常值敏感);投机解码在算力已经喂饱时收益小(受 HBM 带宽限制);PagedAttention 在 batch=1 时收益不如大 batch。所有优化都有它的 sweet spot,离开 workload 谈加速倍数都是耍流氓。系统化的陷阱清单见常见陷阱与反模式。
六、本手册怎么用
推理加速是工程、算法与硬件三者的交汇,本站按"建立概念 → 理解机制 → 拆解案例 → 动手实践"的顺序组织:
- 导读(你在这里):接着读推理 vs 训练 vs 微调切清三种 workload 的边界,用演进简史建立时间线,再用总体架构解剖建立全站地图,最后按学习路径选一条路线对号入座。
- 核心知识:从延迟与吞吐 → 访存与带宽 → Roofline 模型 打下性能分析地基;然后量化、剪枝、蒸馏 拆模型层;算子融合、图优化 拆算子图层;GPU 优化、批处理与调度、模型服务 拆系统硬件层。
- 案例拆解:把抽象概念落到具体引擎——TensorRT、ONNX Runtime、OpenVINO、vLLM、TensorRT-LLM、投机解码、llama.cpp、移动端部署、Triton Server、分布式推理。
- 动手实践:从零构建推理服务、渐进式教程、基准测试、作品集项目、引擎对比、调优实战、设计原则、常见陷阱。
- 随时查阅:术语表、硬件入门、基准数据、精选资源。
延伸阅读
- 推理 vs 训练 vs 微调——本文第二节边界辨析的完整展开
- 总体架构解剖——推理系统的五层架构地图
- 演进简史——从 CPU 推理到 LLM 推理革命的七十年
- 延迟与吞吐——推理性能指标的精确定义
- 访存与带宽——为什么 LLM 推理是访存瓶颈
- 模型量化基础——模型层优化的核心手段
- vLLM 与 PagedAttention——系统层优化的标杆案例
- 从零构建推理服务——把本文的代码片段扩成完整 demo
参考资料
- Kwon et al. Efficient Memory Management for LLM Serving with PagedAttention (SOSP 2023) —— vLLM 与 PagedAttention 原始论文
- Dao et al. FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness (NeurIPS 2022) —— 算子层优化的标杆
- Frantar et al. GPTQ: Accurate Post-Training Quantization for Generative Pre-Trained Transformers (ICLR 2023) —— INT4 权重量化的代表作
- Lin et al. AWQ: Activation-Aware Weight Quantization for LLM Compression and Acceleration (MLSys 2024) —— 激活感知权重量化
- Leviathan et al. Fast Inference from Transformers via Speculative Decoding (ICML 2023) —— 投机解码的早期工程化
- Li et al. EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty (ICML 2024) —— EAGLE 系列的起点
- NVIDIA. TensorRT Developer Guide —— TensorRT 图融合与算子优化官方文档
- NVIDIA. TensorRT-LLM —— LLM 推理引擎的开源仓库与基准