推理优化
推理优化关注的是:模型已经能回答之后,如何让它 更快、更稳、更便宜、更可控。
一句话理解:
推理优化不是单纯把模型换小,而是在答案质量、首 token 延迟、总耗时、并发、成本和稳定性之间做工程平衡。
AI 应用上线后,用户最直观的感受通常不是“这个模型参数量是多少”,而是:
- 多久开始输出。
- 多久完整回答完。
- 高峰期会不会一直转圈。
- 一天 token 成本会不会爆。
- 模型失败时有没有降级。
- RAG、工具、模型到底是哪一步慢。
学习目标
学完本页,你应该能回答:
- 什么是推理,推理和训练有什么区别。
- 为什么大模型通常逐 token 生成,所以会比普通接口慢。
- TTFT、TPOT、P95、输入 token、输出 token、吞吐分别是什么意思。
- 为什么上下文越长、输出越长、并发越高,延迟和成本越高。
- KV Cache、流式输出、批处理、模型路由、缓存、限流分别解决什么问题。
- RAG 场景为什么要同时优化召回质量、token 成本和延迟。
- 缓存 Key 为什么必须包含租户、权限、Prompt 版本和知识库版本。
- 如何写一个带缓存、限流、超时、fallback 的最小 Demo。
- 线上 AI 慢、贵、超时、排队、质量下降时怎么排查。
- 面试时如何讲推理优化。
什么是推理
训练是让模型学习参数,推理是使用已经训练好的模型生成结果。
flowchart TD
A["训练阶段"] --> B["大量数据和算力"]
B --> C["更新模型参数"]
C --> D["得到模型权重"]
D --> E["推理阶段"]
E --> F["输入 Prompt"]
F --> G["逐 token 生成答案"]商业应用大多数时候不训练模型,而是做推理服务:把用户问题、RAG 资料、工具结果和 Prompt 交给模型,然后拿到回答。
推理为什么慢
大模型通常是自回归生成:每次预测下一个 token,再把这个 token 加回上下文,继续预测下一个。
flowchart TD
A["输入 Prompt"] --> B["Tokenizer 转 token"]
B --> C["模型计算下一个 token 概率"]
C --> D["采样或选择 token"]
D --> E["新 token 加入上下文"]
E --> F{"是否生成结束"}
F -- "否" --> C
F -- "是" --> G["解码成文本返回"]所以延迟主要受这些因素影响:
| 因素 | 为什么影响 |
|---|---|
| 模型大小 | 参数越多,每步计算越重 |
| 输入 token | Prompt、历史消息、RAG 片段越多,预填充越慢 |
| 输出 token | 逐 token 生成,输出越长循环越多 |
| 上下文长度 | KV Cache 占用和注意力计算压力增加 |
| 并发数 | 多个请求争抢 GPU、队列和显存 |
| 工具/RAG | 检索、重排、数据库、外部接口也会耗时 |
关键指标
不要一上来就说“模型慢”。要先量化。
| 指标 | 含义 | 说明 |
|---|---|---|
| TTFT | Time To First Token,首 token 时间 | 用户多久看到第一段输出 |
| TPOT | Time Per Output Token,平均每个输出 token 时间 | 生成速度 |
| Total Latency | 总耗时 | 从请求进入到完成返回 |
| P95/P99 | 95% / 99% 请求耗时 | 看高峰和长尾 |
| Input Tokens | 输入 token 数 | Prompt、历史、RAG 上下文 |
| Output Tokens | 输出 token 数 | 模型生成长度 |
| Throughput | 吞吐 | 每秒 token 或每秒请求 |
| Queue Time | 排队时间 | 并发高时尤其重要 |
| Error Rate | 错误率 | 超时、限流、模型失败 |
| Cost Per Request | 单次成本 | 云模型费用或本地资源摊销 |
TTFT 和总耗时区别
流式输出主要改善 TTFT 感知,不一定减少总耗时。
非流式:用户等 8 秒,然后看到完整答案。
流式:用户等 1 秒看到第一个 token,8 秒后完整结束。用户体验通常更看重 TTFT,但系统容量仍然要看总耗时和吞吐。
优化总流程
flowchart TD
A["采集全链路指标"] --> B["拆分耗时"]
B --> C{"瓶颈在哪里"}
C -- "入口排队" --> D["限流、队列、扩容"]
C -- "RAG 检索慢" --> E["索引、TopK、过滤、缓存"]
C -- "Rerank 慢" --> F["减少候选、异步、轻量模型"]
C -- "Prompt 太长" --> G["压缩上下文和历史"]
C -- "模型生成慢" --> H["流式、路由、小模型、批处理"]
C -- "工具慢" --> I["超时、并发、缓存、降级"]
D --> J["压测和评估"]
E --> J
F --> J
G --> J
H --> J
I --> J推理优化的原则:
- 先监控,再优化。
- 先定位瓶颈,再改方案。
- 每次优化都要同时看质量、成本、延迟。
- 不能为了快牺牲安全和权限。
- 不能只测单请求,要压测并发和长尾。
KV Cache
KV Cache 是推理服务里非常重要的概念。
大模型 Transformer 每一层都会计算 Key、Value。生成下一个 token 时,历史 token 的 Key/Value 可以缓存起来,避免每一步都重复计算完整历史。
flowchart TD
A["输入历史 token"] --> B["计算 Key / Value"]
B --> C["写入 KV Cache"]
C --> D["生成新 token"]
D --> E["只为新 token 追加 KV"]
E --> F["继续生成"]KV Cache 的好处:
- 加快连续生成。
- 降低重复计算。
- 支持更好的流式体验。
KV Cache 的代价:
- 占用显存或内存。
- 上下文越长,占用越大。
- 并发越高,占用越大。
- 显存不足会导致 OOM、排队或请求失败。
所以长上下文不是免费的。你把 RAG TopK 从 5 提到 30,可能不仅 token 变贵,还会让 KV Cache 压力上升。
Prefill和Decode为什么必须分开看
一次自回归请求通常有两个模型阶段:
flowchart TD
A["输入Prompt的S个Token"] --> B["Prefill并行处理全部输入"]
B --> C["为每层写入历史KV Cache"]
C --> D["Decode读取历史KV并生成1个Token"]
D --> E["把新Token的KV追加到Cache"]
E --> F{"是否结束"}
F -- "否" --> D
F -- "是" --> G["释放请求KV页"]Prefill
Prefill 一次处理输入序列,矩阵规模较大、并行度高,通常更偏计算密集。输入越长,需要处理的 Token 越多,注意力和线性层计算增加,因此 TTFT 往往上升。
Decode
Decode 每轮只产生一个或少量 Token,但每一层都要读取模型权重以及该请求历史 KV。单步矩阵较小、重复次数多,常更容易受内存带宽、批大小和调度影响。
因此:
- TTFT 高但 TPOT 正常:优先查排队、长 Prompt、Prefill、RAG 前置耗时和冷启动。
- TTFT 正常但 TPOT 高:优先查 Decode 批处理、显存带宽、量化、并发和输出长度。
- 两者都高:先把 Queue、RAG、Prefill、Decode 分开,不要只说“GPU慢”。
总耗时可近似理解为:
totalLatency
≈ queueTime
+ applicationAndRagTime
+ prefillTime
+ outputTokenCount × TPOT
+ networkAndPostProcessTime实际 TPOT 会随并发和批次变化,不能把单请求 TPOT 直接乘到高峰流量。
KV Cache容量怎样计算
对常见 Decoder-only Transformer,一个 Token 每层需要保存 Key 和 Value。简化公式:
KVBytesPerToken
= layers
× 2 // Key和Value
× kvHeads
× headDim
× bytesPerElement一个请求:
RequestKVBytes
≈ KVBytesPerToken × (inputTokens + generatedTokens)多个并发请求:
TotalKVBytes
≈ Σ RequestKVBytes这里使用 kvHeads,不是总 Query Heads。MHA 中二者常相同;GQA/MQA 通过多个 Query Head 共享较少的 K/V Head,能显著降低 KV 容量和读取量。
示例
假设:
layers=32
kvHeads=8
headDim=128
dtype=FP16/BF16,即2字节则:
每Token KV = 32 × 2 × 8 × 128 × 2
= 131072字节
= 128KiB一个总序列 8192 Token 约占:
128KiB × 8192 ≈ 1GiB8 个同等并发理论上仅 KV 就约 8 GiB,还没有计算模型权重、激活、CUDA Context、临时工作区、碎片和运行时预留。
为什么上下文上限不能直接乘并发
所有请求不一定都达到最大长度,可以按真实输入/输出分布估算 P50、P95 和压力边界。但容量保护仍要设置单请求 Token 上限、全局 KV Token 预算和准入控制,否则少量超长请求就能占满显存。
可运行Demo:KV显存和并发预算
from dataclasses import dataclass
@dataclass(frozen=True)
class ModelKVConfig:
layers: int
kv_heads: int
head_dim: int
bytes_per_element: int
def kv_bytes_per_token(config: ModelKVConfig) -> int:
return (
config.layers
* 2 # Key + Value
* config.kv_heads
* config.head_dim
* config.bytes_per_element
)
def gib(value: int) -> float:
return value / (1024 ** 3)
config = ModelKVConfig(
layers=32,
kv_heads=8,
head_dim=128,
bytes_per_element=2,
)
per_token = kv_bytes_per_token(config)
sequence_tokens = 8192
one_sequence = per_token * sequence_tokens
print("KV per token KiB:", per_token / 1024)
print("one 8192-token sequence GiB:", round(gib(one_sequence), 3))
# 24GiB显存中,假设权重与运行时占16GiB,再预留10%安全空间。
gpu_bytes = 24 * 1024 ** 3
non_kv_bytes = 16 * 1024 ** 3
safety_reserve = int(gpu_bytes * 0.10)
kv_budget = gpu_bytes - non_kv_bytes - safety_reserve
max_theoretical_sequences = kv_budget // one_sequence
print("KV budget GiB:", round(gib(kv_budget), 3))
print("theoretical max sequences:", max_theoretical_sequences)这是容量上界估算,不是压测结论。真实并发还受计算吞吐、调度、显存碎片、不同长度分布和SLO限制,必须用目标引擎、模型和硬件压测。
静态Batch为什么不适合长短请求混合
传统静态 Batch 往往等一组请求凑齐,再按批内最长序列补 Padding:
请求A:100 Token
请求B:500 Token
请求C:2000 Token如果一起按 2000 Token 处理,短请求会产生大量 Padding 浪费;生成阶段还会出现已经结束的请求占着 Batch 位置,其他新请求必须等待。
Batch 大通常提高 GPU 利用率和总吞吐,但也可能增加排队和单请求延迟。因此吞吐与延迟不是同时无限改善。
Continuous Batching怎样工作
连续批处理不是等整个请求结束才更新 Batch,而是在 Token 迭代边界动态加入新请求、移除已完成请求:
flowchart TD
A["等待队列中的新请求"] --> B["调度本轮Prefill或Decode Token"]
B --> C["GPU执行当前Batch"]
C --> D["完成请求释放槽位和KV页"]
D --> E["未完成请求进入下一轮"]
E --> F["从队列补入新请求"]
F --> B收益:
- 结束请求快速释放位置。
- 新请求不必等最长请求完整结束。
- GPU 保持更高利用率。
但调度策略会影响公平性:大量长 Prefill 可能阻塞 Decode,导致正在流式输出的请求 TPOT 抖动;只偏爱短请求又可能让长请求饥饿。
常见控制:
- 每轮最大 Batched Token。
- Prefill Chunking,把超长 Prefill 切成多段。
- Prefill/Decode 优先级和时间片。
- 每租户队列、权重和并发上限。
- 等待时间老化,防止长请求永久饥饿。
- Deadline 到期前取消,不让过期请求继续占GPU。
Paged Attention解决什么问题
若为每个请求按最大上下文预留一整块连续 KV 空间,会产生内部浪费;请求长短不同、结束时间不同还会形成外部碎片。
Paged Attention 的核心类似操作系统分页:
逻辑Token位置
→ Block Table
→ 非连续物理KV Block请求增长时按需分配固定大小 Block,结束后回收到池中,不要求整段 KV 在物理显存连续。它提升显存利用率并便于共享前缀,但没有减少每个有效 KV 元素本身的理论容量。
Block 太大浪费尾页,太小则 Block Table 和调度开销增加;需要按引擎实现与负载压测。
Prefix Cache和普通答案缓存不同
Prefix Cache 复用相同前缀已计算的 KV,例如固定 System Prompt、工具定义或公共文档前缀。它减少重复 Prefill,但要求 Token 序列真正一致:一个空格、消息顺序、模板版本或Tokenizer变化都可能导致不命中。
安全边界:
- 不能跨租户复用含私有上下文的前缀。
- Key 包含模型、Tokenizer、Prompt和权限版本。
- 不能把可变时间、随机Nonce放在公共前缀中间破坏命中。
- 前缀失效要随策略和工具Schema发布。
答案缓存直接复用最终响应,风险更高;Prefix Cache 只复用模型内部中间计算,仍会继续生成当前请求答案。
量化为什么能省显存但可能损失质量
模型权重从 FP16/BF16 压到 INT8、INT4 等,可降低权重显存和内存带宽,并可能提高吞吐。近似权重量化过程:
浮点权重w
→ 根据scale和zeroPoint映射到有限整数
→ 计算时反量化或使用量化Kernel需要区分:
- Weight-only:主要量化权重,激活仍较高精度。
- Weight + Activation:更省带宽,但校准和精度挑战更大。
- KV Cache Quantization:进一步省并发KV,但可能影响长上下文质量。
- PTQ:训练后量化,部署快。
- QAT:训练时模拟量化,成本高但可改善精度。
量化不保证一定更快:若硬件或Kernel缺少高效支持,反量化开销可能抵消收益。必须在目标硬件比较 TTFT、TPOT、吞吐、显存、功耗以及分场景质量,特别是数字、代码、结构化输出和长上下文。
Speculative Decoding为什么可能更快
投机解码用较小 Draft Model 一次提出多个候选 Token,再由目标模型并行验证:
flowchart TD
A["当前已接受前缀"] --> B["Draft一次提议多个Token"]
B --> C["Target并行验证候选"]
C --> D["接受连续匹配部分"]
D --> E["在首个不接受位置按Target继续"]
E --> F["进入下一轮"]它不应改变目标模型定义的输出分布,前提是算法正确实现采样验证。速度取决于接受率、Draft成本、验证批大小和硬件。
- Draft与Target行为接近、输出可预测:接受率高,收益大。
- 高温、复杂推理或领域差异大:频繁拒绝,可能无收益甚至更慢。
必须监控接受Token数、接受率、每轮提议数和端到端TPOT,不能只看Draft模型单独有多快。
模型并行解决什么问题
Tensor Parallel
把同一层矩阵切到多张GPU,并行计算后通信聚合。适合单层权重放不下一张卡,但每层都需要高频通信,对互联带宽和拓扑敏感。
Pipeline Parallel
把不同层放到不同GPU/节点,请求微批依次通过阶段。可容纳更大模型,但存在流水线气泡和阶段不均衡;在线小Batch低延迟场景收益不一定好。
Data Parallel / Replica
每个副本有完整模型,请求在副本间分流。扩吞吐最直接,但每个副本都要完整权重,单副本必须先能放下模型。
Expert Parallel
MoE把不同专家分布到设备,路由Token到对应专家。通信、负载不均和热点专家会影响尾延迟。
并行卡数增加不代表线性加速。通信、同步、负载不均、网络和调度开销会降低扩展效率。
容量估算:吞吐、并发和副本
Little's Law 的稳态直觉:
平均在途请求数 L ≈ 到达率 λ × 平均逗留时间 W若每秒 4 个请求、平均完整耗时 5 秒,平均约 20 个请求在系统内。但容量不能只按平均:到达有突发,输出长度和服务时间是长尾,还要用 P95/P99、队列预算和安全余量压测。
Token吞吐需求可粗估:
requiredOutputTokensPerSecond
≈ requestsPerSecond × averageOutputTokens副本估算:
replicas
≥ ceil(requiredThroughput / testedSafeThroughputPerReplica)testedSafeThroughput 必须是在目标 TTFT/TPOT/P95、错误率和显存安全线下测出的持续吞吐,不是模型刚好不OOM时的峰值。
流式输出
流式输出适合聊天、解释、摘要、报告生成。
flowchart TD
A["请求进入"] --> B["模型生成 token"]
B --> C["生成一个就发送一个"]
C --> D["前端持续渲染"]
D --> E["用户更快看到反馈"]适合:
- 长文本回答。
- 聊天助手。
- 用户需要感知进度。
- 模型总耗时不可避免较长。
不适合:
- 必须完整校验 JSON 后再返回。
- 需要事务一致性的工具结果。
- 高风险回答必须先审核。
流式输出也要处理断开:
| 问题 | 处理 |
|---|---|
| 用户关闭页面 | 后端取消模型请求或停止写入 |
| 网络断开 | 记录中断状态 |
| 模型中途失败 | 前端给出失败提示 |
| 已输出不完整 | 标记答案不完整,不进入评估集正样本 |
Prompt 压缩
Prompt 越长,输入 token 越多,通常 TTFT 越长、成本越高。
常见压缩方式:
| 方式 | 说明 | 风险 |
|---|---|---|
| 删除重复规则 | 合并重复的系统提示词 | 不要删安全规则 |
| 历史摘要 | 多轮对话保留摘要 | 摘要可能丢关键信息 |
| RAG 片段筛选 | 只放最相关片段 | TopK 太小会漏答案 |
| 字段裁剪 | 工具结果只保留必要字段 | 裁剪错会影响回答 |
| 结构化模板 | 明确输出格式,减少废话 | 格式过严可能丢解释 |
长对话怎么处理
错误做法:每轮都把所有历史消息塞给模型。
正确做法:
flowchart TD
A["历史对话增长"] --> B{"是否超过窗口或成本阈值"}
B -- "否" --> C["保留近期消息"]
B -- "是" --> D["抽取长期记忆和摘要"]
D --> E["保留关键事实、约束、未完成任务"]
C --> F["组装 Prompt"]
E --> F摘要要保留:
- 用户身份和任务目标。
- 已确认事实。
- 权限和安全限制。
- 未完成步骤。
- 用户偏好。
不要把敏感数据原样写进长期记忆。
RAG 场景优化
RAG 请求通常比普通问答更慢,因为多了检索和上下文。
flowchart TD
A["用户问题"] --> B["问题改写"]
B --> C["Embedding"]
C --> D["向量检索"]
D --> E["关键词检索"]
E --> F["合并去重"]
F --> G["Rerank"]
G --> H["Prompt 组装"]
H --> I["模型生成"]优化点:
| 环节 | 优化 |
|---|---|
| 问题改写 | 只在多轮省略明显时启用 |
| Embedding | 缓存相同问题或标准化问题 |
| 向量检索 | 调整索引、TopK、metadata filter |
| 关键词检索 | 给字段名、错误码、接口名建索引 |
| Rerank | 先召回多,再重排少,不要候选无限大 |
| Prompt | 控制片段数、片段长度、引用格式 |
| 生成 | 控制输出长度,必要时流式 |
RAG 优化不能只减少 TopK。TopK 减少会变快,但可能导致召回率下降。必须用评估集看 Recall@K、引用命中率和拒答率。
缓存设计
缓存可以显著降低成本和延迟,但 AI 缓存比普通接口更容易出错。
可以缓存什么
| 缓存对象 | 注意 |
|---|---|
| Embedding 结果 | Key 包含文本和 Embedding 模型版本 |
| RAG 检索结果 | Key 包含权限范围、知识库版本、问题 |
| 高频公共问答 | 不包含个性化和敏感数据 |
| Prompt 模板 | Key 包含 Prompt 版本 |
| 模型回答 | 必须包含租户、权限、模型、Prompt、知识库版本 |
不能随便缓存什么
- 用户个人隐私回答。
- 实时数据查询结果。
- 权限相关答案。
- 含敏感字段的模型输出。
- 高风险工具调用结果。
安全缓存 Key Demo
import hashlib
def build_cache_key(
*,
tenant_id: str,
user_scope_hash: str,
question: str,
model: str,
prompt_version: str,
knowledge_version: str,
) -> str:
raw = "|".join([
tenant_id,
user_scope_hash,
question.strip(),
model,
prompt_version,
knowledge_version,
])
return hashlib.sha256(raw.encode("utf-8")).hexdigest()如果缓存 Key 只有 question,就可能把 A 租户、A 权限、旧知识库版本的答案返回给 B 用户,这是严重事故。
模型路由
模型路由是根据任务选择合适模型,不是所有问题都用最强模型。
| 场景 | 推荐 |
|---|---|
| 简单分类、意图识别 | 小模型或规则 |
| 格式转换、摘要 | 中等模型 |
| 复杂推理、代码、复杂方案 | 强模型 |
| 敏感数据内网处理 | 本地模型 |
| 高风险回答 | 强模型 + 规则校验 + 人工兜底 |
路由流程:
flowchart TD
A["用户请求"] --> B["识别场景、风险、长度"]
B --> C{"是否高风险"}
C -- "是" --> D["强模型或人工审核"]
C -- "否" --> E{"是否简单任务"}
E -- "是" --> F["小模型或规则"]
E -- "否" --> G["默认模型"]模型路由必须用评估集验证。不能只因为小模型便宜就全部切过去。
限流、排队和降级
AI 服务成本高、耗时长,必须保护系统。
| 手段 | 作用 |
|---|---|
| 限流 | 防止单用户或系统被打爆 |
| 配额 | 控制每日或每月成本 |
| 队列 | 高峰期排队,避免请求全部失败 |
| 超时 | 防止接口无限等待 |
| 降级 | 模型不可用时返回兜底或使用备用模型 |
| 熔断 | 下游连续失败时暂停调用 |
最小 Demo:缓存、限流、超时、fallback
from __future__ import annotations
import hashlib
import time
from collections import defaultdict, deque
cache: dict[str, str] = {}
user_calls: dict[str, deque[float]] = defaultdict(deque)
def cache_key(user_scope: str, question: str, prompt_version: str) -> str:
raw = f"{user_scope}|{question.strip()}|{prompt_version}"
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
def check_rate_limit(user_id: str, limit: int = 3, window_seconds: int = 60) -> bool:
now = time.time()
calls = user_calls[user_id]
while calls and now - calls[0] > window_seconds:
calls.popleft()
if len(calls) >= limit:
return False
calls.append(now)
return True
def call_model(question: str, timeout_seconds: float = 2.0) -> str:
start = time.time()
time.sleep(0.5) # 模拟模型耗时
if time.time() - start > timeout_seconds:
raise TimeoutError("model timeout")
return f"模型回答:{question}"
def chat(user_id: str, user_scope: str, question: str) -> str:
if not check_rate_limit(user_id):
return "当前请求过于频繁,请稍后再试。"
key = cache_key(user_scope, question, "prompt-v1")
if key in cache:
return cache[key]
try:
answer = call_model(question)
except TimeoutError:
return "当前 AI 服务繁忙,请稍后重试。"
cache[key] = answer
return answer
print(chat("u1", "tenantA:roleUser", "什么是 RAG?"))
print(chat("u1", "tenantA:roleUser", "什么是 RAG?"))这个 Demo 虽然简单,但体现了生产意识:
- 先限流。
- 缓存 Key 包含权限范围。
- 模型调用有超时。
- 失败有兜底。
真实项目还要加入 Redis、分布式限流、队列、traceId、token 统计和告警。
成本优化
云模型成本通常与 token 相关,本地模型成本通常与硬件、能耗、运维相关。两类都要算。
云模型成本
单次成本 = 输入 token 单价 * 输入 token 数 + 输出 token 单价 * 输出 token 数优化方向:
- 减少无用上下文。
- 限制输出长度。
- 简单任务走小模型。
- 高频公共问答缓存。
- RAG 控制 TopK 和片段长度。
本地模型成本
综合成本 = GPU/服务器折旧 + 电力 + 运维 + 机房 + 人力 + 低利用率浪费本地不一定更便宜。如果请求量低、模型质量要求高、运维能力不足,云模型可能更合适。
压测怎么做
推理优化必须压测,不能只本地试一个请求。
压测要覆盖:
| 项 | 说明 |
|---|---|
| 短 Prompt | 普通问答 |
| 长 Prompt | RAG、多轮对话 |
| 短输出 | 分类、抽取 |
| 长输出 | 总结、报告 |
| 高并发 | 多用户同时问 |
| 工具慢调用 | 外部接口拖慢 |
| 模型失败 | fallback 是否生效 |
记录指标:
- QPS。
- TTFT。
- P50/P95/P99 总耗时。
- 平均输入/输出 token。
- 错误率、超时率。
- 队列长度。
- GPU/CPU/内存/显存。
- 单次成本。
生产排查流程
flowchart TD
A["AI 请求变慢或成本升高"] --> B["确认影响范围"]
B --> C["拆分入口、RAG、工具、模型耗时"]
C --> D{"主要瓶颈"}
D -- "入口排队" --> E["查限流、队列、并发、扩容"]
D -- "RAG 慢" --> F["查向量库、TopK、Rerank、过滤"]
D -- "Prompt 长" --> G["查历史、工具结果、Chunk 数"]
D -- "模型慢" --> H["查 TTFT、TPOT、模型、并发、显存"]
D -- "工具慢" --> I["查外部接口、超时、重试"]
D -- "成本高" --> J["查输入输出 token、路由、缓存"]首 token 慢
可能原因:
- 输入 Prompt 太长。
- RAG 或工具在模型前耗时。
- 模型预填充慢。
- 请求排队。
- 模型冷启动。
总耗时慢
可能原因:
- 输出太长。
- TPOT 高,模型生成慢。
- 并发抢资源。
- 网络或流式传输慢。
成本突然升高
可能原因:
- Prompt 版本加入了大量规则。
- RAG TopK 增大。
- 历史消息没有裁剪。
- 输出长度没有限制。
- 缓存失效或 Key 设计变化。
- 模型路由切到了更贵模型。
常见坑
| 坑 | 后果 | 正确做法 |
|---|---|---|
| 只看平均耗时 | 长尾用户仍很慢 | 看 P95/P99 |
| 只优化模型 | 可能慢在 RAG 或工具 | 拆分链路耗时 |
| 盲目减少 TopK | 速度快但答案错 | 用评估集看召回 |
| 缓存 Key 只有问题 | 权限串数据 | 加租户、权限、版本 |
| 不限输出长度 | 成本和耗时失控 | 设置 max tokens 和输出格式 |
| 不做限流 | 高峰期拖垮模型服务 | 限流、队列、降级 |
| 不记录 token | 成本无法解释 | 记录输入输出 token |
| 只本地单请求测试 | 上线并发崩 | 做压测和灰度 |
商业场景:医疗数据资产助手
场景:用户问“LIS 检验结果资产 patient_id 是什么?最近采集失败原因是什么?”
链路包括:
- RAG 检索字段说明。
- 工具查询最近采集状态。
- 模型组织答案。
- 返回引用和排查建议。
优化思路:
| 问题 | 优化 |
|---|---|
| 字段说明检索慢 | 缓存字段类 Embedding 和热门 Chunk |
| 工具查询慢 | 设置工具超时,失败时只回答文档部分 |
| Prompt 太长 | 只放 patient_id 相关 Chunk 和脱敏工具结果 |
| 输出太长 | 限制为“解释 + 原因 + 建议”三段 |
| 成本高 | 字段类问题优先本地模型或小模型 |
| 高峰排队 | 对普通问答限流,对管理员保留配额 |
面试标准回答
AI 推理优化怎么做?
可以这样答:
我会先监控再优化,不会直接猜模型慢。指标上看 TTFT、总耗时、P95/P99、输入输出 token、错误率、超时率、队列长度和单次成本。链路上拆入口、RAG、Rerank、工具、Prompt 组装和模型生成。常见优化包括流式输出降低首 token 感知,压缩 Prompt 和历史消息,控制 RAG TopK 和片段长度,缓存 Embedding 和高频问题,按场景做模型路由,增加限流、排队、超时、fallback 和压测。优化后必须用评估集验证质量没有下降。流式输出能不能让模型变快?
流式输出主要降低用户感知等待,让用户更快看到首 token,不一定减少总生成时间。它适合聊天和长文本生成,但如果输出必须完整校验 JSON 或经过高风险审核,就不一定适合直接流式返回。
为什么上下文越长越慢越贵?
上下文越长,输入 token 越多,模型预填充越慢;生成时 KV Cache 占用也会增加。对于云模型,输入 token 会直接增加费用;对于本地模型,长上下文和高并发会增加显存压力,可能导致排队或 OOM。
AI 缓存为什么要带权限和版本?
因为同一个问题在不同租户、不同权限、不同知识库版本、不同 Prompt 版本下答案可能不同。如果缓存 Key 只有问题文本,可能把 A 用户有权限看到的答案返回给 B 用户,也可能把旧知识库答案返回给新版本用户。
AI 请求慢怎么排查?
先看是首 token 慢、总耗时慢还是排队慢。再拆入口、RAG、Rerank、工具、Prompt 和模型耗时。如果 RAG 慢看向量库、TopK、Rerank;如果 Prompt 长看历史和 Chunk;如果模型慢看 TTFT、TPOT、并发和显存;如果成本高看输入输出 token、模型路由和缓存命中率。
关联知识点
| 知识点 | 作用 |
|---|---|
| 大模型基础 | 理解 token、上下文和生成参数 |
| Transformer原理 | 理解逐 token 生成和注意力计算 |
| 本地模型部署 | 理解 GPU、显存、量化、KV Cache |
| RAG知识库 | 理解 RAG 检索和上下文构造 |
| RAG数据治理 | 理解知识库版本和权限对缓存的影响 |
| AI应用架构 | 理解模型网关、异步任务和链路拆分 |
| LLMOps | 理解监控、成本、灰度和反馈闭环 |
| AI面试题 | 查看推理优化相关标准回答 |
本章小结
推理优化的核心不是“越快越好”,而是在质量、速度、成本、安全和稳定性之间找平衡。真正的生产优化要先采集指标,再拆链路定位瓶颈,然后用流式输出、Prompt 压缩、RAG 优化、缓存、模型路由、限流降级和压测逐步改进。每次优化都必须回到评估集,确认答案质量和安全没有退化。
