大模型推理指标
GPU 利用率 90%,用户还是喊卡?大模型推理指标的三重认知陷阱
“我们的模型在公开 Benchmark 上跑出了 200 TPS,为什么上线后用户反馈‘首字等 3 秒、生成像打字机’?”
这不是个例。在大模型推理从实验室走向生产环境的过程中,我们反复观察到一种割裂:监控面板上的指标一片繁荣,用户体验却千疮百孔。问题不在于加速器不够快,而在于我们用错了尺子。
传统 Web 服务的 QPS/延迟模型,在 Token 流式生成的世界里几乎完全失效。本文将拆解三个最致命的认知误区,并给你一个可以立即运行的诊断脚本——不再被白皮书峰值牵着鼻子走。
误区一:负载画像错配——你在测什么?
几乎所有推理框架或硬件白皮书都会亮出一个惊人的 TPS 数字。但当你把同样的模型部署到真实的 RAG 或 Agent 场景中,性能往往打 3-5 折。这是 Benchmark 假设与真实业务负载画像的结构性错配造成的:
| 维度 | Benchmark 典型设定 | 真实业务特征 | 性能影响 |
|---|---|---|---|
| 序列长度 | 固定 128in/128out 或 512in/128out | 输入长度长尾分布(RAG 上下文可达 4K+),输出长度动态变化 | 长输入场景下首字延迟恶化数倍,长输出场景下生成累积延迟显著超出预期 |
| 并发模式 | 恒定 Batch Size 压满 | 请求到达服从泊松分布,Batch Size 频繁抖动 | 实际吞吐低于恒定 Batch 压测值,且波动幅度随请求到达方差增大 |
| 调度开销 | 忽略 Tokenizer/Detokenizer、网络序列化 | 这些开销在短请求/高并发下占比可达 15-30% | 短请求/高并发下框架开销占比上升,实测 TPS 进一步偏离理论值 |
评估供应商或选型时,不能只看官方 TPS。你需要要求提供与自身业务输入输出长度分布匹配的实测数据,或者用下文提供的校准脚本自行验证。基于峰值做的容量规划,上线后大概率面临 3 倍以上的资源缺口。
从系统侧看,这种错配的根源在于:加速器利用率是一个“欺骗性指标”——它无法区分算力是被用于有效 Decode、被 Prefill 抢占,还是消耗在 Padding 填充上。真正能反映推理健康度的监控指标,应该是“实际 Batch Size 均值/P99”和“Prefill/Decode 时间占比”。至于为什么利用率会骗人、以及这些替代指标如何指导优化,我们将在 “误区二:Prefill 与 Decode 是两种病” 一节中深入拆解。
认知校准器:你的业务场景到底能跑多少 TPS?
下面这个 Python 脚本是一个 “认知校准器”,基于昇腾 910B3 + Qwen3.5-27B FP16 的参数,用引入 Batch Size 感知的简化 Roofline 模型 + 经验衰减系数,帮你快速建立“业务参数 → 系统指标”的直觉。
"""
大模型推理性能认知校准器 v3 (Educational Purpose Only)
平台: 昇腾910B3 + Qwen3.5-27B FP16 (混合架构)
修订: KV Cache按Qwen3.5-27B实际架构计算(16层GQA+48层DeltaNet)
Decode阶段引入Batch Size感知, 增加KV Cache显存约束校验
"""
def estimate_llm_performance_v3(
accel_tflops: float = 320.0, # 昇腾910B3 FP16算力 (TFLOPS)
accel_bw_gbs: float = 1900.0, # HBM2e带宽 (GB/s)
model_params_b: float = 27.0, # Qwen3.5-27B Dense
hbm_capacity_gb: float = 64.0, # HBM容量 (GB)
avg_input_len: int = 512,
avg_output_len: int = 128,
batch_size: int = 16, # 运行时平均Batch Size
efficiency_coeff: float = 0.45
):
weight_bytes = model_params_b * 1e9 * 2 # FP16权重 ~54GB
# === Prefill: 算力密集 ===
prefill_flops = 2 * model_params_b * 1e9 * avg_input_len
ttft_s = prefill_flops / (accel_tflops * 1e12)
# === KV Cache: Qwen3.5-27B 混合架构 ===
# 16层标准GQA: 2(K+V) × 4(KV heads) × 256(head_dim) × 2(FP16) = 4096 bytes/层/token
# 48层Gated DeltaNet: 线性注意力, 递归状态固定大小, 不随序列长度增长, 此处忽略常量开销
kv_cache_per_token_bytes = 16 * 2 * 4 * 256 * 2 # ≈ 64 KiB/token
# === Decode: Batch Size 感知 ===
if batch_size <= 1:
bytes_per_decode_token = weight_bytes
else:
# 权重分摊 + KV Cache读取
bytes_per_decode_token = (weight_bytes / batch_size) + kv_cache_per_token_bytes
tpot_s = bytes_per_decode_token / (accel_bw_gbs * 1e9)
# === 显存约束校验 ===
available_for_kv = hbm_capacity_gb - (weight_bytes / 1e9) - 2.0 # 预留2GB激活/框架
max_bs_by_memory = max(1, int(available_for_kv / (kv_cache_per_token_bytes * avg_output_len / 1e9)))
memory_bound = batch_size > max_bs_by_memory
# === 吞吐量 ===
theoretical_tps_decode = batch_size / tpot_s if tpot_s > 0 else float('inf')
effective_tps = theoretical_tps_decode * efficiency_coeff
effective_qps = effective_tps / avg_output_len if avg_output_len > 0 else 0
bottleneck = "Decode(访存)" if tpot_s > ttft_s else "Prefill(算力)"
if memory_bound:
bottleneck += " KV Cache溢出风险"
return {
"estimated_tps": round(effective_tps, 1),
"estimated_qps": round(effective_qps, 2),
"ttft_ms": round(ttft_s * 1000, 1),
"tpot_ms": round(tpot_s * 1000, 1),
"bottleneck": bottleneck,
"max_bs_by_memory": max_bs_by_memory,
"note": f"BS={batch_size}, 效率系数={efficiency_coeff}"
}
# === 示例: 对比Benchmark场景 vs 真实RAG场景 ===
benchmark_result = estimate_llm_performance_v3(
avg_input_len=512, avg_output_len=128, batch_size=32, efficiency_coeff=0.60
)
rag_result = estimate_llm_performance_v3(
avg_input_len=3000, avg_output_len=800, batch_size=6, efficiency_coeff=0.35
)
print(f"Benchmark场景: TPS={benchmark_result['estimated_tps']}, "
f"TTFT={benchmark_result['ttft_ms']}ms, 安全BS上限={benchmark_result['max_bs_by_memory']}, "
f"瓶颈={benchmark_result['bottleneck']}")
print(f"RAG真实场景: TPS={rag_result['estimated_tps']}, "
f"TTFT={rag_result['ttft_ms']}ms, 安全BS上限={rag_result['max_bs_by_memory']}, "
f"瓶颈={rag_result['bottleneck']}")
⚠️ 重要声明:此脚本仅用于建立直觉。KV Cache 估算已针对 Qwen3.5-27B 混合架构校准(16 层 GQA + 48 层 Gated DeltaNet),实际每 Token 占用约 64 KiB,仅为同规模全注意力模型的 1/4。效率系数是一个黑盒参数,仅用于量级估计,不可用于归因分析——若实际 TPS 低于预估值,需通过 Ascend Profiling 定位具体瓶颈(调度/长度方差/框架开销),而非调整该系数。真实容量规划请使用 MindIE/vLLM-ascend 等框架的实际压测数据。
动手实验指南:4 次修改,建立性能直觉
复制上面的代码到本地 Python 环境,依次尝试以下修改,观察输出变化:
- 拉长输入长度:将
avg_input_len从 512 改为 3000,保持其他参数不变。- 你会看到:TTFT 增长约 6 倍,
bottleneck可能从 “Decode(访存)” 切换为 “Prefill(算力)”。 - 直觉收获:长上下文场景下,首字延迟的恶化速度远超生成速度,且瓶颈性质发生根本转变。这就是 RAG 场景“首字等 3 秒”的数学根源。
- 你会看到:TTFT 增长约 6 倍,
- 调低 HBM 带宽:将
accel_bw_gbs从 1900 改为 1200(模拟带宽受限场景),保持其他参数不变。- 你会看到:TPOT 明显上升,但 TTFT 几乎不变。
- 直觉收获:Decode 阶段对带宽极度敏感,而对算力不敏感;Prefill 则相反。同一套硬件在不同阶段的瓶颈完全不同。
- 下调效率系数:将
efficiency_coeff从 0.60 改为 0.35,模拟从恒定 Batch 切换到真实泊松到达。- 你会看到:TPS 和 QPS 同步缩水近一半,但 TTFT/TPOT 不变。
- 直觉收获:在 Batch 未发生资源争抢的前提下,调度开销和长度方差吃掉的主要是吞吐量,而非单请求延迟。高并发下的性能衰减,首先体现在 Batch 填充效率下降上。但一旦 Prefill 开始抢占 Decode 资源,单请求延迟也会随之恶化——这正是下一节要讨论的“两阶段资源争抢”问题。
- 注意:
efficiency_coeff是黑盒参数(见上方重要声明),请勿通过调整此系数来拟合实测数据以进行归因。正确的做法是使用 Ascend Profiling 工具拆解各阶段耗时,再针对性优化。
- 调整 Batch Size:将
batch_size从 1 逐步改为 8、16、32、48,观察 TPOT 和max_bs_by_memory的变化。- 你会看到:TPOT 随 BS 增大而非线性下降(权重读取被分摊),但当 BS 超过
max_bs_by_memory时,瓶颈标记出现 ⚠️ 警告。 - 直觉收获:Decode 耗时并非与并发无关,而是存在“权重摊销饱和点”;同时显存容量是硬约束,BS 不是越大越好。吞吐量优化的天花板,往往不在算力或带宽,而在 KV Cache 能装下多少并发序列。
- 你会看到:TPOT 随 BS 增大而非线性下降(权重读取被分摊),但当 BS 超过
💡 提示:每次只改一个变量,保持其他不变,才能隔离出单一因素的影响。这四个实验的结果,恰好对应了下一节要深入拆解的核心问题。
以上四个单变量实验,分别隔离出了输入长度、HBM 带宽、调度效率、Batch Size 对推理指标的独立影响。它们共同揭示了一个被 Benchmark 掩盖的事实:大模型推理不存在统一的“性能瓶颈”,只存在随负载动态迁移的阶段性瓶颈。
这正是第二个、也是最普遍的认知误区所在:把 Prefill 和 Decode 当作一个整体来度量、来优化、来承诺 SLA。用户抱怨的“首字慢”和“生成卡”,本质上是两种完全不同的问题,需要两套独立的诊断逻辑。把它们混为一谈,所有的调优都是在隔靴搔痒。
误区二:Prefill 与 Decode 是两种病,不能混着治
为什么“平均延迟”是最危险的指标
在传统 Web 服务中,一个请求的延迟是一个原子值:从接收到响应,一条曲线,一个数字。但大模型推理的请求生命周期天然分裂为两个阶段:
- Prefill(预填充):一次性处理全部输入 Token,并行计算 KV Cache。这是算力密集型操作,耗时取决于输入长度和加速器 FLOPS。
- Decode(解码):逐 Token 自回归生成,每步仅计算一个 Token。这是访存密集型操作,耗时取决于模型权重大小和 HBM 带宽,与输入长度几乎无关。
这两个阶段的硬件瓶颈、优化方向、对业务参数的敏感度完全不同。用“平均延迟”或单一 P99 把它们揉在一起,就像用“平均体温”诊断一个同时患有骨折和感冒的病人——数字看起来正常,但两种病都没治对。
双阶段指标体系:TTFT 与 TPOT
正确的做法是将延迟拆分为两个独立指标,分别对应两个阶段:
| 指标 | 全称 | 度量对象 | 硬件瓶颈 | 业务敏感参数 | 用户体感 |
|---|---|---|---|---|---|
| TTFT | Time To First Token | Prefill 阶段耗时 | 加速器算力 (FLOPS) | 输入长度、Batch Size | “点了发送之后等多久才开始出字” |
| TPOT | Time Per Output Token | Decode 阶段单 Token 耗时 | HBM 带宽 (GB/s) | 模型参数量、量化精度、Batch Size | “出字速度像打字机还是像流式输出” |
这个拆分直接决定了优化策略的有效性:
- TTFT 过高时,优化重心应放在 Prefill 阶段:Prefix Caching 可减少重复计算,Chunked Prefill 可将长序列切分为多个小块与 Decode 交错执行,避免单次 Prefill 阻塞整个 Batch。但需注意:Chunked Prefill 并非免费午餐——它通过牺牲部分 Decode 吞吐量来换取 TTFT 的可控性,在输出长度较长的场景下可能导致 TPOT 恶化。同样,PD 分离(Prefill-Decode Disaggregation)虽能彻底解耦两阶段资源争抢,但引入了 KV Cache 跨节点传输开销,在网络带宽不足或序列较长时,反而可能增加端到端 TTFT。没有无条件正确的优化方案,只有与业务负载特征匹配的权衡选择。
- TPOT 过高时:应该关注量化(INT8/INT4)、KV Cache 压缩、Speculative Decoding,或者更换更高带宽的硬件。此时堆叠更多算力对单请求生成速度几乎没有帮助。
吞吐量与延迟的非线性权衡
更反直觉的是,提升吞吐量的手段往往会恶化其中一个延迟指标。这也解释了上一节提到的“利用率为何是欺骗性指标”:利用率只反映算力被占满的程度,却不区分占用是在做有效生成还是在做 Batch 填充带来的资源争抢。
Continuous Batching 是提高 TPS 的核心技术:当某个请求完成 Decode 后,立即插入新请求的 Prefill,避免加速器空等。但这意味着正在 Decode 的请求会被迫与新请求的 Prefill 共享算力和 HBM 带宽。结果就是:
- TPS 上升了(单位时间处理的 Token 总数增加)
- TPOT 可能变差(Decode 步骤被 Prefill 抢占资源)
- TTFT 也可能变差(新请求的 Prefill 排队等待当前 Decode 批次完成)
高吞吐量是以牺牲部分请求的延迟确定性为代价换来的,而这个代价在“平均延迟”中被彻底抹平了。
落地建议:SLA 必须分阶段定义
基于以上分析,推理服务的 SLA 不应该再写成“P99 延迟 < 2s”这种模糊表述。推荐的分级定义方式:
- TTFT P95 < 800ms(保障绝大多数用户的首字体感)
- TPOT P95 < 50ms(保障生成流畅度,对应 ~20 tokens/s)
- TPS ≥ X(在满足上述延迟约束的前提下,衡量系统容量)
这三个指标构成一个约束面:在固定硬件上,你只能在其中两个之间做权衡,第三个由物理定律决定。容量规划的本质,就是在这个约束面上找到符合业务优先级的最优解点。
误区三:QPS ≠ TPS,Token 维度的换算陷阱
为什么“100 QPS”在大模型语境下是一句废话
在传统 Web 服务中,QPS 是一个自包含的容量单位:100 QPS 意味着每秒处理 100 个原子请求,每个请求的资源消耗大致恒定。但在大模型推理中,QPS 脱离了 Token 维度就失去了物理意义。
一个生成 50 Token 的闲聊请求和一个生成 2000 Token 的代码补全请求,消耗的 Decode 算力相差 40 倍,但它们都只贡献 1 QPS。如果你基于“峰值 QPS”做容量规划,结果要么是短请求场景下加速器严重闲置,要么是长请求场景下 TPOT 雪崩、SLA 全面击穿。
正确的容量单位应该是 Token/s(TPS),而 QPS 只是 TPS 在特定业务负载下的投影。两者之间的换算关系,才是容量规划的真正起点。
换算公式:从业务参数到系统指标
QPS 与 TPS 的换算并不复杂,但必须区分 Prefill 和 Decode 两个阶段,因为它们的 Token 消耗模式完全不同:
Decode TPS = QPS × avg_output_len
Prefill TPS = QPS × avg_input_len
Total TPS ≈ Prefill TPS + Decode TPS (稳态近似)
⚠️ 口径说明:此处的 Total TPS 仅用于量级对比,不代表 Prefill 和 Decode 的资源消耗可直接相加——两者的硬件瓶颈完全不同(前者吃算力,后者吃带宽),实际容量规划需分别约束。
这个公式揭示了一个关键事实:QPS 对 TPS 的放大倍数,完全由业务的输入输出长度分布决定。同样的 50 QPS:
- 在客服场景(200in / 150out)→ Total TPS ≈ 17,500
- 在 RAG 场景(3000in / 800out)→ Total TPS ≈ 190,000
后者是前者的 10.9 倍。如果你用客服场景的压测数据去规划 RAG 场景的容量,资源缺口就是 10 倍。这就是为什么“Benchmark QPS”或“历史平均 QPS”在大模型容量规划中几乎总是错的。
Token 维度对监控与调度的深层影响
换算公式解决的是规划问题,但 Token 维度的特殊性还会渗透到运行时系统的每一个环节。以下是三个最容易被忽视的影响点:
监控埋点必须双轨制
仅监控 Request-level QPS 会掩盖真实的负载波动。推荐同时采集 Token-level TPS(分 Prefill/Decode),并以avg_output_len作为关联维度。当 QPS 稳定但 TPS 突增时,说明请求的平均输出长度发生了变化——这通常是上游 Prompt 模板变更或用户行为迁移的信号,而非流量攻击。限流策略不能只用 QPS
基于 QPS 的限流在长度异质化场景下形同虚设。一个恶意或异常的超长请求可以绕过 QPS 限制,却吃光全部 Decode 带宽。生产级推理服务应实现 Token-aware Rate Limiting:以“每秒允许消耗的 Decode Token 数”为限流维度,QPS 仅作为辅助约束。MindIE/vLLM-ascend 等框架已支持此类策略,但默认关闭,需显式配置。Batch Size 的最优值是动态的
传统服务的 Batch Size 优化目标是固定的(通常等于 CPU 核数或连接池大小)。但在大模型推理中,最优 Batch Size 取决于当前队列中请求的平均输出长度和 KV Cache 剩余容量:短请求多时可以开更大 Batch 以提升吞吐,长请求多时必须缩小 Batch 以保护 TPOT SLA 和避免显存溢出。这也是 Continuous Batching 框架的核心价值之一——它把 Batch Size 从一个静态配置项变成了一个运行时自适应变量。
落地工具:业务参数 → 容量估算速查表
将上述换算逻辑固化,可以得到一张实用的速查表。以下数值基于 昇腾 910B3 + Qwen3.5-27B FP16、效率系数 0.45 的假设,所有 QPS 数值均经 KV Cache 显存约束反推验证,仅作量级参考:
| 业务场景 | avg_in | avg_out | 单卡最大安全BS | QPS=50时Total TPS | 单卡可支撑QPS (TTFT<1s, TPOT<50ms) | 首要瓶颈 |
|---|---|---|---|---|---|---|
| 客服对话 | 200 | 150 | 48 | 17,500 | ~38 | Decode带宽 + KV Cache容量 |
| RAG问答 | 3000 | 800 | 6 | 190,000 | ~3 | Prefill算力 + KV Cache容量 |
| 代码补全 | 1500 | 300 | 18 | 90,000 | ~13 | Decode带宽 |
| 文档摘要 | 6000 | 1200 | 4 | 360,000 | ~1.5 | KV Cache容量(极端受限) |
⚠️ 使用注意:
- “单卡最大安全BS”已扣除权重占用(~54GB)和 2GB 框架预留,仅基于 KV Cache 容量计算(Qwen3.5-27B 混合架构,~64 KiB/token)。
- “单卡可支撑 QPS”是在该安全 BS 下、同时满足 TTFT/TPOT SLA 的保守估计(见上方重要声明关于效率系数的说明)。若业务可容忍更高延迟,QPS 可上浮,但必须先验证 KV Cache 不溢出。
- 此表仅作量级参考,严禁直接用于生产容量规划。 真实部署请以 MindIE/vLLM-ascend 实测压测数据为准。
QPS 数值推导说明:
表中“单卡可支撑 QPS”按以下链条计算:
max_bs_by_memory= floor((HBM容量 - 权重占用 - 框架预留) / (kv_cache_per_token_bytes × avg_output_len))Decode TPS= max_bs_by_memory / TPOT × efficiency_coeffQPS上限= Decode TPS / avg_output_len- 最终取值 = min(QPS上限, TTFT SLA约束下的QPS)
以 RAG 场景为例:max_bs_by_memory=6 → Decode TPS≈2400 → QPS上限≈3.0,同时 TTFT<1s 约束下 QPS≈3.2,取较小值 ~3。所有数值均为保守估计,实际部署请以实测为准。
注意:此表固定了 TTFT<1s 和 TPOT<50ms 两个 SLA 约束,因此 TPS 成为唯一自由变量,“三选二”约束面在此退化为单维上限。若业务可放宽任一延迟约束(例如 TPOT 容忍 80ms),QPS 上限可显著上浮,但需重新验证用户体验是否可接受。
这张表的价值不在于具体数字,而在于它展示了“同一硬件、不同业务、容量差 25 倍”的现实。容量规划的第一步,永远是准确刻画自身业务的输入输出长度分布,而非询问供应商“这张卡能跑多少 QPS”。
从认知到落地:推理服务的性能治理框架
上线 Checklist:把三个误区变成五道防线
前文拆解的三个认知误区,最终需要固化为可执行的工程规范。以下 Checklist 按服务生命周期排列,每一项都对应前文的具体误区与解决方案:
- [ ] 负载画像先行:在选型或压测前,采集至少一周的生产流量样本,统计输入/输出长度的分布(均值、P50、P95、P99),而非仅记录平均值。(对应误区一)
- [ ] SLA 分阶段定义:拒绝“P99 延迟 < Xs”的模糊表述,明确 TTFT P95、TPOT P95、TPS 下限三个独立指标,并标注其对应的业务优先级。(对应误区二)
- [ ] 压测负载匹配生产:Benchmark 必须使用与生产负载画像一致的输入输出长度分布和并发到达模式,效率系数按业务复杂度校准(简单场景 0.5-0.7,复杂场景 0.25-0.45)。(对应误区一)
- [ ] 监控双轨制埋点:同时采集 Request-level QPS 和 Token-level TPS(分 Prefill/Decode),并以
avg_output_len作为关联维度;增加“实际 Batch Size P99”和“Prefill/Decode 时间占比”两个健康度指标。(对应误区二 + 误区三) - [ ] 限流策略 Token-aware:生产环境启用基于 Decode Token/s 的限流,QPS 仅作为辅助约束;验证超长请求不会绕过限流导致 TPOT 雪崩或 KV Cache 溢出。(对应误区三)
💡 使用建议:此 Checklist 适合作为推理服务上线评审的准入条件。未勾选项不应阻塞上线,但必须记录风险缓解措施和复查时间表。
附录:性能问题诊断决策树
当用户反馈“卡”或监控告警触发时,按以下路径定位根因。此决策树可直接打印贴在工位旁,或嵌入内部运维文档:
flowchart TD
A[用户反馈/监控告警] --> B{哪个指标异常?}
B -->|TTFT 超标| C[Prefill 阶段问题]
B -->|TPOT 超标| D[Decode 阶段问题]
B -->|TPS/QPS 下降但延迟正常| E[吞吐量/调度问题]
B -->|全部异常| F[系统性过载, 先扩容再诊断]
C --> C1{输入长度是否突增?}
C1 -->|是| C2["检查上游Prompt变更/RAG检索结果膨胀<br/>→ 截断/Chunked Prefill/Prefix Cache"]
C1 -->|否| C3{Batch Size P99 是否过高?}
C3 -->|是| C4["Prefill 排队严重<br/>→ 限制Max Batch Size/启用Priority Scheduling"]
C3 -->|否| C5["算力瓶颈<br/>→ 横向扩展/模型蒸馏/FlashAttention升级"]
D --> D1{输出长度是否突增?}
D1 -->|是| D2["检查生成策略/Stop Token缺失<br/>→ 修复生成逻辑/加长度上限"]
D1 -->|否| D3{HBM带宽是否饱和?}
D3 -->|是| D4["访存瓶颈<br/>→ 量化/KV Cache压缩/Speculative Decoding"]
D3 -->|否| D5{调度策略是否允许Prefill插入Decode批次?}
D5 -->|是| D6["Continuous Batching资源争抢<br/>→ 启用Chunked Prefill/PD分离/调小Max Batch Size"]
D5 -->|否| D7["Batch Size静态过大<br/>→ 调小Max Batch Size/检查请求队列积压"]
E --> E1{实际Batch Size均值是否过低?}
E1 -->|是| E2["请求到达稀疏或长度方差大<br/>→ Continuous Batching/动态Padding优化"]
E1 -->|否| E3{效率系数是否骤降?}
E3 -->|是| E4["调度/框架异常<br/>→ 检查版本回退/配置变更/硬件故障"]
E3 -->|否| E5["负载画像漂移<br/>→ 重新采集样本, 更新容量规划"]
注:决策树中“调度策略是否允许 Prefill 插入 Decode 批次”指的是:当一个新请求到来时,系统是否会为了尽快处理它而打断正在生成中的其他请求。如果会,就可能造成“生成卡顿”;如果不会,则可能是 Batch Size 设置过大导致排队过久。
获取可编辑版本:此决策树的 Mermaid 源码、Visio 源文件、及 Notion 模板已整理至 [GitHub Gist / 内部文档链接占位符],欢迎 Fork 后根据自身技术栈定制。
结语:KV Cache 才是真正的天花板
大模型推理的性能治理,本质上是在物理约束、业务需求、工程成本三者之间寻找动态平衡。TTFT、TPOT、TPS、QPS 这些指标,只是我们描述这个平衡点的语言,而非目标本身。
但如果要从本文中带走一个最具体的结论,我希望是这个:在大多数真实业务场景中,KV Cache 容量往往比算力更早成为瓶颈。
无论是速查表中 RAG 场景单卡仅 ~3 QPS 的残酷现实,还是实验 4 中 Batch Size 触及显存上限时的警告,都在指向同一个事实:当模型权重已经占据 54GB HBM 后,留给 KV Cache 的空间极其有限。即便加速器利用率还有余量,即便算力远未跑满,只要 KV Cache 装满,新的请求就只能排队等待——而此时监控面板上的利用率数字,可能依然好看。
所以,下次当你规划推理容量时,请先问自己一个问题:我的 KV Cache 能装下多少个并发序列?这个问题的答案,比任何 Benchmark 峰值都更接近真相。
如果你在实战中踩过本文没提到的坑,或者对决策树的某个分支有不同经验,欢迎在评论区聊聊。大模型推理的工程实践还在快速迭代,今天总结的“最佳实践”可能明天就得改——写这篇文章的目的,也不是给一个标准答案,而是希望能和大家一起把这些问题聊得更清楚一些。