LLM 推理优化全景实战:从 PagedAttention 到投机解码——让大模型推理成本下降 70% 的技术革命(2026)
引言:推理成本战的背景
2026 年,大语言模型推理领域正在经历一场静默但深刻的革命。GPT-4 级别模型的推理成本在过去 12 个月内下降了约 70%,这不是单一突破的结果,而是多个技术方向合力的产物:
- MoE 架构工程成熟:Mixtral、Grok-1 等开源 MoE 模型让推理时只激活部分参数成为标配
- 量化技术逼近无损:INT4/FP8 在 70B 以上模型上,任务性能损失已控制在 3% 以内
- 投机解码工业化:Speculative Decoding 从论文走向生产,实现 30%-50% 的加速
- KV Cache 管理革新:vLLM 的 PagedAttention 让单卡吞吐量提升 3-5 倍
本文将从架构原理到代码实战,系统性地解析这些核心技术,帮助读者理解并应用这些让大模型"用得起"的关键技术。
一、LLM 推理的本质瓶颈
1.1 自回归生成的计算特性
大语言模型的推理过程本质上是自回归(Auto-regressive)的:每次生成一个 token,都需要将之前所有的 token 作为上下文重新计算注意力。这种特性带来了两个核心瓶颈:
计算瓶颈(Compute-bound):在 Prefill 阶段(处理输入 prompt),需要并行处理输入序列的所有 token,计算量与序列长度呈平方关系。
内存瓶颈(Memory-bound):在 Decode 阶段(逐个生成输出 token),每次生成都需要从显存读取全部模型参数,但只进行极少的计算。这导致 GPU 计算核心大量空闲,性能受限于显存带宽。
# 传统自回归生成的伪代码
def autoregressive_generate(model, prompt, max_tokens):
tokens = tokenize(prompt)
for _ in range(max_tokens):
# 每次都要读取全部模型参数!
logits = model.forward(tokens) # O(n) 显存读取
next_token = sample(logits[-1])
tokens.append(next_token)
return tokens
1.2 显存读取的瓶颈量化
以 70B 模型为例,假设:
- 模型参数:70B × 2 bytes (FP16) = 140 GB
- 显存带宽:A100 为 2039 GB/s
- 每次生成 1 个 token 需要读取全部参数
理论上的最大生成速度:
2039 GB/s ÷ 140 GB ≈ 14.5 tokens/s
这就是为什么 70B 模型在单卡 A100 上的生成速度很难超过 15 tokens/s 的根本原因——不是算力不够,而是显存带宽跑满了。
1.3 KV Cache:避免重复计算的缓存机制
KV Cache 是解决重复计算的关键技术。在注意力计算中,每个 token 会生成对应的 Key 和 Value 向量。KV Cache 的核心思想是:把这些已经计算过的 K、V 保存下来,后续生成时直接复用。
# 带 KV Cache 的生成过程
def generate_with_kv_cache(model, prompt, max_tokens):
tokens = tokenize(prompt)
# Prefill 阶段:计算所有输入 token 的 KV
kv_cache = model.prefill(tokens) # 只计算一次
# Decode 阶段:只计算新 token,复用之前的 KV
for _ in range(max_tokens):
# 只读取 KV Cache,不重新计算
logits, kv_cache = model.decode_one(tokens[-1], kv_cache)
next_token = sample(logits)
tokens.append(next_token)
return tokens
但 KV Cache 本身也带来了新问题:显存占用随序列长度线性增长。对于长上下文场景,KV Cache 可能占用几十 GB 的显存,成为新的瓶颈。
二、PagedAttention:vLLM 的内存管理革命
2.1 传统 KV Cache 的内存碎片问题
在传统实现中,每个请求的 KV Cache 是连续分配的。假设最大序列长度为 4096,每个 token 的 KV 占用 2KB,则每个请求需要预分配:
4096 tokens × 2KB = 8MB per request
问题在于:
- 预分配浪费:短对话可能只用到 512 tokens,但已经分配了 8MB
- 内存碎片:请求结束后释放的内存可能无法被新请求利用
- 无法共享:多轮对话中相同的前缀无法复用 KV Cache
2.2 PagedAttention 的核心思想
vLLM 借鉴操作系统的虚拟内存分页管理,提出了 PagedAttention 机制:
核心创新:将 KV Cache 分割为固定大小的块(Block),通过页表实现非连续内存管理。
传统方式(连续分配):
Request 1: [KV1][KV2][KV3][KV4]............] (预分配,大量空闲)
Request 2: [KV1][KV2]......................] (预分配,大量空闲)
PagedAttention(按需分配):
Block Pool: [B1][B2][B3][B4][B5][B6][B7][B8]...
Request 1: B1 → B3 → B5 (逻辑连续,物理不连续)
Request 2: B2 → B4 → B6 (逻辑连续,物理不连续)
2.3 PagedAttention 的实现细节
class PagedAttention:
def __init__(self, num_blocks: int, block_size: int, num_heads: int, head_dim: int):
self.block_size = block_size # 每个块包含的 token 数,通常为 16
self.num_heads = num_heads
self.head_dim = head_dim
# 物理内存池:所有块共享
self.k_cache = torch.zeros((num_blocks, block_size, num_heads, head_dim))
self.v_cache = torch.zeros((num_blocks, block_size, num_heads, head_dim))
# 空闲块列表
self.free_blocks = list(range(num_blocks))
# 每个请求的页表:logical_block_id → physical_block_id
self.page_tables = {}
def allocate_block(self, request_id: int) -> int:
"""为请求分配一个新块"""
if not self.free_blocks:
raise OOMError("No free blocks available")
physical_block = self.free_blocks.pop()
if request_id not in self.page_tables:
self.page_tables[request_id] = []
self.page_tables[request_id].append(physical_block)
return physical_block
def write_kv(self, request_id: int, token_idx: int, key: torch.Tensor, value: torch.Tensor):
"""将 KV 写入对应的块"""
logical_block = token_idx // self.block_size
block_offset = token_idx % self.block_size
# 确保有足够的块
while len(self.page_tables[request_id]) <= logical_block:
self.allocate_block(request_id)
physical_block = self.page_tables[request_id][logical_block]
# 写入物理内存
self.k_cache[physical_block, block_offset] = key
self.v_cache[physical_block, block_offset] = value
def read_kv(self, request_id: int, token_idx: int) -> tuple:
"""读取指定位置的 KV"""
logical_block = token_idx // self.block_size
block_offset = token_idx % self.block_size
physical_block = self.page_tables[request_id][logical_block]
return self.k_cache[physical_block, block_offset], self.v_cache[physical_block, block_offset]
2.4 PagedAttention 的注意力计算
关键挑战:当 KV 分散在不同物理块中时,如何高效计算注意力?
def paged_attention(query: torch.Tensor,
page_table: List[int],
k_cache: torch.Tensor,
v_cache: torch.Tensor,
block_size: int) -> torch.Tensor:
"""
PagedAttention 的核心计算
Args:
query: (num_heads, head_dim) 当前 token 的 query
page_table: 逻辑块到物理块的映射
k_cache: (num_blocks, block_size, num_heads, head_dim)
v_cache: 同上
"""
num_heads, head_dim = query.shape
num_blocks = len(page_table)
# 收集所有相关的 K、V(可能跨多个物理块)
keys = []
values = []
for physical_block in page_table:
keys.append(k_cache[physical_block]) # (block_size, num_heads, head_dim)
values.append(v_cache[physical_block])
# 拼接成连续张量
keys = torch.cat(keys, dim=0) # (total_tokens, num_heads, head_dim)
values = torch.cat(values, dim=0)
# 标准注意力计算
# Attention = softmax(Q @ K^T / sqrt(d)) @ V
scores = torch.einsum('hd,thd->ht', query, keys) / (head_dim ** 0.5)
attention_weights = torch.softmax(scores, dim=-1)
output = torch.einsum('ht,thd->hd', attention_weights, values)
return output
2.5 vLLM 的性能提升
PagedAttention 带来的核心收益:
| 指标 | 传统实现 | PagedAttention | 提升 |
|---|---|---|---|
| 显存利用率 | 20%-40% | 80%-95% | 2-4x |
| 有效 batch size | 受限于预分配 | 按需扩展 | 3-5x |
| 吞吐量 | 基准 | 3-5x | 显著 |
# vLLM 启动示例
python -m vllm.entrypoints.api_server \
--model meta-llama/Llama-2-70b-hf \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.95 \
--max-model-len 4096 \
--block-size 16
三、投机解码:以小博大的加速艺术
3.1 投机解码的核心思想
投机解码(Speculative Decoding)的核心逻辑:用一个轻量、快速的"草稿模型"提前预测多个 token,再由目标大模型批量验证。
这就像"助理先拟草稿,专家再批量审核"——助理(小模型)快速产出初步结果,专家(大模型)不用逐字修改,只需一次性确认或修正。
传统自回归:
大模型 → token1 → 大模型 → token2 → 大模型 → token3 → ...
每次都要读取全部参数,逐个生成
投机解码:
小模型 → token1, token2, token3, token4, token5(快速猜测)
大模型 → 批量验证这 5 个 token(一次读取,并行验证)
结果:可能接受 3 个,拒绝 2 个
3.2 投机解码的数学保证
关键定理:投机解码的输出分布与原始目标模型完全一致。
这是通过拒绝采样(Rejection Sampling)机制保证的:
def speculative_decode(
target_model, # 大模型(验证方)
draft_model, # 小模型(提案方)
input_ids: torch.Tensor,
gamma: int = 5 # 每次猜测的 token 数
) -> tuple:
"""
投机解码的单次迭代
返回:(接受的 token 序列, 接受数量)
"""
# 阶段 1:草稿模型自回归生成 γ 个提议 token
draft_tokens = []
draft_probs = []
current_ids = input_ids
for _ in range(gamma):
logits = draft_model.forward(current_ids)
prob = torch.softmax(logits[-1], dim=-1)
token = torch.multinomial(prob, 1)
draft_tokens.append(token)
draft_probs.append(prob[token])
current_ids = torch.cat([current_ids, token], dim=-1)
# 阶段 2:目标模型批量验证
target_logits = target_model.forward(current_ids) # 一次读取,并行计算
target_probs = torch.softmax(target_logits, dim=-1)
# 阶段 3:拒绝采样验证
accepted_tokens = []
n_accepted = 0
for i, draft_token in enumerate(draft_tokens):
target_prob = target_probs[len(input_ids) + i - 1, draft_token]
draft_prob = draft_probs[i]
# 接受概率 = min(1, target_prob / draft_prob)
accept_prob = min(1.0, target_prob / draft_prob)
if torch.rand(1) < accept_prob:
# 接受这个 token
accepted_tokens.append(draft_token)
n_accepted += 1
else:
# 拒绝,从调整后的分布采样
adjusted_prob = max(0, target_prob - draft_prob)
adjusted_prob = adjusted_prob / adjusted_prob.sum()
corrected_token = torch.multinomial(adjusted_prob, 1)
accepted_tokens.append(corrected_token)
break
return torch.cat(accepted_tokens), n_accepted
3.3 投机解码的性能分析
加速比公式:
设:
- $T_{target}$:目标模型生成 1 个 token 的时间
- $T_{draft}$:草稿模型生成 1 个 token 的时间
- $\alpha$:平均接受率(通常 0.6-0.8)
- $\gamma$:每次猜测的 token 数
加速比:
$$S = \frac{T_{target}}{T_{draft} \cdot \gamma + T_{target}} \cdot \frac{\alpha \cdot \gamma}{1}$$
当 $T_{draft} \ll T_{target}$ 且 $\alpha$ 较高时,加速比可达 2-3x。
实际性能数据:
| 配置 | 原始速度 | 投机解码速度 | 加速比 |
|---|---|---|---|
| LLaMA-70B + LLaMA-7B draft | 15 t/s | 45 t/s | 3.0x |
| Qwen-72B + Qwen-7B draft | 18 t/s | 52 t/s | 2.9x |
| Mixtral-8x7B + Mistral-7B draft | 25 t/s | 38 t/s | 1.5x |
注意:MoE 模型本身已经只激活部分参数,投机解码收益较小。
3.4 vLLM 中的投机解码配置
from vllm import LLM, SamplingParams
# 配置投机解码
llm = LLM(
model="meta-llama/Llama-2-70b-hf",
speculative_model="meta-llama/Llama-2-7b-hf", # 草稿模型
num_speculative_tokens=5, # 每次猜测 5 个 token
tensor_parallel_size=4,
)
sampling_params = SamplingParams(
max_tokens=100,
temperature=0.7,
)
outputs = llm.generate(["Hello, how are you?"], sampling_params)
3.5 DFlash:块扩散模型的突破
2026 年 2 月,ZLab 提出了 DFlash,用**块扩散模型(Block Diffusion Model)**替代传统的自回归草稿模型:
核心创新:草稿模型不再逐个 token 生成,而是一次性并行生成整个 token 块。
传统投机解码(自回归草稿):
Draft: token1 → token2 → token3 → token4 → token5(串行,5 次前向传播)
DFlash(块扩散草稿):
Draft: [token1, token2, token3, token4, token5](并行,1 次前向传播)
DFlash 的架构:
class DFlashDraft:
"""块扩散草稿模型"""
def __init__(self, target_model, draft_layers=5, block_size=16):
self.target_model = target_model
self.draft_layers = draft_layers # 草稿模型层数(通常 5 层)
self.block_size = block_size # 每次生成的块大小
def generate_block(self, prefix_tokens: torch.Tensor) -> torch.Tensor:
"""
一次性生成整个 token 块
关键:使用双向注意力,块内 token 互相可见
"""
# 从目标模型获取隐藏状态作为上下文
target_hidden = self.target_model.get_hidden_states(prefix_tokens)
# 块扩散生成
# 初始化:随机噪声或目标模型的 embedding
block_tokens = self.initialize_block()
# 扩散去噪过程
for t in reversed(range(self.diffusion_steps)):
# 双向注意力:块内所有 token 同时更新
block_tokens = self.denoise_step(
block_tokens,
context=target_hidden,
timestep=t
)
return block_tokens # (block_size,) 生成的 token 序列
DFlash 的性能:
| 模型 | 传统投机解码 | DFlash | 提升 |
|---|---|---|---|
| Qwen3-8B | 2.1x 加速 | 2.8x 加速 | +33% |
| LLaMA-3.1-8B | 2.0x 加速 | 2.6x 加速 | +30% |
四、量化技术:从 FP16 到 INT4 的精度保卫战
4.1 量化的基本原理
量化的目标:用更低精度的数值表示模型参数,减少显存占用和计算开销。
量化公式:
$$Q(x) = \text{round}\left(\frac{x}{s}\right) + z$$
其中:
- $s$:缩放因子(scale)
- $z$:零点(zero point)
- $\text{round}$:舍入函数
反量化:
$$\hat{x} = s \cdot (Q(x) - z)$$
4.2 量化方法的分类
| 方法 | 量化对象 | 精度损失 | 加速效果 | 适用场景 |
|---|---|---|---|---|
| 动态量化 | 仅权重 | 极小 | 有限 | 快速部署 |
| 静态量化 | 权重 + 激活 | 小 | 较好 | 生产环境 |
| GPTQ | 权重(逐层) | 小 | 好 | 离线量化 |
| AWQ | 权重(激活感知) | 极小 | 好 | 高精度需求 |
| GGUF/llama.cpp | 权重 | 小 | 好 | CPU 推理 |
4.3 GPTQ:逐层最优量化
GPTQ 的核心思想:逐层量化,最小化每层的输出误差。
def gptq_quantize_layer(
layer_weight: torch.Tensor, # (out_features, in_features)
layer_input: torch.Tensor, # 校准数据 (num_samples, in_features)
bits: int = 4
) -> tuple:
"""
GPTQ 量化单层
返回:(量化权重, 缩放因子)
"""
out_features, in_features = layer_weight.shape
# 计算海森矩阵(Hessian)的逆
# H = 2 * X^T @ X,其中 X 是输入
H = 2 * layer_input.T @ layer_input
H_inv = torch.inverse(H)
# 初始化
Q = layer_weight.clone()
scale = torch.zeros(out_features)
# 逐列量化
for j in range(in_features):
# 计算量化误差
w_col = Q[:, j]
q_col = quantize(w_col, bits)
scale = (w_col.max() - w_col.min()) / (2**bits - 1)
# 量化
Q[:, j] = q_col
# 更新剩余列,补偿量化误差
error = (w_col - q_col) / H[j, j]
Q[:, j+1:] -= error.unsqueeze(1) @ H[j, j+1:].unsqueeze(0)
return Q, scale
4.4 AWQ:激活感知的量化
AWQ(Activation-aware Weight Quantization)的核心洞察:并非所有权重都同等重要。
关键发现:某些通道的激活值始终很大,这些通道的权重精度更重要。
def awq_quantize(
weight: torch.Tensor,
activations: torch.Tensor, # 校准数据的激活值
bits: int = 4
) -> tuple:
"""
AWQ 量化
核心步骤:
1. 分析激活值分布,识别重要通道
2. 对重要通道使用更高精度
3. 对非重要通道使用低精度
"""
# 计算每个通道的激活幅度
activation_salience = activations.abs().mean(dim=0) # (in_features,)
# 归一化
activation_salience = activation_salience / activation_salience.max()
# 根据激活幅度调整量化范围
# 重要通道:更大的量化范围(更精细)
# 非重要通道:更小的量化范围(更粗糙)
quantized_weight = torch.zeros_like(weight)
scale = torch.zeros(weight.shape[0])
for i in range(weight.shape[0]):
# 根据激活重要性调整缩放
salience = activation_salience[i]
# 重要通道使用更大的缩放(更精细的量化)
adjusted_scale = compute_scale(weight[i], bits, salience)
scale[i] = adjusted_scale
quantized_weight[i] = quantize(weight[i], adjusted_scale, bits)
return quantized_weight, scale
4.5 量化精度对比
在 MMLU 基准测试上的精度损失:
| 模型 | FP16 | INT8 | INT4 (GPTQ) | INT4 (AWQ) |
|---|---|---|---|---|
| LLaMA-7B | 44.5 | 44.3 | 43.8 | 44.1 |
| LLaMA-70B | 69.7 | 69.5 | 68.9 | 69.3 |
| Qwen-72B | 77.4 | 77.2 | 76.5 | 77.0 |
AWQ 在 INT4 量化下,精度损失控制在 1% 以内。
4.6 vLLM 加载量化模型
from vllm import LLM
# 加载 AWQ 量化模型
llm = LLM(
model="TheBloke/Llama-2-70B-AWQ",
quantization="awq",
tensor_parallel_size=4,
)
# 加载 GPTQ 量化模型
llm = LLM(
model="TheBloke/Llama-2-70B-GPTQ",
quantization="gptq",
tensor_parallel_size=4,
)
五、MoE 架构:只激活需要的专家
5.1 MoE 的基本原理
混合专家模型(Mixture of Experts, MoE)的核心思想:模型容量很大,但每次推理只激活一小部分参数。
Dense 模型:
每次推理激活全部参数(如 70B)
MoE 模型:
总参数:70B(8 个专家,每个 10B + 2B 共享)
每次推理只激活:12B(1 个专家 + 共享参数)
激活率:17%
5.2 MoE 的架构设计
class MoELayer(nn.Module):
"""MoE 层的实现"""
def __init__(self, hidden_dim: int, num_experts: int, top_k: int = 1):
super().__init__()
self.hidden_dim = hidden_dim
self.num_experts = num_experts
self.top_k = top_k
# 门控网络:决定路由到哪些专家
self.gate = nn.Linear(hidden_dim, num_experts)
# 专家网络
self.experts = nn.ModuleList([
FeedForward(hidden_dim) for _ in range(num_experts)
])
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Args:
x: (batch_size, seq_len, hidden_dim)
"""
batch_size, seq_len, hidden_dim = x.shape
# 计算门控分数
gate_logits = self.gate(x) # (batch, seq, num_experts)
gate_probs = torch.softmax(gate_logits, dim=-1)
# 选择 top-k 专家
top_k_probs, top_k_indices = torch.topk(gate_probs, self.top_k, dim=-1)
# 归一化
top_k_probs = top_k_probs / top_k_probs.sum(dim=-1, keepdim=True)
# 计算专家输出
output = torch.zeros_like(x)
for k in range(self.top_k):
# 当前专家索引
expert_indices = top_k_indices[:, :, k] # (batch, seq)
expert_weights = top_k_probs[:, :, k:k+1] # (batch, seq, 1)
# 对每个专家,收集对应的 token
for expert_id in range(self.num_experts):
# 找到路由到这个专家的所有 token
mask = (expert_indices == expert_id)
if not mask.any():
continue
# 提取这些 token
expert_input = x[mask] # (num_tokens, hidden_dim)
# 专家计算
expert_output = self.experts[expert_id](expert_input)
# 加权累加
output[mask] += expert_weights[mask] * expert_output
return output
5.3 MoE 的推理优化
MoE 的主要挑战:专家路由的负载均衡。
如果所有 token 都路由到同一个专家,这个专家会成为瓶颈,其他专家闲置。
解决方案:
- 辅助损失(Auxiliary Loss):训练时加入负载均衡损失
- 专家并行(Expert Parallelism):不同专家分布在不同 GPU
- Token 重组:推理时动态重组 token,提高专家利用率
def expert_parallel_forward(
x: torch.Tensor,
experts: List[nn.Module],
gate: nn.Module,
num_gpus: int
) -> torch.Tensor:
"""
专家并行的前向传播
每个 GPU 存储部分专家
"""
# 计算路由
gate_probs = gate(x)
top_k_probs, top_k_indices = torch.topk(gate_probs, k=1)
# 按 GPU 分组
# GPU 0: 专家 0-3
# GPU 1: 专家 4-7
# ...
outputs = []
for gpu_id in range(num_gpus):
# 这个 GPU 负责的专家
expert_start = gpu_id * (len(experts) // num_gpus)
expert_end = (gpu_id + 1) * (len(experts) // num_gpus)
# 找到路由到这些专家的 token
mask = (top_k_indices >= expert_start) & (top_k_indices < expert_end)
if not mask.any():
continue
# 发送到对应 GPU 计算
local_tokens = x[mask]
local_expert_ids = top_k_indices[mask] - expert_start
# 在这个 GPU 上计算
local_output = compute_experts(local_tokens, local_expert_ids, experts[expert_start:expert_end])
outputs.append((mask, local_output))
# 合并结果
final_output = torch.zeros_like(x)
for mask, output in outputs:
final_output[mask] = output
return final_output
5.4 MoE 推理性能
| 模型 | 总参数 | 激活参数 | 推理速度 | 相对 Dense |
|---|---|---|---|---|
| Mixtral-8x7B | 47B | 13B | 25 t/s | 2.5x |
| Grok-1 | 314B | 86B | 15 t/s | 3.6x |
| DeepSeek-MoE | 16B | 2.4B | 50 t/s | 4.2x |
六、生产级部署实战
6.1 vLLM 完整部署示例
from vllm import LLM, SamplingParams
from vllm.engine.arg_utils import EngineArgs
from vllm.engine.llm_engine import LLMEngine
# 方式 1:高级 API
llm = LLM(
model="Qwen/Qwen2.5-72B-Instruct",
tensor_parallel_size=4, # 4 卡并行
gpu_memory_utilization=0.95, # 显存利用率
max_model_len=8192, # 最大上下文长度
block_size=16, # PagedAttention 块大小
# 投机解码配置
speculative_model="Qwen/Qwen2.5-7B-Instruct",
num_speculative_tokens=5,
# 量化配置
quantization="awq",
)
sampling_params = SamplingParams(
max_tokens=512,
temperature=0.7,
top_p=0.9,
repetition_penalty=1.05,
)
# 批量推理
prompts = [
"请解释什么是机器学习?",
"Python 中如何实现单例模式?",
"请分析快速排序的时间复杂度。",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(f"Prompt: {output.prompt}")
print(f"Generated: {output.outputs[0].text}")
6.2 vLLM API 服务器
# 启动 OpenAI 兼容的 API 服务器
python -m vllm.entrypoints.openai.api_server \
--model Qwen/Qwen2.5-72B-Instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.95 \
--max-model-len 8192 \
--port 8000 \
--enable-prefix-caching \
--speculative-model Qwen/Qwen2.5-7B-Instruct \
--num-speculative-tokens 5
客户端调用:
import openai
client = openai.OpenAI(
base_url="http://localhost:8000/v1",
api_key="dummy"
)
response = client.chat.completions.create(
model="Qwen/Qwen2.5-72B-Instruct",
messages=[
{"role": "system", "content": "你是一个有帮助的助手。"},
{"role": "user", "content": "请解释什么是注意力机制?"}
],
max_tokens=512,
temperature=0.7,
)
print(response.choices[0].message.content)
6.3 性能监控与调优
from vllm import LLM
llm = LLM(model="Qwen/Qwen2.5-72B-Instruct")
# 获取引擎统计信息
stats = llm.llm_engine.get_stats()
print(f"总请求数: {stats['num_requests']}")
print(f"总生成 token 数: {stats['num_generated_tokens']}")
print(f"平均延迟: {stats['avg_latency_ms']:.2f} ms")
print(f"吞吐量: {stats['throughput_tokens_per_sec']:.2f} tokens/s")
# 显存使用情况
memory_stats = llm.llm_engine.get_memory_stats()
print(f"KV Cache 使用: {memory_stats['kv_cache_usage']:.2f} GB")
print(f"模型权重: {memory_stats['model_weights']:.2f} GB")
print(f"空闲显存: {memory_stats['free_memory']:.2f} GB")
6.4 调优参数速查表
| 参数 | 推荐值 | 说明 |
|---|---|---|
gpu_memory_utilization | 0.90-0.95 | 预留给 KV Cache 的显存比例 |
max_model_len | 按需设置 | 最大上下文长度,影响 KV Cache 大小 |
block_size | 16 | PagedAttention 块大小,16 是经验最优值 |
max_num_seqs | 256 | 最大并发序列数 |
max_num_batched_tokens | 8192 | 单批次最大 token 数 |
七、总结与展望
7.1 技术演进脉络
2022: 早期探索
├── 模型并行(Tensor Parallel)
└── 简单量化(INT8)
2023: 架构创新
├── PagedAttention (vLLM)
├── 投机解码
└── GPTQ/AWQ 量化
2024: 工程成熟
├── MoE 架构普及
├── Prefix Caching
└── FP8 量化支持
2025-2026: 系统融合
├── 投机解码 + PagedAttention
├── DFlash 块扩散
├── 异构推测解码
└── 推理成本下降 70%
7.2 选型决策树
需要推理加速?
├── 是 → 显存是否充足?
│ ├── 充足 → 使用投机解码(加速 2-3x)
│ └── 不足 → 使用量化(INT4/FP8)
│
└── 需要高吞吐?
├── 是 → vLLM + PagedAttention
└── 否 → 标准推理
模型是否为 MoE?
├── 是 → 专家并行 + 负载均衡
└── 否 → 标准张量并行
上下文长度?
├── > 32K → 启用 Prefix Caching
└── < 32K → 标准 KV Cache
7.3 未来趋势
- 存算一体:专用推理芯片(如 Groq LPU)突破冯·诺依曼瓶颈
- KV Cache 压缩:通过低秩近似压缩 KV Cache
- 动态投机:根据上下文动态调整草稿模型大小
- 多模态融合:统一的多模态推理优化框架
参考文献
- vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention (SOSP 2023)
- Fast Inference from Transformers via Speculative Decoding (ICML 2023)
- GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers (ICLR 2023)
- AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration (MLSys 2024)
- DFlash: Block Diffusion for Speculative Decoding (arXiv 2026)
- Mixtral of Experts (arXiv 2024)
本文系统性地解析了 LLM 推理优化的核心技术,从 PagedAttention 的内存管理革命,到投机解码的以小博大策略,再到量化技术和 MoE 架构的工程实践。这些技术的融合,正在让大模型推理从"用得起"走向"用得好"。