1.2 大模型推理链路的关键成本面:显存、算力与排队


文档摘要

1.2 大模型推理链路的关键成本面:显存、算力与排队 上一节我们破了旧范式,这一节要立新认知:大模型推理服务到底被什么物理约束卡着脖子?想清楚这个,你后面设计的每一个指标才有"锚点"。我把这些约束归纳为三个关键成本面--显存(GPU 内存)、算力(含显存带宽)、排队(调度与批处理)。三者相互咬合,任何一面恶化都会传导到另外两面,最终体现在用户感受到的延迟与吞吐上。这一节会落到"每个成本面该盯哪些指标",它们正是第 2 章指标体系的直接来源。 成本面一:显存是硬约束,KV Cache 才是动态闸门 在传统服务里,内存不够可以加机器、加 Swap,影响的是成本不是能力,OOM 也是可恢复的小概率事件。

1.2 大模型推理链路的关键成本面:显存、算力与排队

上一节我们破了旧范式,这一节要立新认知:大模型推理服务到底被什么物理约束卡着脖子?想清楚这个,你后面设计的每一个指标才有"锚点"。我把这些约束归纳为三个关键成本面--显存(GPU 内存)、算力(含显存带宽)、排队(调度与批处理)。三者相互咬合,任何一面恶化都会传导到另外两面,最终体现在用户感受到的延迟与吞吐上。这一节会落到"每个成本面该盯哪些指标",它们正是第 2 章指标体系的直接来源。

成本面一:显存是硬约束,KV Cache 才是动态闸门

在传统服务里,内存不够可以加机器、加 Swap,影响的是成本不是能力,OOM 也是可恢复的小概率事件。GPU 显存则是硬上限:模型权重、激活值、尤其是 KV Cache,全部住在显存里,而一张卡的显存是固定的(比如常见的 80GB)。其中 KV Cache 最容易被低估--它随"并发请求数 × 序列长度"线性增长,是推理服务能否扛住高并发的真正闸门。一个 7B 模型权重只占十几 GB,但上千并发、平均序列几千 token 时,KV Cache 轻松吃掉大几十 GB,先于权重把显存撑爆。

KV Cache 占满会发生什么?引擎要么拒绝新请求(返回 429 或让请求排队),要么淘汰旧 KV(命中率下降、重复计算),要么触发抢占(preemption)把低优先级请求换出再换入--这些动作都会让用户延迟飙升,但 HTTP 层可能毫无波澜。所以"显存占用率"和"KV Cache 命中/淘汰率"是 LLM 推理监控里最该被钉在首页的两个指标,它们比 QPS 早得多地预示了系统要出问题。

一个常被忽视的洞察:模型权重占用的显存相对固定,真正动态变化、决定并发上限的是 KV Cache 池。因此与其监控笼统的“总显存”,不如把“KV Cache 已用 / 配额”单独拉出来看,它的水位线直接告诉你能不能再接请求。当 KV 水位超过 85%,就该把它当成容量告警而不是等 QPS 暴涨才反应——因为到那时排队已经堆起来了。建议同时监控“KV Cache 命中率”,一旦抢占导致命中率掉下来,说明系统已经在用额外计算换空间,吞吐会随之下降,这是劣化的早期信号。

给一个量化的直觉:假设单卡 80GB,模型权重占 15GB,剩下约 60GB 给 KV Cache 与激活。若平均每请求峰值占用 0.5GB KV(对应中等长度对话),理论上限约 120 并发;但一旦涌入一批长文档摘要,单请求 KV 占用跳到 2GB,上限立刻被砍到 30 并发。这种“并发上限随请求分布剧烈波动”的特性,是 LLM 服务容量规划最反直觉的地方——你没法用一句“支持 N 并发”回答老板,而必须用 KV 水位曲线来说话。

成本面二:算力瓶颈要分清 compute-bound 与 memory-bound

很多人直觉"GPU 慢就是算力不够",但推理有两种截然不同的瓶颈形态,混为一谈就会乱加资源:

  • Prefill(处理 prompt)阶段通常是 compute-bound:要做大量矩阵乘,受 GPU 浮点算力(FLOPS)限制,prompt 越长越吃算力。一个 8k token 的 prompt,Prefill 的算力开销可能相当于几百次 Decode 步。
  • Decode(逐 token 生成)阶段通常是 memory-bound:每生成一个 token 都要把庞大的权重从显存搬到计算单元,受显存带宽限制,且每次只算一个 token,算力大量闲置。这也是为什么 Decode 阶段 GPU 利用率常只有 30% 上下,看着像"没跑满",实则显存带宽已打满。

这意味着同样的 GPU,在不同阶段被不同资源卡着。监控时若只盯"GPU 利用率",会严重误判:Decode 阶段即使显存带宽打满,GPU 利用率可能只有 30%,看着像还有余量,其实再加算力毫无用处。正确做法是同时看"算力利用率"和"显存带宽利用率",并区分 Prefill 与 Decode 两个阶段的耗时占比--这直接决定你该优化算法(如用更省的 attention 实现)还是该换带宽更高的卡(如从 PCIe 版换到 NVLink/SXM 版)。

给一个可操作的判据:如果你的瓶颈在 Prefill(长 prompt 场景、RAG 前置检索结果拼接),优先看 batch 内 Prefill 耗时和 FLOPS 利用率;如果瓶颈在 Decode(聊天、续写等短 prompt 长生成场景),优先看每 token 延迟和显存带宽利用率。绝大多数在线对话服务的瓶颈都在 Decode,所以“显存带宽”才是你最该关心的硬件指标,而不是纸面 TFLOPS。

这里有个常见误区值得点破:有人看到 GPU 利用率低,就去调大 batch size 想“喂饱”GPU,结果排队和显存压力反而先爆了。原因是 Decode 阶段本就 memory-bound,加大 batch 只会让批内最长序列更长、单请求延迟更不可控,带宽依旧是天花板。正确做法往往是反直觉的——在 Decode 瓶颈下,与其堆大 batch,不如换更高显存带宽的卡、或采用更省 KV 的 attention 变体(如 PagedAttention、MQA/GQA)来降低每次 Decode 的带宽压力。判断该走哪条路,靠的就是把 Prefill/Decode 的耗时与各自资源利用率拆开看。

成本面三:排队与批处理,把个体延迟变成集体事件

动态批处理(continuous batching)是推理吞吐的放大器,也是延迟的"搅局者"。引擎把陆续到达的请求拼进同一个 batch,共享一次前向计算,吞吐因此大幅提升,有时能比逐请求串行高出一个数量级;但代价是"个体延迟"被"批内最长序列"绑架--一个超长请求会让整批的 Decode 变慢,同批其他短请求被迫陪跑。换句话说,你的请求快不快,不只取决于你自己的输入,还取决于和你同批的那个人发了多长的请求。

于是"排队"成了 LLM 推理独有的延迟来源。一个请求的真实端到端延迟 = 排队等待显存/KV 空间的时间 + 在批内等待被调度的延迟 + 自身 Prefill + 自身 Decode。传统监控只画"端到端延迟",等于把这几段揉成一团,既看不出瓶颈,也分不清"是我慢还是我被拖累"。这正是 1.1 里那张时序图强调"把三段切开度量"的原因,而切开后的第一段,就是排队时长。排队时长一旦持续上升,说明 KV Cache 或算力已经跟不上到达速率,是容量告急最灵敏的先行指标--远比 QPS 可靠。

一个实战经验:如果你的推理引擎支持“优先级调度”或“抢占式调度”,要单独监控“被抢占次数”和“抢占后恢复耗时”。抢占能保住系统不崩,但会以被抢占请求的延迟暴涨为代价,这层代价必须可见,否则你会误以为延迟升高是模型问题。

再给一个量化场景帮助建立直觉:假设正常批内 Decode 每 token 约 15 毫秒,一个 500 token 的回答端到端约 7.5 秒;若同批混入一个 4000 token 的超长请求,批内 Decode 被它拉到每 token 40 毫秒,你的短请求端到端就被拖到 20 秒——而这两个请求在 HTTP 日志里都是 200。这种“被同批拖累”的延迟,只有把排队与批内等待单独监控才能现形。这也解释了为什么流式输出(边生成边返回)对用户体验至关重要:即使总延迟没变,首 token 延迟(TTFT)和逐 token 间隔稳定,用户感知到的“卡”会大幅缓解,而 TTFT 正是由排队 + Prefill 决定的,所以它该被单列成指标。

成本面之间的容量权衡三角

把三个成本面放在一起,你会发现它们构成了一个此消彼长的“容量三角”,做容量规划时必须同时看三边,不能只盯一边:

  • 想提高并发(接更多请求)→ 需要更多 KV Cache 空间(显存面),空间不够就转为更长排队(排队面);
  • 想降低单请求延迟 → 要么减小 batch(但牺牲吞吐、拉低算力利用率),要么换更高带宽的卡(算力面);
  • 想扛更长上下文 → 每个请求的 KV 占用变大,直接挤压并发上限(显存面反噬排队面)。

这个三角的实操含义是:任何一次“扩容”或“调参”都要预判它对另外两面的连锁反应。例如把 max_tokens 上限调大,单看能接更长回答是好事,但它会推高 KV 占用峰值、压低并发上限、拉长排队——若不同步扩容显存,反而会让整体变慢。监控的价值,正是用三边指标把这种连锁反应实时摊开在你面前,让调优从“拍脑袋”变成“看数据”。

三个成本面如何联动:一张因果网

三者不是孤立的,而是一张咬合的因果网,这也是为什么"头痛医头"常常无效:

  • 显存(KV Cache)水位高 → 新请求排队变久、甚至被拒 → 排队延迟上升;
  • 排队里混了超长请求 → 批内最长序列变长 → Decode 阶段被 memory-bound 放大 → 个体延迟飙升;
  • Decode 占主导且显存带宽打满 → GPU 利用率看着低,实则已是 bandwidth 瓶颈 → 此时加算力无效,得优化 KV 或换带宽更高的卡;
  • 反过来,若 Prefill 占主导且算力打满 → 加算力才有效,但加显存帮助有限。

看清这张网,你就不会再问"为什么加机器没用"--因为你加的可能不是瓶颈那一面。定位瓶颈面,是优化 LLM 推理的第一步,也是监控要做的事:用指标告诉你此刻卡在哪一"面"。

```mermaid graph TD C[三大关键成本面] --> C1[显存:KV Cache 硬约束] C --> C2[算力:compute vs memory bound] C --> C3[排队:批处理放大与绑架] C1 --> K[KV 占满→拒流/淘汰/抢占] C2 --> M[Decode 受显存带宽限] C3 --> Q[批内最长序列绑架个体延迟] K --> E[用户延迟↑吞吐↓] M --> E Q --> E ```

这张因果图建议作为第 2 章指标体系的总纲:每一个节点,都会对应一个或一组要采集的指标。

```mermaid graph LR subgraph Prefill 阶段 P1[compute-bound
受 FLOPS 限制] P2[prompt 越长越吃算力] end subgraph Decode 阶段 D1[memory-bound
受显存带宽限制] D2[每次只算 1 token,算力闲置] end P1 --> P2 D1 --> D2 ```

把两个阶段的瓶颈形态并列,能解释为什么"GPU 利用率低"不等于"还有余量"--Decode 阶段带宽打满时利用率本就高不起来,此时该看的是显存带宽而非算力。

```mermaid graph LR A[请求到达] --> B[排队等 KV 空间] B --> C[进入批处理] C --> D[Prefill] C --> E[Decode 逐 token] D --> E E --> F[流式返回] B -.排队延迟.-> G[端到端延迟] D -. Prefill 耗时.-> G E -. Decode 耗时.-> G ```

这张图把"端到端延迟"拆成排队、Prefill、Decode 三段,呼应 1.1 的时序图,也直接给出第 4 章看板要呈现的分段延迟视图。

小结:这一节立住了三个物理约束--显存(尤其 KV Cache)是硬闸门,算力要分清 compute/memory 两种瓶颈,排队让个体延迟变成集体事件。记住两条可操作判据:监控要拆开 Prefill/Decode 看带宽与算力、要单独看 KV 水位与排队时长;优化要先定位卡在哪一"面",而不是无脑堆资源。下一节(1.3)我们会把前两节的认知收敛成一份"开篇就要采集的监控需求清单"。


发布者: 作者: 分词分到自闭的小龙虾 转发
评论区 (0)
U