推理优化¶
⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。
图源:Google Cloud GKE 教程 - Build and deploy an agentic AI application with ADK and vLLM on GKE,许可:CC BY 4.0。这里用真实推理服务部署图替换原占位导图,帮助理解 CPU 编排层、GPU 推理层与模型服务边界。
📌 定位说明:本章侧重推理优化的工程部署实践。 - 📖 推理优化的算法原理与技术研究请参考 LLM 学习/02-大模型核心技术/02-推理优化技术
📖 章节导读¶
推理优化是通过各种技术和方法提高大模型推理速度、降低资源消耗的关键技术。本章将深入探讨推理优化的原理、方法和实践。
🎯 学习目标¶
- 理解推理优化的核心原理
- 掌握 KV Cache 等关键技术
- 学会使用批处理和流水线优化
- 了解编译优化技术
- 掌握 vLLM 与 SGLang 推理框架的使用与选型
- 理解 Speculative Decoding 的原理、变种与生产部署
- 掌握大厂面试中的相关问题
📑 本章目录¶
- 12.1 推理优化概述 — 为什么需要推理优化、优化层次
- 12.2 KV Cache — KV Cache 原理、实现与内存优化
- 12.3 批处理优化 — Static Batching → Continuous Batching → PagedAttention
- 12.4 编译优化 — 算子融合、CUDA Graph、Torch.compile
- 12.5 推理框架:vLLM 与 SGLang — 框架对比、部署实践
- 12.6 Speculative Decoding(投机解码) — 原理、变种(EAGLE/Medusa/MTP)、生产部署
- 12.7 MoE 模型推理优化 — MoE 架构特点、专家并行、显存优化
- 12.8 练习题 — 动手实践
- 12.9 面试准备 — 高频面试题与参考答案
12.1 推理优化概述¶
12.1.1 为什么需要推理优化¶
挑战:
- 计算成本高:
- 大模型参数量大
- 计算复杂度高
-
需要大量 GPU 资源
-
延迟要求高:
- 实时应用需要低延迟
- 用户体验要求快速响应
-
竞争需要高性能
-
并发需求大:
- 多用户同时访问
- 需要处理高并发
-
需要弹性扩展
-
成本压力大:
- GPU 成本高
- 能耗成本高
- 需要成本优化
优化目标:
- 降低延迟:减少推理时间
- 提高吞吐量:增加并发处理能力
- 降低成本:减少资源消耗
- 保持精度:优化不损失精度
12.1.2 优化层次¶
优化层次:
各层优化:
- 算法层:
- KV Cache
- Flash Attention
-
算法优化
-
模型层:
- 模型量化
- 模型剪枝
-
知识蒸馏
-
框架层:
- 框架优化
- 算子融合
-
内存优化
-
硬件层:
- GPU 优化
- 专用芯片
- 分布式计算
12.1.3 优化指标¶
关键指标:
- 延迟(Latency):
- 单次推理时间
- 首字延迟(TTFT)
-
总生成时间
-
吞吐量(Throughput):
- 每秒请求数(RPS)
- 每秒 Token 数
-
并发处理能力
-
资源利用率:
- GPU 利用率
- 显存占用
-
能耗
-
成本:
- 每次推理成本
- 每小时成本
- 总成本
12.2 KV Cache¶
12.2.1 KV Cache 原理¶
定义:KV Cache 是一种缓存机制,用于缓存 Transformer 模型中自注意力层的 Key 和 Value 矩阵,避免重复计算。
原理:
在自回归生成过程中,每个 token 的生成都需要计算之前所有 token 的注意力。 KV Cache 将之前计算的 K 和 V 缓存起来,后续生成时直接使用,避免重复计算。
数学表示:
对于第 t 个 token,注意力计算为:
使用 KV Cache 后:
其中: - Cache_K: 缓存的 K 矩阵 - Cache_V: 缓存的 V 矩阵
优化效果:
- 计算量:减少约 50%
- 显存占用:增加约 30%
- 速度提升:2-3 倍
12.2.2 KV Cache 实现¶
基础实现:
import torch
import torch.nn as nn
class KVCache:
"""KV Cache实现"""
def __init__(self, max_batch_size: int, max_seq_len: int, num_heads: int, head_dim: int):
"""
初始化KV Cache
Args:
max_batch_size: 最大批次大小
max_seq_len: 最大序列长度
num_heads: 注意力头数
head_dim: 每个头的维度
"""
self.max_batch_size = max_batch_size
self.max_seq_len = max_seq_len
self.num_heads = num_heads
self.head_dim = head_dim
# 初始化缓存
self.cache_k = torch.zeros(
max_batch_size,
num_heads,
max_seq_len,
head_dim
)
self.cache_v = torch.zeros(
max_batch_size,
num_heads,
max_seq_len,
head_dim
)
# 当前位置
self.current_pos = 0
def update(self, k: torch.Tensor, v: torch.Tensor):
"""
更新缓存
Args:
k: Key张量 [batch_size, num_heads, seq_len, head_dim]
v: Value张量 [batch_size, num_heads, seq_len, head_dim]
"""
batch_size = k.size(0)
seq_len = k.size(2)
# 更新缓存
self.cache_k[:batch_size, :, self.current_pos:self.current_pos+seq_len, :] = k
self.cache_v[:batch_size, :, self.current_pos:self.current_pos+seq_len, :] = v
# 更新位置
self.current_pos += seq_len
def get(self) -> tuple:
"""
获取缓存
Returns:
(k, v) 缓存的Key和Value
"""
k = self.cache_k[:, :, :self.current_pos, :]
v = self.cache_v[:, :, :self.current_pos, :]
return k, v
def clear(self):
"""清空缓存"""
self.current_pos = 0
self.cache_k.zero_()
self.cache_v.zero_()
# 使用示例
kv_cache = KVCache(
max_batch_size=1,
max_seq_len=1024,
num_heads=12,
head_dim=64
)
# 生成过程
for i in range(10):
# 模拟计算K和V
k = torch.randn(1, 12, 1, 64)
v = torch.randn(1, 12, 1, 64)
# 更新缓存
kv_cache.update(k, v)
# 获取缓存用于注意力计算
cached_k, cached_v = kv_cache.get()
# 使用缓存的K和V进行注意力计算
# ...
12.2.3 KV Cache 优化¶
优化策略:
- PagedAttention:
- 将 KV Cache 分页管理
- 动态分配显存
-
减少显存浪费
-
KV Cache 压缩:
- 压缩 KV Cache
- 减少显存占用
-
平衡精度和效率
-
多级缓存:
- 使用多级缓存
- 提高缓存命中率
- 减少计算量
代码实现:
class PagedKVCache:
"""分页KV Cache"""
def __init__(self, num_heads: int, head_dim: int, page_size: int = 128):
"""
初始化分页KV Cache
Args:
num_heads: 注意力头数
head_dim: 每个头的维度
page_size: 页大小
"""
self.num_heads = num_heads
self.head_dim = head_dim
self.page_size = page_size
# 页表
self.page_table = {}
# 页池
self.page_pool_k = []
self.page_pool_v = []
def allocate_page(self) -> int:
"""分配页"""
if not self.page_pool_k:
# 创建新页
page_k = torch.zeros(1, self.num_heads, self.page_size, self.head_dim)
page_v = torch.zeros(1, self.num_heads, self.page_size, self.head_dim)
self.page_pool_k.append(page_k)
self.page_pool_v.append(page_v)
return len(self.page_pool_k) - 1
else:
# 复用空闲页
return self._find_free_page()
def update(self, k: torch.Tensor, v: torch.Tensor, seq_idx: int):
"""
更新缓存
Args:
k: Key张量
v: Value张量
seq_idx: 序列索引
"""
# 计算页索引
page_idx = seq_idx // self.page_size
offset = seq_idx % self.page_size
# 分配页
if page_idx not in self.page_table:
self.page_table[page_idx] = self.allocate_page()
# 更新页
actual_page_idx = self.page_table[page_idx]
self.page_pool_k[actual_page_idx][:, :, offset:offset+1, :] = k
self.page_pool_v[actual_page_idx][:, :, offset:offset+1, :] = v
def get(self, seq_len: int) -> tuple:
"""
获取缓存
Args:
seq_len: 序列长度
Returns:
(k, v) 缓存的Key和Value
"""
# 计算需要的页数
num_pages = (seq_len + self.page_size - 1) // self.page_size
# 收集页
k_pages = []
v_pages = []
for page_idx in range(num_pages):
if page_idx in self.page_table:
actual_page_idx = self.page_table[page_idx]
k_pages.append(self.page_pool_k[actual_page_idx])
v_pages.append(self.page_pool_v[actual_page_idx])
# 拼接页
k = torch.cat(k_pages, dim=2)[:, :, :seq_len, :]
v = torch.cat(v_pages, dim=2)[:, :, :seq_len, :]
return k, v
12.3 批处理优化¶
12.3.1 批处理原理¶
定义:批处理是将多个请求组合成一个批次一起处理,以提高 GPU 利用率和吞吐量。
优势:
- 提高 GPU 利用率:
- GPU 擅长并行计算
- 批处理充分利用 GPU
-
提高计算效率
-
降低延迟:
- 分摊固定开销
- 减少启动时间
-
提高吞吐量
-
降低成本:
- 提高资源利用率
- 降低每次推理成本
- 优化整体成本
挑战:
- 序列长度不同:
- 不同请求长度不同
- 需要 Padding
-
浪费计算资源
-
动态批次:
- 请求到达时间不同
- 需要动态组批
- 增加复杂度
12.3.2 连续批处理¶
原理:连续批处理(Continuous Batching)允许不同长度的序列在同一个批次中处理,避免 Padding 浪费。
实现:
import torch
from collections import deque
class ContinuousBatchProcessor:
"""连续批处理器"""
def __init__(self, max_batch_size: int, max_seq_len: int):
"""
初始化连续批处理器
Args:
max_batch_size: 最大批次大小
max_seq_len: 最大序列长度
"""
self.max_batch_size = max_batch_size
self.max_seq_len = max_seq_len
# 请求队列
self.request_queue = deque() # deque双端队列,两端操作O(1)
# 活跃请求
self.active_requests = {}
def add_request(self, request_id: str, input_ids: torch.Tensor):
"""
添加请求
Args:
request_id: 请求ID
input_ids: 输入token IDs
"""
self.request_queue.append({
'id': request_id,
'input_ids': input_ids,
'seq_len': input_ids.size(0)
})
def get_batch(self) -> tuple[torch.Tensor, list[str]]:
"""
获取批次
Returns:
(batch_input_ids, request_ids)
"""
if not self.request_queue:
return None, []
# 选择请求组成批次
batch_requests = []
total_tokens = 0
while self.request_queue and len(batch_requests) < self.max_batch_size:
request = self.request_queue[0]
# 检查是否超过最大序列长度
if total_tokens + request['seq_len'] > self.max_seq_len:
break
# 添加到批次
batch_requests.append(self.request_queue.popleft())
total_tokens += request['seq_len']
if not batch_requests:
return None, []
# 准备批次数据
max_len = max(req['seq_len'] for req in batch_requests)
batch_input_ids = torch.zeros(
len(batch_requests),
max_len,
dtype=torch.long
)
request_ids = []
for i, req in enumerate(batch_requests): # enumerate同时获取索引和元素
batch_input_ids[i, :req['seq_len']] = req['input_ids']
request_ids.append(req['id'])
return batch_input_ids, request_ids
def complete_request(self, request_id: str):
"""
完成请求
Args:
request_id: 请求ID
"""
if request_id in self.active_requests:
del self.active_requests[request_id]
# 使用示例
processor = ContinuousBatchProcessor(
max_batch_size=4,
max_seq_len=1024
)
# 添加请求
processor.add_request("req1", torch.randint(0, 1000, (50,)))
processor.add_request("req2", torch.randint(0, 1000, (100,)))
processor.add_request("req3", torch.randint(0, 1000, (75,)))
# 获取批次
batch_input_ids, request_ids = processor.get_batch()
print(f"批次大小: {len(request_ids)}")
print(f"批次形状: {batch_input_ids.shape}")
12.3.3 动态批处理¶
原理:动态批处理根据请求到达时间动态组批,平衡延迟和吞吐量。
实现:
import time
import asyncio # Python标准异步库
class DynamicBatchProcessor:
"""动态批处理器"""
def __init__(self, max_batch_size: int, max_wait_time: float = 0.01):
"""
初始化动态批处理器
Args:
max_batch_size: 最大批次大小
max_wait_time: 最大等待时间(秒)
"""
self.max_batch_size = max_batch_size
self.max_wait_time = max_wait_time
# 请求队列
self.request_queue = []
self.last_batch_time = time.time()
async def add_request(self, request_id: str, input_ids: torch.Tensor): # async def定义协程函数
"""
添加请求
Args:
request_id: 请求ID
input_ids: 输入token IDs
"""
self.request_queue.append({
'id': request_id,
'input_ids': input_ids,
'seq_len': input_ids.size(0),
'arrival_time': time.time()
})
async def get_batch(self) -> tuple[torch.Tensor, list[str]]:
"""
获取批次
Returns:
(batch_input_ids, request_ids)
"""
if not self.request_queue:
return None, []
current_time = time.time()
time_since_last_batch = current_time - self.last_batch_time
# 检查是否应该组批
should_batch = (
len(self.request_queue) >= self.max_batch_size or
time_since_last_batch >= self.max_wait_time
)
if not should_batch:
return None, []
# 组批
batch_requests = self.request_queue[:self.max_batch_size]
self.request_queue = self.request_queue[len(batch_requests):]
self.last_batch_time = current_time
# 准备批次数据
max_len = max(req['seq_len'] for req in batch_requests)
batch_input_ids = torch.zeros(
len(batch_requests),
max_len,
dtype=torch.long
)
request_ids = []
for i, req in enumerate(batch_requests):
batch_input_ids[i, :req['seq_len']] = req['input_ids']
request_ids.append(req['id'])
return batch_input_ids, request_ids
# 使用示例
async def main():
processor = DynamicBatchProcessor(
max_batch_size=4,
max_wait_time=0.01
)
# 添加请求
await processor.add_request("req1", torch.randint(0, 1000, (50,))) # await等待异步操作完成
await asyncio.sleep(0.005)
await processor.add_request("req2", torch.randint(0, 1000, (100,)))
# 获取批次
batch_input_ids, request_ids = await processor.get_batch()
print(f"批次大小: {len(request_ids)}")
# 运行
asyncio.run(main()) # 创建事件循环运行顶层协程
12.4 编译优化¶
12.4.1 TorchScript¶
原理:TorchScript 将 PyTorch 模型转换为静态图,优化推理性能。
实现:
import torch
import torch.nn as nn
# 定义模型
class SimpleModel(nn.Module):
def __init__(self):
super().__init__() # super()调用父类方法
self.linear = nn.Linear(10, 5)
def forward(self, x):
return self.linear(x)
# 创建模型
model = SimpleModel()
# 转换为TorchScript
scripted_model = torch.jit.script(model)
# 保存模型
scripted_model.save("model_scripted.pt")
# 加载并推理
loaded_model = torch.jit.load("model_scripted.pt")
input_data = torch.randn(1, 10)
output = loaded_model(input_data)
12.4.2 TensorRT¶
原理:TensorRT 是 NVIDIA 的高性能推理优化器,支持多种优化技术。
实现:
import torch
from torch2trt import torch2trt
import tensorrt as trt
# 创建模型
model = SimpleModel()
model.eval()
# 示例输入
example_input = torch.randn(1, 10).cuda()
# 转换为TensorRT
trt_model = torch2trt(
model,
[example_input],
fp16_mode=True,
max_workspace_size=1 << 25
)
# 保存模型
torch.save(trt_model.state_dict(), "model_trt.pth")
# 推理
output = trt_model(example_input)
12.4.3 ONNX Runtime¶
原理:ONNX Runtime 是一个跨平台的高性能推理引擎。
实现:
import torch
import onnx
import onnxruntime as ort
# 导出模型为ONNX
model = SimpleModel()
model.eval()
dummy_input = torch.randn(1, 10)
torch.onnx.export(
model,
dummy_input,
"model.onnx",
input_names=["input"],
output_names=["output"],
dynamic_axes={"input": {0: "batch_size"}}
)
# 使用ONNX Runtime推理
ort_session = ort.InferenceSession("model.onnx")
input_name = ort_session.get_inputs()[0].name
output_name = ort_session.get_outputs()[0].name
# 推理
input_data = torch.randn(1, 10).numpy()
outputs = ort_session.run([output_name], {input_name: input_data})
12.5 推理框架: vLLM 与 SGLang¶
推理框架是连接模型与线上服务的关键基础设施。 vLLM 和 SGLang 是当前最主流的两个开源 LLM 推理框架。
12.5.1 vLLM 简介¶
vLLM 由 UC Berkeley Sky Lab 团队开发,以 PagedAttention 为核心,是目前社区使用最广泛的推理引擎。
核心特性: - PagedAttention:将 KV Cache 按页管理(类似操作系统虚拟内存),消除显存碎片 - Continuous Batching:动态插入/移除请求,最大化 GPU 利用率 - Tensor Parallelism / Pipeline Parallelism:多卡推理支持 - OpenAI 兼容 API:/v1/chat/completions 直接对接现有代码
# vLLM 快速启动服务
from vllm import LLM, SamplingParams
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct", tensor_parallel_size=1)
params = SamplingParams(temperature=0.7, max_tokens=512)
prompts = ["解释什么是Transformer", "写一首关于AI的诗"]
outputs = llm.generate(prompts, params)
for output in outputs:
print(output.outputs[0].text)
12.5.2 SGLang 详解¶
SGLang ( Structured Generation Language )由 UC Berkeley LMSYS 团队开发,专注于结构化生成与高效前缀共享,在多轮对话和 Agent 场景中性能突出。截至 2025 年 6 月,SGLang 在 GitHub 上已获得近 15K Stars,被 xAI、浪潮信息等企业用于生产部署。
📌 SGLang 版本说明:当前稳定版本为 v0.4.x,带来零开销批处理调度器、缓存感知负载均衡器等重大性能提升。
核心特性:
| 特性 | 说明 |
|---|---|
| RadixAttention | 基于 Radix Tree 自动管理前缀 KV Cache ,相同前缀的请求共享缓存,无需手动管理 |
| Compressed FSM / xgrammar | 使用压缩有限状态机约束结构化输出(JSON Schema),比逐 token 采样快 数倍,v0.4 版本基于 xgrammar 库,JSON 解码速度可达其他方案的 10 倍 |
| 零开销批处理调度器 | v0.4 新特性,CPU 调度与 GPU 计算重叠,吞吐量提升 1.1 倍 |
| 缓存感知负载均衡器 | v0.4 新特性,智能路由机制,吞吐量提升 1.9 倍,缓存命中率提高 3.8 倍 |
| Chunked Prefill | 将长前缀分块预填充,避免长 prompt 阻塞短请求 |
| Torch.compile 深度集成 | 自动编译计算图,Llama 70B 推理速度提升 ~20%(v0.3 版本提升 1.5 倍) |
| DeepSeek MLA 优化 | v0.3 版本优化,DeepSeek 模型注意力速度提升 7 倍 |
| FP8 量化推理 | 原生支持 FP8 W8A8 量化 + FP8 KV 缓存,兼顾精度与速度 |
| 多 LoRA 批处理 | 支持多个 LoRA 适配器同时加载和推理 |
| 推测解码 | 内置投机采样加速,DeepSeek 模型解码吞吐量提升 1.9 倍 |
| 张量/流水线/专家/数据并行 | 支持 TP、PP、EP、DP 多种并行策略 |
| 多模态支持 | 支持 LLaVA-OneVision(多图像/视频)、视觉语言模型 |
RadixAttention 原理:
传统方式:每个请求独立计算全部 KV Cache
请求A: [System Prompt] + [User A] → 独立 KV Cache
请求B: [System Prompt] + [User B] → 独立 KV Cache(重复计算 System Prompt)
RadixAttention:自动识别共享前缀,复用 KV Cache
Radix Tree:
[System Prompt] ── KV Cache(共享)
├── [User A] → 增量 KV Cache
└── [User B] → 增量 KV Cache
→ 节省 ~30-60% 显存,吞吐提升 2-5x(前缀越长效果越明显)
SGLang v0.4 性能突破:
SGLang v0.4 版本核心优化(2025年发布):
1. 零开销批处理调度器
├── CPU 调度与 GPU 计算时间重叠
└── 吞吐量提升 1.1 倍
2. 缓存感知负载均衡器
├── 智能请求路由,优先分配到缓存命中高的节点
├── 吞吐量提升 1.9 倍
└── 缓存命中率提高 3.8 倍
3. DeepSeek MLA 优化
├── 针对 DeepSeek 模型专用注意力机制优化
└── 速度提升 7 倍
4. Torch.compile 加速
└── 推理速度提升 1.5 倍
实测性能(共享前缀批量请求):
- 吞吐量:158,596 token/s
- 缓存命中率:75%
SGLang 安装方法:
# 方法1:pip 安装(推荐)
pip install --upgrade pip
pip install "sglang[all]"
pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/
# 方法2:从源码安装(获取最新功能)
git clone -b v0.4.0 https://github.com/sgl-project/sglang.git
cd sglang
pip install --upgrade pip
pip install -e "python[all]"
pip install flashinfer -i https://flashinfer.ai/whl/cu121/torch2.4/
# 方法3:Docker 部署
docker run --gpus all -p 30000:30000 -v /models:/models lmsysorg/sglang
SGLang Engine 代码示例:
import sglang as sgl
# 方式1:离线批量推理
llm = sgl.Engine(model_path="Qwen/Qwen2.5-7B-Instruct")
prompts = ["什么是大模型推理优化?", "解释KV Cache的作用"]
outputs = llm.generate(prompts, sampling_params={"temperature": 0.7, "max_new_tokens": 256})
for out in outputs:
print(out["text"])
llm.shutdown()
# 方式2:启动 OpenAI 兼容服务器
# 终端执行:
# python -m sglang.launch_server --model Qwen/Qwen2.5-7B-Instruct --port 8000
# 客户端调用(与 vLLM/OpenAI 完全兼容)
import openai
client = openai.OpenAI(base_url="http://localhost:8000/v1", api_key="none")
response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[{"role": "user", "content": "用JSON格式输出三个中国城市的信息"}],
response_format={"type": "json_object"}, # 结构化输出
)
print(response.choices[0].message.content)
SGLang 性能优化技巧:
# 1. 启用 Torch.compile 加速
llm = sgl.Engine(
model_path="meta-llama/Llama-3-8B-Instruct",
enable_torch_compile=True, # 推理速度提升 1.5 倍
)
# 2. 张量并行(多卡部署)
llm = sgl.Engine(
model_path="meta-llama/Llama-3-70B-Instruct",
tensor_parallel_size=4, # 使用 4 卡并行
)
# 3. DeepSeek 模型优化(启用 MLA)
llm = sgl.Engine(
model_path="deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
enable_mla=True, # MLA 注意力加速 7 倍
)
# 4. FP8 量化部署
llm = sgl.Engine(
model_path="meta-llama/Llama-3-8B-Instruct",
quant_method="fp8", # FP8 W8A8 量化
)
# 5. 结构化输出(xgrammar 加速)
json_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
},
"required": ["name", "age"]
}
output = llm.generate(
"生成一个AI工程师的简介",
sampling_params={"max_new_tokens": 200, "json_schema": json_schema}
)
SGLang 部署示例(浪潮信息 NF5688G7 + DeepSeek R1 671B):
# 单机部署 DeepSeek R1 671B,支持 1000+ 并发
python -m sglang.launch_server \
--model-path deepseek-ai/DeepSeek-R1 \
--tensor-parallel-size 8 \
--port 30000 \
--enable-mla \
--quantization fp8
# 性能指标:
# - 显存带宽:4.8 TB/s
# - GPU P2P 带宽:900 GB/s
# - 并发用户:1000+
# - 吞吐量:158,596 token/s
# SGLang 原生支持 JSON Schema 约束(Compressed FSM 加速)
from sglang import Engine
llm = Engine(model_path="Qwen/Qwen2.5-7B-Instruct")
json_schema = {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer", "minimum": 0},
"skills": {"type": "array", "items": {"type": "string"}}
},
"required": ["name", "age", "skills"]
}
output = llm.generate(
"生成一个AI工程师的简介",
sampling_params={"max_new_tokens": 200, "json_schema": json_schema}
)
print(output[0]["text"]) # 保证输出符合 JSON Schema
12.5.3 vLLM vs SGLang 对比¶
| 对比维度 | vLLM | SGLang |
|---|---|---|
| 开发团队 | UC Berkeley Sky Lab | UC Berkeley LMSYS |
| 核心创新 | PagedAttention | RadixAttention + xgrammar |
| KV Cache 管理 | 分页内存管理(按页) | Radix Tree 前缀共享(按语义) |
| 结构化输出 | Outlines 集成 | 原生 xgrammar ,JSON 速度快 10 倍 |
| 多轮对话 | 普通性能 | 前缀共享显著加速(2-5x) |
| 编译优化 | 有限 | Torch.compile 深度集成(1.5x) |
| v0.4 新特性 | PagedAttention v2 | 零开销调度器 + 缓存感知负载均衡 |
| 社区生态 | 更成熟,文档丰富 | 快速增长(15K+ Stars) |
| 模型支持 | 更广泛 | 主流模型均支持 |
| 吞吐量(共享前缀场景) | 基准 | 1.5-5x 提升 |
| 首 Token 延迟 | 基准 | 通常更低( Chunked Prefill ) |
| API 兼容性 | OpenAI 兼容 | OpenAI 兼容 |
| 多模态支持 | 可用 | LLaVA-OneVision 原生支持 |
| 国产模型优化 | 一般 | DeepSeek MLA 7 倍加速 |
| 量化支持 | FP8/AWQ/INT4 | FP8/FP4/INT4/AWQ/GPTQ |
选择建议:
选择 vLLM 的场景:
✅ 需要最广泛的模型兼容性(HuggingFace 默认支持)
✅ 团队已有 vLLM 经验和部署
✅ 简单的单轮问答服务
✅ 需要稳定的生产环境(生态更成熟)
选择 SGLang 的场景:
✅ Agent 多轮调用(大量共享 System Prompt)
✅ 需要 JSON/结构化输出约束(xgrammar 加速)
✅ 共享前缀的批量请求(如同一文档的多个问题)
✅ DeepSeek/国产模型部署(MLA 7 倍加速)
✅ 追求极致吞吐和低延迟(v0.4 性能领先)
✅ 多模态应用(LLaVA-OneVision)
生产环境选型参考(2025 Q2):
| 场景 | 推荐框架 | 原因 |
|---|---|---|
| 电商客服(多轮对话) | SGLang | 前缀共享 + 结构化输出 |
| 代码生成平台 | SGLang | DeepSeek 优化 + 高吞吐 |
| 通用 API 服务 | vLLM | 生态成熟 + 模型广泛 |
| 文档问答(RAG) | SGLang | 长上下文 + 批量查询 |
| 多模态应用 | SGLang | LLaVA-OneVision 原生支持 |
| 边缘部署 | vLLM | 资源受限场景优化更好 |
12.6 Speculative Decoding (投机解码)¶
Speculative Decoding 是一种利用小模型加速大模型推理的技术,在不损失输出质量的前提下显著降低延迟。
12.6.1 核心原理¶
思路:用一个小而快的 Draft Model 先「猜」多个 token ,再用大模型一次性验证,接受正确的 token 、拒绝错误的 token 。
传统自回归解码(每步 1 token):
大模型 → t1 → 大模型 → t2 → 大模型 → t3 → ... (N 步)
Speculative Decoding(每步可能接受 γ 个 token):
小模型 → [t1, t2, t3, t4, t5](猜 γ=5 个)
大模型 → 一次前向传播验证 → 接受 [t1, t2, t3],拒绝 t4
→ 平均每步 >1 token,加速 2-3x
12.6.2 数学保证¶
Speculative Decoding 的关键优势是无损——输出分布与直接使用大模型完全一致。
验证策略基于拒绝采样: - 对 Draft Model 生成的 token \(t_i\),计算接受概率:
- 若拒绝,从修正分布中重新采样:
12.6.3 实现示例¶
import torch
def speculative_decode(target_model, draft_model, input_ids, gamma=5, temperature=1.0):
"""
Speculative Decoding 简化实现
target_model: 大模型(如 70B)
draft_model: 小模型(如 7B,同系列)
gamma: 每轮猜测的 token 数
"""
generated = input_ids.clone()
while len(generated[0]) < input_ids.shape[1] + 100: # 简化:生成100个token
# Step 1: Draft Model 自回归生成 γ 个候选 token
draft_tokens = []
draft_probs = []
draft_input = generated.clone()
for _ in range(gamma):
with torch.no_grad(): # 禁用梯度计算,节省内存(推理时使用)
logits = draft_model(draft_input).logits[:, -1, :] / temperature
probs = torch.softmax(logits, dim=-1)
token = torch.multinomial(probs, 1)
draft_tokens.append(token)
draft_probs.append(probs)
draft_input = torch.cat([draft_input, token], dim=-1)
# Step 2: Target Model 一次前向传播验证所有候选
with torch.no_grad():
all_input = torch.cat([generated] + draft_tokens, dim=-1)
target_logits = target_model(all_input).logits / temperature
# Step 3: 逐个验证,基于拒绝采样
accepted = 0
for i in range(gamma):
target_prob = torch.softmax(target_logits[:, -(gamma - i + 1), :], dim=-1)
draft_prob = draft_probs[i]
token = draft_tokens[i]
# 接受概率
accept_prob = torch.min(
torch.ones_like(target_prob),
target_prob / (draft_prob + 1e-10)
)
if torch.rand(1).item() < accept_prob[0, token[0, 0]].item():
generated = torch.cat([generated, token], dim=-1)
accepted += 1
else:
# 从修正分布采样
residual = torch.clamp(target_prob - draft_prob, min=0)
residual = residual / residual.sum(dim=-1, keepdim=True)
new_token = torch.multinomial(residual, 1)
generated = torch.cat([generated, new_token], dim=-1)
break
if accepted == gamma:
# 所有猜测都被接受,额外从 target 采样一个
bonus_prob = torch.softmax(target_logits[:, -1, :], dim=-1)
bonus_token = torch.multinomial(bonus_prob, 1)
generated = torch.cat([generated, bonus_token], dim=-1)
return generated
12.6.4 加速效果与适用场景¶
| 场景 | 加速比 | 说明 |
|---|---|---|
| Draft = 同系列小模型 | 2-3x | 如 Llama-70B + Llama-7B |
| Draft = 自身浅层层 | 1.5-2x | Self-Speculative Decoding |
| 高温度采样(创意写作) | 1.3-1.5x | 接受率低,加速有限 |
| 低温度/贪心解码(代码/翻译) | 2.5-3.5x | 接受率高,加速显著 |
适用条件: - ✅ Draft Model 与 Target Model 分布相近(同系列效果最好) - ✅ 解码为主的任务(非长 prefill ) - ✅ 模型越大加速越明显(大模型前向传播开销大) - ❌ 不适合 batch 很大的高吞吐场景(额外的 draft 计算抵消收益)
12.6.5 变种与发展演进¶
Speculative Decoding 自 2022 年提出以来,学术界和工业界发展出多种变种,在接受率、实现复杂度、适用场景等方面各有优劣。
12.6.5.1 EAGLE(投矛策略)¶
EAGLE(Enhanced Autoregressive Model via Greedy Leveraging and Efficiency)来自 Meta AI,采用自回归树搜索策略,被 ICML 2024 接收。
核心思想: - Draft Model 不再逐 token 生成,而是构建一棵多叉树同时探索多个候选路径 - 利用已计算的 KV Cache 加速树的扩展 - 验证时并行处理树的多个分支
传统 Speculative Decoding(γ=4):
[A] → [B] → [C] → [D] (线性串行猜测)
EAGLE(多叉树猜测):
[ROOT]
/ | \\
[A] [B] [C] (第一层:3个候选)
/ \\ / \\ / \\
[D][E][F][G][H][I] (第二层:扩展6个候选)
性能表现: - Llama-2-70B 加速 2.5-3x - 接受率比传统方法提升 15-20% - 适合代码生成、数学推理等多步推理任务
EAGLE 代码示例:
import torch
import torch.nn.functional as F
class EAGLEDrafting:
"""EAGLE 投机解码实现"""
def __init__(self, target_model, draft_model, max_depth=3, branching=3):
self.target_model = target_model
self.draft_model = draft_model
self.max_depth = max_depth
self.branching = branching
def build_draft_tree(self, input_ids, kv_cache=None):
"""
构建 draft 树
Args:
input_ids: 输入 token IDs
kv_cache: 现有的 KV Cache
Returns:
tree_tokens: 树中所有候选 token
tree_probs: 对应概率分布
path_to_accept: 每个分支的接受路径
"""
tree_tokens = [input_ids]
tree_probs = []
current_ids = input_ids
current_kv = kv_cache
for depth in range(self.max_depth):
# 批量预测所有叶子节点
next_layer_tokens = []
next_layer_probs = []
for branch_idx, branch_ids in enumerate(tree_tokens):
# 复用 KV Cache 加速
logits = self.draft_model(branch_ids, past_key_values=current_kv)
probs = F.softmax(logits[:, -1, :], dim=-1)
# 选择 top-k 候选
top_k_probs, top_k_ids = torch.topk(probs, self.branching, dim=-1)
for i in range(self.branching):
next_token = top_k_ids[:, i:i+1]
next_layer_tokens.append(
torch.cat([branch_ids, next_token], dim=-1)
)
next_layer_probs.append(top_k_probs[:, i])
tree_tokens = next_layer_tokens
tree_probs = next_layer_probs
return tree_tokens, tree_probs
def verify_and_sample(self, tree_tokens, tree_probs):
"""
并行验证树中所有候选,选择最优路径
Returns:
accepted_ids: 被接受的 token 序列
accepted_count: 被接受的 token 数量
"""
# 拼接所有候选序列
max_len = max(t.shape[1] for t in tree_tokens)
padded_tokens = [
torch.cat([t, t.new_zeros(1, max_len - t.shape[1])], dim=-1)
for t in tree_tokens
]
batch_tokens = torch.cat(padded_tokens, dim=0)
# Target Model 一次前向传播验证
with torch.no_grad():
target_logits = self.target_model(batch_tokens).logits
# 计算每条路径的接受概率
best_path_idx = 0
best_path_score = 0.0
for idx, (tokens, draft_prob) in enumerate(zip(tree_tokens, tree_probs)):
# 计算这条路径的累积得分
path_score = draft_prob.item()
if path_score > best_path_score:
best_path_score = path_score
best_path_idx = idx
return tree_tokens[best_path_idx], best_path_score
12.6.5.2 Medusa(多头投机)¶
Medusa 来自 UT Austin 和 Princeton,提出多头投机策略,每个"头"独立预测一个候选 token,然后并行验证。
核心思想: - 不再使用独立的 Draft Model,而是在 Target Model 最后一层添加多个预测头 - 每个预测头预测下一个 token 的不同候选 - 一次前向传播同时生成多个候选序列
传统投机解码:
输入 → [小模型] → [大模型验证] → 输出
Medusa:
输入 → [共享主干] → [头1: 预测 t+1] → [头2: 预测 t+2] → [头3: 预测 t+3]
↓
[Target Model 一次前向传播,输出多个预测]
↓
并行验证所有候选,接受正确的 token
Medusa 训练: - 预测头与原模型联合训练 - 使用原模型输出的分布作为监督信号 - 训练目标是最小化预测头与主模型输出的 KL 散度
class MedusaPredictionHead(nn.Module):
"""Medusa 预测头"""
def __init__(self, hidden_size, vocab_size, num_heads=3):
super().__init__()
self.num_heads = num_heads
# 每个头有自己的输出层
self.heads = nn.ModuleList([
nn.Linear(hidden_size, vocab_size, bias=False)
for _ in range(num_heads)
])
def forward(self, hidden_states):
"""
Args:
hidden_states: [batch, seq_len, hidden]
Returns:
predictions: [batch, seq_len, num_heads, vocab_size]
"""
return torch.stack([head(hidden_states) for head in self.heads], dim=2)
class MedusaModel(nn.Module):
"""带 Medusa 头的模型"""
def __init__(self, base_model, num_medusa_heads=3):
super().__init__()
self.base_model = base_model
self.hidden_size = base_model.config.hidden_size
self.vocab_size = base_model.config.vocab_size
# 添加 Medusa 预测头
self.medusa_heads = MedusaPredictionHead(
self.hidden_size,
self.vocab_size,
num_heads=num_medusa_heads
)
def forward(self, input_ids):
# 主干网络前向传播
outputs = self.base_model(input_ids, output_hidden_states=True)
hidden_states = outputs.hidden_states[-1] # 最后一层
# Medusa 预测
medusa_logits = self.medusa_heads(hidden_states)
return {
'main_logits': outputs.logits,
'medusa_logits': medusa_logits
}
def medusa_decode(self, input_ids, temperature=1.0):
"""
Medusa 解码流程
1. 一次前向传播获取所有预测头的输出
2. 构建候选序列
3. 验证并接受
"""
outputs = self.forward(input_ids)
# 解析各预测头的输出
medusa_logits = outputs['medusa_logits'] # [batch, seq, num_heads, vocab]
candidates = []
for head_idx in range(self.medusa_heads.num_heads):
head_logits = medusa_logits[:, -1, head_idx, :] / temperature
probs = F.softmax(head_logits, dim=-1)
token = torch.multinomial(probs, 1)
candidates.append(token)
# 构建候选序列:[input, cand1, cand2, cand3]
candidate_sequence = torch.cat([input_ids] + candidates, dim=-1)
# Target 验证
with torch.no_grad():
verification_logits = self.base_model(candidate_sequence).logits
# 逐个验证并接受
accepted = []
for i, cand_token in enumerate(candidates):
target_prob = F.softmax(verification_logits[:, input_ids.shape[1] + i, :], dim=-1)
cand_prob = F.softmax(medusa_logits[:, -1, i, :], dim=-1)
accept_prob = torch.min(
torch.ones_like(target_prob),
target_prob / (cand_prob + 1e-10)
)
if torch.rand(1).item() < accept_prob[0, cand_token[0, 0]].item():
accepted.append(cand_token)
else:
# 拒绝:从修正分布采样
residual = torch.clamp(target_prob - cand_prob, min=0)
residual = residual / residual.sum(dim=-1, keepdim=True)
new_token = torch.multinomial(residual, 1)
accepted.append(new_token)
break
return torch.cat([input_ids] + accepted, dim=-1)
性能表现: - 无需额外小模型,部署简单 - Llama-7B 加速 2x - 接受率取决于预测头数量(通常 3-5 个头) - 适合资源受限场景(边缘设备、移动端)
12.6.5.3 Self-Speculative Decoding(自投机)¶
Self-Speculative Decoding 不使用外部 Draft Model,而是利用 Target Model 自身的浅层预测、深层验证。
核心思想: - 将模型分为两部分:浅层(Draft)和深层(Target) - 浅层快速生成候选 token - 深层一次前向传播验证所有候选
模型结构:
[浅层 1-12] → 生成候选 t+1, t+2, t+3...
↓
[深层 13-48] → 验证候选(一次前向)
工作流程:
输入 → [浅层] → 候选1, 候选2, 候选3
↓
[深层一次前向] → 验证 → 接受/拒绝
实现方式: - Early Exit:在浅层处 exit,用浅层输出预测下一个 token - Shadow Model:训练一个浅层版本的网络作为 draft
class SelfSpeculativeDecoder:
"""自投机解码器"""
def __init__(self, model, num_draft_layers, num_draft_tokens=5):
"""
Args:
model: 原始模型
num_draft_layers: 用作 draft 的层数
num_draft_tokens: 每轮猜测的 token 数
"""
self.model = model
self.num_draft_layers = num_draft_layers
self.num_draft_tokens = num_draft_tokens
self.total_layers = model.config.num_hidden_layers
def draft_generation(self, input_ids, draft_kv_cache):
"""
浅层 draft 生成
"""
draft_tokens = []
current_ids = input_ids
current_kv = draft_kv_cache
for _ in range(self.num_draft_tokens):
# 只通过浅层
hidden = self.model.model.embed_tokens(current_ids)
for layer_idx in range(self.num_draft_layers):
hidden = self.model.model.layers[layer_idx](hidden, current_kv)
# 预测下一个 token
logits = self.model.lm_head(hidden[:, -1, :])
probs = F.softmax(logits, dim=-1)
next_token = torch.multinomial(probs, 1)
draft_tokens.append(next_token)
current_ids = torch.cat([current_ids, next_token], dim=-1)
return draft_tokens
def verify(self, input_ids, draft_tokens):
"""
深层验证
"""
# 拼接 draft tokens
all_ids = torch.cat([input_ids] + draft_tokens, dim=-1)
# 一次完整前向传播
with torch.no_grad():
outputs = self.model(all_ids, use_cache=True)
# 逐个验证
accepted_tokens = []
for i, draft_token in enumerate(draft_tokens):
target_logits = outputs.logits[:, input_ids.shape[1] + i, :]
target_probs = F.softmax(target_logits, dim=-1)
# 简化:直接贪心接受 top-1
target_token = torch.argmax(target_probs, dim=-1, keepdim=True)
if torch.all(target_token == draft_token):
accepted_tokens.append(draft_token)
else:
# 拒绝:使用 target 的预测
accepted_tokens.append(target_token)
break
return accepted_tokens
性能表现: - 零额外内存开销(无需加载小模型) - 加速比 1.5-2x(比外部 draft 模型低) - 适合无法加载多个模型的场景
12.6.5.4 Falcon(增强半自回归草案)¶
Falcon 来自中国电信,被 AAAI 2025 接收,提出增强半自回归草案 + 自定义验证树。
核心创新: - 半自回归草案:不是逐个生成 token,而是一次生成多个 token(类似 blockwise parallel decoding) - 自定义验证树:根据验证结果动态调整树结构 - 加速比达到 2.91-3.51x,成本降至 ⅓
传统 AR Draft(自回归):
Token: [t1] → [t2] → [t3] → [t4] → [t5] (5次串行生成)
SAR Draft(半自回归):
Token: [t1, t2, t3, t4, t5] (1次并行生成)
↓
验证树: [t1]
/ | \\
[t2] [t3] [t4] (动态选择)
12.6.5.5 MTP(Multi-Token Prediction)¶
MTP 由 Meta 提出,被 DeepSeek-V3 采用,核心是一次性预测多个 token 而非一个。
与 Speculative Decoding 的区别: - Speculative Decoding:验证已生成的候选(draft → target) - MTP:同时预测多个未来位置(一次前向,多个输出)
AR 解码(标准):
Step 1: 预测 t+1
Step 2: 预测 t+2
...
MTP 解码:
Step 1: 同时预测 [t+1, t+2, t+3, t+4] (4个预测头)
Step 2: 验证并接受
DeepSeek-V3 MTP 实现: - 4 个预测头,每个头预测 1 个 token - 预测头与主模型共享 embedding 和 output projector - 训练时使用独占目标函数(主模型和预测头用不同目标训练)
12.6.5.6 变种对比与选型¶
| 变种 | Draft 来源 | 额外内存 | 加速比 | 适用场景 | 局限 |
|---|---|---|---|---|---|
| 传统 SD | 独立小模型 | 高(~7B) | 2-3x | 追求极致加速 | 需加载两个模型 |
| EAGLE | 独立小模型 | 高 | 2.5-3x | 多步推理任务 | 树搜索复杂度高 |
| Medusa | 预测头 | 低 | 1.5-2x | 边缘部署 | 需重新训练 |
| Self-SD | 自身浅层 | 零 | 1.5-2x | 资源受限 | 加速效果有限 |
| Falcon | SAR Draft | 中 | 2.9-3.5x | 工业部署 | 实现复杂 |
| MTP | 预测头 | 中 | 1.5-2x | 训练加速 | 验证开销大 |
选型建议:
选 EAGLE:追求最佳加速效果,且任务有多步推理特征(代码、数学)
选 Medusa:边缘设备部署,内存受限,无法加载多个模型
选 Self-SD:不想改模型,只想用现有模型获得加速
选 Falcon:大厂生产部署,需要 3x+ 加速
选 MTP:训练阶段加速,或配合其他推理优化使用
12.6.6 生产环境实践¶
vLLM 中的 Speculative Decoding:
from vllm import LLM, SamplingParams
# 启用投机解码
llm = LLM(
model="meta-llama/Llama-3-70B-Instruct",
tensor_parallel_size=4,
speculative_model="meta-llama/Llama-3-8B-Instruct", # 小模型作为 draft
num_speculative_tokens=5, # 每轮猜测 5 个 token
)
# 推理(与普通调用完全相同)
params = SamplingParams(temperature=0.7, max_tokens=512)
output = llm.generate(["解释量子计算"], params)
print(output[0].outputs[0].text)
SGLang 中的 Speculative Decoding:
import sglang as sgl
# 启动带投机解码的 SGLang
# python -m sglang.launch_server \
# --model-path meta-llama/Llama-3-70B-Instruct \
# --tensor-parallel-size 4 \
# --speculative-model meta-llama/Llama-3-8B-Instruct \
# --speculative-num-tokens 5
# 或在 Engine 中配置
llm = sgl.Engine(
model_path="meta-llama/Llama-3-70B-Instruct",
tensor_parallel_size=4,
speculative_model="meta-llama/Llama-3-8B-Instruct",
speculative_num_tokens=5,
)
output = llm.generate("解释量子计算", sampling_params={"max_new_tokens": 512})
HuggingFace Transformers 中的 Medusa:
from transformers import AutoModelForCausalLM, AutoTokenizer
# 加载带 Medusa 的模型
model = AutoModelForCausalLM.from_pretrained(
"princeton-nlp/Llama-7B-Medusa",
torch_dtype="auto",
device_map="auto"
)
# Medusa 解码
tokenizer = AutoTokenizer.from_pretrained("princeton-nlp/Llama-7B-Medusa")
input_ids = tokenizer("解释量子计算", return_tensors="pt").input_ids.to(model.device)
# 使用 medusa_decode 而非标准 generate
outputs = model.medusa_decode(input_ids, medusa_temperature=0.7)
print(tokenizer.decode(outputs[0]))
12.7 MoE模型推理优化¶
📌 章节说明:本节专门讲解混合专家(Mixture of Experts, MoE)模型的推理优化技术,包括DeepSeek-V3、Mixtral、DeepSeek-MoE等主流MoE架构的推理策略与优化实践。
12.7.1 MoE推理优化的核心挑战¶
┌─────────────────────────────────────────────────────────────────┐
│ MoE推理核心挑战 │├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. 稀疏激活带来的负载均衡问题 │
│ ├── 不同专家被激活概率不均 │
│ ├── 部分专家过载,部分专家空闲 │
│ └── 影响整体计算效率和延迟 │
│ │
│ 2. 专家并行通信开销 │
│ ├── 跨GPU/跨节点路由激活的专家 │
│ ├── All-to-All通信开销大 │
│ └── 通信与计算难以有效重叠 │
│ │
│ 3. 显存占用问题 │
│ ├── 总参数量大(数百B) │
│ ├── 需要存储所有专家权重 │
│ └── KV Cache + 专家权重 → 显存压力巨大 │
│ │
│ 4. 批处理效率问题 │
│ ├── 不同请求激活的专家不同 │
│ ├── 难以有效 batching │
│ └── GPU利用率不稳定 │
│ │
└─────────────────────────────────────────────────────────────────┘
12.7.2 MoE推理架构设计¶
class MoEInferenceArchitecture:
"""MoE推理架构设计"""
def __init__(self, model_name: str):
self.model_name = model_name
self.expert_config = self.get_expert_config(model_name)
def get_expert_config(self, model_name: str) -> dict:
"""获取各MoE模型的专家配置"""
configs = {
# DeepSeek-V3: 671B总参数,37B激活
"DeepSeek-V3": {
"total_params": "671B",
"activated_params": "37B",
"num_shared_experts": 1,
"num_routed_experts": 256,
"top_k": 8,
"expert_parallel": "EP144", # Decode阶段
"部署单元": "18节点",
"每卡专家数": "2路由 + 1共享",
},
# Mixtral 8x7B: 总46B,激活约12B
"Mixtral-8x7B": {
"total_params": "46.7B",
"activated_params": "12B",
"num_experts": 8,
"top_k": 2,
"expert_type": "SwiGLU",
"部署": "单机8卡",
},
# DeepSeek-MoE-16B: 总16B,激活约2B
"DeepSeek-MoE-16B": {
"total_params": "16.4B",
"activated_params": "2B",
"num_shared_experts": 1,
"num_routed_experts": 64,
"top_k": 4,
"特点": "细粒度专家,组合自由度更高",
},
}
return configs.get(model_name, {})
def design_ep_layout(self, num_gpus: int, model_name: str) -> dict:
"""
设计专家并行布局
Args:
num_gpus: 可用GPU数量
model_name: 模型名称
Returns:
专家并行配置
"""
# DeepSeek-V3 推理部署配置
if "DeepSeek-V3" in model_name:
return {
"prefill": {
"expert_parallel": 32, # EP32
"dp": 32,
"部署单元": "4节点",
"冗余专家": 32,
"每卡路由专家": 9,
"每卡共享专家": 1,
},
"decode": {
"expert_parallel": 144, # EP144
"dp": 144,
"部署单元": "18节点",
"冗余专家": 32,
"每卡路由专家": 2,
"每卡共享专家": 1,
}
}
return {}
12.7.3 专家选择与负载均衡策略¶
class ExpertLoadBalancer:
"""专家负载均衡器"""
def __init__(self, num_experts: int, top_k: int):
self.num_experts = num_experts
self.top_k = top_k
self.expert_counts = [0] * num_experts # 统计各专家使用次数
self.expert_bias = [0.0] * num_experts # 专家偏置(用于负载均衡)
def router(self, input_features: torch.Tensor, temperature: float = 1.0) -> tuple:
"""
路由器:为每个token选择top_k个专家
Args:
input_features: 输入特征 [batch_size, seq_len, hidden_dim]
temperature: 温度参数(控制分布平滑度)
Returns:
(expert_indices, weights): 选中的专家索引和权重
"""
# 门控网络计算专家得分
# 实际实现中这是一个线性层 W_g
gates = self.compute_gates(input_features) # [batch_size, seq_len, num_experts]
# 添加偏置用于负载均衡(DeepSeek的无辅助损失策略)
gates = gates + torch.tensor(self.expert_bias, device=gates.device)
# 选择top_k专家
top_k_gates, top_k_indices = torch.topk(gates, self.top_k, dim=-1)
# Softmax归一化权重
top_k_weights = F.softmax(top_k_gates / temperature, dim=-1)
# 更新统计(用于监控和调整)
self.update_statistics(top_k_indices)
return top_k_indices, top_k_weights
def update_statistics(self, expert_indices: torch.Tensor):
"""更新专家使用统计"""
# 统计每个专家被选中的次数
for idx in expert_indices.flatten().unique():
self.expert_counts[idx.item()] += 1
def adjust_bias(self, target_load: float = 1.0):
"""
调整专家偏置以实现负载均衡
DeepSeek的无辅助损失策略:
- 根据实际负载动态调整专家偏置
- 避免辅助损失对模型性能的干扰
- 保持训练稳定性
Args:
target_load: 目标负载率
"""
total_tokens = sum(self.expert_counts)
if total_tokens == 0:
return
for i in range(self.num_experts):
actual_load = self.expert_counts[i] / total_tokens
# 如果实际负载低于目标,增加偏置使其更可能被选中
# 如果实际负载高于目标,减少偏置使其更少被选中
self.expert_bias[i] += (target_load - actual_load) * 0.1
# 重置计数
self.expert_counts = [0] * self.num_experts
def get_load_balance_metrics(self) -> dict:
"""获取负载均衡指标"""
total = sum(self.expert_counts)
if total == 0:
return {"status": "no_data"}
loads = [c / total for c in self.expert_counts]
return {
"expert_loads": loads,
"std": np.std(loads), # 标准差越小越均衡
"max_load": max(loads),
"min_load": min(loads),
"balance_ratio": min(loads) / max(loads) if max(loads) > 0 else 0,
}
12.7.4 MoE推理优化策略¶
class MoEInferenceOptimizer:
"""MoE推理优化器"""
def __init__(self, model: nn.Module, config: dict):
self.model = model
self.config = config
# ========== 策略1: 专家缓存与预加载 ==========
def cache_experts(self, device: str = "cuda"):
"""
专家权重缓存:将所有专家权重预加载到GPU
优化效果:
- 避免推理时动态加载专家权重
- 减少显存访问延迟
"""
for expert in self.model.experts:
expert.to(device)
# ========== 策略2: 动态专家并行 ==========
def dynamic_expert_parallel(
self,
batch_size: int,
seq_len: int,
phase: str = "decode"
) -> int:
"""
动态选择专家并行度
Args:
batch_size: 批次大小
seq_len: 序列长度
phase: 阶段(prefill/decode)
Returns:
最优专家并行度
"""
if phase == "prefill":
# Prefill阶段计算密集,可用较高EP
return 32
else:
# Decode阶段延迟敏感,降低EP以减少通信
return min(144, self.config.get("num_gpus", 8))
# ========== 策略3: 计算通信重叠 ==========
def overlap_compute_comm(
self,
expert_ids: list,
expert_outputs: dict,
next_batch: dict
) -> torch.Tensor:
"""
计算与通信重叠
双batch策略:
- Batch A在进行计算时,Batch B进行通信
- 两者交错执行,掩盖通信开销
"""
# 实现细节:需要异步All-to-All通信
# 使用CUDA流实现计算与通信并行
pass
# ========== 策略4: 冗余专家部署 ==========
def deploy_redundant_experts(
self,
num_original: int,
redundancy: int = 32
) -> int:
"""
部署冗余专家
DeepSeek-V3策略:
- 部署单元包含32个冗余路由专家
- 应对负载不均衡时专家溢出
- 减少跨节点通信
Args:
num_original: 原始专家数
redundancy: 冗余专家数
Returns:
总专家数(含冗余)
"""
return num_original + redundancy
# ========== 策略5: 稀疏激活优化 ==========
def sparse_activation(
self,
hidden_states: torch.Tensor,
top_k: int
) -> tuple:
"""
稀疏激活优化
仅对top_k专家进行计算,大幅降低计算量
"""
# 路由计算
gates = self.model.gate(hidden_states)
# 选择top_k
top_k_gates, top_k_indices = torch.topk(gates, top_k, dim=-1)
# 稀疏执行:仅计算选中的专家
outputs = self.sparse_forward(hidden_states, top_k_indices)
return outputs, top_k_indices
def sparse_forward(
self,
hidden_states: torch.Tensor,
expert_indices: torch.Tensor
) -> torch.Tensor:
"""
稀疏前向传播
仅对激活的专家进行计算
"""
# 实际实现中需要处理混合专家的稀疏计算
# 通常使用专家ID索引进行分组计算
outputs = []
for batch_idx in range(hidden_states.size(0)):
token_hidden = hidden_states[batch_idx:batch_idx+1]
# 对每个token,选择对应的专家
for token_idx, experts in enumerate(expert_indices[batch_idx]):
expert_output = self.compute_single_expert(
token_hidden[token_idx],
experts
)
outputs.append(expert_output)
return torch.stack(outputs)
12.7.5 DeepSeek-V3 推理优化实践¶
class DeepSeekV3Optimizer:
"""DeepSeek-V3专用推理优化器"""
def __init__(self, model_path: str = "deepseek-ai/DeepSeek-V3"):
self.model_path = model_path
self.quantization_config = {
"quant_method": "fp8",
"fmt": "e4m3",
"weight_block_size": [128, 128],
"activation_scheme": "dynamic",
}
def optimize_with_fp8(self, model: nn.Module) -> nn.Module:
"""
FP8量化推理优化
DeepSeek-V3原生FP8权重,支持:
- SGLang:原生FP8推理
- LMDeploy:原生FP8推理
- TensorRT-LLM:BF16推理
"""
# 权重量化:Block-wise 128x128
# 激活量化:Per-token动态量化
return quantized_model
def optimize_mla(self, model: nn.Module) -> nn.Module:
"""
MLA(Multi-Head Latent Attention)优化
核心优化:
- KV Cache压缩93.3%
- 低秩潜在向量替代完整KV矩阵
"""
# MLA通过将KV压缩到低维空间
# 推理时仅需缓存压缩向量
# 需要时通过上投影矩阵恢复
return optimized_model
def get_deployment_config(self, num_gpus: int, phase: str = "decode") -> dict:
"""
获取DeepSeek-V3推理部署配置
Args:
num_gpus: 可用GPU数量
phase: prefill 或 decode
Returns:
优化后的部署配置
"""
if phase == "prefill":
return {
"expert_parallel": 32,
"dp": 32,
"部署单元": "4节点",
"冗余路由专家": 32,
"每卡路由专家": 9,
"每卡共享专家": 1,
"通信优化": "双batch计算通信重叠",
}
else: # decode
return {
"expert_parallel": min(144, num_gpus),
"dp": min(144, num_gpus),
"部署单元": f"{num_gpus // 8}节点",
"冗余路由专家": 32,
"每卡路由专家": 2,
"每卡共享专家": 1,
"通信优化": "EP内All-to-All",
}
def benchmark_throughput(
self,
num_gpus: int = 8,
input_len: int = 1024,
output_len: int = 128
) -> dict:
"""
基准测试DeepSeek-V3推理吞吐量
Returns:
性能指标
"""
# H800 8卡配置下的预期性能
return {
"prefill_throughput": "约10万tokens/s",
"decode_throughput": "约60 tokens/s",
"memory_per_gpu": "约40GB",
"total_memory": f"{num_gpus * 40}GB",
}
12.7.6 MoE推理框架对比¶
┌─────────────────────────────────────────────────────────────────────────┐
│ MoE推理框架支持对比 │├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 框架 │ MoE支持 │ DeepSeek-V3支持 │ 特点 │
│ ────────────┼──────────────────┼──────────────────┼─────────────────────────│
│ SGLang │ 原生支持 │ FP8原生支持 │ RadixAttention前缀复用 │
│ vLLM │ MoE专家并行 │ BF16/FP8 │ PagedAttention │
│ LMDeploy │ MoE优化 │ FP8原生支持 │ TurboMind引擎 │
│ TensorRT-LLM │ 专家并行 │ BF16推理 │ 高性能TensorRT优化 │
│ MindIE │ MoE支持 │ BF16推理 │ 昇腾AI加速 │
│ │
│ 选择建议: │
│ • 追求最高推理速度:TensorRT-LLM(需NVIDIA) │
│ • 追求FP8原生支持:SGLang 或 LMDeploy │
│ • 跨平台兼容:vLLM │
│ • 昇腾硬件:MindIE │
│ │
└─────────────────────────────────────────────────────────────────────────┘
12.7.7 MoE推理优化面试要点¶
┌─────────────────────────────────────────────────────────────────────────┐
│ MoE推理优化面试要点 │├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Q1: MoE模型推理的主要挑战是什么? │
│ ──────────────────────────────────────────────────────────────────────│
│ 答:主要挑战有四方面: │
│ 1. 负载均衡:专家激活不均,需要动态调整 │
│ 2. 通信开销:跨GPU专家并行带来All-to-All通信 │
│ 3. 显存压力:总参数大(数百B),需高效管理 │
│ 4. 批处理效率:不同请求激活专家不同,难以有效batching │
│ │
│ Q2: DeepSeek-V3的MLA如何实现KV Cache压缩? │
│ ──────────────────────────────────────────────────────────────────────│
│ 答:MLA通过低秩联合压缩技术: │
│ • 将高维KV矩阵压缩到低维潜在空间(d_c << d_h × n_h) │
│ • 推理时仅需缓存压缩向量,而非完整KV矩阵 │
│ • KV Cache压缩率达93.3% │
│ • 通过上投影矩阵在计算时动态恢复 │
│ │
│ Q3: MoE的负载均衡策略有哪些? │
│ ──────────────────────────────────────────────────────────────────────│
│ 答:主流策略有: │
│ • 辅助损失函数:添加负载均衡损失项(简单但影响主任务) │
│ • DeepSeek无辅助损失:动态调整专家偏置项 │
│ • 专家容量限制:设置专家最大处理token数 │
│ • 冗余专家部署:部署额外专家应对溢出 │
│ │
│ Q4: MoE推理如何实现计算通信重叠? │
│ ──────────────────────────────────────────────────────────────────────│
│ 答:双batch策略: │
│ • Batch A计算时,Batch B进行通信 │
│ • 两者交错执行,掩盖通信延迟 │
│ • Prefill阶段尤其有效 │
│ │
│ Q5: DeepSeek-V3的专家并行配置是什么? │
│ ──────────────────────────────────────────────────────────────────────│
│ 答:分阶段配置: │
│ • Prefill:EP32 + DP32,4节点部署 │
│ • Decode:EP144 + DP144,18节点部署 │
│ • 每卡:2路由专家 + 1共享专家(Decode) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
12.7.8 MoE推理优化小结¶
┌─────────────────────────────────────────────────────────────────────────┐
│ MoE推理优化核心要点 │├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 1. 架构层面: │
│ • MLA:KV Cache压缩93.3% │
│ • DeepSeekMoE:共享专家 + 细粒度路由专家 │
│ • 无辅助损失负载均衡:动态偏置调整 │
│ │
│ 2. 部署层面: │
│ • Prefill:EP32,适合计算密集 │
│ • Decode:EP144,适合延迟敏感 │
│ • 冗余专家:32个冗余路由专家 │
│ │
│ 3. 优化策略: │
│ • 计算通信重叠:双batch策略 │
│ • 稀疏激活:仅计算top_k专家 │
│ • FP8量化:E4M3格式,Block-wise 128x128 │
│ │
│ 4. 框架选择: │
│ • SGLang/LMDeploy:原生FP8支持 │
│ • TensorRT-LLM:高性能NVIDIA优化 │
│ • vLLM:通用性好,跨平台支持 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
12.8 练习题¶
练习题 1:KV Cache¶
题目:实现一个简单的 KV Cache 机制。
参考答案:
class SimpleKVCache:
def __init__(self, num_heads, head_dim, max_seq_len):
self.cache_k = torch.zeros(num_heads, max_seq_len, head_dim)
self.cache_v = torch.zeros(num_heads, max_seq_len, head_dim)
self.current_pos = 0
def update(self, k, v):
self.cache_k[:, self.current_pos, :] = k
self.cache_v[:, self.current_pos, :] = v
self.current_pos += 1
def get(self):
return self.cache_k[:, :self.current_pos, :], self.cache_v[:, :self.current_pos, :]
练习题 2:批处理¶
题目:实现一个简单的批处理器。
参考答案:
class SimpleBatchProcessor:
def __init__(self, max_batch_size):
self.max_batch_size = max_batch_size
self.request_queue = []
def add_request(self, request_id, input_ids):
self.request_queue.append((request_id, input_ids))
def get_batch(self):
if not self.request_queue:
return None, []
batch_requests = self.request_queue[:self.max_batch_size]
self.request_queue = self.request_queue[len(batch_requests):]
request_ids = [req[0] for req in batch_requests]
batch_data = torch.stack([req[1] for req in batch_requests])
return batch_data, request_ids
12.9 面试准备¶
12.9.1 大厂面试题¶
字节跳动面试题:
- 问题:什么是 KV Cache?它有什么优势?
参考答案: - KV Cache 缓存自注意力的 Key 和 Value - 优势: - 避免重复计算 - 减少计算量约 50% - 提升推理速度 2-3 倍 - 增加显存占用约 30%
- 问题:连续批处理相比传统批处理有什么优势?
参考答案: - 避免 Padding 浪费 - 提高 GPU 利用率 - 降低计算成本 - 提高吞吐量
腾讯面试题:
- 问题:如何优化大模型的推理性能?
参考答案: - KV Cache:缓存注意力计算 - 批处理:提高 GPU 利用率 - 模型量化:减少计算量 - 编译优化:优化执行图 - 硬件优化:使用专用硬件
- 问题:PagedAttention 有什么优势?
参考答案: - 动态分配显存 - 减少显存浪费 - 支持更长的序列 - 提高显存利用率
阿里巴巴面试题:
- 问题:推理优化有哪些层次?
参考答案: - 算法层:KV Cache 、 Flash Attention - 模型层:量化、剪枝、蒸馏 - 框架层:算子融合、内存优化 - 硬件层:GPU 优化、专用芯片
- 问题:在实际项目中如何优化推理性能?
参考答案: - 性能分析:识别瓶颈 - 选择优化:选择合适的优化技术 - 实现优化:实现优化方案 - 测试验证:测试优化效果 - 持续监控:监控性能指标 - 持续优化:根据反馈持续优化
12.9.2 面试技巧¶
技巧 1:理论联系实际
结合实际项目经验,说明如何应用推理优化。
技巧 2:性能分析
展示性能分析的方法和工具。
技巧 3:优化选择
说明如何选择合适的优化技术。
技巧 4:效果评估
说明如何评估优化效果。
📝 本章小结¶
本章系统介绍了推理优化的核心内容:
- ✅ 推理优化概述:为什么需要、优化层次、优化指标
- ✅ KV Cache:原理、实现、优化
- ✅ 批处理优化:批处理原理、连续批处理、动态批处理
- ✅ 编译优化:TorchScript 、 TensorRT 、 ONNX Runtime
- ✅ 推理框架:vLLM 与 SGLang 对比及选型
- ✅ Speculative Decoding:投机解码原理、变种(EAGLE/Medusa/Self-SD/Falcon/MTP)与生产实践
- ✅ 练习题:KV Cache 、批处理
- ✅ 面试准备:大厂面试题和解答技巧
通过本章学习,你应该能够: - 理解推理优化的核心原理 - 掌握 KV Cache 等关键技术 - 学会使用批处理和流水线优化 - 了解编译优化技术 - 能够根据场景选择 vLLM 或 SGLang - 理解 Speculative Decoding 的数学保证、变种(EAGLE/Medusa/Self-SD/Falcon/MTP)与工程实现 - 准备好应对大厂面试
🔗 下一步¶
下一章我们将深入学习多模态应用,掌握如何处理和生成多模态数据。
继续学习: 13-多模态应用.md
💡 思考题¶
-
什么是 KV Cache?它有什么优势?
缓存已计算的 Key/Value 向量,避免每次生成新 Token 时重复计算前文的 Attention 。优势:生成速度提升数十倍(从 O(n²)降为 O(n))。挑战: KV Cache 显存随序列长度线性增长(128K context 可占数十 GB), PagedAttention/GQA 可缓解。
-
连续批处理相比传统批处理有什么优势?
传统(Static Batching):等所有请求生成完才返回,最慢请求拖慢全扑 0 。连续(Continuous Batching):请求完成即时释放资源并插入新请求, GPU 利用率提升 10-20x 。 vLLM/TGI 均默认启用。核心思想:让 GPU 永远不闲置。
-
如何优化大模型的推理性能?
分层优化:①模型层(量化、蒸馏、剪枝) ②注意力层(FlashAttention 、 GQA 、 MQA 、 Sliding Window) ③解码层(KV Cache 、 Speculative Decoding 、 Early Exit) ④服务层(连续批处理、 PagedAttention 、请求调度) ⑤硬件层(TensorRT 编译、 Tensor 并行)。开箱即用: vLLM 已集成多数优化。
-
PagedAttention 有什么优势?
借 Linux 虚拟内存分页思想管理 KV Cache :将连续的 KV Cache 分为固定大小的 Block(页),按需分配。优势:①显存浪费从 60-80%降低刼<4% ②尾部填充减少 ③支持共享 Prefix(多请求复用系统提示词的 KV Cache) ④同等显存下并发请求数提升 5x+。这是 vLLM 的核心创新。
-
在实际项目中如何优化推理性能?
路线:①先用 vLLM 部署(开箱即用大部分优化) ②测量 throughput 和 P99 latency 基线 ③尝试量化(AWQ/GPTQ)观察性能/质量权衡 ④开启 Speculative Decoding(若时延敏感) ⑤调整 batch_size/max_num_seqs 找最优点 ⑥TensorRT 编译(NVIDIA 平台)。监控: token/s 、首 Token 延迟(TTFT)、 GPU 利用率。
-
SGLang 的 RadixAttention 相比 vLLM 的 PagedAttention 有什么优势?适用于什么场景?
RadixAttention 用 Radix Tree(基数树)管理 KV Cache ,实现前缀级别的自动复用。优势:多轮对话/多路并行场景下 KV Cache 命中率更高, LRU 自动淘汰无需手动管理。适用场景:大量 Shared Prefix(同一系统提示词)、 Tree of Thoughts 推理、多轮 Agent 对话。 PagedAttention 更通用, RadixAttention 在特定模式下更高效。
-
Speculative Decoding 为什么能保证输出分布无损?它的加速比受哪些因素影响?
采用拒绝采样(rejection sampling)策略:草稿模型生成候选 token ,目标模型并行验证,按 min(1, p_target/p_draft)概率接受。数学证明接受的 token 服从目标模型分布。加速比受:①草稿模型接受率(与目标模型越接近越好) ②草稿模型速度(越小越快) ③推测 token 数(通常 4-8) ④任务确定性(确定性任务加速比更高)。典型加速 2-3x 。
📚 参考资料¶
- "Efficient Attention: Attention with Linear Complexities" - Katharopoulos et al.
- "FlashAttention: Fast and Memory-Efficient Exact Attention" - Dao et al.
- "PagedAttention: Efficient Attention for Long Sequences" - Kwon et al.
- vLLM Documentation
- SGLang Documentation - https://sgl-project.GitHub.io/
- "Efficient Memory Management for Large Language Model Serving with PagedAttention" - Kwon et al.
- "Fast Inference of Mixture-of-Experts Language Models with Offloading" - Eliseev & Mazur
- "Accelerating Large Language Model Decoding with Speculative Sampling" - Leviathan et al.
- TensorRT Documentation
最后更新日期: 2026-03-26 适用版本: LLM 应用指南 v2026
