大模型推理指标

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 环境,依次尝试以下修改,观察输出变化:

  1. 拉长输入长度:将 avg_input_len 从 512 改为 3000,保持其他参数不变。
    • 你会看到:TTFT 增长约 6 倍,bottleneck 可能从 “Decode(访存)” 切换为 “Prefill(算力)”。
    • 直觉收获:长上下文场景下,首字延迟的恶化速度远超生成速度,且瓶颈性质发生根本转变。这就是 RAG 场景“首字等 3 秒”的数学根源。
  2. 调低 HBM 带宽:将 accel_bw_gbs 从 1900 改为 1200(模拟带宽受限场景),保持其他参数不变。
    • 你会看到:TPOT 明显上升,但 TTFT 几乎不变。
    • 直觉收获:Decode 阶段对带宽极度敏感,而对算力不敏感;Prefill 则相反。同一套硬件在不同阶段的瓶颈完全不同。
  3. 下调效率系数:将 efficiency_coeff 从 0.60 改为 0.35,模拟从恒定 Batch 切换到真实泊松到达。
    • 你会看到:TPS 和 QPS 同步缩水近一半,但 TTFT/TPOT 不变。
    • 直觉收获在 Batch 未发生资源争抢的前提下,调度开销和长度方差吃掉的主要是吞吐量,而非单请求延迟。高并发下的性能衰减,首先体现在 Batch 填充效率下降上。但一旦 Prefill 开始抢占 Decode 资源,单请求延迟也会随之恶化——这正是下一节要讨论的“两阶段资源争抢”问题。
    • 注意efficiency_coeff 是黑盒参数(见上方重要声明),请勿通过调整此系数来拟合实测数据以进行归因。正确的做法是使用 Ascend Profiling 工具拆解各阶段耗时,再针对性优化。
  4. 调整 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 能装下多少并发序列。

💡 提示:每次只改一个变量,保持其他不变,才能隔离出单一因素的影响。这四个实验的结果,恰好对应了下一节要深入拆解的核心问题。

以上四个单变量实验,分别隔离出了输入长度、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 维度的特殊性还会渗透到运行时系统的每一个环节。以下是三个最容易被忽视的影响点:

  1. 监控埋点必须双轨制
    仅监控 Request-level QPS 会掩盖真实的负载波动。推荐同时采集 Token-level TPS(分 Prefill/Decode),并以 avg_output_len 作为关联维度。当 QPS 稳定但 TPS 突增时,说明请求的平均输出长度发生了变化——这通常是上游 Prompt 模板变更或用户行为迁移的信号,而非流量攻击。

  2. 限流策略不能只用 QPS
    基于 QPS 的限流在长度异质化场景下形同虚设。一个恶意或异常的超长请求可以绕过 QPS 限制,却吃光全部 Decode 带宽。生产级推理服务应实现 Token-aware Rate Limiting:以“每秒允许消耗的 Decode Token 数”为限流维度,QPS 仅作为辅助约束。MindIE/vLLM-ascend 等框架已支持此类策略,但默认关闭,需显式配置。

  3. 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”按以下链条计算:

  1. max_bs_by_memory = floor((HBM容量 - 权重占用 - 框架预留) / (kv_cache_per_token_bytes × avg_output_len))
  2. Decode TPS = max_bs_by_memory / TPOT × efficiency_coeff
  3. QPS上限 = Decode TPS / avg_output_len
  4. 最终取值 = 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 峰值都更接近真相。

如果你在实战中踩过本文没提到的坑,或者对决策树的某个分支有不同经验,欢迎在评论区聊聊。大模型推理的工程实践还在快速迭代,今天总结的“最佳实践”可能明天就得改——写这篇文章的目的,也不是给一个标准答案,而是希望能和大家一起把这些问题聊得更清楚一些。