模型是同一份,部署的人不同,性能可以差一个数量级。LLM 推理性能调优最忌讳「盲人摸象」——一会儿调这个参数、一会儿换个引擎,没有定位就乱调。本文给出一份可执行的调优检查清单:先明确优化目标(吞吐还是延迟),再按「显存→计算→带宽→调度」逐层定位瓶颈,最后按优先级上优化手段,并用基准测试闭环验证。照着清单走,性能问题能快速收敛。
前置:/ai-inference-benchmark/(基准测试方法)、/ai-kernel-fusion-optimization/(算子融合)、/ai-cuda-memory-optimization/(显存优化)、/ai-fp8-inference/(FP8 量化)、/ai-attention-optimization/(注意力优化)。
目录
- 1. 先定目标:吞吐、延迟还是成本
- 2. 建立基线:基准测试与指标采集
- 3. 瓶颈定位第一层:显存
- 4. 瓶颈定位第二层:计算与带宽
- 5. 瓶颈定位第三层:调度与排队
- 6. 优化手段:解码、前缀缓存与批调度
- 7. 优化手段:融合、量化与注意力
- 8. 优化手段:引擎与硬件的最后一公里
- 9. 验证闭环:A/B 对比、回归与持续调优
- 10. 速查表与一句话记忆
- 延伸阅读
1. 先定目标:吞吐、延迟还是成本
调优前先回答「优化给谁看」:
三个目标(往往互相冲突):
□ 吞吐(Throughput):每秒生成多少 token / 处理多少请求
→ 关注「总产出」,适合离线批处理/高并发服务
□ 延迟(Latency):单个请求首 token / 端到端耗时
→ 关注「响应快」,适合实时对话/交互
□ 成本(Cost):每 1M token 的成本
→ 关注「单位产出成本」,适合大规模服务
关键权衡:
□ 提高吞吐 → 往往牺牲延迟(大 batch 拖慢单请求)
□ 降低延迟 → 往往牺牲吞吐(小 batch 亏算力)
□ 降成本 → 可能靠量化(精度换)、大 batch(延迟换)
选型的本质:
□ 在线交互(聊天/助手)→ 延迟优先,兼顾吞吐
□ 离线批处理(批量生成/评测)→ 吞吐优先
□ 规模化服务 → 成本优先(在延迟达标内)
目标设定示例:
延迟:P95 TTFT < 500ms、P95 TPOT < 30ms/token
吞吐:1 卡 > 3000 token/s(7B FP16)
成本:< $0.5 / 1M token
→ 写清楚,才能判断「优化到位没有」
工程要点:调优第一原则是**「先立目标,再动手」**——吞吐/延迟/成本三选一或组合,写成可量化的指标(P95 TTFT、token/s、$/1M token)。很多调优失败是因为「没有目标」或「目标互相打架」。目标定了,后面每一步都能验证「值不值」。
2. 建立基线:基准测试与指标采集
没有基线就没有「优化了多少」的参照:
必须采集的指标:
□ TTFT(Time To First Token):首 token 延迟
□ TPOT(Time Per Output Token):每 token 生成延迟
□ ITL(Inter-Token Latency):token 间延迟(≈ TPOT)
□ 吞吐:token/s(总)、req/s(请求)
□ 显存:峰值、KV cache 占用、是否换页/超卖
基准测试方法:
□ 用真实负载形状(上下文长度分布、并发数、流式比例)
□ 别用「完美提示词」测(要模拟真实分布)
□ 跑足够时长(覆盖预热、避开冷启动)
□ 采集 P50/P95/P99(只看均值会骗人)
环境隔离:
□ 同引擎、同权重、同卡型再对比
□ 记录:引擎版本、量化、batch 策略、上下文设置
□ 每次改动只动一个变量(否则分不清谁的效果)
基线表示例:
指标 基线(FP16 默认)
TTFT P95 900ms
TPOT P95 45ms
吞吐(1卡 7B) 1800 token/s
峰值显存 16.5GB
→ 每次优化后回填,形成「调优记录表」
工程要点:基线是**「调优的记账本」**——建一张指标表,记录基线 + 每次改动后的值,每次只动一个变量。吞吐、延迟、显存三个维度都要记。没有基线的调优 = 拍脑袋;有基线的调优 = 可追溯的迭代。
3. 瓶颈定位第一层:显存
显存是 LLM 推理的「第一道闸门」——不够就什么都白搭:
显存占用拆解:
□ 模型权重:FP16 下 1B ≈ 2GB(FP8 减半 → 1GB/B)
□ KV cache:随「并发 × 上下文长度」增长(最易爆)
□ 激活/中间缓冲:forward 时临时占用
□ 引擎运行时开销:碎片、预分配
检查方法:
□ 峰值显存 vs 卡容量:接近 100% → 显存瓶颈
□ KV cache 占比:高 → 并发/长度受限
□ 有没有 swap/重算:有 → 显存严重不足,性能暴跌
显存瓶颈的后果:
□ OOM → 请求失败/服务抖动
□ 换页(swap)/重算 → 延迟飙升(比慢更可怕)
□ 被迫减 batch → 吞吐上不去
显存判断:
峰值 / 卡容量 > 90% → 显存紧,先解决
KV cache 占比 > 60% → 并发/长度受限,看下面优化
有 swap/重算 → 立刻处理(性能灾难)
工程要点:显存瓶颈的排查顺序是**「看峰值 → 看 KV 占比 → 看 swap」**——OOM 和 swap 是灾难(性能暴跌),必须先解决。显存优化手段(见第 7/8 篇):量化减权重(FP8)、KV cache 量化/共享、前缀缓存、控制并发与 max 上下文。显存问题不解决,调后面的计算/调度全是徒劳。
4. 瓶颈定位第二层:计算与带宽
显存够的前提下,看「卡在算力还是带宽」:
两类瓶颈:
□ 算力受限(compute-bound):算力打满,带宽没用满
→ 特征:GPU 利用率 90%+,但 token 吞吐没到预期
□ 带宽受限(bandwidth-bound):卡在搬数据,算力闲置
→ 特征:GPU 利用率不高,但每 token 很慢
LLM 的典型画像:
□ 预填充(prefill):算力密集(并行矩阵乘)→ compute-bound
□ 解码(decode):每次只生成一个 token → 权重搬运为主
→ 解码阶段几乎总是 bandwidth-bound(weight-bound)
判断工具:
□ nvidia-smi / nsight:看 SM 利用率、内存带宽利用率
□ 对比「理论算力」vs「实际 token/s」判断卡在哪
对应解法:
□ compute-bound → 融合/注意力优化/大 batch 吃满算力
□ bandwidth-bound → 量化(FP8/INT8 减字节)、KV 缓存优化
解码带宽估算(7B FP16):
每 token 要读 ~14GB 权重(7B×2B)
H100 带宽 ~3.35TB/s → 理论下限 ~4.2ms/token
实际 6~10ms → 说明带宽没用满,有优化空间
→ 量化减半 → 理论下限减半 → 解码提速
工程要点:分清「算力受限还是带宽受限」是**「选优化手段的分水岭」**——预填充是算力密集,解码是带宽密集(weight-bound)。解码慢先用量化(砍权重字节,直接提速);预填充慢看融合与注意力优化。用 nsight 看 SM/带宽利用率确认,别靠猜。
5. 瓶颈定位第三层:调度与排队
GPU 很空但延迟高?问题往往在**「调度层」**:
调度层检查项:
□ 批大小:batch 太小 → 算力吃不饱;太大 → 延迟高
→ 看「每 batch 的 token 吞吐」是否到峰值
□ 连续批处理(continuous batching):有没有启用?
→ 没有 → 请求排队等「整批走完」,吞吐大跌
□ 排队等待:请求在队列里等多久?
→ 队列 P95 高 → 扩容 or 优化 batch 策略
□ 并发限制:max concurrency 设太低 → 排队
□ 流式 vs 非流式:流式让首 token 更快(TTFT 改善)
调度层的症状:
□ GPU 利用率 60% 但 P95 延迟很高 → 调度浪费
□ 队列长度长期 > 0 → 扩容 or 提升单实例吞吐
□ token/s 上不去但算力没用满 → batch 策略问题
调度判断:
GPU 利用率高 + 延迟高 → 排队/并发限制(扩或调)
GPU 利用率低 + token 低 → batch 太小(加大 batch)
GPU 利用率低 + token 正常 → 已是带宽瓶颈(走量化)
工程要点:调度层的问题是**「GPU 很空但请求很慢」**——十有八九是 batch 策略或排队。连续批处理(continuous batching)是现代 LLM 推理的标配(vLLM/TensorRT-LLM 都内置),确认已启用。看「GPU 利用率 × 队列长度」的组合判断是扩容、调 batch 还是已到带宽天花板。
6. 优化手段:解码、前缀缓存与批调度
从「最高性价比」的优化手段开始上:
① 解码策略:
□ 贪心/beam 的 batch 友好性:贪心更好批处理
□ 流式输出:TTFT 改善明显(首 token 先回)
□ max_tokens 合理:限制生成长度 = 控延迟/成本
② 前缀缓存(Prefix Caching / PD 分离):
□ 相同 system prompt/历史 → KV cache 复用
□ 多轮对话/多请求共享前缀 → 省预填充算力
□ 效果:命中场景 TTFT 大幅下降、吞吐提升
□ 代价:需要缓存管理(LRU、分页缓存)
③ 批调度:
□ continuous batching:动态插队/调度 token 级
□ 预填充/解码分离(PD-disaggregation):各管各
□ 优先级:交互请求优先,批处理让路
优先级排序(性价比):
1. continuous batching(引擎默认,确认开启)
2. 前缀缓存(多轮/共享前缀场景,收益大)
3. 流式 + 合理 max_tokens(小改动大收益)
4. 解码策略(贪心优先,配合 batch)
→ 先软件策略,再上融合/量化
工程要点:软件层的优化**「先小后大」**——先确认 continuous batching 开启、前缀缓存用上、流式与 max_tokens 合理,再谈融合量化。前缀缓存对「多轮对话/共享 system prompt」的收益尤其大(省预填充算力 + TTFT 下降)。这层优化零硬件成本,性价比最高。
7. 优化手段:融合、量化与注意力
软件层榨干后,上「硬核优化」:
④ 算子融合(Kernel Fusion):
□ 相邻算子合并 → 省中间张量的显存读写
□ 例:Attention + RMSNorm + 量化合并、MLP 融合
□ 引擎内置(TensorRT-LLM/vLLM 自动),也可手写
□ 收益:带宽受限场景显著
⑤ 量化(FP8/INT8/AWQ/GPTQ):
□ FP8:权重减半,解码提速(weight-bound 场景)
□ KV cache 量化:4bit/8bit,省显存 + 省带宽
□ 精度代价:任务级验证(见 FP8 篇)
□ 收益:带宽受限的 LLM 解码「最直接提速」
⑥ 注意力优化:
□ FlashAttention:省显存 + 提速(I/O 优化)
□ GQA/MQA:多头 KV 共享 → KV cache 大减
□ 稀疏注意力 / 窗口注意力:长上下文省算力
□ 收益:长上下文 + 大并发场景最明显
硬核优化排序:
1. 注意力优化(FlashAttention/GQA,结构级收益)
2. 算子融合(引擎内置,白拿)
3. FP8/量化(带宽直降)
4. KV cache 量化(并发/长上下文红利)
工程要点:硬核优化的核心是**「砍字节 + 砍 I/O」——FP8 量化砍权重字节(带宽受限解码直接提速)、FlashAttention 砍中间 I/O、GQA 砍 KV cache。这些都靠引擎内置(TensorRT-LLM/vLLM 默认开启)。注意:量化和注意力优化要用第 1 步的目标做精度/效果验收**,不是无脑开。
8. 优化手段:引擎与硬件的最后一公里
策略与 Kernel 都优化后,还有「最后一公里」:
⑦ 引擎/框架层面:
□ TensorRT-LLM / vLLM / Triton:选对引擎(对比篇)
□ 引擎版本:新版本常有调度/内核优化
□ 编译选项:max-batch、max-seq-len 匹配负载
□ CUDA Graph:减启动开销(小 batch 场景明显)
⑧ 硬件/系统层面:
□ 卡型:算力 vs 带宽匹配负载(解码重 → 带宽大的卡)
□ 多卡:张量并行(TP)跨卡分权重 → 带宽扩大
□ 存储:权重放 NVMe/本地(加载快,冷启动快)
□ 网络:TP/DP 集群的互联带宽(跨卡通信开销)
⑨ 端点优化:
□ 超时/重试/限流:防雪崩
□ 批处理入口:合并请求(batching at gateway)
□ 负载均衡:按实例能力分发
最后一公里检查:
引擎版本是否最新?CUDA Graph 开没开?
max-batch/max-seq-len 是否匹配真实负载?
卡型带宽是否匹配(解码重选带宽卡)?
→ 这些「配置级」优化常被忽略,但收益不小
工程要点:最后一公里是**「配置与系统层」**——引擎版本/编译参数/CUDA Graph(小 batch 减启动)、卡型与负载匹配(解码重 → 高带宽卡)、TP 并行扩带宽。这些「配置级」改动成本低、常被忽略,但可能是「从 70 分到 90 分」的关键。检查清单式过一遍,别漏。
9. 验证闭环:A/B 对比、回归与持续调优
调完不是结束,要**「闭环验证」**:
验证流程:
1. 单变量对比:每次只改一个优化(记基线)
2. A/B:旧配置 vs 新配置,同负载、同环境
3. 回归:跑基准测试全套,看所有指标(别只看一个)
4. 线上验证:小流量灰度 → 对比线上指标 → 放大
5. 成本核算:性能提升 ÷ 是否值得复杂度
常见陷阱:
□ 只看平均不看 P95/P99(长尾是用户体感)
□ 用假负载测(真实负载分布才有意义)
□ 多个优化同时上(分不清谁有效)
□ 只测一次(波动误判)
持续调优:
□ 负载画像会变(上下文长度/并发)→ 定期复测
□ 引擎升级 → 复测收益/回归
□ 记录调优日志(配置 + 指标 + 结论)→ 可复用
调优闭环(循环):
目标 → 基线 → 定位瓶颈 → 选优化 → A/B 验证 → 上线
↑ ↓
└──────────── 复测/回归/成本核算 ←────────┘
工程要点:验证闭环是**「让调优可信」**——单变量 A/B、全套指标回归、线上灰度、成本核算。陷阱集中在「假负载 + 只看均值 + 混着改」三个坑。调优不是一次性动作,负载画像会变,要建立「调优日志 + 定期复测」的持续机制。
10. 速查表与一句话记忆
| 问题 | 一句话答案 |
|---|---|
| 先做什么 | 定目标(吞吐/延迟/成本)+ 建基线指标表 |
| 第一层瓶颈 | 显存(峰值/KV 占比/swap,先解决灾难) |
| 第二层瓶颈 | 算力 vs 带宽(预填充 compute、解码 bandwidth) |
| 第三层瓶颈 | 调度(batch 策略/排队/continuous batching) |
| 软件优化 | continuous batching、前缀缓存、流式、max_tokens |
| 硬核优化 | FlashAttention/GQA、融合、FP8、KV 量化 |
| 最后一公里 | 引擎版本、CUDA Graph、卡型带宽、TP 并行 |
| 验证 | 单变量 A/B、全套指标、线上灰度、成本核算 |
| 最直接提速 | 带宽受限场景用 FP8 量化(砍权重字节) |
| 调优原则 | 一次一个变量,有基线,能复测 |
一句话记忆:调优清单 = 先定目标(吞吐/延迟/成本)+ 建基线(TTFT/TPOT/token-s/显存)+ 逐层定位(显存→计算带宽→调度)+ 软件优化先行(continuous batching/前缀缓存/流式)+ 硬核优化跟进(FlashAttention/GQA/融合/FP8)+ 最后一公里(引擎/CUDA Graph/卡型)+ A/B 闭环验证(单变量/全套指标/灰度)——「先定位再优化,一次动一个变量,用指标闭环说话」。
延伸阅读
- /ai-inference-benchmark/ — 基准测试方法与指标
- /ai-kernel-fusion-optimization/ — 算子融合与 CUDA Graph
- /ai-cuda-memory-optimization/ — 显存优化与 KV cache
- /ai-fp8-inference/ — FP8 量化减带宽
- /ai-attention-optimization/ — FlashAttention 与 GQA
- /ai-vllm-system/ — vLLM 调度与批处理
- 高性能计算专题 — 内核与硬件优化
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。