大模型推理与部署入门

训练只是万里长征第一步,如何让模型快速、低成本地服务用户才是工业界最关心的问题。本文从 LLM 推理的基本原理讲起,系统覆盖 KV Cache 管理、推理引擎(vLLM / SGLang / TensorRT-LLM)、量化技术、Speculative Decoding、Prefill/Decode 解耦等核心技术,并提供可量化的性能分析方法。

📑 目录


1. LLM 推理基础

1.1 自回归生成

LLM 的文本生成是逐 token 进行的。每一步根据已有的所有 token 预测下一个 token,直到生成结束符或达到最大长度:

1
2
3
4
5
6
输入:  "今天天气"
Step 1: "今天天气" → 预测 "很"
Step 2: "今天天气很" → 预测 "好"
Step 3: "今天天气很好" → 预测 "。"
Step 4: "今天天气很好。" → 预测 <EOS>
输出: "很好。"

每一步的预测都需要对所有已有 token 做一次完整的 Attention 计算。如果每次都从头算,计算量随序列长度二次增长。KV Cache 就是为了解决这个问题而生的——模型每生成一个字都要”回忆”之前所有字的信息,KV Cache 就是把这些”回忆”存起来复用,避免每次都从头想。就像考试时把公式抄在草稿纸上,后面直接查,不用每次重新推导。

1.2 Prefill 与 Decode 两阶段

LLM 推理分为两个特性截然不同的阶段:

通俗地说,Prefill 像一次性读完一整页书(并行处理),Decode 像一个字一个字地写回信(串行生成)。

Prefill(预填充)阶段

处理用户输入的 prompt。所有输入 token 可以并行计算,一次性生成所有 token 的 KV Cache。

1
2
3
4
输入: "请解释量子计算的基本原理"(假设 10 个 token)
→ 10 个 token 并行计算,一次前向传播
→ 生成 10 组 KV Cache
→ 输出第一个生成 token

特性:

  • Compute Bound:输入 token 多,矩阵乘法的 batch 维度大,计算量大
  • 决定 TTFT(Time To First Token,首 token 延迟)

Decode(解码)阶段

逐个生成输出 token。每步只处理 1 个新 token,但需要读取所有历史 KV Cache。

1
2
3
4
Step 1: 新 token "量" + 读取 10 组历史 KV → 预测 "子"
Step 2: 新 token "子" + 读取 11 组历史 KV → 预测 "计"
...
每步只算 1 个 token,但要读越来越多的 KV Cache

特性:

  • Memory Bound:每步只有 1 个 token 的计算量,但要从 HBM 读取大量 KV Cache
  • 决定 TPOT(Time Per Output Token,每 token 延迟)
  • Decode 阶段通常占总推理时间的 90%+

1.3 关键性能指标

指标 含义 影响因素
TTFT Time To First Token,首 token 延迟 Prompt 长度、Prefill 速度
TPOT Time Per Output Token,每 token 生成延迟 KV Cache 读取速度、模型大小
吞吐量 token/s 或 request/s Batch 大小、GPU 利用率
P50/P95/P99 延迟分位数 尾延迟敏感场景
Goodput 满足 SLO 的有效吞吐 TTFT SLO + TPOT SLO

推理链路全景

1
2
3
4
5
6
7
8
9
10
11
12
13
用户请求

[Tokenize] → token IDs ← CPU,通常 <1ms

[Prefill] → 处理 prompt + 生成 KV Cache ← GPU,Compute Bound

[Decode] → 逐 token 生成 ← GPU,Memory Bound(主要耗时)

[Sampling] → 从 logits 中采样 token ← GPU/CPU,<1ms

[Detokenize] → token IDs → 文本 ← CPU,<1ms

返回用户

1.4 Prefill 和 Decode 的计算特性对比

以 LLaMA-7B(d_model=4096, n_heads=32, n_layers=32)为例:

阶段 计算量 (FLOP) 数据搬运量 计算强度 瓶颈类型
Prefill (seq=2048) ~28 TFLOP ~14 GB (权重) ~2000 FLOP/B Compute Bound
Decode (1 token) ~14 GFLOP ~14 GB (权重+KV) ~1 FLOP/B Memory Bound

Decode 的计算强度比 Prefill 低约 2000 倍——GPU 大部分时间在等数据从 HBM 搬过来,而不是在算。这就是为什么推理优化的核心是少搬数据


2. KV Cache:推理的显存刺客

2.1 什么是 KV Cache

在自回归生成中,每一步 Attention 都需要所有历史 token 的 K 和 V 向量。如果每步都重新计算,计算量为 O(N^2)。KV Cache 的思路:把已计算过的 K 和 V 缓存起来,每步只计算新 token 的 K、V,追加到缓存中。

1
2
3
4
5
Step 1: Q_1, K_1, V_1  → cache: K=[K_1], V=[V_1]
Step 2: Q_2, K_2, V_2 → cache: K=[K_1,K_2], V=[V_1,V_2]
Step 3: Q_3, K_3, V_3 → cache: K=[K_1,K_2,K_3], V=[V_1,V_2,V_3]
...
每步 Attention 只需计算 Q_new 与所有缓存 K 的注意力分数

2.2 KV Cache 显存公式

1
2
3
4
每层每 token 的 KV Cache = 2 × n_kv_heads × d_head × sizeof(dtype)
(2 是 K 和 V 两份)

总 KV Cache = n_layers × seq_len × batch_size × 每层每 token KV Cache

各模型的 KV Cache 开销(FP16, batch=1, seq=4096):

模型 n_layers n_kv_heads d_head 每 token KV seq=4096 总 KV Cache
LLaMA-7B (MHA) 32 32 128 512 KB 2 GB
LLaMA-70B (GQA, 8组) 80 8 128 320 KB 1.25 GB
Mistral-7B (GQA, 8组) 32 8 128 128 KB 0.5 GB

当 batch_size 增大时,KV Cache 线性增长:

1
2
3
4
LLaMA-7B, seq=4096:
batch=1: 2 GB
batch=16: 32 GB
batch=64: 128 GB ← 已经超过单卡 80GB 显存!

KV Cache 是限制推理 batch size(进而限制吞吐量)的主要因素。这就是为什么 GQA/MQA/MLA 这些减少 KV Head 数量的 Attention 变种对推理如此重要。

2.3 KV Cache 的碎片问题

不同请求的序列长度不同,且在推理过程中动态增长。如果为每个请求预分配最大长度的连续显存,会造成严重的内部碎片(浪费)和外部碎片:

1
2
3
4
5
显存空间:
[Request A KV (实际 1K, 预留 4K)][碎片][Request B KV (实际 2K, 预留 4K)][碎片]
↑ 浪费 3K ↑ 浪费 2K

总浪费可达 60-80%!

这个问题由 PagedAttention 解决(详见下文)。


3. 推理引擎核心技术

3.1 PagedAttention

vLLM 提出的 PagedAttention 将操作系统虚拟内存分页的思想引入 KV Cache 管理。传统方式是给每个请求预订一整间包房,不管坐几个人都占着;PagedAttention 改成了自助餐厅的拼桌制,按需分配座位,吃完立刻清桌。具体来说:

  • KV Cache 不要求连续内存,而是分成固定大小的页(Page/Block)(如每页 16 个 token)
  • 页按需动态分配和回收,消除碎片
  • 使用页表映射逻辑位置到物理位置
1
2
3
4
5
6
7
8
传统方式:
Request A: [████████░░░░░░░░] ← 连续分配,大量空闲空间
Request B: [████░░░░░░░░░░░░]

PagedAttention:
物理页池: [Page0][Page1][Page2][Page3][Page4][Page5]...
Request A 页表: 逻辑页 0→Page2, 1→Page5, 2→Page0 (按需分配)
Request B 页表: 逻辑页 0→Page1, 1→Page4 (按需分配)

收益

  • 显存利用率接近 **100%**(几乎零碎片)
  • 内存浪费从 60-80% 降至 <4%
  • 支持 Copy-on-Write:相同前缀的请求可以共享 KV Cache 页,进一步省显存

3.2 Continuous Batching

传统 Static Batching 中,一个 batch 内所有请求必须同时开始、同时结束。短请求生成完毕后必须等最长的请求,造成 GPU 空转。打个比方,传统方式是旅行团模式——必须等所有人到齐才出发;Continuous Batching 是公交车模式——到站就上下客,车一直在跑。

1
2
3
4
5
6
7
8
9
10
11
Static Batching:
Request A: [████████████████]
Request B: [████████░░░░░░░░] ← B 完成后 GPU 空闲,等 A
Request C: [████░░░░░░░░░░░░] ← C 完成后 GPU 空闲更长

Continuous Batching:
Request A: [████████████████]
Request B: [████████]
Request D: [████████████] ← B 完成后立刻插入新请求 D
Request C: [████]
Request E: [██████████████] ← C 完成后立刻插入新请求 E

核心思想:以 iteration(一次 decode step) 为调度粒度,而非以 request 为粒度。每次 decode step 都可以动态地加入新请求或释放已完成的请求。

收益:吞吐量提升 2-10 倍(取决于请求长度差异)。

3.3 Prefix Cache / RadixAttention

当多个请求共享相同的前缀(如 system prompt、few-shot examples)时,可以复用已计算的 KV Cache,避免重复 prefill:

1
2
3
Request A: [系统提示词(2K tokens)] + "问题A" → prefill 系统提示词 → 缓存
Request B: [系统提示词(2K tokens)] + "问题B" → 命中缓存,跳过 2K prefill
Request C: [系统提示词(2K tokens)] + "问题C" → 命中缓存,跳过 2K prefill

vLLM 的 Automatic Prefix Caching:自动检测请求间的公共前缀并缓存。

SGLang 的 RadixAttention:使用 Radix Tree(基数树)管理所有缓存的 KV,支持任意前缀匹配(不只是固定的 system prompt),更灵活。

3.4 Chunked Prefill

长 prompt 的 Prefill 计算量大,如果和 Decode 请求放在同一个 batch 中,会严重拖慢 Decode 的延迟(互扰问题)。

Chunked Prefill 将长 prompt 分成多个小块(chunk),每块和 Decode 请求混合处理:

1
2
3
4
5
6
7
8
9
10
不分块:
一次 step: [Prefill 8K tokens + Decode 32 requests]
← Prefill 巨大,Decode 请求被阻塞很久

分块(chunk=512):
Step 1: [Prefill chunk1 (512) + Decode 32 requests]
Step 2: [Prefill chunk2 (512) + Decode 32 requests]
...
Step 16: [Prefill chunk16 (512) + Decode 32 requests]
← 每步 Decode 延迟稳定

4. 主流推理框架

4.1 vLLM

vLLM 是当前最流行的 LLM 推理引擎,由 UC Berkeley 的研究团队开发。

核心特性

  • PagedAttention:近乎零碎片的 KV Cache 管理
  • Continuous Batching:动态请求调度
  • Automatic Prefix Caching:自动前缀复用
  • 多种量化支持:GPTQ、AWQ、FP8、INT8
  • Speculative Decoding 支持
  • 多 GPU 张量并行

快速上手

1
2
3
4
5
6
7
8
9
# 安装
pip install vllm

# 离线推理
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--tensor-parallel-size 1 \
--max-model-len 8192 \
--gpu-memory-utilization 0.9
1
2
3
4
5
6
7
8
9
10
11
# Python API
from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Llama-3.1-8B-Instruct")
sampling_params = SamplingParams(temperature=0.7, max_tokens=256)

prompts = ["请解释什么是量子计算", "写一首关于AI的诗"]
outputs = llm.generate(prompts, sampling_params)

for output in outputs:
print(output.outputs[0].text)

OpenAI 兼容 API

1
2
3
4
5
6
7
8
9
10
11
# 启动服务
vllm serve meta-llama/Llama-3.1-8B-Instruct --port 8000

# 调用(与 OpenAI API 完全兼容)
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Llama-3.1-8B-Instruct",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 256
}'

4.2 SGLang

SGLang 由 UC Berkeley 的 LMSYS 团队开发,核心优势在于复杂推理场景的优化。

核心特性

  • RadixAttention:基于 Radix Tree 的 KV Cache 复用,支持任意前缀匹配
  • 结构化输出加速(cFSM):JSON、正则表达式约束的生成加速
  • 前端编程语言:用 Python 表达复杂的多轮/多步生成逻辑
  • 高效 Runtime:在长上下文和多轮对话场景下吞吐领先

快速上手

1
2
3
4
5
6
7
# 安装
pip install sglang[all]

# 启动服务
python -m sglang.launch_server \
--model meta-llama/Llama-3.1-8B-Instruct \
--port 8000
1
2
3
4
5
6
7
8
9
10
11
12
13
# SGLang 前端编程(复杂推理场景)
import sglang as sgl

@sgl.function
def multi_step_reasoning(s, question):
s += sgl.system("你是一个逻辑推理专家")
s += sgl.user(question)
s += sgl.assistant(sgl.gen("think", max_tokens=512))
s += sgl.user("请给出最终答案,用JSON格式")
s += sgl.assistant(sgl.gen("answer", max_tokens=256,
regex=r'\{.*\}')) # 结构化输出

result = multi_step_reasoning.run(question="1+1=?")

4.3 TensorRT-LLM

NVIDIA 官方推出的 LLM 推理引擎,做了最深度的硬件优化。

核心特性

  • 深度 CUDA 优化:手写高效 kernel,充分利用 Tensor Core / TMA
  • Paged KV Cache + Inflight Batching(即 Continuous Batching)
  • FP8 / INT4 / INT8 量化全面支持
  • Speculative Decoding 支持
  • 与 Triton Inference Server 集成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 转换模型
python convert_checkpoint.py \
--model_dir ./llama-7b \
--output_dir ./trt_ckpt \
--dtype float16

# 构建 TRT-LLM 引擎
trtllm-build \
--checkpoint_dir ./trt_ckpt \
--output_dir ./trt_engine \
--gemm_plugin float16

# 运行
python run.py --engine_dir ./trt_engine --input_text "Hello!"

4.4 框架选型

场景 推荐框架 原因
通用在线服务 vLLM 社区活跃、功能全面、API 兼容 OpenAI
复杂 Agent / 多轮对话 SGLang RadixAttention 复用效率高、前端编程灵活
结构化输出(JSON) SGLang cFSM 约束生成加速
极致性能 / NVIDIA 全家桶 TensorRT-LLM 最深度的硬件优化
长上下文(>32K) SGLang / vLLM 都有良好支持
快速原型验证 vLLM 最简单的 API、pip install 即用

5. 量化技术

量化是推理优化的核心手段之一。通过降低数据精度,可以减少显存占用、降低 HBM 带宽需求、提高吞吐量。形象地说,量化就像把高清照片压缩成缩略图——虽然细节少了一点,但文件小了好几倍,传输和存储都更快。

5.1 量化基础

量化的本质是将高精度浮点数(FP16/BF16)映射为低精度整数或浮点数:

1
2
3
4
FP16 (16 bit): 高精度,每个参数 2 字节
INT8 (8 bit): 精度下降,每个参数 1 字节,显存减半
INT4 (4 bit): 精度进一步下降,每个参数 0.5 字节,显存减至 1/4
FP8 (8 bit): Hopper 原生支持,精度介于 INT8 和 FP16 之间

量化公式(线性量化)

1
2
量化:   x_int = round(x_fp / scale) + zero_point
反量化: x_fp = (x_int - zero_point) * scale

5.2 Weight-Only 量化:GPTQ 与 AWQ

只量化模型权重,激活值保持 FP16。适合 Decode 阶段(memory bound,权重读取是瓶颈)。

GPTQ(GPT Quantization)

  • 使用少量校准数据,逐层贪心量化
  • 通过 Hessian 矩阵信息补偿量化误差(OBQ/OBS 方法)
  • 支持 3-bit / 4-bit weight-only 量化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 使用 AutoGPTQ 量化
from auto_gptq import AutoGPTQForCausalLM
from transformers import AutoTokenizer

model_name = "meta-llama/Llama-3.1-8B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoGPTQForCausalLM.from_pretrained(model_name)

# 用校准数据量化
model.quantize(
examples=calibration_data,
batch_size=4,
bits=4, # 4-bit 量化
group_size=128, # 每 128 个权重共享一组量化参数
)
model.save_quantized("./llama-8b-gptq-4bit")

AWQ(Activation-aware Weight Quantization)

  • 核心观察:不是所有权重通道同等重要,activation 幅度大的通道更重要
  • 对重要通道进行缩放保护,然后再量化
  • 比 GPTQ 更快(不需要逐层反向传播),精度相当或更优
1
2
3
4
5
6
7
8
# 在 vLLM 中使用 AWQ 量化模型
from vllm import LLM

llm = LLM(
model="TheBloke/Llama-3.1-8B-AWQ",
quantization="awq",
dtype="half",
)

5.3 W8A8 量化:SmoothQuant

同时量化权重和激活值到 INT8。关键挑战:激活值中存在异常值(outlier),直接量化会导致大量精度损失。

SmoothQuant 的核心思想:通过一个可学习的缩放因子,将激活值中的 outlier “转移”到权重上(权重更均匀,更好量化):

1
2
3
4
5
原始:      Y = X @ W        (X 有 outlier,难量化)

SmoothQuant: Y = (X / s) @ (s * W)
← 激活变平滑 ← 权重吸收 outlier
← 好量化 ← 分布略偏但可接受

效果:INT8 矩阵乘法,在保持精度的同时吞吐提升 ~1.5-2 倍

5.4 FP8 量化

Hopper 架构(H100)原生支持 FP8(E4M3 和 E5M2 两种格式),Tensor Core 可以直接执行 FP8 矩阵乘法。

1
2
3
FP16:  1 符号 + 5 指数 + 10 尾数 → 高精度
FP8 E4M3: 1 符号 + 4 指数 + 3 尾数 → 精度优先
FP8 E5M2: 1 符号 + 5 指数 + 2 尾数 → 范围优先

FP8 是目前精度与性能的最佳平衡点

  • 精度损失极小(通常 <1% perplexity 退化)
  • H100 上 FP8 算力是 FP16 的 2 倍
  • 工程实现简单(无需复杂的量化校准)

5.5 KV Cache 量化:KIVI

当瓶颈不在权重而在 KV Cache 时(长上下文、大并发),对 KV Cache 进行量化:

  • Key 按 channel 维度量化(不同 channel 分布差异大)
  • Value 按 token 维度量化(不同 token 分布差异大)
  • 可以量化到 2-bit,KV Cache 显存减少 8 倍
1
2
FP16 KV Cache: batch=64, seq=4096, LLaMA-7B → 128 GB
2-bit KV Cache: 同样配置 → 16 GB ← 可以服务更多并发

5.6 量化选择决策树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
你的瓶颈是什么?

├─ 模型太大,放不下
│ ├─ 需要极致压缩 → INT4 weight-only (AWQ/GPTQ)
│ └─ 适度压缩即可 → FP8 或 W8A8 (SmoothQuant)

├─ 吞吐不够(Decode 慢)
│ ├─ 权重搬运是瓶颈 → INT4 weight-only (AWQ)
│ └─ 计算是瓶颈 → FP8(利用 2x 算力)

├─ KV Cache 爆了(长上下文/高并发)
│ └─ KV Cache 量化 (KIVI, 2-4 bit)

└─ 要求最高精度
└─ FP8(精度损失最小)

5.7 “更低 bit 反而更慢”的原因

量化不总是带来加速,以下情况 INT4 可能比 FP16 更慢:

原因 说明
Kernel 开销 INT4 需要 dequantize 操作(解包 + 反量化),引入额外计算
Packing/Unpacking 4-bit 数据需要位操作来打包/解包,有固定开销
Batch 太小 batch=1 时,计算量太小,kernel 启动开销占比大,量化收益被抵消
缺乏优化 Kernel 不是所有量化方案都有高效的 CUDA kernel 实现(如 Marlin 专门优化 INT4 GEMM)

经验法则:INT4 在 batch=16-32 以上才能稳定获得加速;batch=1 时 FP8 或 FP16 可能更快。


6. Speculative Decoding

6.1 核心思想

Decode 阶段的瓶颈是 memory bound——每步只生成 1 个 token,GPU 算力严重浪费。Speculative Decoding 的思路:用一个小的 Draft 模型快速”猜”多个 token,再用大的 Target 模型一次性并行验证。 就像让一个小助手(Draft 模型)先快速猜一串答案,再让专家(Target 模型)一次性审核——猜对的直接用,猜错的重来,整体比专家自己一个个写要快。

1
2
3
4
5
6
7
8
9
传统 Decode(逐 token):
Target: [t1] → [t2] → [t3] → [t4] → [t5]
5 步,每步读一次完整权重

Speculative Decoding:
Draft: [d1, d2, d3, d4, d5] ← 小模型快速猜 5 个 token
Target: [验证 d1-d5] ← 大模型一次前向,并行验证
结果: d1✓ d2✓ d3✓ d4✗ ← 接受前 3 个,第 4 个拒绝
→ 3 个 token 只用了 1 步 Target 推理!

6.2 正确性保证:Speculative Sampling

Speculative Decoding 不是”近似”,而是数学上精确的。通过 Speculative Sampling 算法,保证最终输出的 token 分布与直接用 Target 模型生成完全一致:

1
2
3
4
对于 Draft 模型提议的 token x:
- 如果 p_target(x) >= p_draft(x):无条件接受
- 如果 p_target(x) < p_draft(x):以概率 p_target(x)/p_draft(x) 接受
- 被拒绝后,从修正后的分布中重新采样

这保证了最终分布严格等于 Target 分布——无偏加速

6.3 Speculative Decoding 的变体

Medusa

不使用外部 Draft 模型,而是在 Target 模型上加多个额外的解码头(Decoding Head),每个头预测未来不同位置的 token:

1
2
3
4
5
6
7
Target 模型

[原始 LM Head] → 预测 t+1
[Medusa Head 1] → 预测 t+2
[Medusa Head 2] → 预测 t+3
...
→ 组合成 candidate tree → 并行验证

优势:不需要单独的 Draft 模型,部署简单。

EAGLE-2

使用动态 Draft Tree——根据校准的置信度分数决定 Tree 的形状和深度,更激进地产生可接受 token。

Block Verification

将 token 级别的逐个验证升级为 block 级别的联合验证,进一步减少验证开销,稳定榨出 5-8% 的额外速度。

6.4 什么时候 Speculative 不赚

条件 为什么不赚
高温度采样 温度越高,Draft 和 Target 分布越不一致,接受率低
Draft 模型太差 猜的不准,大部分被拒绝,白白浪费 Draft 计算
大 batch batch 大时 Decode 已经接近 compute bound,加 Draft 反而增加计算量
短生成 生成 token 少,Speculative 的启动开销占比大
特殊任务(代码/数学) 这类任务 token 分布更尖锐,Draft 模型更难猜准

经验法则:Speculative Decoding 在 batch=1-4、温度 0-0.7、中长生成 场景下收益最大,典型加速 1.5-2.5 倍


7. 系统架构:Prefill/Decode 解耦

7.1 为什么需要解耦

Prefill 和 Decode 的计算特性截然不同:

特性 Prefill Decode
计算类型 Compute Bound(大矩阵乘) Memory Bound(读 KV Cache)
每次处理 token 数 数百~数千 1
延迟要求 不太敏感(用户等第一个字) 极度敏感(每个字的延迟)
资源需求 需要高算力 需要高带宽

当 Prefill 和 Decode 混合在同一组 GPU 上时:

1
2
3
4
混合 batching 的问题:
Step 1: [长 Prefill (2K tokens)] + [32 个 Decode 请求]
↑ Prefill 大矩阵乘法霸占 GPU
↓ 32 个 Decode 请求被阻塞,TPOT 飙升

长 Prefill 和 Decode 共享 GPU 资源,造成互扰——Decode 的尾延迟(P95/P99)爆炸。

7.2 解耦架构

将 Prefill 和 Decode 分配到不同的 GPU 池

1
2
3
4
5
6
7
8
9
用户请求

[调度器] ── 路由请求
↓ ↓
[Prefill GPU 池] [Decode GPU 池]
计算密集型 GPU 带宽优先型 GPU
处理 prompt 逐 token 生成
↓ ↑
KV Cache 迁移 ─────────┘

7.3 关键论文

DistServe(OSDI’24)

系统化论证了解耦的收益,核心贡献:

  • 提出 goodput 指标:满足 TTFT 和 TPOT SLO 的有效吞吐
  • 证明解耦在大多数 SLO 组合下都优于聚合
  • 提出基于 goodput 的资源配比优化

Splitwise(ISCA)

从硬件和成本角度分析:

  • Prefill 池使用算力型 GPU(如 H100)
  • Decode 池可以使用性价比更高的 GPU(高带宽但算力需求低)
  • 在吞吐、成本、功耗三个维度优化

TaiChi(2025)

将聚合和解耦统一到一个框架中:

  • 不是”解耦一定好”,而是根据工作负载特征动态选择
  • 面向不同 SLO 组合做最优 goodput

7.4 解耦的挑战

挑战 说明 缓解方案
KV Cache 迁移 Prefill 完成后需要将 KV Cache 传给 Decode 池 高速网络(NVLink/IB)+ 流水线传输
调度复杂度 需要根据负载动态分配 Prefill/Decode 资源 goodput 驱动的调度算法
尾延迟 迁移本身引入额外延迟 预取 + 重叠传输
队列震荡 突发请求导致某个池过载 弹性伸缩 + 溢出处理

8. 性能分析与 Benchmark

8.1 核心指标体系

推理性能不是一个单一数字,需要多维度衡量:

1
2
3
4
5
6
7
改动(量化/内核/调度/解耦)

固定压测配置(模型/上下文/并发/温度)

输出指标: TTFT / TPOT / P50 / P95 / token吞吐 / 显存占用

对比 baseline:回退就拦截

一份合格的性能报告应包含

指标 含义 量化方式
TTFT P50/P95 首 token 延迟 ms
TPOT P50/P95 每 output token 延迟 ms/token
吞吐量 系统每秒处理的 token 总数 token/s
QPS 系统每秒完成的请求数 req/s
GPU 显存占用 权重 + KV Cache + 其他 GB
GPU 利用率 计算单元使用率 %

8.2 压测工具

GenAI-Perf

NVIDIA 提供的 LLM 推理压测工具,直接输出 TTFT / TPOT / 吞吐等关键指标:

1
2
3
4
5
6
7
8
9
10
11
12
# 安装
pip install genai-perf

# 压测 vLLM 服务
genai-perf \
--model meta-llama/Llama-3.1-8B-Instruct \
--backend vllm \
--url http://localhost:8000 \
--input-tokens-mean 512 \
--output-tokens-mean 256 \
--concurrency 32 \
--num-prompts 1000

vLLM 内置 Benchmark

1
2
3
4
5
6
7
8
9
10
11
12
# vLLM 离线吞吐测试
python -m vllm.benchmarks.benchmark_throughput \
--model meta-llama/Llama-3.1-8B-Instruct \
--input-len 512 \
--output-len 256 \
--num-prompts 1000

# vLLM 在线延迟测试
python -m vllm.benchmarks.benchmark_serving \
--model meta-llama/Llama-3.1-8B-Instruct \
--request-rate 10 \
--num-prompts 500

8.3 性能分析工具

工具 层级 用途
nvidia-smi 系统级 GPU 利用率、显存、温度
torch.profiler 框架级 算子耗时、shape 分析
Nsight Systems 系统级 CPU-GPU 交互、时序分析
Nsight Compute Kernel 级 单个 kernel 的 SM/Memory 利用率

定位瓶颈的思路

1
2
3
4
5
6
7
1. nvidia-smi 看 GPU 利用率
├─ 利用率低 → Nsight Systems 看 CPU/调度是否拖后腿
└─ 利用率高 → 看 TPOT 是否达标
├─ TPOT 高 → Nsight Compute 看 kernel 是 compute bound 还是 memory bound
│ ├─ Memory bound → 量化、KV Cache 优化
│ └─ Compute bound → 更好的 kernel(FlashAttention)、更大 batch
└─ TTFT 高 → Prefill 优化、Chunked Prefill

9. 选型决策树

当你遇到推理性能问题时,按以下思路定位方向:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
你最痛的是哪一个?

├─ TTFT 很大(首 token 慢)
│ ├─ prompt 很长/上下文很长
│ │ → Chunked Prefill / 更快 GEMM / 更强 Prefill GPU
│ ├─ CPU/调度慢
│ │ → nsys 找 host bottleneck;换更成熟 runtime(vLLM/TRT-LLM)
│ └─ 频繁重复前缀
│ → Prefix/KV 复用(Automatic Prefix Caching / RadixAttention)

├─ TPOT 很大(续杯慢)
│ ├─ decode 明显 memory-bound
│ │ → FlashAttention/FlashInfer + KV Cache 管理优化
│ ├─ token-by-token 串行受限
│ │ → Speculative Decoding / Medusa / EAGLE-2
│ └─ batch 太小吃不到量化收益
│ → Continuous Batching + 合理并发

├─ 显存爆了(batch 上不去/长上下文撑不住)
│ ├─ KV Cache 占用大
│ │ → PagedAttention + KV Cache 量化 (KIVI)
│ └─ 权重占用大
│ → INT4 weight-only (AWQ/GPTQ) 或 FP8

└─ 尾延迟 P95 爆炸
├─ prefill/decode 互扰明显
│ → Prefill/Decode 解耦(DistServe/Splitwise 思路)
└─ SLO 既要 TTFT 又要 TPOT
→ TaiChi 类"聚合+解耦统一"路线

10. 自我检验清单

  • 能解释 Prefill 和 Decode 的计算特性差异(compute bound vs memory bound)
  • 能给出 KV Cache 的显存占用公式,并据此估算给定 batch size 下的显存需求
  • 能把推理链路拆成 tokenize → prefill → decode → sampling → postprocess,并标出每段耗时特征
  • 能解释 PagedAttention 解决了什么问题,与传统连续 KV Cache 的区别
  • 能解释 Continuous Batching 与 Static Batching 的差异与收益
  • 能区分并选择 W8A8 vs Weight-only INT4 vs FP8 的适用场景
  • 能解释 Speculative Decoding 的正确性保证(分布不变)
  • 能给出”什么时候 speculative 不赚”的判断依据
  • 能用 vLLM 或 SGLang 部署一个模型并做压测,输出 TTFT / TPOT / 吞吐指标
  • 能给出推理引擎选型建议:低延迟、长上下文、多并发、结构化输出各用什么

📚 参考资料