大模型推理系统工程:性能、诊断与优化

v0.12 | 18 章 | 61 张正文插图 + 49 张增补图 | 第三方公开数据与公开结果分列,每处数值均标注来源与证据层级
微信扫码关注公众号 AI算力局微信扫码
或搜一搜「AI算力局」

第1章 一次推理请求的完整路径

核心论点:先确定研究对象和测量边界,再讨论模型快慢;一次业务任务、一次接口请求和一次模型执行不是同一个单位。

本章导航

读完本章,你应该能够:

  1. 给一次业务完成过程分层命名——任务、请求、模型调用、尝试、会话——并把客户端状态、服务端状态、任务结果与资源释放分成四张互不混淆的账;
  2. 在同一个可比时间域内定义端到端时间,并按依赖图而不是按日志列表重建关键路径;
  3. 说出"同一个模型名称"之外还必须固定哪些制品身份,以及可复现性有哪四个层次;
  4. 把流式响应中的协议事件、观测层间隔与 Token 数分开识别,知道块边界不等于分词边界;
  5. 把引擎拆成前端、调度器、Model Runner、执行后端等职责层,并知道"声明支持"与"实际启用"需要用不同强度的证据;
  6. 对账发出、接收、完成、取消、超时和未终结的请求,识别幽灵负载与重试放大,并知道重试放大的假设边界。

1.1 系统边界:任务、请求、会话与模型调用

本节要回答的问题:我们究竟在优化哪一种完成单位?

一个团队报告"我们的大模型服务很慢,端到端要 6.7 秒"。这句话没有错,但它几乎不包含可执行的优化信息:这 6.7 秒里有检索、有工具查询、有模型调用、有客户端渲染,而团队准备购买的新型加速卡只能影响其中的一段。工程判断的第一步永远是拆分与命名——把"慢"这个形容词,换成一张谁都能核对的账本。

1.1.1 业务任务与接口请求

设想一个合同审查助手。用户上传一份 30 页的 PDF,点击"分析风险点",约 6.7 秒后看到结果。用户感知到的是一次业务任务(task):从点击到答案可读。系统内部的动作序列必须写清楚,尤其是依赖关系——哪些步骤必须等别人先完成。

本例采用的结构是:主模型生成与条款查询工具之间是独立预取关系,即工具在任务早期被触发(触发点 = 文档解析完成后),其结果只供最后的结果组装消费(消费者 = S8),且必须在 S8 开始前完成(汇合点 = S8 起点,早于 S6 结束)。只有在这三个条件被实际记录证实之后,才能把工具耗时视为可被覆盖的区间;若工具结果要被答案本身消费("工具提出调用 → 执行 → 结果回填 → 后续生成"),它就是串行的一段,不能自动隐藏。

段 内容 墙钟耗时 (s) 依赖 谁负责
S1 网关鉴权与配额 0.08 串行前置 平台
S2 文档解析与切块 0.35 S1 之后 应用
S3 向量检索召回 12 段 0.90 S2 之后 检索服务
S4 重排模型对 12 段打分 0.60 S3 之后 推理服务(评分调用)
S5 主模型 Prefill(8k 输入) 1.20 S4 之后 推理服务
S6 主模型 Decode(900 输出) 3.10 S5 之后 推理服务
S7 条款查询工具(S2 完成后立即触发,独立预取) 0.45 不阻塞 S5/S6,汇合点固定为 S8 起点 外部工具
S8 组装、校验、返回 0.12 S6 与 S7 均完成后 应用
S9 客户端解析与渲染 0.35 收到全部内容后 客户端

以上数值为构造数据。其中列入配套E0清单的算术关系已执行自动检查,其余项不据此宣称程序验证:

计算口径 结果 (s) 说明
S1—S6 加 S8(不含 S9、不含工具) 6.35 服务端到"最后一字节写出"的关键路径
上项加 S9 6.70 用户视角端到端(工具与解码重叠的情形)
九段全部串行相加 7.15 工具结果被答案消费时的端到端
重叠情形下盲目相加的高估量 0.45 等于 S7 的耗时
若把 Decode 缩短 30% 后 S7 是否仍被覆盖 仍被覆盖 本例 S7 在 0.88 s 完成,S5 在 3.13 s 完成;即使 Decode 归零,S7 也不成为关键路径
重试是否计入 6.70 s 否 6.70 s 是"首次尝试即成功"的单次时间线;重试另开一条 attempt 记录,其耗时进入 attempt 层统计与成本分子
仅 Decode 缩短 30% 5.77 其余段与关键路径结构不变
仅 Decode 理想化为零 3.60 条件化乐观下界,不是实机可达值

由此得到的结论必须写成条件句:在本案例中,假设 S7 与解码的重叠关系已被事件记录确认,且调整后其他阶段与关键路径不变,端到端时间为 6.70 秒。将 3.10 秒的解码耗时缩短 30%,可减少 0.93 秒,使端到端降至 5.77 秒。仅优化解码,即使将其耗时理想化为零,剩余串行阶段仍为 3.60 秒,因此单靠这一段无法达到 3 秒目标。这并不证明同时优化 Prefill、检索、重排或任务结构也无法达标。 另外,3.60 秒是把一个阶段理想化到零得到的乐观下界,用于判断"单段优化能否达成目标",不可用作任何可达性能的承诺。

这段推导的价值在于:它能在花钱之前识别出哪些路径不可能达成目标。 在上面的假设下,"只优化 Decode"这条路径被排除;至于同时优化 Prefill、检索、重排,或改变任务结构(流式呈现、先要点后全文、进一步并行化检索与工具)能否达到 3 秒,本账本没有给出结论——它需要各自的账与各自的实验。不要把"单段优化不可行"扩大成"只有改任务结构才可行"。

要把这类账本写对,先要把对象分层。下表给出五类常被混用的对象;注意它们不全是"完成单位"——会话是上下文容器,不是一种完成:

对象 定义 典型标识 失败/异常意味着什么
业务任务 task 用户视角的一次交付,由预先写好的任务契约判定成功 task_id 未按契约交付(可能部分成功)
接口请求 request 一次对服务的逻辑调用 request_id 这次调用未按契约返回
物理尝试 attempt 一次实际发出的网络尝试(含重试) attempt_id 这次尝试超时或被拒
模型调用 model call 一次对模型的调用:生成、评分、嵌入等不同类型 model_call_id 这次调用未完成
会话 session 多次请求共享的上下文容器 session_id 上下文丢失或被错误复用

两个常被忽略的细节:

  • 一次生成不等于一次前向。 通常所说的"生成 900 个 Token"在实现上意味着数百次迭代执行;把 model call 写成"一次前向 + 生成"是不准确的。评分类调用(如上面的 S4 重排)与生成类调用的资源特征也不同,不应共用一套口径。
  • 会话不必是任务或请求的严格父级。 多个任务可以共享一个会话,一个任务也可以横跨多个会话;用 session 作为统计层级的根节点会算错。

另设一个“主请求发生一次重试”的映射变体(不属于前述首次尝试即成功的 6.70 s 时间线),避免"一次任务等于几次调用"停留在口头:

层 本例计数 说明
task 1 一次合同风险分析
request 2 R1 重排服务调用;R2 主模型调用
attempt 3 R2 曾超时一次,客户端重试后成功
model call 2 个逻辑调用;实际执行数待核对 若 R2 两个 attempt 都进入执行且未去重,则有 3 次模型执行;若命中幂等去重才可能只有 2 次

之所以不写"7 次请求",是因为那需要另一份调用图;没有映射的数量不要出现在案例里。

1.1.2 会话状态与多次调用

多轮对话让完成单位进一步复杂化。第二轮请求必须携带第一轮的上下文,而上下文的存放位置至少有三种:客户端拼接后重发、服务端按会话标识保存、以及模型服务内部复用 KV 状态。三者的性能含义完全不同,但对外表现可能一模一样。

按结构划分,负载至少有四类,它们在本书后续章节中会反复出现:

结构 特征 上下文传递方式 关键测量差异
独立请求 请求之间无关联 不需要 可把每次请求视为独立样本
线性会话 第 \(n\) 轮依赖前 \(n-1\) 轮 重发或服务端保存 需区分历史长度、实际未复用的 Token 数、算法工作量与墙钟时间
共享历史分支 同一历史派生多个后续 共享前缀 + 分叉 前缀复用命中率成为主要变量(见 v0.3 设计稿第4章)
独立子任务 一个任务拆成多个并行子调用 各自独立上下文 需按任务统计完成时间与成本

线性会话的增长要小心表述。历史长度逐轮变大,但实际需要处理的 Token 数取决于有多少被复用:若引擎命中了前缀缓存,需要处理的是未复用的那部分;若没有命中,才是全部历史。算法工作量(与未复用 Token 数相关)与墙钟时间之间还隔着排队、批组成与设备占用,因此不能把"输入变长 8 倍"直接写成"耗时增加 8 倍"。正确做法是分开记录四个量:历史 Token 数、未复用 Token 数、算法工作量、墙钟时间,缺哪一个都无法复核。

多分支的成败判定必须预先写进任务契约,而不是事后解释。举例:

任务契约规定"三个并行分支须在 8 秒内全部返回,否则降级为部分结果"。实测三个子调用的服务端终态都是 completed,但最慢的那个在 8.3 秒才返回,超过截止时间,于是 task_outcome 记为 partial_success(按契约降级)。

这不是矛盾:三个子调用成功描述的是调用层,部分成功描述的是任务层。两者分属不同层次,靠 task_id 聚合。

反过来也成立:"任一子调用失败即任务失败"不是通则,它只适用于全部分支必需、且没有回退、重试、冗余或降级策略的流程。有降级路径的任务,子调用失败可能只导致质量下降。任务是否成功,永远由契约判定,不由某一个子调用的状态判定。

1.1.3 系统内外责任边界

确定了完成单位之后,还要划定被测系统(System Under Test,SUT)的边界。同一次合同审查,若 SUT 只定义为"主模型推理服务",那么 S2 的解析与 S3 的检索都是外部依赖;若 SUT 定义为"合同审查应用整体",它们就是内部段。两种定义都合理,但不能混用:在 A 定义下优化检索取得的收益,不能记到 B 定义的账上。

判断一段耗时归属,可以反复问三个问题:

  1. 引入者是谁? 这段代码/服务由谁部署、谁配置、谁升级?
  2. 能否单独测量? 有没有一个接口或事件,可以在不触发其他段的情况下测到它?不能单独测量的段,只能整体观测,也就无法单独归因。
  3. 优化后谁负责维护? 若一段耗时优化后无人承接回归测试,这个收益会在下一个版本悄悄消失。

外部依赖必须单独记账,而不是笼统归入"其他"。至少保留三条记录:调用次数、墙钟耗时分布(不只是均值)、失败与超时次数。很多"推理服务变慢"的事故,根因其实是下游检索服务的 P99 从 80 ms 涨到 900 ms——而如果压测时把检索 mock 掉了,这个变量永远不会出现在你的实验里。

一个实用的做法是维护"责任边界表",随每次实验档案一起保存:

责任边界表
--------------------------------------------------
SUT 定义          : 主模型推理服务(HTTP 接口 /v1/chat)
包含              : 网关鉴权、排队、prefill、decode、流式返回
外部依赖          : 向量检索、条款查询工具、重排模型
外部依赖记账方式  : 单独计时 + 记录失败;不并入主服务延迟
工具依赖假设      : 条款查询为独立预取,汇合于结果组装前(待事件记录确认)
排除              : 客户端渲染、公网链路(用同机房压测客户端规避)
--------------------------------------------------

本节要点

  • 任务、请求、模型调用、尝试是完成相关对象,会话是上下文容器;四者的失败语义互不相同,且会话不是它们的严格父级。
  • 端到端时间不是各段之和;只有按依赖建立的关键路径可以直接相加,并须说明重叠已被什么证据确认。
  • 单段优化的收益上限由该段在关键路径中的占比决定;"某段归零"只是条件化乐观下界。
  • 任务成功由预先定义的契约判定;子调用状态与任务结果分属不同层次。

常见误区

  • 误:"服务吞吐 120 QPS"描述了业务能力。 正:先说明分母是任务、请求还是 attempt,是否含重试与外部依赖耗时。
  • 误:把日志中所有耗时相加得到端到端时间。 正:先在同一可比时间域定义 \(T_{\text{end}}-T_{\text{start}}\),再按依赖图重建路径;盲目相加可能高估,也可能低估。
  • 误:任一子调用失败,任务必然失败。 正:取决于契约是否允许降级、重试、冗余或部分结果;不能作为通则。
  • 误:接口全部返回 200,所以任务都成功了。 正:多调用任务需按 task_id 与截止时间聚合判定。

本节验收

  1. 你能为自己的系统写出"任务—请求—模型调用—attempt"映射表,并标出每层各自的失败语义。
  2. 你能按依赖图算出某一优化手段对端到端的改善上限,而不是只看局部加速比。
  3. 你能写出 SUT 边界与外部依赖记账方式,并说明工具/依赖的触发点、消费者与汇合点。
  4. 你能指出 linear session 中"历史长度、未复用 Token、算法工作量、墙钟时间"四个量各自的取值来源。

依据与核验入口:R18;工具调用的接口分工待按所选引擎文档核验,见章末 CH01-G07。


1.2 从客户端发出到最后一段响应

本节要回答的问题:时间消耗在哪里,哪些阶段可以重叠,以及用什么时间域才能合法地相减?

1.2.1 请求进入与排队

一次请求从发出到进入模型计算,要经过若干道"门":

  1. 连接建立:DNS、TCP、TLS 握手。连接复用(HTTP keep-alive / HTTP/2 多路复用)可以把这部分摊薄到接近零,但压测工具若每次新建连接,就会把握手成本算进每一次请求。
  2. 网关与鉴权:限流、鉴权、配额检查、请求体校验。反向代理可能缓冲请求体,也可能缓冲响应体,缓冲策略直接改变用户看到的首字节时间。
  3. 准入与排队:队列可能有多级——网关一级、引擎前端一级、调度器内还有一级。请求可能在任意一级被拒绝(429 或 503),而被拒绝的请求若不计数,成功率就是被过滤过的。
  4. 批组装:调度器决定本轮把哪些请求放进同一个批次(第5章)。一个请求从入队到真正开始计算的等待,常常比计算本身更长,且分布是重尾的。

排队之所以关键,是因为它对负载敏感:在到达率接近服务能力的区域,排队延迟随负载非线性增长。但不要因此否定轻载测量——轻载的价值是建立基础开销与诊断对照:同配置下的连接开销、鉴权耗时、单请求纯计算时间,都是后续排队的基线。它的不足在于不足以单独预测高负载下的容量与尾延迟,这才是要在受控过载实验中补齐的部分。

1.2.2 模型运行与输出返回

请求被选中后进入两段性质不同的工作,这里的表述需要带上适用条件:

  • Prefill:逻辑上处理输入序列并构建每层的键值状态。实现上不一定"一次性算完整段":引擎可以把长输入切成块(chunked prefill)与推理请求组批,命中前缀缓存时只需处理未复用的那部分。
  • Decode:每次生成一个 Token 并访问已缓存的状态。

常见的概括是"Prefill 计算密集、Decode 带宽密集"。这个概括在典型 Transformer 模型、典型批量与上下文长度、给定设备的条件下很有用,但它不是定义,也不能替代测量。真实瓶颈由算术强度、所选执行路径(注意力后端、是否有 CUDA Graph、量化内核)、是否需要跨卡通信以及实测共同决定;某些形状与批大小下 Decode 也可能回到计算受限。因此本书把这句话当作待验证的起点假设,而不是结论。(第3章给出 Roofline 与该判断的量化方法;第11、12章给出不同侧的证据。)

由此得到一条可直接使用的规则:长输入短输出与短输入长输出是两类不同的负载,用其中一类测出的结论不能外推到另一类。

生成开始后输出以流式返回。路径上至少有三个独立缓冲点:服务进程写缓冲、网关响应缓冲、客户端读缓冲与渲染节流。任何一个都可能把"早就生成好了"变成"用户还在等"。

还有一个容易被忽略的真相:多个请求的工作可以在同一次调度迭代中被组织到一起,但这不等于它们在设备上"同时执行",更不能推出"某个请求自己的 Prefill 与它后续的 Decode 没有先后依赖"。例如 vLLM 的优化文档说明,其核心调度把待处理的 decode 请求排在前面,在 max_num_batched_tokens 预算允许的范围内再调度 prefill,放不下的 prefill 会被自动切块 SOURCE_REGISTERED。这说明的是批的组织方式,把它读成"Prefill 与 Decode 的时间可以任意重叠"就越界了。更严格地说,某些引擎在请求被抢占后会重新执行 prefill 阶段——任何"prefill 只发生一次"的假设都需要按实现核验。

1.2.3 关键路径、时间域与不发生重复计入

第一层:端到端的定义。 端到端时间首先是在同一个可比时间域内的两个事件之差:

\[ T_{\text{e2e}} \;=\; t_{\text{end}} - t_{\text{start}} , \quad t_{\text{end}}, t_{\text{start}} \in \text{同一个已确认可比的 clock\_domain} \]

在此之前谈论"关键路径"是没有意义的,因为跨时间域的差值是伪精确量。

第二层:用依赖图重建路径。 只有当事件依赖图覆盖了等待、计算、传输等相关区间,并按实际依赖建立偏序之后,才能用路径上的区间之和重建端到端。这是一套重建方法,不是对任意日志都成立的恒等式。两个方向的反例都必须记住:

  • 只记录了 100 ms 的内核耗时、遗漏了 900 ms 的排队等待时,日志之和(100 ms)小于真实的端到端(1000 ms);
  • 若同一区间被嵌套 span 或多处记录重复登记,日志之和又可能大于端到端。

因此不能写"所有已记录组件耗时之和 ≥ 端到端"这样的无条件结论。(上述反例是逻辑构造,用于说明定义边界,不是任何系统的测量值。)

第三层:分流水去重。 最常见的重复计入来自把服务端区间与客户端区间混在同一列。正确做法是每条流水各自重建,每行写明 start_event、end_event、clock_domain 与是否落在当前观测路径上。

服务端流水(时钟域:server_monotonic;下表时间戳通过带误差上界的跨机关联与 client.send 对齐,误差上界记为 \(\varepsilon\)):

start_event end_event 区间 (ms) 在关键路径 说明
engine.enqueued (21.0) engine.scheduled (140.0) 119.0 是 引擎排队与批组装
engine.scheduled (140.0) prefill.end (620.0) 480.0 是 输入 4096 Token,本例假设无前缀复用
prefill.end (620.0) first_token_generated (634.0) 14.0 是 首 Token 首 Token 采样完成
first_token_generated (634.0) first_chunk_sent (641.0) 7.0 是 采样、序列化并写出首个响应块
first_chunk_sent (641.0) last_chunk_sent (4210.0) 3569.0 是 后续 Token 生成与写出
last_chunk_sent (4210.0) done_event (4213.0) 3.0 是 终端事件与用量统计

服务端自 engine.enqueued 起的总计:\(119+480+14+7+3569+3 = 4192\) ms,等于 \(4213-21\),无重复计入。

客户端流水(时钟域:client_monotonic):

start_event end_event 区间 (ms) 在关键路径 说明
client.send (0.0) tcp.connected (12.0) 12.0 是 连接建立(本例未复用)
tcp.connected (12.0) gateway.auth.ok (19.5) 7.5 是 鉴权
gateway.auth.ok (19.5) engine.enqueued (21.0) 1.5 是 交接进入引擎队列
engine.enqueued (21.0) client.first_chunk (668.0) 647.0 是 跨第一段,无 \(\varepsilon\) 上界时不得再拆分
client.first_chunk (668.0) client.rendered (4250.0) 3582.0 是 继续接收直至渲染完成

客户端总计:\(12+7.5+1.5+647+3582 = 4250\) ms,等于 \(4250-0\),同样无重复计入。注意这里不再把 \(641 \rightarrow 668\) 的 27 ms 单列——它已包含在 647 ms 区间内,若再单列一次,总和会变成 4277 ms,凭空多出 27 ms。这是重复计入的典型形态:同一区间被登记两次。凡是"求和"的表格,都应给出这样一条交叉验证。

由此可以报告三个合法的"端到端/首字"数字,以及它们的口径:

  • 服务端视角端到端:4192 ms(engine.enqueued → done_event,服务端时钟域);
  • 客户端视角端到端:4250 ms(client.send → client.rendered,客户端时钟域);
  • 服务端视角 TTFT:\(634-21 = 613\) ms(必须从入队时刻起算,不是 634 ms);
  • 客户端视角 TTFT:\(668-0 = 668\) ms;
  • 两者之差 55 ms:由进入引擎前的 21 ms(客户端时钟域)与 634→668 的 34 ms 组成;34 ms 在带误差上界 \(\varepsilon\) 的关联下可以报告为一段传播/网关缓冲时间,但它不是两个 TTFT 口径之差本身。若两个时间域无法校准,则只报告本地间隔与因果顺序,不输出伪精确的单向延迟。

本节要点

  • 端到端的定义是同一可比时间域内的两事件之差;依赖图上的路径重建是方法,不是无条件恒等式。
  • 日志之和相对真实端到端既可偏小也可偏大,取决于遗漏还是重复登记。
  • 服务端流水与客户端流水必须分别重建,每行标注起止事件、时钟域与是否在路径上。
  • 轻载测量用于建立基线开销与诊断对照;不能单独推出高负载容量与尾延迟。

常见误区

  • 误:组件耗时之和一定大于等于端到端。 正:遗漏等待会偏小,嵌套/重叠记录会偏大;两种都要防。
  • 误:服务端日志显示 200 ms,客户端测到 800 ms,所以有一方测错了。 正:两者都对,差额来自网络、网关缓冲与客户端处理,需逐段定位而不是否定一方。
  • 误:连续批处理意味着 Prefill 与 Decode 的时间可以随意重叠。 正:它描述的是批的组织策略;同一请求自身的阶段先后依赖仍然存在,"是否重叠"要按实现和 trace 判断。
  • 误:轻载下测出的延迟没有什么参考价值。 正:它给出基础开销基线;不足的是不能据此预测高负载行为。

本节验收

  1. 你能为自己的服务写出至少两条流水的区间表,标注 start/end 事件、时钟域与是否在关键路径上。
  2. 你能自证流水区间之和没有重复计入,并给出交叉验证算式。
  3. 你能指出哪些区间因缺乏跨时间域映射而无法进一步拆分,并把它们保留为不可拆分,而不是猜测补齐。
  4. 你能给出自己系统中 Prefill/Decode 瓶颈假设的验证计划,而不是把它当作既定事实。

依据与核验入口:R16 R18


1.3 模型制品、Tokenizer 与聊天模板

本节要回答的问题:同一个模型名称为什么还不足以定义测试对象?

两份实验报告都写"使用某 8B 模型,输入 2000 字中文",结果相差 40%,而两者都没有撒谎。差异可能来自权重提交号、聊天模板、分词器版本或停止条件。模型名称是一个品牌标签,不是实验对象的完整身份。本节给出必须固定的最小制品集合,并纠正两个常被写错的推论。

1.3.1 权重与配置身份

一个可复现的模型对象至少包含:

  • 仓库与修订号:组织名/模型名 + revision(commit sha)。浮动分支会随上游更新而改变内容,实验记录里必须写 sha,不能写分支名。
  • 权重文件与格式:分片数量、文件名与哈希、参数格式。权重损坏或分片缺失往往表现为"能加载但输出异常",比直接报错更危险。
  • 配置文件:层数 \(L\)、隐藏维度 \(d_{\text{model}}\)、注意力头数 \(h\)、键值头数 \(h_{kv}\)、词表大小 \(V\)、最大位置长度、位置编码类型、量化配置字段。
  • 精度与量化方案:存储精度、是否带缩放因子、量化粒度、哪些层被排除在量化之外。

其中 \(h_{kv}\) 直接决定每个 Token 的 KV 状态规模。同一个家族里,MHA(\(h_{kv}=h\))、GQA(\(h_{kv}<h\))、MQA(\(h_{kv}=1\))的显存占用可能相差数倍。实验报告不写 \(h_{kv}\),任何关于显存与容量的结论都无法复核。

量化配置尤其不能被压缩成一个词。名义上的"4 bit 模型"可能是:权重 4 bit、激活 16 bit、累加 FP32、KV 保持 BF16;也可能是权重与激活都 4 bit、KV 也 4 bit;还可能是分块缩放格式,若干元素共享一个缩放因子,实际每位存储开销高于标称值。三种方案的速度、显存与质量完全不同,把它们统称为"4 bit"会让所有比较失去意义(第11章给出完整的存储字节账本)。

1.3.2 分词和模板展开

模型看到的是 Token 序列,不是字符串。映射由分词器与聊天模板共同决定,二者必须分开理解,因为归因时需要隔离变量。

分词器负责把文本切成 Token 并映射为 id,需要记录文件哈希、词表大小、特殊 Token 集合、归一化与预分词规则。聊天模板则把多轮对话渲染成模型实际看到的字符串,决定角色标记格式、系统提示位置、分隔方式、工具定义如何展开。

下面的算例刻意把两个因素分开。算例 1:消息与工具定义完全相同,只有模板不同:

算例 1(仅模板不同,用于隔离模板变量)
输入:system="你是严谨的法务助理"  user="列出三个风险点"   两份样例均不带工具定义

模板 A(简洁标记)
  <|system|>你是严谨的法务助理<|end|><|user|>列出三个风险点<|end|><|assistant|>
  → 展开后 Token 数:34

模板 B(完整角色描述)
  <|im_start|>system
  你是严谨的法务助理
  <|im_end|>
  <|im_start|>user
  列出三个风险点
  <|im_end|>
  <|im_start|>assistant
  → 展开后 Token 数:41

算例 2:在模板 A 的基础上增加工具 Schema:

算例 2(仅增加工具定义,用于说明另一类输入膨胀)
模板 A + 工具定义(约 180 Token 的 JSON Schema)
  → 展开后 Token 数:约 226

两个算例支持的结论不同,不能混着用:算例 1 说明模板差异可以带来约 20% 的输入长度差异;算例 2 说明工具定义可以让输入膨胀近 7 倍。若在对比实验中同时改了模板和工具定义,就只能说"完整模型输入不同",不能把差异归因到模板本身。

由此得到硬性记录要求:必须同时记录用户原文的受控标识、模板版本、展开后的完整输入文本(或其哈希)与实际输入 Token 数。禁止用字符数估算 Token 数——中英混排、代码、数字、标点的 Token 密度差异极大。正确做法是用所选分词器实际编码后计数,并把计数脚本与分词器版本一并存档。

1.3.3 生成配置与停止条件

输出长度不是模型的自由意志,它由生成配置决定:

参数 作用 对测量的影响
max_tokens 输出上限 只有当实际生成触到上限时才改变输出长度;上限不同不必然改变输出
stop / stop_token_ids 停止串或停止 Token 决定实际停止位置,改变长度分布
temperature / top_p / top_k 采样策略 影响长度、内容多样性,也影响推测解码接受率
seed 随机种子 控制随机状态,但默认不等于可复现(见下)
logprobs 返回对数概率 增加序列化与传输开销
结构化约束 JSON Schema / 语法约束 改变每步候选集合与单步开销

不要把 max_tokens 不同直接判成"结果不可比"。正确做法是先记录差异,再判断它在本次实验中是受控自变量(例如专门研究长度上限的影响),还是需要在相同档位下比较,或是可作分层对比(按是否触到上限分组)。只有当实际输出触到上限时,它才是真实的混淆因素。

1.3.4 可复现性的四个层次

固定 seed 不等于结果可复现,这是常识;但不能因此把"可复现"降格为"分布稳定"了事。可复现性至少有四个层次,声明哪一层就提供哪一层的证据:

层次 问题 典型证据
L1 可重建 制品、配置、环境能否复原 制品清单 + 运行身份(见 1.5.4)
L2 数值一致 选定条件下是否要求逐 Token / 逐数值相同 确定性配置 + 跨批次数值对比
L3 统计稳定 随机生成下任务质量与输出分布是否稳定 重复实验的质量分布
L4 指标稳定 性能指标在重复实验中是否稳定 多次运行的分布与置信区间

为什么 L2 不能轻易放弃:某些框架已经提供了条件化的确定性路径。以 vLLM 官方文档为例(来源记录见本书来源编号):

"vLLM does not guarantee the reproducibility of the results by default, for the sake of performance." SOURCE_REGISTERED

要获得可复现结果,离线模式可关闭多进程以获得确定性调度(文档同时警告这会改变用户代码的随机状态),或启用 batch invariance;在线模式的官方途径只有 batch invariance。并且文档明确限定:

"Even with the above settings, vLLM only provides reproducibility when it runs on the same hardware and the same vLLM version." SOURCE_REGISTERED

batch invariance 自身的状态也必须写清:官方文档标注 beta,有独立的功能跟踪 issue,仅在满足硬件条件(NVIDIA 计算能力 8.0 及以上,或支持 Triton 的 Intel XPU)时可用,通过环境变量启用;实现上会使用确定性内核并关闭部分非确定性优化(如张量并行下的某些自定义 all-reduce),因此官方提示可能带来性能损失,并给出已验证模型清单 SOURCE_REGISTERED。这些已核验信息共同说明一个边界:存在明确的、有代价的、带适用范围的确定性路径,不能因为默认不可复现就只谈统计稳定。

反过来,统计稳定也不能替代语义正确,更不能作为确定性回归失败的免责条件。某项声明要验收哪一层,就提供那一层的证据。

制品清单可以固化成下面这张表,随每次实验保存:

项 记录内容 示例写法
模型仓库与修订 组织/名称 + commit sha org/model@a1b2c3d
权重格式与哈希 分片清单 + sha256 model-00001-of-00004.safetensors
结构关键字段 \(L\)、\(d_{\text{model}}\)、\(h\)、\(h_{kv}\)、\(V\)、最大位置 见第2章账本(设计稿)
精度方案 存储 / 激活 / 累加 / KV 分列 权重 FP8、激活 BF16、累加 FP32、KV FP8
分词器 文件哈希 + 词表大小 tokenizer.json@sha256:…
模板 模板来源与版本 + 展开哈希 chat_template@v3
生成配置 max_tokens / stop / 采样 / 约束 全量记录,不写"默认"
输入 Token 数 实际编码结果(均值与分布) 均值 2130,P95 3980
可复现层次 本次验收 L1—L4 中的哪几层 L1 + L3

本节要点

  • 模型名称不是实验身份;\(h_{kv}\) 与精度方案缺一项,容量与性能结论就无法复核。
  • 模板差异与工具定义膨胀是两类不同变量,归因时必须隔离;禁止用字符数估算 Token 数。
  • max_tokens 差异要先记录、再判定是否构成混淆因素,不要直接判为不可比。
  • 可复现有四个层次;框架已提供条件化确定性路径,但带 beta 状态、硬件要求与性能代价。

常见误区

  • 误:"我们用的是同一个 8B 模型",可以直接比较。 正:核对 commit sha、量化方案、模板与停止条件;缺项即说明不可比的具体理由。
  • 误:设了 seed,结果就应该每次一样。 正:默认不保证可复现;需要 L2 时要走条件化确定性路径并说明适用范围。
  • 误:只要质量分布稳定,就说明可复现性没问题。 正:那是 L3;若声称 L2(数值一致),必须用同版本同硬件下的数值对比来验证。

本节验收

  1. 你能列出完整制品清单,让另一人复原同一套输入 Token 序列。
  2. 你能区分模板变量与工具定义变量,并说明各算例支持哪个结论。
  3. 你能说明权重、激活、累加、KV 四项精度分别是什么。
  4. 你能指出本次实验验收可复现的哪一层,并给出对应证据类型。

依据与核验入口:R29 DOC-W02


1.4 API、网关、流式事件与会话状态

本节要回答的问题:首字节、首个内容事件和首个可见答案为什么不同,以及"块"能证明什么、不能证明什么?

1.4.1 协议事件不必互斥,内容分类用于定义指标

一次流式响应由一系列事件组成。按内容性质可以分为五类,用于决定"什么算第一个内容":

类别 典型内容 计为"首个内容" 说明
控制事件 keep-alive 注释、ping 否 维持连接,不含内容
元数据事件 仅含 role/id/model 的块 否 无可呈现文本
工具调用事件 模型返回的调用信息(待调用方执行) 否(不是答案正文) 需与后续生成内容分开理解
内容事件 推理内容或正式答案文本 视指标定义 推理内容可能不对用户可见
终端事件 结束标记、finish_reason、用量统计 否 常携带 usage

这五类不是互斥的:同一个事件可以同时携带内容、工具调用信息结束标志与用量统计。分类的目的是确定指标口径,不是把每个事件强行归到一格。

本书统一使用四个时间戳(工具字段映射见《指标字典与最小测量样例》§4):

  • \(t_{\text{first-byte}}\):客户端收到第一个响应字节;
  • \(t_{\text{first-content-event}}\):客户端收到第一个非空内容事件(注意:不是第一个协议事件);
  • \(t_{\text{first-visible}}\):客户端收到第一个可见答案 Token;
  • \(t_{\text{done}}\):收到终端事件。

对于先输出推理内容再输出正式答案的模型,\(t_{\text{first-content-event}}\) 与 \(t_{\text{first-visible}}\) 之间可能相隔数秒。若压测工具统计前者、业务方关心后者,就会出现"监控显示首 Token 800 ms,用户却说等了 5 秒"的经典矛盾。这不是某一方错了,是两个指标名字相近而定义不同。

1.4.2 Token 数、间隔与"块"不能互相替代

这里有一组必须分开的量,混用会让性能结论失效:

量 来源 能证明什么 不能证明什么
生成 Token ID 服务端/模型侧计数 实际生成了多少 Token 不能从客户端块数反推
服务端 usage 字段 服务端统计 官方口径的用量 不一定等于客户端重新分词的结果
拼接后重新分词 客户端侧处理 文本层面的长度 不等于生成 Token 数,分词边界可被拼接改变
网络块数 传输层观测 写出节奏 块边界 ≠ 分词器边界;一块可含多个 Token,一个 Token 也可能被拆在协议层面 分片传输

由此得到一条操作规则:按 delta 字段累加可以重建内容,但不能据此宣称拿到了准确的 Token 数或逐 Token 生成时刻。如果只能取得分块到达时刻,就只能报告"该观测层的 intervals",并在报告中写明观测层。

指标定义上也有同样的坑。以 vLLM 官方指标文档为例(参见所列来源正文与版本记录):vllm:inter_token_latency_seconds 按流式输出事件采样,记录相邻输出之间的墙钟间隔;vllm:request_time_per_output_token_seconds 则按已完成请求记录一次,定义为 \((端到端延迟 - TTFT) / (输出 Token 数 - 1)\);文档明确指出,当一个输出包含多个 Token 时(文档举例为推测解码),或聚合权重不同时,两者会不一致,并且对输出不超过一个 Token 的请求,TPOT 记为 0 而基准统计会排除它们,因此 Prometheus 直方图与基准统计的 TPOT 可能不同 SOURCE_REGISTERED。所以"ITL 的均值等于 TPOT"只在每个输出恰好一个 Token 的近似下成立。

工具调用还带来一个额外区分:模型返回调用信息与调用方实际执行函数是两个环节。官方工具调用文档明确区分这两者 SOURCE_ENTRY_ONLY(尚未独立取到该页正文,故此处只作机制说明,具体字段名按所选引擎版本核验)。编排层等待外部函数的时间,不应记入模型生成时间,也不应把它算进"模型很慢"的结论里。

最后,工具迁移要按版本解释。AIPerf 与 GenAI-Perf 的字段映射必须结合工具版本与 parser 版本读取,同名字段不代表同口径 SOURCE_REGISTERED(与本书既有登记 S17 指向同一入口);完整算例见《指标字典与最小测量样例》§5 的 Sample-B0,本书正式编排时并入第7—9章。

1.4.3 网关、分块与客户端呈现

中间环节会改变用户观测到的时刻:

  • 网关响应缓冲:反向代理若开启响应缓冲,攒够字节或时间才下发,流式会退化成"批量下发",首字节显著变长而服务端日志毫无变化。
  • 压缩:引入额外缓冲窗口,小分块可能被压在一起下发。
  • 分块粒度:服务端可能每生成若干个 Token 或每若干毫秒写一次,一个 SSE 块不等于一个 Token。
  • 协议转换:HTTP/1.1 与 HTTP/2、SSE 与 WebSocket 的转换层可能合并或拆分帧。
  • 客户端处理:JSON 解析、UI 更新、渲染节流都会让"收到"与"看到"分离。

诊断办法是两端同时采集:客户端按事件打时间戳,服务端在同一进程内记录写出时刻。服务端写出均匀而客户端呈台阶状 → 问题在传输或客户端;服务端写出本身就有长间隔 → 问题在上游生成或调度。把两端对齐到同一时间域的方法见第9章,并须遵守 1.2.3 的时间域规则;各项指标的定义、分母与剔除规则见《指标字典与最小测量样例》§2—3。

1.4.4 有状态 API 与模型缓存是三个不同的层

现代推理服务提供不同层次的"记忆",语义完全不同:

  1. 协议层会话:服务端按会话标识保存消息历史,客户端只发增量。节省的是网络传输与客户端拼接,模型仍可能重算全部上下文。
  2. 引擎内前缀缓存:复用已计算的 KV 状态,节省的是 Prefill 计算,与协议层是否保存历史无关。
  3. 外部缓存服务:独立进程或存储池,跨实例共享,涉及传输成本与一致性问题。

把"API 有状态"与"复用了 KV 状态"混为一谈,会导致错误的容量判断。还有一个陷阱:保存下来的状态不等于仍然有效。模型 revision、模板或配置变化后,旧缓存可能与当前模型不对应,复用会带来难以察觉的质量退化;状态失效策略的键应包含模型 revision、模板版本与配置哈希。

【版本化实现】有状态接口的存储与重试行为会随版本变化,必须按所选版本核验。已登记快照:SGLang 发布记录涉及 Responses 相关存储行为变更(v0.5.20,发布日 2026-09-18,查阅日 2026-09-29,body_verified=true,runtime_verified=false)SOURCE_REGISTERED。选择此类接口时至少核验四项:会话身份如何生成、重新请求的行为、服务重启后可恢复的范围、客户端取消能否传播到服务端。API 名称相似不代表状态语义相同。

本节要点

  • 事件分类用于确定指标口径,类别之间不必互斥;工具调用事件要与答案正文分开。
  • 四个时间戳中,\(t_{\text{first-content-event}}\) 与 \(t_{\text{first-visible}}\) 必须区分,否则会与用户体感脱节。
  • 生成 Token ID、服务端 usage、重新分词与网络块数是四件不同的事;块边界不是分词边界。
  • ITL 与按请求计算的 TPOT 在多 Token 输出下会不一致;工具迁移按版本与 parser 解释。

常见误区

  • 误:按 delta 累加,就得到了准确的 Token 数与逐 Token 时刻。 正:只能得到该观测层的内容与到达时刻;Token 数应以服务端计数为准。
  • 误:首个流式事件就是用户开始看到答案的时刻。 正:可能是推理内容或工具调用信息;可见答案要按 \(t_{\text{first-visible}}\) 统计。
  • 误:服务端用了有状态 API,上下文重算的开销就省掉了。 正:协议层保存与引擎/外部缓存复用是不同机制,需分别取证。
  • 误:缓存命中就代表结果正确。 正:revision、模板或配置变化后旧状态可能失效,必须有失效键。

本节验收

  1. 你能把一次真实流式响应按五类标注,并给出四个时间戳。
  2. 你能说明 Token ID / usage / 重新分词 / 块数四者在本系统的取值差异。
  3. 你能解释 ITL 与 TPOT 在何种条件下相等、何种条件下不一致。
  4. 你能为有状态接口列出需按版本核验的四项行为。

依据与核验入口:R01 R12 S17 DOC-W07


1.5 引擎、调度器、Model Runner 与执行后端

本节要回答的问题:前端、调度器、执行器分别掌握什么信息,以及如何取证"实际走了哪条路径"?

把推理引擎看成一组职责,而不是背诵某个版本的类名。具体实现可以合并或拆分这些职责,但诊断时必须找到等待、决策和执行各发生在哪里。

1.5.1 职责分解

职责 掌握的信息 常见故障表现 诊断时关心的问题
协议前端 请求体、采样参数、会话标识 参数解析失败、模板展开异常 实际收到的输入 Token 数
准入与排队 队列长度、限流配额 429/503、排队时间重尾 被拒绝的请求是否计入分母
调度器 每轮批组成、抢占决策 延迟抖动、长请求饥饿 本轮批大小与组成
缓存/状态管理 块分配、命中、回收 显存不足、命中率骤降 命中来自哪一层、传输了多少
Model Runner 权重驻留、一次前向的组织 加载慢、形状回退 实际走哪条执行路径
采样与停止判定 分布、停止条件 输出长度异常、重复 停止条件是否按预期生效
输出与序列化 写出节奏、分块策略 首字节延迟、客户端台阶 写出时刻与生成时刻的差

这张表的价值在于按职责逐格排除:排队时间长 → 看准入与调度;首字节慢但生成快 → 看输出与序列化;生成慢但队列空 → 看 Runner 与后端。调度展开在第5章,观测展开在第9章。

1.5.2 Model Runner、图执行与采样

Model Runner(模型执行器)不是"模型本身",而是持有权重、组织一次前向过程、管理输入输出张量与状态块映射的执行层。它决定:张量形状(批大小 × 序列长度 × 隐藏维度)、逻辑请求到物理状态块的映射(第4章)、是否走图执行、采样在哪个设备完成。

图捕获不是"匹配就重放、不匹配就 eager"这样一个二选一结局。 以某个引擎的 CUDA Graph 设计为例,形状差异的处理至少包含以下机制(设计正文见本书 N27)SOURCE_REGISTERED:

  • padding + 描述符匹配:批次用 padded 后的 num_tokens、num_reqs、uniform、has_lora 组成 BatchDescriptor 作为分派键,num_tokens 可以是补齐后的长度;
  • 形状桶(bucket):可用分派键由 cudagraph_capture_sizes 等配置初始化,命中则复用或按需要捕获并缓存;
  • 已捕获图的复用:uniform-decode 批次可以复用相同 batch_size 的 non-uniform 图;
  • 多种模式:NONE(全部 eager,调试与暖身用)、PIECEWISE(仅部分算子入图,attention 保持 eager)、FULL、FULL_DECODE_ONLY、FULL_AND_PIECEWISE;
  • 运行时分派与自动降级:分派器按 FULL → PIECEWISE → NONE 的优先级查找,找不到键则返回 NONE 走 eager;后端能力不足时按 AttentionCGSupport(ALWAYS / UNIFORM_BATCH / UNIFORM_SINGLE_TOKEN_DECODE / NEVER)自动降级到最接近的受支持模式;特定路径(如 cascade attention)被固定分派到 PIECEWISE 或 NONE;
  • 捕获时机:图在 runner 首次以非 NONE 模式调用前向(dummy run)时捕获,暖身过程由 GPU model runner 用 NONE 模式执行。

这段机制说明的重点不是让读者记住模式名,而是:同一份形状变化,在不同引擎、不同模式、不同后端下可能落到不同的分支,结局可能多于两种。各模式的具体取值、优先级与适用后端属于版本化实例,完整枚举与固定版本的取值放在第11章,本章只保留"形状 → 描述符/桶 → 分派 → 可能的降级"这条关系链。因此正文只写机制与分支结构,具体默认值与优先级一律按固定版本另行登记。该文档是浮动页面,上述内容未固化 commit,不能被当作任意版本的出厂默认值。

编译与图优化同样有代价:编译耗时、缓存失效条件、产物与形状/硬件的绑定关系。第11章讨论"如何证明实际用了编译产物"。

采样也不是免费的:构建候选集合、排序、温度与惩罚、停止串判定、结构化约束下的合法 Token 掩码,都可能占用可观的每步时间。

把"第一次请求特别慢"拆开,通常会命中下表中的一个或多个来源,它们共同构成冷启动成本(第16章纳入容量模型):

来源 层 典型特征 记账要求
权重加载与分片读取 Runner / 存储 与模型体积成正比,受存储或网络带宽限制 单独计时,不混入首次请求延迟
权重格式转换或量化预处理 Runner 只在特定精度方案出现 记录是否发生与耗时
图捕获或形状桶生成 Runner / 后端 首次以非 eager 模式前向时触发 记录桶集合与命中率
缓存池初始化与热身 状态管理 可用容量从零爬升 记录达到稳态容量的时间
内核自动调优 后端 同一形状第二次明显更快 记录开关与结果缓存位置

1.5.3 内核 dispatch 与四种状态

真正完成计算的是注意力内核、GEMM、MoE 路由与通信内核。引擎通常准备多条候选路径,按硬件型号、数据类型、张量形状与功能开关选择,选择失败则按 1.5.2 的分派规则落到其它分支。记录时统一用四个状态:

状态 含义 记录要求
默认启用 该版本无需额外配置即生效 版本与生效条件
显式开启 需配置项或环境变量 开关名与生效证据
回退/降级 声明支持但运行时未走该路径 触发条件、日志特征、降级后的目标模式
不支持 该版本/硬件/形状组合不可用 不支持的边界与依据

"声明支持"不等于"实际启用"。形状不在支持范围、数据类型组合不被接受、后端能力等级不足、编译依赖缺失,都会让运行时走到另一条路径。但反过来说,也不是"只要不满足就静默回退"——某些实现会显式报错,某些会降级到相邻模式,某些通过 padding 命中已有桶。写成哪一种,都必须有该版本的证据。

证据强度分三档,不可混用:源码阅读证明"可能走这条路径";启动日志与配置回显证明"声明了什么";trace 中的内核名与调用次数才能证明"这一次实际走了哪条路径"。第9章给出把三者分开记录的方法。

1.5.4 固定版本调用图和运行身份

             ┌──────────────────────────────────────────────┐
客户端 ──────▶│ 协议前端  (协议解析 / 模板展开 / 参数校验)      │
             └───────────────┬──────────────────────────────┘
                             ▼
             ┌──────────────────────────────────────────────┐
             │ 准入与队列  (限流 / 排队 / 抢占)                │
             └───────────────┬──────────────────────────────┘
                             ▼
             ┌──────────────────────────────────────────────┐
             │ 调度器  (连续批处理 / 批组成 / 状态块分配)        │
             └───────┬───────────────────────┬──────────────┘
                     ▼                       ▼
        ┌────────────────────┐   ┌──────────────────────────┐
        │ 缓存/状态管理       │   │ Model Runner              │
        │ (块表 / 命中 / 回收) │◀──│ (权重驻留 / 前向组织 / 图) │
        └────────────────────┘   └────────────┬─────────────┘
                                              ▼
                     ┌──────────────────────────────────────┐
                     │ 执行后端 (attention / GEMM / MoE / 通信)│
                     │   命中 → 执行;未命中 → 降级/显式报错/eager│
                     └────────────────────┬─────────────────┘
                                          ▼
                     ┌──────────────────────────────────────┐
                     │ 采样与停止判定 → 输出写出                │
                     └──────────────────────────────────────┘

让这张图对应到具体系统,必须记录运行身份:

取证项 内容 获取方式
引擎版本 产品名 + release/tag + commit sha 启动日志、包版本、镜像摘要
生效配置 实际生效的关键参数(非命令行原文) 配置回显、启动日志
Runner 身份 哪一代执行器、是否落到非默认分支 日志、源码路径、环境变量
图/编译模式 实际模式、桶集合、是否新捕获 日志、trace
后端选择 attention / GEMM / 量化内核各走哪条 日志、trace 中的内核名
模型与 revision 仓库 + sha + 精度方案 制品清单(见 1.3)
硬件与驱动 型号、驱动、运行时版本 设备查询输出
证据强度 源码/日志/trace 分别支持哪些结论 由采集方式决定

【版本化实现】以下快照只说明这些版本"写了什么",runtime_verified=false:

  • vLLM v0.29.0(发布日 2026-09-09,查阅日 2026-09-29,body_verified=true):Model Runner V2 默认化、保留特定回退、旧入口弃用 SOURCE_REGISTERED。此处的 V2 指模型执行器,不是整个产品的新代际名称,不应简写为"vLLM V2"。
  • SGLang v0.5.20(发布日 2026-09-18,查阅日 2026-09-29):CPU-only Simulator、Responses 存储变更、Prefill CP v1 路径移除 SOURCE_REGISTERED。"路径移除"是兼容性变化,旧教程里的开关在新版本可能已不存在。
  • TensorRT-LLM v1.3.0rc28(发布日 2026-09-23,查阅日 2026-09-29):预发布状态,具体遗留入口清理条目仍需逐项核验,不能据此直接生成迁移步骤 SOURCE_REGISTERED。RC 不能写成 GA,也不一定是主实验环境的合适选择。

三条记录共同说明:旧教程的命令与开关不能默认等价于当前入口。实验前先读所选版本的发布说明,把"移除/弃用/改名"记进实验档案(第18章版本治理)。

【前沿与验证缺口】1.5.2 引用的设计文档是浮动页面,未固化 commit,其模式名称与默认值不得当作任意发布版的事实。要确认某个版本的实际路径,需要固定版本的启动日志与 trace;在此之前只能写"该版本说明……"。

本节要点

  • 按七项职责排除问题,而不是按组件名猜测。
  • Model Runner 是组织一次前向的执行层;图执行通过 padding、形状桶、复用与自动降级处理形状差异,结局多于两种。
  • 内核路径要按默认启用、显式开启、回退/降级、不支持四态记录,并由该版本证据支撑。
  • 运行身份八项必填,且必须标注每项的证据强度。

常见误区

  • 误:形状不匹配就一定会重捕获,否则一定 eager。 正:可能 padding 命中已有桶、复用相邻模式的图、自动降级或显式报错;取决于版本与后端能力等级。
  • 误:配置里开了某个内核,运行时就用了它。 正:形状、dtype、硬件能力与编译依赖任一不满足都会改变落点;必须用 trace 取证。
  • 误:读源码确认了分支,就等于验证了执行路径。 正:源码证明可能性,trace 证明事实;两者分开记录。
  • 误:浮动文档(stable/latest)里写的默认值就是当前发布行为。 正:浮动页面需固定 commit 或存档后才能作为版本行为证据。

本节验收

  1. 你能按七项职责把一次变慢归因到责任单元。
  2. 你能说明自己系统处理形状差异的分支结构,并指出默认模式的取值依据来自哪个版本。
  3. 你能给出至少一条"声明支持但实际落到其它分支"的场景及其取证方式。
  4. 你能填出运行身份表并标注每行证据强度。

依据与核验入口:R01 R23 S02 S04 DOC-W05


1.6 超时、取消、重试与资源回收

本节要回答的问题:客户端不再等待后,服务端是否还在工作,以及这段工作该记在哪本账上?

1.6.1 四本账,而不是一张状态表

之前版本把所有层次塞进一张状态表,导致"超时到底是终态还是中间状态"说不清。正确的做法是维护四本互相独立、靠标识关联的账:

账 归属 典型取值 由谁判定
client_status 客户端 sent / received / timeout / abandoned / parse_error 客户端时钟
server_status 服务端 accepted / queued / running / streaming / completed / cancelled / failed 服务端记录
task_outcome 业务 success / partial_success / failed / unknown 任务契约
resource_release_status 资源层 held / request_released / block_reusable / prefix_retained / process_released / unknown 服务端或更底层的观测

要点:

  • created 不等于 sent。前者是构造完成,后者是实际发出;混用会让"请求丢在哪"无法判断。
  • client_timeout 不是服务端的互斥终态。客户端超时那一刻,服务端的 server_status 可能是 running、completed,甚至因为还在等下游而处于 queued。
  • task_outcome 由契约判定,不由任一层的状态机械推导(见 1.1.2 的例子)。
  • 关联靠 task_id / session_id / request_id / attempt_id,且会话不必是其余三者的严格父级。

1.6.2 超时语义不止一种

本段讨论的是客户端等待超时。但它不是唯一的截止时间语义,至少还有三类需要区分:网关或反向代理的超时、服务端处理自身的截止时间、以及服务端作为客户端调用下游 RPC 时的 deadline。

这一点在 gRPC 官方指南里写得很清楚(参见所列来源正文与版本记录):deadline 是客户端不愿再等待的时间点;服务端超过该时间点后,客户端会以 DEADLINE_EXCEEDED 失败,同时 gRPC 服务端会在客户端 deadline 过后自动以 CANCELLED 取消该调用;但服务端应用自身有责任停止它为这个 RPC 启动的活动——官方建议长时间运行的流程周期性检查 RPC 是否已被取消 SOURCE_REGISTERED。该文档还说明了 deadline 传播的实现方式:由于 deadline 是绝对时间点,跨机传播会受时钟偏差影响,gRPC 将其转换为已扣除消耗时间的 timeout SOURCE_REGISTERED。

这段引述给出三个工程含义:(1)"超时"在不同层有不同动作,不能只当作客户端概念;(2)即便框架自动取消,应用层的清理仍是应用自己的责任;(3)跨服务传播 deadline 时必须处理时钟偏差,这与 1.2.3 的时间域规则是一致的。

1.6.3 取消的验收:不要要求"立刻打断前向"

平台层打断一个正在与同批其它请求共享的前向,在很多实现里既不安全也不经济。因此取消的验收应写成如下检查清单,按所选实现逐项核验:

  1. 取消何时被接收:哪一层的哪个事件标志着服务端知道客户端不要了(连接关闭、收到取消语义、代理转发了 FIN)?
  2. 是否停止调度新的工作:该请求是否立刻不再被排入后续批次?这是最先应满足的一条。
  3. 已提交工作如何安全结束:是等到当前迭代结束、还是允许中途终止?共享批如何处理?
  4. 输出如何被抑制:后续 Token 是否仍会被写出?客户端是否会收到本应丢弃的内容?
  5. 资源何时可安全回收,以及释放到了哪个层级。

第 5 条要特别注意分级:

释放层级 含义 观测方式
请求不再持有块 该请求的逻辑所有权已释放 服务端请求终态记录
块可重新分配 块回到池中,可被其它请求使用 引擎内部指标
保留可复用前缀 块未回收但可以被同前缀请求复用 缓存层指标
进程向驱动释放显存 显存真正回到驱动/设备层面 进程与设备层面的观测

显存读数的短期变化不能作为是否泄漏的判断依据。很多运行时出于性能考虑会缓存分配而不立即归还;判断"取消后资源没被回收"需要上述分层的证据,而不是一次 nvidia-smi 快照。此项需要按所选实现的源码或内部观测验证 [SOURCE_ENTRY_ONLY]。

1.6.4 重试放大与其假设边界

重试把一次逻辑请求变成多次物理尝试。设每一轮的条件失败概率恒为 \(p\)、且只有前一次失败后才发起下一次重试,最多重试 \(r\) 次,则每个逻辑请求的期望物理尝试次数为:

\[ \mathbb{E}[N_{\text{attempt}}] \;=\; 1 + p + p^{2} + \cdots + p^{r} \;=\; \frac{1 - p^{\,r+1}}{1 - p}, \quad 0 \le p < 1 \]

\(p\) 为无量纲概率。例如 \(p = 0.2\)、\(r = 2\) 时 \(\mathbb{E}[N_{\text{attempt}}] = 1.24\),即到达率被放大 \(24\%\) [DERIVED]。

这两条假设必须写在结论前面,否则公式会被误用:真实系统中各轮失败概率通常不同(重试往往发生在已经变慢的长尾请求上,其条件失败概率高于平均值),而且重试本身会进一步提高 \(p\),形成正反馈——这也是为什么可靠性工程强调退避、抖动与放弃条件 SOURCE_REGISTERED。

另一个常见错误是把请求数放大直接当成资源放大。请求数放大 \(24\%\) 不等于显存或算力放大 \(24\%\):被重试的请求确实偏向长尾(输入长、输出长),资源放大系数因此通常高于请求数放大,但具体倍数取决于长度分布与到达过程,必须由你自己的数据算出来,不能引用通用倍数。本书不给这个倍数,也不声称任何 40% 一类的通用值。

还有一类重复工作不来自重试,而来自幂等性缺失:任务内部子调用被重复触发、缓存键设计不当导致同样的工作每次重算、上游超时后整单重排队。识别办法是把 attempt 与 model_call 分别计数,观察"每任务的模型调用次数"这一比值——它在稳定系统中应基本恒定,突然升高通常意味着重复工作。

本节要点

  • 客户端状态、服务端状态、任务结果、资源释放是四本账,靠四级标识关联,不能合并成一张单列状态表。
  • 超时语义至少有客户端等待超时、网关超时、服务端截止时间、下游 RPC deadline 四类。
  • 取消的验收是五项行为清单,其中"不再调度新工作"是首要项;不要要求无条件立刻打断共享前向。
  • 资源释放有四个层级;显存读数不能单独证明或否证泄漏。
  • 重试放大公式的前提是"各轮条件失败概率相同且仅在前次失败后重试";请求数放大不等于资源放大。

常见误区

  • 误:超时后重试,成功率高就说明系统健康。 正:重试掩盖长尾失败并放大最贵请求的负载;应按 attempt 统计,并按契约判定任务结果。
  • 误:取消了就等于资源释放了。 正:要区分"请求不再持有块""块可再分配""进程归还显存"三个层级。
  • 误:请求数放大 24%,资源也放大 24%。 正:长尾请求的资源强度不同,倍数须由自身数据测得。
  • 误:客户端超时后服务端肯定停止了。 正:取决于取消是否被接收并传播;必须查服务端终态。

本节验收

  1. 你能为自己的系统建立四本账,并用一个请求走完它们。
  2. 你能列出所选协议/框架的超时与取消传播机制。
  3. 你能给出取消的五项行为检查结果,并注明哪些未能取证。
  4. 你能写出重试策略的边界(次数、退避、抖动、放弃条件)与所用假设。

依据与核验入口:R25 S18


1.7 实验:为一条请求建立可解释档案

本节要回答的问题:最少需要哪些记录,才能重建一次请求?

1.7.1 系统档案与事件采集

系统档案回答"被测对象是什么",包含五项:SUT 边界与依赖假设(1.1.3)、制品清单与可复现层次(1.3)、运行身份(1.5.4)、观测点清单、时钟约定(含主机、时间命名空间、时钟种类、校准状态与误差上界)。

采集顺序是"先两端、后内部"。但要修正一个权限假设:拥有接口调用权限(E1)不等于拥有服务端日志、调度日志或显存观测权限。因此观测能力必须按资源层级分层评估。

1.7.2 时间线重建与时间域规则

四种情形覆盖基本形态:短请求、长输入、长输出、主动取消。

关于时钟,规则要写成以时间域为边界,而不是以进程为边界:

  • 在同一个已确认可比的 clock_domain 内可以相减。Python 官方文档说明 monotonic() 自 3.5 起、macOS 自 3.10 起、Windows 的 perf_counter() 自 3.10 起,时钟在所有进程间共享;同时强调返回值的参考点未定义,只有两次调用之差有效 SOURCE_REGISTERED。所以"只有同一进程才能相减"过强,但"参考点未定义,只能做差"是硬要求。
  • 跨主机需要同步墙钟、时钟映射或带误差上界的关联方法,并把 \(\varepsilon\) 记入事件记录。
  • 无法校准时,只报告本地间隔与因果顺序,不输出伪精确的单向延迟。

(顺带一处机制旁证:vLLM 指标设计要求由 engine core 统一记录 QUEUED、SCHEDULED、PREEMPTED、NEW_TOKENS 等事件时间戳,理由正是前端看不到某些事件的时刻,且区间必须基于同一进程的单调时间戳计算 SOURCE_REGISTERED。这说明"同域相减"是真实工程约束,而不仅仅是学院式洁癖。)

重建完成后把每个区间标注为四类之一:实测(有可关联的起止事件)、推导(由其它量算出,写清算式与假设)、仿真(写明模型与输入)、未知(保持空白)。允许标注清楚的估计进入时间图,禁止的是把估计伪装成实测。有假设的推导是有效信息,伪装才是问题。

1.7.3 观测缺口说明

缺口要写成结构化条目(谁影响、补齐需要什么、当前状态),随结论一起保存。它有两个作用:提醒读者结论边界;作为下一次补齐证据的待办清单。

1.7.4 最小事件档案:两条请求走完四本账

前面分散在各节的案例,这里用同一组原始事件串起来。档案含两条请求,全部为构造数据:

R1(正常完成)——即 Sample-B0:

事件 时刻 (ms) 时钟域
client.send 0 client
engine.queued 21 server(与 client 域已校准,\(\varepsilon\) 有上界)
engine.first_token_generated 634 server
server.last_content 4210 server
client.terminal 4243 client

R2(客户端超时、服务端稍后完成,随后重试成功):

事件 时刻 (ms) 说明
client.send(attempt A1) 0 首次尝试
client.abandon(reason=timeout) 2000 客户端超时
server.completed(A1) 5100 服务端仍在算并最终完成——幽灵负载
client.send(attempt A2) 2000 重试
server.completed(A2) 6300 服务端完成
client.terminal(A2) 6350 客户端收到

从同一档案导出四本账:

请求 client_status server_status task_outcome resource_release_status
R1 received(4243 ms) completed(4213 ms) success(契约:≤5 s 且质量通过) 请求已结束;块是否可再分配:未知
R2-A1 timeout(2000 ms) completed(5100 ms) — 未知(未取证;不得推断为已释放)
R2-A2 received(6350 ms) completed(6300 ms) success(契约:≤8 s)——但资源成本必须包含 A1;5100 ms 是墙钟停留时间,不是已测设备计算时间 未知

三处必须由档案直接读出的东西:

  1. 同一时刻的两本账可以不同:2000 ms 时客户端记 timeout,服务端仍在 running——这不是矛盾,是两个观测层。
  2. 幽灵负载有成本:A1 在服务端停留至 5100 ms,其中计算、排队和状态占用须分项取证;它没有向客户端交付结果;它必须进成本分子。
  3. 任务成功不等于浪费为零:R2 的任务结果是 success,但它的资源账包含两次尝试。

剩余未知(写入缺口,不猜测补齐):内部排队与批组成的细分时刻、实际执行路径、释放层级。

E0 可运行样例(输入 → 期望输出)。本档案同时充当事件解析器的单元测试输入——不需要 GPU:

断言 期望
client_status(R2-A1) timeout,不是 failed
server_status(R2-A1) completed,且完成晚于客户端放弃时刻
任务层聚合(R2) 1 个任务、2 个 attempt,完成计数只增加 1
成本分子(R2) 覆盖 A1 与 A2 的实际资源消耗;不能直接把两段墙钟时长当成计算时间
未取证的释放状态 记为 unknown,不填 released
跨时间域相减 若 \(\varepsilon\) 缺失,拒绝输出单向延迟

内部采集(排队细分、执行路径、释放层级)继续属于 E2,不因为有了这份档案就变成"已验证"。

本节要点

  • 系统档案五要素,其中时钟约定要含误差上界。
  • 时钟规则以时间域为界:同域可相减、跨主机需映射、无法校准只报本地间隔。
  • 四类标注:实测 / 推导 / 仿真 / 未知;禁止的是伪装,不是估计。
  • E1 权限不等于服务端观察权限,实验需分层设计。

本节验收

  1. 你能提交五要素齐全的系统档案。
  2. 你能为四种情形各给出一条带 start/end 事件与时钟域的时间线。
  3. 你能把每个区间归入四类标注之一,未知区间保持空白。
  4. 你能说明本章哪些结论因资源层级限制只能停在 client 侧。

依据与核验入口:R16 S01 DOC-W07


1.8 深化:关键路径的敏感性,以及取消后还剩多少工作

1.8.1 不是给活动排序,而是给依赖求最长路

把阶段表示为有向无环图。节点 \(v\) 的持续时间为 \(d_v\),前驱集合为 \(pred(v)\),则最早完成时间满足

\[E_v=d_v+\max_{u\in pred(v)}E_u,\]

没有前驱时取最大值为零。多个终点再接一个零耗时汇合节点,即可得到整个任务的完成时间。这一递推同时处理串行与并行,不需要先猜“哪些时间应该相加”。图中的边应表达实际依赖,而不是日志打印顺序;并行工具只有被后续消费者等待时,才可能影响汇合点。

第1.1节的工具在 S2 结束即启动:S2 完成于0.43 s,工具完成于0.88 s;主模型 Prefill 完成于3.13 s。在这组固定参数下,工具比主路径至少早2.25 s,即使 Decode 耗时归零也不会接管关键路径。原来的泛化提醒“解码变快后工具可能成为瓶颈”,对另一些图成立,却不适用于这个数值实例。

更一般地说,局部阶段缩短 \(\delta\) 不一定使端到端也缩短 \(\delta\)。假设两条并行分支分别耗时2 s和5 s,汇合耗时为5 s。把短分支从2 s优化为1 s,端到端不变;把长分支从5 s优化到1 s,端到端只能降到2 s。优化过程中最长路会切换,所以局部占比仅在一段参数区间内有解释力,不能无限外推。

图1-R1 请求依赖与并行工具的时间线

图中数据来自本章参数及依赖递推,不是某个实际应用的观测。配套程序对6.70 s、5.77 s、3.60 s以及工具完成时刻执行断言;图只回答算术和依赖是否一致。

1.8.2 单向延迟需要误差预算

设主机A的时间戳误差不超过 \(\varepsilon_A\),主机B不超过 \(\varepsilon_B\)。即使两台机器都使用墙钟,\(t_B-t_A\) 仍应解释为一个误差不超过 \(\varepsilon_A+\varepsilon_B\) 的区间,而不是一个无误差的点。若估计传输仅20 μs、同步误差却达100 μs,报告20 μs没有诊断意义;此时应改用同端往返时间、发送与确认事件,或明确只保留因果先后。

同样,两个本地持续时间之间可以比较长短,但这不会自动产生网络的单向分解。例如客户端668 ms与引擎613 ms之差可作为边界残差讨论,前提是同一次调用、可比时钟速率与端点语义已经固定;要再把残差拆成进入引擎前与离开引擎后,必须增加跨域映射证据。边界残差还可能包含未入账的串行化、代理和异步处理,不能全命名为网络耗时。

1.8.3 资源账是积分,不是把请求停留时间相加

请求超时后还占用多少显存,应记录随时间变化的持有量 \(M_i(t)\),其占用成本可表示为 \(\int M_i(t)dt\),单位为 GiB·s。设备计算成本则可按归属明确的执行活动或共享批分摊策略记录;排队一秒不等于计算一秒。对共享块应先按物理身份去重,再讨论按请求如何分摊,避免四个分支持有同一块就收费四份物理显存。

因此,取消闭环至少有两个独立终点:业务不再等待的时刻,以及资源重新可用的时刻。二者之差并非都能消除:当前共享前向可能需要安全完成。但是这个窗口若随取消量持续累积,就会降低后续准入能力。第4章检查物理块,第9章检查观测证据,第12章进一步处理跨进程的所有权与租约。这里建立的是这些后续分析共同使用的时间和资源单位。


1.9 实验图表补充:把请求时间线变成可检验的优化上限

本章先补一张能直接指导实验优先级的图,而不是为已有流程图配一张没有来源的GPU trace。沿用1.1节首次尝试成功的任务:端到端6.70秒、Decode占3.10秒,独立预取工具不在关键路径上。把Decode缩短30%,减少的是0.93秒,任务变成5.77秒;并不是整任务也缩短30%。

更重要的是第三根柱。把Decode理想化为零,仍有3.60秒串行工作。因此在“其余阶段与依赖不变”的条件下,仅优化Decode无法达到3秒。这是一项排除性结论,不是要求模型达到零时延,也不排除同时优化检索、Prefill或任务结构的可能。

怎样接入实际实验:为每个span记录开始、结束、父依赖、时间域、attempt_id和资源释放状态;先重建DAG,再用最长路径复算。实验改变了重叠关系时,须重新求关键路径,而不是机械扣掉某个局部耗时。对没有校准的跨主机时钟,只分别报告本域区间,不直接相减。

图1-V1 单段优化能把任务缩短多少?

图1-V1 单段优化能把任务缩短多少?

数据身份:TEACHING_CALCULATION。条件:S7独立预取被覆盖;其余阶段与依赖不变;原始6.70s、Decode3.10s。

方案 端到端/s 相对原值减少/%
原始任务 6.7 0.0
Decode 缩短30% 5.77 13.88
Decode 理想归零 3.6 46.27

复算公式:T=6.70-3.10*r。

出处与边界:本书1.1、1.8;3.60s是条件化乐观下界,不是设备可达性能。

数据与绘图点 · PNG · 重绘代码

1.10 第三方数据补充:把"一次请求有多慢"拆成阶段与尾部

本章前面用的是任务上限与配置推导。这一节补两条真正的第三方端到端实测:阶段拆分与尾延迟放大。

阶段拆分(Splitwise,HPCA 2024,Table IV;DGX-A100 与 DGX-H100,batch=1,P50,Llama2-70B)。Coding 负载的 E2E 为 856 ms(A100)/493 ms(H100),而 TTFT 只有 185/95 ms;Conversation 负载的 E2E 高达 4957/3387 ms,TTFT 仅 155/84 ms。E2E 与 TTFT 之间那一大段,就是 decode 逐个 token 累积出来的。从 A100 换到 H100,TTFT 约减半,但 E2E 只降到原来的 0.58–0.68 倍——硬件把首字等的时间砍了一半,却砍不动解码段的总时长,因为解码段由输出 token 数决定。

尾延迟放大(Kolluru,arXiv:2511.17593v1,Table 1;4×A100-80GB,25 并发,ShareGPT,vLLM v0.6.1 / TGI v2.3.0)。一个反直觉的结果:vLLM 的 TTFT p50 是 0.87 s,比 TGI 的 0.64 s 更差;但 vLLM 的总时延与 TPOT 全面更低。更要紧的是放大倍数——总时延的 p99/p50,vLLM 为 2.80 倍、TGI 为 3.20 倍。只报平均值会同时掩盖两件事:框架之间的取舍,以及尾部被放大后的真实体验。选框架时,"谁的 p50 更好看"和"谁的尾巴更稳"是两个独立问题。

边界:Splitwise 是 batch=1 的 P50,不代表高并发;Kolluru 为单作者预印本、无误差棒。

图1-T1 一次请求的阶段拆分:Decode 主导端到端(第三方实测)

图1-T1 一次请求的阶段拆分:Decode 主导端到端(第三方实测)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:Splitwise(微软研究院,HPCA 2024)Table IV;DGX-A100 与 DGX-H100,batch size 1,P50;Llama2-70B;生产 trace(coding 中位输入 1500/输出 13;conversation 中位输入 1020/输出 129)。

负载 阶段 A100/ms H100/ms
Coding TTFT(prefill) 185 95
Coding TBT(decode,每token) 52 31
Coding E2E 856 493
Conversation TTFT(prefill) 155 84
Conversation TBT(decode,每token) 40 28
Conversation E2E 4957 3387

出处与边界:Z01;batch size 1 的 P50,非高并发;仅一个模型与两类 trace;2023 年 H100 SXM 数据。论文为自研方案方,非中立基准组织。

数据与绘图点 · PNG · 重绘代码

图1-T2 尾延迟放大:p50 到 p99 不是同一个世界(第三方实测)

图1-T2 尾延迟放大:p50 到 p99 不是同一个世界(第三方实测)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:Kolluru(arXiv:2511.17593v1)Table 1;4×A100-80GB,25 并发,ShareGPT,prompt 均值 512 / 生成均值 256 tokens,每配置 1000 请求取中位数;vLLM v0.6.1 与 TGI v2.3.0。

模型 框架 分位 TTFT/s 总时延/s TPOT/s
LLaMA-2-70B vLLM p50 0.87 31.24 0.122
LLaMA-2-70B vLLM p95 2.34 58.92 0.231
LLaMA-2-70B vLLM p99 3.67 87.45 0.343
LLaMA-2-70B TGI p50 0.64 39.87 0.157
LLaMA-2-70B TGI p95 1.45 82.14 0.323
LLaMA-2-70B TGI p99 2.18 127.39 0.501

出处与边界:Z02;单作者预印本,未经同行评审;无误差棒;框架版本为 2024 年。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 如何证明"慢在模型外",而不是仅凭端到端时延猜测?

答案方向:先按 1.1 定义 SUT 边界与完成单位并写清依赖关系;再按 1.2 在同一时间域内定义端到端,用依赖图重建关键路径,把每个区间标为实测/推导/仿真/未知;最后用干预 + 复测验证——例如把检索替换为固定返回的桩服务,观察端到端是否按预期下降。若下降量与检索段耗时不符,说明存在重叠或口径问题。关键在于干预,而不是看比例推断(第10章展开)。注意:轻载下的这条结论不能外推到高负载。

2. 网关重试两次后成功,任务是否成功?各自怎么记账?

答案方向:四本账分开写。client_status:两次 timeout、第三次收到完整响应。注意客户端超时不是服务端终态——不能顺手把前两次直接记为服务端失败。server_status:三次 attempt 必须逐一对账,取值可能是 completed、cancelled、failed 或仍未确定(服务端可能在客户端放弃后仍完成/仍在运行),每一项都要有服务端记录支撑,不能用客户端的超时推断。task_outcome:按预先定义的任务契约判定——契约若规定"最终收到可用答案且总耗时在契约内即成功",则为 success;若规定了总截止时间或不允许重试,则需按契约条款判定。resource_release_status:前两次尝试占用的资源是否释放,必须按 1.6.3 的层级取证,未取得证据时记 unknown。成本侧还要把两次失败尝试消耗的资源计入,而不是被"最终成功"掩盖。

3. 如何证明实际使用了配置要求的 runner 与内核,而不是落到其它分支?

答案方向:按 1.5.4 填运行身份表并标证据强度——源码只证明可能性;启动日志的配置回显证明声明;运行期trace或等价插桩,结合调用关联、形状和编译身份,才能支撑这一次的实际路径;内核名称本身不总是唯一身份。同时按 1.5.2 记录形状差异的处理分支(padding / 桶命中 / 降级 / eager / 报错),并记录形状、dtype、后端能力等级等触发条件;输出长度或形状变化后必须重新取证。


本章验收清单

节 验收标准
1.1 能给出五类对象与四级标识的映射;能按依赖图说明某优化的端到端改善上限;能用契约而不是单点状态判定任务结果
1.2 时间线按两条流水分别重建,含 start/end 事件与时钟域;区间之和经交叉验证无重复计入;能说明哪些区间因缺乏跨域映射而不可拆分
1.3 另一名读者能复原相同输入 Token 序列;能隔离模板变量与工具定义变量;能说明本次验收可复现的层次(L1—L4)
1.4 能标注五类事件与四个时间戳;能区分 Token ID、usage、重新分词与块数;能说明 ITL 与 TPOT 在何种条件下不一致
1.5 能按职责归因;能说明本系统处理形状差异的分支结构及版本依据;能填出运行身份表并标证据强度
1.6 能建立四本账并对账;能按五项清单核验取消;能写出重试放大的假设边界
1.7 每条解释都有对应事件;带假设的估计已标注为推导/仿真;未知区间保持空白;结论不超出资源层级允许的范围

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第2章 Prefill、Decode 与模型执行

核心论点:模型结构决定需要做哪些工作,负载与实现决定这些工作怎样消耗资源;不能把阶段名称直接当成瓶颈结论。

本章导航

读完本章,你应该能够:说清"生成一个 Token"需要经过哪些步骤,以及推测解码如何改变"每轮一个 Token"的基线;分别估算 Prefill 与 Decode 的工作量与访存量;用模型配置算出每个 Token 的状态字节数,并知道常规公式何时失效;区分 MoE 的总参数、激活参数与驻留权重;把推理内容、可见答案、媒体输入、结构化约束与外部工具等待分别计量。

单位约定:状态容量用二进制单位(KiB/MiB/GiB),带宽与算力用十进制(GB/s、TFLOP/s)。两者混写是本书明文禁止的做法(写作规范 §6)。


2.1 自回归生成与一次解码迭代

本节要回答的问题:生成一个新 Token 需要哪些已有信息?

2.1.1 条件概率与前向计算

自回归模型在每一步做的是同一件事:给定已有上下文 \(x_{1:t}\),计算下一个 Token 的条件分布 \(P(x_{t+1}\mid x_{1:t})\),再从中取出一个。计算过程是一次前向:

  1. 输入 Token 经嵌入层得到形状为 \([B,\,S,\,d_{\text{model}}]\) 的张量(\(B\) 批大小、\(S\) 序列长度、\(d_{\text{model}}\) 隐藏维度);
  2. 依次通过 \(L\) 层,每层做注意力与前馈计算,输出同形张量;
  3. 最后一层经输出投影得到 logits,形状为 \([B,\,S,\,V]\)(\(V\) 为词表大小);
  4. 取最后一个位置的 logits 做采样。

在未命中前缀、未分块的普通 Prefill 示意中,\(S\) 等于输入长度;分块时 \(S\) 是本轮实际处理的位置数。普通 Decode 每个序列通常输入一个待消费 Token;推测验证可以一次处理多个位置,不能再说每次前向只推进一个位置。这就是两阶段工作量差异的根源:同样的权重,处理的 Token 数量差了几个数量级。

2.1.2 采样与停止

logits 经温度、top-p/top-k 等处理得到采样分布,抽出一个 Token id,再判定是否停止(EOS、停止串、max_tokens、结构化约束完成)。采样与停止判定不是免费的:构建候选集合、排序、掩码计算都要时间,批很大或词表很大时它们是可观的每步开销。

2.1.3 计算图与生成循环

生成循环与模型层内部计算要分开描述。伪代码如下(不冒充任何框架源码):

# 伪代码;不是具体引擎 API。
# 不变量:cache 只包含已经经过目标模型前向的位置。
cache, logits = prefill(prompt_tokens)
pending = sample(logits_at_last_prompt_position(logits))
outputs = [pending]                 # 刚采样的 Token 尚未写入目标 KV
while not should_stop(outputs):
    cache, logits = forward_and_commit(cache, pending)
    # 上一步把 pending 的 K/V 写入 cache;不是把下一个 Token 的状态提前写入。
    pending = sample(logits)
    outputs.append(pending)

# 推测解码的接口层示意:实现还要处理验证失败与截断。
# target_cache 保留已消费的位置,pending 是最近采样但尚未消费的位置。
drafts = propose_drafts(pending, k)
provisional_cache, distributions = verify_target(target_cache, [pending] + drafts)
accepted_prefix, correction_or_bonus = exact_accept_reject(distributions, drafts)
target_cache = keep_consumed_prefix(provisional_cache, pending, accepted_prefix)
outputs.extend(accepted_prefix)
outputs.append(correction_or_bonus)  # correction/bonus 暂为新的 pending
pending = correction_or_bonus
# 若停止符落在 accepted_prefix 内,应先截断并同步裁剪状态与输出。

首个采样结果就触发停止时,不再无条件执行一步 Decode。推测分支里,“提交了哪些 KV 位置”和“对外接受了哪些 Token”仍是两笔账:校正 Token 通常尚未经过目标模型前向。

"每轮只产生一个 Token"只适用于普通自回归基线。开启推测解码后,一次前向可能验证多个候选,因此必须区分三个量:前向次数、解码迭代次数、最终接受的 Token 数。把"迭代次数"当成"输出 Token 数"会让所有吞吐指标失真(第7章 7.3.2 已规定:被拒的候选不进产出分子)。

推测解码必须区分两个不同的计数:

\[ \mathbb{E}[\text{接受的草稿数}] = \sum_{i=1}^{k}\alpha^{i} \qquad \mathbb{E}[\text{本轮推进量}] = \sum_{i=0}^{k}\alpha^{i} \]

后者包含被拒后的校正 Token(或全部接受时的额外 Token),因此比前者多 1 个量级为 1 的项。可算例:\(k=4\)、\(\alpha=0.7\) 时两者分别为 1.7731 与 2.7731——不能把它们都叫"接受草稿数"。

若整轮相对成本(含提出草稿、目标验证、校正与相关开销)为普通一步的 \(c=1.3\) 倍,则每 Token 相对成本为 \(1.3/2.7731 = 0.469\),理论加速约 2.13 倍。注意这里的 \(c\) 必须是整轮成本:只给目标验证的成本不足以推出整个系统的加速比;接受率随负载、批大小与模型而变,不是常数;被拒的 Token 消耗资源但不产出内容。

本节要点

  • 前向次数 ≠ 解码迭代次数 ≠ 接受的 Token 数,三者必须分开计数。
  • Prefill 逻辑上处理输入中未复用的位置,可分成多次前向;普通 Decode 与推测验证的输入长度和推进量分别计数。
  • 采样与停止判定有成本,批大或词表大时不可忽略。
  • 推测解码的理想加速 = 期望总推进量 ÷ 整轮相对成本,且依赖接受率这一实测变量。

常见误区

  • 误:迭代 300 次就是生成了 300 个 Token。 正:开启候选验证后两者不同;分别计数。
  • 误:推测解码开启就等于快 \(k\) 倍。 正:实际倍数由接受率与验证成本决定,可能小于 1(反而变慢)。
  • 误:采样只是选个数字,不花时间。 正:排序、掩码与停止判定在每步都有成本。

本节验收

  1. 你能区分前向次数、解码迭代次数与最终接受 Token 数。
  2. 你能用接受率与相对成本估算推测解码的收益区间。
  3. 你能说明被拒绝的候选在吞吐与成本两处分别如何记账。

依据与核验入口:R04


2.2 Prefill:从输入序列构建状态

本节要回答的问题:输入长度增加,会增加哪些工作?

2.2.1 批量处理已知输入

Prefill 处理的是已经给定的上下文:整段输入可见,因此可以在序列维度与批维度上充分并行。它的实际工作量取决于两件事:实际需要计算的 Token 数(若命中前缀缓存,未复用的部分才是真正要算的)与批内有效 Token 的编排方式(引擎可能把长输入切成块与解码请求组批,第1章 1.2.2、第5章)。

2.2.2 注意力与线性层工作量

Prefill 的计算量大致分两块:

  • 线性层:\(\text{FLOPs}_{\text{lin}} \approx 2\,P\,T\),其中 \(P\) 为参与计算的参数量,\(T\) 为实际计算的 Token 数;
  • 注意力:\(\text{FLOPs}_{\text{attn}} \approx 4\,L\,S^{2}\,d_{\text{model}}\)(含 \(QK^{\top}\) 与 \(AV\) 两次矩阵乘)。

可算例:取 \(L=32\)、\(d_{\text{model}}=4096\)、稠密 \(P=7\times10^{9}\)、\(S=T=4096\):

\[ \text{FLOPs}_{\text{attn}} = 4\times32\times4096^{2}\times4096 \approx 8.8\ \text{TFLOP} \qquad \text{FLOPs}_{\text{lin}} = 2\times7\times10^{9}\times4096 \approx 57.3\ \text{TFLOP} \]

先说清口径:15.3% 是"注意力 ÷ 线性项";若按"注意力 ÷ 两项总和"计,则为 13.30%。两个口径都合法,但必须声明。

输入 \(S\) 线性项 TFLOP 注意力项 TFLOP 注意力 / 两项总和
4096 57.34 8.80 13.30%
16384 229.38 140.74 38.03%

长度翻 4 倍后注意力占比从 13.30% 升到 38.03%,但它仍未超过线性项——所以不能不加定义地写"注意力成为主要项"。按这个简化公式,两项相等的长度为

\[S^{\ast} = \frac{2P}{4Ld_{\text{model}}} = \frac{2\times7\times10^{9}}{4\times32\times4096} \approx \mathbf{26703} \ \text{Token}\]

这是代数阈值,来自简化公式,不是硬件上瓶颈真正切换的实测点。

还要说明 \(4LS^{2}d\) 的形式:它按完整方阵计数。因果语言模型只需下三角(约为一半),实现是否真正跳过被掩码的部分取决于内核与布局;因此"理论有效计算量"与"实际执行计算量"要分开记账。上表是内部一致性复算,不构成对该公式全部适用范围的认证。

补充推导:命中前缀之后究竟省掉什么。 设已有 \(H\) 个前缀 Token 的状态可完全复用,本次新增 \(U\) 个 Token,采用标准全因果注意力,暂不考虑稀疏、滑窗、共享层与重算。新增的第 \(i\) 个 query 仍需关联它之前的 \(H+i\) 个位置,因此新增的有效 query-key 对数为

\[N_{\text{pairs}} = \sum_{i=1}^{U}(H+i) = UH + \frac{U(U+1)}{2}\]

在每个 query-key 对的 \(QK^{\top}\) 与 \(AV\) 合计约 \(4d_{\text{model}}\) 次运算、共 \(L\) 层的模型下,注意力部分的有用计算量约为 \(4Ld_{\text{model}}\,[\,UH + U(U+1)/2\,]\)。

算例:\(H=4096\)、\(U=128\):

情形 有效 query-key 对数 相对
独立 128 Token 请求(\(H=0\)) \(128\times129/2 = 8{,}256\) 1.0×
复用 4096 前缀 + 新增 128 \(128\times4096 + 8{,}256 = 532{,}544\) 64.5×
不复用,4224 全部重算 \(4224\times4225/2 = 8{,}923{,}200\) 1080.9×

两个结论:复用长前缀后新增128个Token,与独立128个Token请求的注意力工作量不同(本例有效query-key对数相差约64.5倍,不代表全请求耗时同倍);命中前缀相对全部重算省下约 94% 的注意力对数,但这是注意力部分,投影/前馈的线性项仍随实际处理的 Token 数走,所以不能把"命中率"直接换算成端到端提速。实际内核的块形状、数据读取、padding 与调度另需取证。

2.2.3 首输出与状态建立

Prefill 的产出是两样东西:第一个输出 Token 的 logits,以及后续生成所需的每层状态。因此它的结束位置与"首 Token 时间"高度相关,但不能把端到端 TTFT 全部算成 Prefill 计算——其中还包含排队、网络与网关缓冲(第1章 1.2.3、第7章 7.2.2)。

状态规模是第4章的主角,这里只给出后续会反复引用的基本式。对常规注意力(MHA/GQA/MQA),每个 Token、每层需要存 K 与 V 两份:

\[ b_{\text{token}} = 2 \cdot h_{kv} \cdot d_{\text{head}} \cdot L \cdot b_{\text{dtype}} \quad [\text{byte/token}] \]

取 \(h_{kv}=8\)、\(d_{\text{head}}=128\)、\(L=32\)、\(b_{\text{dtype}}=2\)(BF16):

\[ b_{\text{token}} = 2\times8\times128\times32\times2 = 131072\ \text{B} = \mathbf{128\ KiB/token} \]

一条 4096 Token 的序列即 \(128\ \text{KiB}\times4096 = \mathbf{512\ MiB}\)。这个数字后面会一直用到。

本节要点

  • Prefill 处理已给定上下文,可充分并行;实际工作量取决于未复用 Token 数与批内编排。
  • 在本节全注意力简化模型中,线性项随 \(S\) 增长、注意力项随 \(S^2\) 增长;是否占主导还取决于结构、长度区间和执行内核。
  • 首 Token 时间与 Prefill 强相关,但 TTFT 不等于 Prefill 计算时间。
  • 常规 KV 公式给出 128 KiB/token、4096 序列 512 MiB 的量级参照。

常见误区

  • 误:输入 4096 Token,Prefill 时间就是"每 Token 耗时 × 4096"。 正:注意力项是平方增长,且排队与网络不属于 Prefill。
  • 误:命中了前缀缓存,输入长度还是按全部计入。 正:实际计算的是未复用部分;两者都要记录。
  • 误:KV 公式对所有模型都成立。 正:压缩/共享/递归结构必须换公式(见 2.4)。

本节验收

  1. 你能解释"输入更长"与"实际需要重算的输入更多"的区别。
  2. 你能算出本例在 4096 与 16384 两种长度下注意力项的占比变化。
  3. 你能给出本模型每个 Token 的状态字节数,并说明公式成立的前提。

依据与核验入口:R03 R07


2.3 Decode:逐步生成与状态访问

本节要回答的问题:为什么并发、上下文和输出长度都影响生成表现?

2.3.1 单步权重与状态访问

Decode 每一步要读三样东西:权重、已有状态、以及上一步的输出 Token。以 7B 稠密 BF16 模型为例,单步访存为

\[ M_{\text{weights}} = P \cdot b_{\text{dtype}} = 7\times10^{9}\times2 = 14\ \text{GB} \] \[ M_{\text{state,seq}} = b_{\text{token}} \cdot S = 128\ \text{KiB}\times4096 = 512\ \text{MiB} \approx 0.5369\ \text{GB} \]

注意第二项随上下文长度增长,第一项与上下文无关但与模型大小成正比。批大小为 \(B\) 时,在"权重一次读入后在同一批内被复用"的理想近似下,权重访存被摊薄,状态按序列累加:

\[ M_{\text{step}} \approx M_{\text{weights}} + B \cdot M_{\text{state,seq}} \]

取 \(B=8\):\(M_{\text{step}} \approx 14 + 8\times0.5369 = \mathbf{18.29\ GB}\) [DERIVED]。若设备带宽为 \(2.0\ \text{TB/s}=2000\ \text{GB/s}\),则单步下界

\[ t_{\text{step}} \approx \frac{18.29}{2000} = 9.15\ \text{ms} \]

对应单序列约 \(109\ \text{token/s}\)、整批 \(8/0.00915 \approx 874\ \text{token/s}\)。这是理想流量下界:它假设权重与状态各读一次、无额外拷贝,并假设计入的权重与状态必须从该层存储读入。实际 DRAM 流量可因临时缓冲、重复加载而增加,也可因片上缓存命中而低于“全部从 HBM 读取”的估算,因此"理想流量 / 带宽"只在该流量假设成立时给出时间下界,不能当作预测值;两者的差额本身就是诊断对象(第9章)。这是带宽下界的粗略估算,不是实测值:它忽略了内核效率、缓存命中、通信与调度开销(第3章给出完整模型)。

2.3.2 活跃序列与批次复用

上式已经说明了批处理的价值:权重访存被 \(B\) 个序列摊薄。算术强度(FLOP/byte)随 \(B\) 上升——\(B=1\) 时约 \(14\ \text{GFLOP}/14.54\ \text{GB}\approx0.96\),\(B=8\) 时约 \(112\ \text{GFLOP}/18.29\ \text{GB}\approx6.12\) [DERIVED]。这就是为什么批大小是 Decode 阶段最关键的变量之一(调度策略见第5章)。

但批大小受制于显存:每个活跃序列都要驻留自己的状态,\(B\) 与 \(S\) 的乘积决定容量上限(第4章)。

2.3.3 长输出的累计成本

Decode 的成本是逐步累加的,且随输出变长而恶化:状态随已生成 Token 增长,每步都要读更多数据。因此长输出的边际成本递增,这与 Prefill 一次性付清的形态完全不同。

所以"Decode 永远是显存带宽瓶颈"这句话只能当作起点假设:随并发、上下文、并行策略与模型结构变化,瓶颈可能迁移——大批长上下文时可能是容量不足导致抢占与重算(第4、5章),多卡时可能变成通信(第6章),小批短上下文时可能是内核启动与调度开销占主导。正确的做法是给出工作量分解,并写成至少两种可区分的假设,再用第9章的观测去判别。

本节要点

  • 单步访存 = 权重(与模型大小成正比)+ 状态(随上下文与并发增长)。
  • 批处理摊薄权重访存、提高算术强度,但受显存容量约束。
  • 全注意力下,随上下文增长的状态访问项会累积;总耗时是否明显超线性仍取决于权重项、缓存、调度以及是否为滑窗或递归结构。
  • "Decode 一定是带宽瓶颈"是待验证假设,不是结论。

常见误区

  • 误:Decode 慢就一定是显存带宽不够。 正:还可能是容量不足、通信、调度开销或内核效率;要写竞争假设并测量。
  • 误:批大小越大越好。 正:受显存容量与尾延迟约束;过大可能引发抢占与重算。
  • 误:输出长度翻倍,耗时也翻倍。 正:不能无条件按倍数推断;全注意力状态访问含增长项,权重主导或有界窗口时可以接近线性。

本节验收

  1. 你能为同一模型写出至少两种不同的 Decode 瓶颈假设。
  2. 你能说明区分这两种假设需要哪些测量(并指向第9章的哪一类证据)。
  3. 你能估算本模型在给定批大小与上下文下的单步访存量。

依据与核验入口:R04 R30


2.4 注意力与状态结构:全量、压缩、稀疏与混合

本节要回答的问题:模型结构如何改变状态的种类、增长方式和实际缓存布局?

2.4.1 MHA、GQA、MQA 与常规 K/V 账本

查询头数 \(h\) 与键值头数 \(h_{kv}\) 的关系决定状态的份数:

结构 关系 每 Token 字节(同参数下) 相对倍率
MHA \(h_{kv}=h\) \(2\cdot h\cdot d_{\text{head}}\cdot L\cdot b\) 最大
GQA \(h_{kv}<h\) \(2\cdot h_{kv}\cdot d_{\text{head}}\cdot L\cdot b\) 按 \(h_{kv}\) 缩放
MQA \(h_{kv}=1\) \(2\cdot d_{\text{head}}\cdot L\cdot b\) 最小

沿用 2.2.3 的参数(\(d_{\text{head}}=128\)、\(L=32\)、\(b=2\)):GQA(\(h_{kv}=8\))为 128 KiB/token,MQA(\(h_{kv}=1\))为 16 KiB/token——相差 8 倍,正好是 \(h_{kv}\) 之比。这就是为什么 \(h_{kv}\) 必须写进实验记录:只看"模型 7B"推不出状态规模。

2.4.2 MLA 与压缩表示:常规公式在这里失效

压缩潜在表示(如 MLA)不存两份 K/V,而是存一个低维潜在向量(外加少量用于位置编码的解耦部分)。此时不能套用 \(2\,h_{kv}\,d_{\text{head}}\) 公式,必须按其真实维度计算:

\[ b_{\text{token}}^{\text{MLA}} = \left(d_{c} + d_{r}\right)\cdot L \cdot b_{\text{dtype}} \]

取 \(d_c=512\)(潜在维度)、\(d_r=64\)(解耦位置部分)、\(L=32\)、\(b=2\):

\[ b_{\text{token}}^{\text{MLA}} = (512+64)\times32\times2 = 36864\ \text{B} = \mathbf{36\ KiB/token} \]

与同规模 GQA 的 128 KiB 相比为 \(131072/36864 \approx \mathbf{3.56}\) 倍。

但"压缩存储"不等于"执行时也是压缩的":后端可能展开、复制或另行缓存。因此状态预算要分三栏记:逻辑字节(按模型设计算)、后端实际布局、实测分配。三者不一致时,容量规划引用实际执行层,而差异本身要登记为证据缺口。

2.4.3 稀疏索引、压缩主缓存与重算

稀疏注意力减少的是参与计算的位置,不自动等比例减少被保存的状态。三类机制要分开:

机制 减少什么 不减少什么 额外代价
稀疏选择 参与 Attention 的位置数 被保存的主缓存 索引器本身也是状态
压缩主缓存 每位置的存储字节 计算时可能需要解压 解压/重算的计算量
重算(recompute) 存储占用 计算量 用计算换存储

判断这类结构对容量的影响,只能靠逐项账本,不能靠"它用了稀疏所以显存一定更小"的直觉。

2.4.4 跨层共享、递归状态与混合层

还有一种容易算错的情形:跨层共享的状态按物理身份只计一次,不能按层数重复相加。若 \(m\) 层共享同一份状态,它在容量账本里是一份,而不是 \(m\) 份。

递归类结构(线性注意力、门控增量规则等)的状态大小与上下文长度无关(常数级 per layer per sequence),但每层都有一份。混合层布局(部分层用注意力、部分层用递归)必须逐层记账:

状态类型 是否随上下文增长 每层份数 是否跨层共享 何时重算
注意力主缓存 是(每个新 Token 追加) 1(按 \(h_{kv}\) 计) 可能 被抢占或失效时
稀疏索引状态 视实现 1 可能 重算或增量更新
递归状态 否(常数) 1 可能 每步原地更新
跨层共享对象 视实现 共享层共用一份 是 失效时
临时展开张量 否 按需 否 每次前向

2.4.5 模型卡、配置与实际后端布局的三重对照

最后要回答一个高频问题:模型卡写 FP4,而后端记录的是另一种存储格式,该引用哪一层?

答案是:容量与带宽预算引用实际执行层(后端实际分配与访问的格式),因为它是真正占用显存与带宽的量;但模型卡的设计意图也要登记,因为它是判断"这个数字为什么是这样"的依据,也是版本变化时复核的抓手。两者不一致时,把差异写进证据缺口,而不是挑一个好看的数字。

作为版本化资料案例:某模型卡描述了压缩/跨层共享结构与模型侧低精度主缓存,某引擎发布说明记录了特定硬件路径上的分块低精度存储 SOURCE_REGISTERED S02。这两项记载用于说明"模型设计"与"后端落实"必须分开看,不能用于推算未经运行的显存或吞吐(均为页面快照,runtime_verified=false)。

本节要点

  • 常规 KV 公式只在 MHA/GQA/MQA 且无压缩共享时成立;\(h_{kv}\) 是必须记录的配置字段。
  • 压缩表示按真实潜在维度计算;同规模下与 GQA 可差 3.5 倍以上。
  • 稀疏减计算不自动减存储;跨层共享只计一次;递归状态不随上下文增长。
  • 三重对照:逻辑字节 / 后端实际布局 / 实测分配;容量引用实际执行层。

常见误区

  • 误:用了 MLA,就把 \(h_{kv}\) 填进常规公式。 正:公式不适用;按潜在维度算,并核对后端是否展开。
  • 误:稀疏注意力省显存。 正:省的是计算;存储要按账本算,索引器还可能新增占用。
  • 误:跨层共享的状态乘以层数。 正:按物理身份计一次。
  • 误:模型卡写了 FP4,就按 FP4 算显存。 正:以后端实际分配为准,并登记两者差异。

本节验收

  1. 你能指出常规 KV 公式在何种结构下必须替换。
  2. 你能为一份模型配置写出分项状态账本(类型/增长/共享/重算/分配位置)。
  3. 你能说明模型设计与后端布局不一致时,容量预算引用哪一层及理由。

依据与核验入口:R04 R28 S12


2.5 Dense、MoE 与混合状态模型

本节要回答的问题:参数总量为什么不能直接代表每步计算与驻留内存?

2.5.1 三张清单:参与计算、占用存储、发生传输

MoE 与 Dense 的根本区别在于:计算只走被选中的专家,但所有专家都要在内存里。因此必须用三张清单分别记账,而不是用一个参数量预测全部性能:

清单 Dense MoE
参与计算 全部参数 每 Token 只有被激活的专家参数
占用存储 全部参数 全部专家(除非显式卸载到主机/存储)
发生传输 多卡时为激活、部分和等前向中间量(不含训练梯度) 被路由 Token 的表示(dispatch/combine)、可能的专家权重搬运

可算例:设某 MoE 总参数 \(80\times10^{9}\)(BF16 下 160 GB 驻留),每 Token 从 64 个专家中选 2 个、激活参数 \(8\times10^{9}\)。则:

  • 单步计算量 \(=2\times8\times10^{9} = 16\ \text{GFLOP/token}\)(按激活参数);
  • 驻留显存 \(= 80\times10^{9}\times2 = \mathbf{160\ GB}\)(按总参数);
  • 计算量与驻留的差异达到 10 倍。

因此"MoE 的激活参数等于 8B,所以可以当 8B 稠密模型用"是错的:其参数参与的线性计算项按约 8B 计、未卸载权重驻留按约 80B 计;这不等于与 8B Dense 同速。

2.5.2 专家路由与通信工作量

路由把每个 Token 送到被选中的专家;跨卡部署时,这需要在设备间搬运 Token 表示。通信量的量级为

\[ M_{\text{route}} \approx 2 \cdot B \cdot d_{\text{model}} \cdot b_{\text{dtype}} \cdot k \]

其中 \(k\) 为每 Token 选择的专家数,系数 2 表示去程(dispatch)与回程(combine)。取 \(B=8\)、\(d_{\text{model}}=4096\)、\(b=2\)、\(k=2\):

\[ M_{\text{route}} \approx 2\times8\times4096\times2\times2 = 262144\ \text{B} = \mathbf{256\ KiB/步} \]

这个数字必须带边界说明:它是一层上的逻辑 payload(\(B\) 个 Token、top-2、去回程合计),不是"整模型每步网络量"。要写成后者,还需说明:是否所有专家都跨设备(同设备命中不产生网络流量)、同一 Token 是否被重复发送到多个 rank、是否计元数据与填充、以及是全局批还是单个 rank 的批。单看字节数不大,但这是每一步都要发生的同步等待:它的耗时取决于延迟与同步频率,而不是带宽(第6章展开)。此外还有两个工程问题:

  • 专家偏斜(skew):某些专家被过度选择,导致持有它们的设备成为尾延迟来源;
  • 容量因子与丢弃:超过专家容量的 Token 可能被丢弃或改路由,影响质量。

2.5.3 注意力状态与递归状态并存

混合状态模型可能同时维护注意力缓存与递归状态。两者增长规则不同(前者随上下文线性增长,后者常数),不能合并成一个"KV 大小",也不能用其中一个的经验去推另一个。分层记账见 2.4.4 的表。

2.5.4 混合结构的模型与后端证据

作为资料案例:某模型卡描述了门控增量规则与稀疏注意力的混合结构,且在该快照中明确标注为实验预览 SOURCE_REGISTERED。使用这类结构时有两条纪律:

  1. 预览/实验状态不等于可复现的主实验环境——主案例应选择版本可冻结、行为可验证的对象(第1章"三层写作结构");
  2. 激活参数量不等于同参数稠密模型的等价性能——计算、存储、传输三条账要分别算,路由与通信的实测留到第6章。

本节要点

  • MoE 要分三张清单:计算按激活参数、存储按总参数、传输按路由量。
  • 线性计算项按激活参数、未卸载驻留按总参数——激活参数不能代表驻留内存。
  • 路由通信是每步同步,关注延迟与偏斜,不只是字节数。
  • 混合状态模型要分层记账;预览模型不作主实验环境。

常见误区

  • 误:MoE 激活 8B,显存就按 8B 算。 正:未选中的专家仍需驻留;按总参数算存储。
  • 误:路由传输才 256 KiB,通信可以忽略。 正:它是每步同步,瓶颈常在延迟与偏斜而非带宽。
  • 误:混合结构只需一个"状态大小"数字。 正:注意力与递归状态增长规则不同,分层记账。

本节验收

  1. 你能分别估计 MoE 的计算量、权重驻留与会话状态。
  2. 你能说明路由通信的瓶颈形态(带宽 vs 延迟/同步)。
  3. 你能说明预览状态模型在本书中的使用边界。

依据与核验入口:R05 R30 S12


2.6 推理模型、多模态与结构化生成负载

本节要回答的问题:文本之外的输入与输出约束如何进入性能分析?

2.6.1 内部生成与可见答案

推理(思考)型模型会先产生一段内部生成,再输出可见答案。这带来三条计量规则:

  1. 三者分开计数:内部生成 Token、可见答案 Token、以及计费/用量字段可能互不相等;
  2. 两个"首次"分开记录:首内部 Token 与首可见 Token 对应不同体验(第7章 7.2.1 的 TTFT/TTFO);
  3. 观测不到的内部生成记未知,不得用可见长度反推,也不得填 0。

结构化输出还要额外记录:格式约束类型(JSON Schema / 语法)、解析成功率、以及因约束失败而重生成的次数——后者消耗资源但不产出内容,与推测解码被拒的候选属同一类(不进产出分子,进成本分子)。

2.6.2 编码器与媒体输入

多模态负载在文本之外多了三段:媒体读取与解码、预处理(缩放/分块/采样)、编码器执行。这三段通常发生在主机或独立编码器上,应单独计时,不要并入文本 Prefill 的时间。

记录字段建议:媒体类型与尺寸、分辨率或时长、分块与采样策略、编码后产生的 Token 数、以及编码器耗时。不要把"图像数量"强行换算成通用文本 Token——不同模型的投影方式与 Token 数差异很大,换算只能来自该模型的实际编码结果。

2.6.3 结构约束与工具调用

工具调用负载要区分三段时间:模型生成调用信息、编排层等待外部函数、以及结果回填后的续生成。其中编排层等待属于外部依赖,不计入模型生成时间(第1章 1.1.3、第8章 8.3.3)。实验里建议用注入的固定延迟代替真实外部调用,以便复现与解释。

多轮分支任务还要同时报告两件事:模型工作量(Token 与状态)与任务完成条件(必需分支、容错策略、截止时间),后者按 task_id 判定(第7章 7.5)。

本节要点

  • 内部生成、可见答案、用量字段分开计数;不可观测的内部生成记未知。
  • 多模态的读取/预处理/编码三段单独计时;媒体 Token 数来自实际编码。
  • 工具调用的编排等待不进模型生成时间;实验用注入延迟替代真实外部调用。
  • 分支任务同时报告模型工作量与任务完成条件。

常见误区

  • 误:可见答案 300 Token,所以生成了 300 Token。 正:内部生成可能数倍于此;分开记录或标记为未知。
  • 误:一张图约等于 N 个文本 Token。 正:取决于模型与预处理;只能实测编码结果。
  • 误:工具调用慢就是模型慢。 正:编排等待是外部依赖,单独计时。

本节验收

  1. 你能把模型服务时间与外部工具时间分开,并各给一个测量点。
  2. 你能为媒体输入列出记录字段与实际编码 Token 数。
  3. 你能同时报告格式/任务质量,而不只报速度。

依据与核验入口:R06 R18 S17


2.7 实验:把请求映射为工作量

本节要回答的问题:怎样从配置和 trace 校验工作量估计?

2.7.1 张量尺寸提取

从模型配置文件提取决定工作量的字段:\(L\)、\(d_{\text{model}}\)、\(h\)、\(h_{kv}\)、\(d_{\text{head}}\)、\(V\)、最大位置、精度方案;若为 MoE 则加专家数、每 Token 激活专家数、专家容量因子;若为混合结构则加递归状态维度与层布局。提取结果随实验存档,并绑定模型 revision(第1章 1.3.1)。

2.7.2 输入输出分离扫描

两个方向分开扫描,一次只动一个变量:

  • 固定输出、扫输入:固定 max_tokens 与实际输出长度,输入按 512 / 2048 / 8192 / 32768 变化,观察首 Token 时间与状态占用;
  • 固定输入、扫输出:固定输入,让输出自然停止,记录实际输出长度分布(不是设置上限),观察单步时间与端到端时间。

再叠加并发档位,观察单步时间随批大小的变化,验证 2.3.2 的"权重访存被摊薄"预期。

2.7.3 模型估计与实际执行对照

把估计值(逻辑字节、理论 FLOPs、带宽下界)与实测放同一张表,分栏比较:

项 逻辑/理论 实际执行 差异 可能的解释
每 Token 状态字节 128 KiB 待填 — 后端展开/压缩/共享
Prefill 计算量 57.3 + 8.8 TFLOP 待填 — 内核效率、未复用 Token
Decode 单步访存 18.29 GB(\(B=8\)) 待填 — 缓存命中、额外拷贝
输出长度 设置上限 1024 实际分布 — 提前停止

无法解释的残差本身就是诊断材料——它指向第9章的观测与第10章的假设检验。实验的目标不是证明"某阶段一定更慢",而是检验工作量模型能解释哪些变化。

无 GPU 时的支线:只做模型配置解析(E0),产出逻辑字节账本与理论工作量,不填写实际分配与实测栏。

本节要点

  • 配置字段绑定 revision;MoE 与混合结构要额外提取对应字段。
  • 输入输出分开扫描,输出长度取实际分布而非设置上限。
  • 估计与实际分栏对照;残差是后续诊断的输入。
  • 无资源时只做配置解析,不填实测栏。

常见误区

  • 误:max_tokens=1024,所以输出是 1024 Token。 正:那是上限;必须记录实际停止原因与长度分布。
  • 误:理论 FLOPs 与实测对不上,说明公式错了。 正:残差通常来自内核效率、缓存命中与调度,是诊断线索。

本节验收

  1. 所有曲线的自变量来自真实 Token 计数,而非设置上限。
  2. 你至少交付一份经典结构与一份当代结构的分项状态账本。
  3. 未测量的实际分配栏保持空白,不填推算值冒充实测。

依据与核验入口:R04 S11 S02


2.8 深化:从真实模型配置到状态字节,再到注意力执行

2.8.1 模型不能冒充任意同规模模型

参数规模相近的模型可以有不同层数、KV头数和状态结构。官方 Qwen2.5-7B-Instruct 配置给出28层、隐藏维度3584、28个查询头和4个KV头 N11。在标准GQA、头维128、BF16、全部28层均保存K/V的假设下,本书由配置推得

\[b_{token}=2\times28\times4\times128\times2=57344\text{ B}=56\text{ KiB}.\]

一条4096 Token序列为224 MiB,而不是贯穿模型的512 MiB。前者来自真实结构字段加上明确的存储假设,后者来自32层、8个KV头的独立设定。它们都可以用于,但不应在不同章节不加说明地替换。这个例子选择较早的模型,是为了提供可核查的结构基线,不是推荐它代表最新性能。

对MLA,直接读取 num_key_value_heads 后套常规双份K/V公式可能高估。DeepSeek-V3 配置中的潜在维度512、独立RoPE维度64、61层提供了另一种计算入口 N12。若执行后端每层仅缓存一份512维潜在向量和一份64维位置键,二者都用BF16,主体状态为

\[61(512+64)\times2=70272\text{ B}=68.625\text{ KiB/token}.\]

这是指定压缩缓存布局下的推导,未计MTP、暂存、索引、对齐和复制;若后端展开成逐头K/V,实际容量和流量就不是这个数。由此得到的工程顺序应当是:配置识别结构,源码或运行元数据识别布局,分配记录确认实际字节。

2.8.2 为什么MLA的存储公式和执行公式可以不同

设第 \(j\) 个注意力头的键由潜在状态 \(c_t\) 投影而来,\(k_{t,j}=W^K_jc_t\)。对暂不含位置编码的内积分数,

\[q_j^T k_{t,j}=q_j^TW^K_jc_t=(W_j^{K\,T}q_j)^Tc_t.\]

这说明可以把键投影吸收到查询侧,然后直接与潜在状态做内积,而不必把全部历史键显式展开。若值也为 \(v_{t,j}=W^V_jc_t\),则加权和满足 \(\sum_t a_tW^V_jc_t=W^V_j\sum_t a_tc_t\),同样允许改变计算次序。

这些等式解释了压缩缓存为什么有实现空间,却不能证明任意位置编码与精度路径都能如此交换。RoPE的作用位置、缩放、混合精度以及分组布局可能要求保留额外状态或选择不同内核。性能判断还要比较“避免的历史展开”与“新增的查询投影、潜在空间计算和布局转换”,不能只把潜在维度除以头数当成加速比。

2.8.3 FlashAttention减少什么,并不减少什么

FlashAttention论文将注意力优化放在分层存储和IO感知计算上 N13。用数学推导可以理解它为什么不必把整个 \(S\times S\) 分数矩阵长期放到HBM:对一行分数,把当前处理区间的最大值记为 \(m\),指数和记为 \(\ell\),未归一化的加权值和记为 \(o\)。新块分数为 \(s\)、对应值为 \(V\),则可以更新

\[m'=\max(m,\max s),\] \[\ell'=e^{m-m'}\ell+\sum e^{s-m'},\] \[o'=e^{m-m'}o+\sum e^{s-m'}V.\]

最后输出 \(o'/\ell'\)。旧块只需保留三个归约状态,不必保存所有历史分数。这是分块后维持同一softmax数学结果的关键;它并不把全注意力的有效query-key对数从平方级改成线性级。有限精度下计算次序改变,仍需按相应数值契约验收。

性能收益因此要分成三个问题:分数矩阵的中间读写减少多少;分块、流水和硬件指令是否适合该形状;引擎本次是否调用该实现。只在第二层展示一个漂亮的微基准,不能省掉第三层的路径取证,也不能证明整个业务请求按相同比例变快。

2.8.4 2026结构变化应写成状态分类,而不是产品名堆砌

本次核对的DeepSeek-V4.1-Flash模型卡描述了CED和跨层共享的CSA2;Qwen3.8-Flash-Next模型卡将Gated DeltaNet与稀疏注意力组合,并标明实验预览属性 N20。这些结构说明,经典“每层两份KV、每一步同等工作量”的假设不能无限延用。

更稳健的表达是为每类状态登记三个函数:驻留字节 \(M(S)\)、每步读取 \(Q(S)\)、更新计算 \(F(S)\)。递归状态可能使 \(M\) 对长度有界,但中间检查点仍需空间;稀疏访问可能降低 \(Q\),却未必等比例降低必须保留的 \(M\);共享层可以减少物理份数,却不保证每层都不再访问同一数据。新模型出现时更新这三个函数,比再加一行“某模型支持”更能帮助读者做工程决策。


2.9 实验图表补充:用官方模型配置补齐状态量数据

模型名称只能指向候选制品,状态账本必须落实到结构参数。X01/X02给出了两个可公开复算的配置:Qwen2.5-7B-Instruct采用28层、4个KV头、头维128;DeepSeek-V3压缩MLA按61层、512维潜在KV与64维RoPE状态计算。以下均额外假定缓存以BF16的2字节保存。

Qwen每Token逻辑KV为 \(2\times28\times4\times128\times2=57344\) 字节,即56 KiB;DeepSeek-V3为 \(61\times(512+64)\times2=70272\) 字节,即68.625 KiB。图中把上下文长度从4096增加到32768,展示相同存储假设下的线性状态增长。

读图时不要做模型排名。两者参数规模、注意力结构和执行路径都不同。本章前文128 KiB/Token仍是独立配置;不能把它与56 KiB视为同一模型优化前后。MLA也不能用“KV头数×头维”的普通GQA公式替代。

实验补录:同一长度分别记录逻辑字节数、分块后分配字节、每rank分片/复制数、allocated/reserved及设备总占用。图中只预测第一项;MTP状态、写时复制峰值、通信工作区不在此曲线中。

图2-V1 真实模型配置:随上下文增长的逻辑KV状态

图2-V1 真实模型配置:随上下文增长的逻辑KV状态

数据身份:CONFIG_DERIVED。条件:Qwen:28层/4KV头/头维128;DSV3:61层/(512+64);全部按2字节存储、单序列、未分片。

配置 Token KiB/Token MiB
Qwen2.5-7B GQA 4096 56.0 224.0
Qwen2.5-7B GQA 8192 56.0 448.0
Qwen2.5-7B GQA 16384 56.0 896.0
Qwen2.5-7B GQA 32768 56.0 1792.0
DeepSeek-V3 压缩MLA 4096 68.625 274.5
DeepSeek-V3 压缩MLA 8192 68.625 549.0
DeepSeek-V3 压缩MLA 16384 68.625 1098.0
DeepSeek-V3 压缩MLA 32768 68.625 2196.0

复算公式:GQA=2*L*h_kv*d_head*b; MLA=L*(d_c+d_r)*b。

出处与边界:X01、X02;不是模型优劣比较;不含块对齐、复制、MTP、分配器、工作区或物理显存。

数据与绘图点 · PNG · 重绘代码

2.10 第三方数据补充:KV 池占多大,长上下文又把内存推到哪

本章前面用配置推导说明"KV 随并发线性涨"。这一节补两组第三方数据:公开部署里 KV 池的真实占比,以及长上下文把内存推到什么量级。

KV 池占多大(PagedAttention/vLLM,SOSP 2023,Table 1;OPT-13B/66B/175B,GCP A2)。三个部署配置里,KV 池分别为 12 GB、21 GB、264 GB,占总显存的 30%–41%;参数量占 65%–54%。"KV Cache 是瓶颈"这句话只在特定规模与长度下成立:13B 单卡部署时,权重仍是最大那一项。这也是为什么"把池内利用率提上去"(分页管理)比"单纯加显存"更先被采用。

一处必须同时标注的原文矛盾。该论文正文写 OPT-13B 每 token KV 为 800 KB,但它自己的算式 2×5120×40×2 B ≈ 1.6 MB,相差 2 倍。本书引用该表时两个数都不单独采信,只把它当作"每 token KV 在百 KB 到 MB 量级"的佐证。

长上下文推到哪(KVQuant,arXiv:2401.18079v3,Table 7/8;FP16 KV,batch=1,闭式估算)。LLaMA-7B 在 32K/128K/1M 上下文下的 KV 估算为 16 GB/64 GB/512 GB。1M 上下文的 512 GB 远超单卡 80 GB——这张图的价值不在精确值,而在量级:长上下文不是"开个参数",而是把内存需求推到必须量化或多卡分片的地步。同文给出 int4 量化后 128K 可降至 16 GB。

边界:Table 1 的 KV 池是部署配置推算,非 nvidia-smi 隔离实测;KVQuant 原表标题明确是 estimates(闭式公式),且只有 BS=1 与 BS=4 两点,没有随并发的连续曲线。

图2-T1 KV 池不是整卡显存:三个规模的公开部署配置(第三方实测部署)

图2-T1 KV 池不是整卡显存:三个规模的公开部署配置(第三方实测部署)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:PagedAttention/vLLM(SOSP 2023)Table 1;OPT-13B/66B/175B;GCP A2 实例;KV 池为部署配置推算(总显存−权重−开销)。

模型规模 参数量/GB KV池/GB 总显存/GB 最大KV槽位
13B(1×A100 40GB) 26 12 40 15.7K
66B(4×A100) 132 21 160 9.7K
175B(8×A100-80GB) 346 264 640 60.1K

出处与边界:Z03;【原文数值自相矛盾】正文称 OPT-13B 每 token KV 为 800 KB,但其自身算式 2×5120×40×2 B ≈ 1.6 MB,相差 2 倍,引用时须同时标注。KV 池为部署配置推算,非 nvidia-smi 隔离实测。

数据与绘图点 · PNG · 重绘代码

图2-T2 KV 随序列长度线性增长:估算表而非实测(第三方估算)

图2-T2 KV 随序列长度线性增长:估算表而非实测(第三方估算)

数据身份:THIRD_PARTY_ESTIMATE(第三方公开数据)。条件:KVQuant(arXiv:2401.18079v3,NeurIPS 2024)Table 7/8;FP16 KV,batch size 1;闭式公式估算。

模型 32K/GB 128K/GB 1M/GB
LLaMA-7B 16 64 512
LLaMA-13B 25 100 800
LLaMA-30B 49 195 1560
LLaMA-65B 80 320 2560

出处与边界:Z04;【非实测】原表标题明确为 estimates;仅 BS=1 与 BS=4 两点,无随并发的连续曲线;未评测精度。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 相同激活参数的 Dense 与 MoE 为什么不能直接等价比较?

因为三条账不同:Dense 的计算、存储、传输都由同一组参数决定;MoE 的计算按激活参数、存储按总参数、传输按路由量。2.5.1 的算例里,激活 8B 而驻留 160 GB(80B 总参数的 BF16),线性计算项按激活参数、未卸载驻留按总参数,相差 10 倍。此外 MoE 还有每步同步的路由通信与专家偏斜,这些在稠密模型里不存在。因此只能用"参与计算 / 占用存储 / 发生传输"三张清单分别比较,不能用一个参数量换算。

2. 推测解码中"每轮延迟"和"每 Token 时间"如何区分?

"每轮延迟"是一次验证迭代的墙钟时间(含提出候选、一次前向、接受判定),"每 Token 时间"是最终被接受的输出 Token 分摊到的时间。若每轮最终推进 \(m\) 个 Token(含校正或额外 Token)、每轮耗时为普通一步的 \(c\) 倍,则每 Token 相对成本为 \(c/m\)。\(m\) 由接受率决定(\(k=4,\alpha=0.7\) 时 \(m\approx2.77\)),而 \(c\) 是完整验证轮相对普通一步的实测成本,不预设其固定值。只看每轮延迟会漏掉接受率,只看接受率会漏掉验证成本,两者都要报;被拒绝的 Token 不进产出分子但要进成本分子。

3. 模型卡用 FP4 而后端记录不同存储格式时,状态预算应该引用哪一层?

引用实际执行层——即后端真正分配与访问的格式,因为它决定显存占用与带宽消耗;同时登记模型卡的设计意图与两者差异,作为版本变化时的复核抓手(2.4.5)。资料记载中存在这种不一致:模型卡描述模型侧的低精度主缓存,而引擎发布说明记录特定硬件路径上的另一种分块低精度存储 SOURCE_REGISTERED S02。这些是页面快照(runtime_verified=false),可以用来说明"模型设计与后端落实必须分开看",但不能用来推算未经运行的显存或吞吐。


本章验收清单

节 验收标准
2.1 能区分前向次数、解码迭代次数与最终接受 Token 数;能用接受率与相对成本估算推测解码收益区间
2.2 能分别解释"输入更长"与"实际需要重算的输入更多";能把线性项与 \(S^{2}\) 项分开
2.3 能为同一模型写出至少两种 Decode 瓶颈假设并说明区分它们的测量
2.4 能指出常规 KV 公式何时适用、何时必须替换;模型配置可追溯;共享对象只按物理身份计一次;未知后端布局不靠模型名猜测
2.5 能分别估计计算量、权重驻留与会话状态;不把 MoE 简化为同激活参数 Dense 模型
2.6 能把模型服务时间与外部工具时间分开,并同时报告格式或任务质量
2.7 所有曲线的自变量来自真实 Token 计数;未把设置上限当成实际生成长度;未测量的实际分配栏保持空白

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第3章 计算、显存、带宽与通信模型

核心论点:性能模型用于给出有条件的边界与可检验预测,不是把理论峰值包装成业务吞吐。

本章使用的设备参数

为避免"峰值算术"脱离单位,全章统一使用一组显式声明的参数(非任何具体产品):

参数 取值 说明
稠密 BF16 峰值算力 \(F_{\text{peak}}\) \(1.0\times10^{15}\) FLOP/s(1000 TFLOP/s) 仅用于单位换算演示
设备带宽 \(B_{\text{peak}}\) \(2.0\times10^{12}\) B/s(2000 GB/s) 十进制单位
由此得到的 ridge point \(F_{\text{peak}}/B_{\text{peak}} = \mathbf{500}\) FLOP/byte 计算受限与访存受限的分界

ridge point 是两条理想吞吐上界的交点。强度低于 500 FLOP/byte 时,带宽屋顶更低;高于它时,计算屋顶更低。这只是在给定计量层级和流量假设下判断哪个上界更紧,不能排除启动、依赖、通信或低占用率把实际性能压在两条屋顶之下。真实设备须按有效精度与实际资源重新建模。


3.1 资源清单:单卡、服务器、互联域与资源池

本节要回答的问题:哪些资源数字真正属于这次部署?

规格书给的是边界条件,实际分配到这个实例的手才是当前的账。要记录的不只是加速器:

资源 必须记录 常见遗漏
加速器 型号、数量、可见性(是否独占、是否 MIG/虚拟化切分)、驱动与运行时版本 共享租户
主机内存与 CPU 配额、NUMA 绑定、容器限制、是否超售 CPU 配额被限但不报错
存储 权重所在介质、读取带宽、是否有本地缓存层 冷启动被网络盘拖慢
网络 网卡、到服务端的路径、是否跨机房 流式返回被代理缓冲
互联 卡间拓扑(是否同一 scale-up 域)、跨节点链路 把机柜带宽当成节点内带宽

一个易被忽略的事实:容器里看到的"可用资源"可能与实际独占性不一致。显存可能被其它实例共享、CPU 配额可能被限流而不产生错误、网络路径可能经过额外的代理层。因此资源账至少分三列:额定规格 / 运行观测 / 人工假设,任何未实测的项都要标为假设。

产品形态与成熟度也要分开登记:公开发布(GA)、维护版、预览(Preview/RC)、路线图与"已可获取"是五种不同状态,预览与路线图不能作为容量承诺的依据。以整系统级平台为例,机柜级产品资料会给出卡数、机柜内互联域与跨节点网络的边界,可用来练习"整系统资源账本",但产品页规格不构成某个时间点的完整产品排行榜。成熟的上一代环境(如 H100 一类)仍可继续作为可重复基线 SOURCE_REGISTERED S22。

依据与核验入口:R17 R30 S22


3.2 计算量、数值精度与有效算力

本节要回答的问题:FLOPs、FLOP/s 与实际执行工作量如何对应?

3.2.1 计数约定

先统一计数,否则所有比较都无效:一次乘加(MAC)记为 2 FLOPs。于是形状为 \([M,K]\times[K,N]\) 的 GEMM:

\[ W = 2MKN \quad [\text{FLOP}] \]

稠密与稀疏的峰值不能混用。某些硬件提供结构化稀疏(如每四个元素保留两个非零的 2:4)的加速路径,其标称峰值可达稠密的 2 倍,但这只在权重满足相应稀疏布局、且内核真正走该路径时成立。用稀疏峰值解释稠密执行,是最常见的一类"理论对不上实测"。

3.2.2 四项精度要分开写

"这个模型是 FP8"或"4 bit"这类说法承载不了工程判断,必须分四项:

项 含义 为什么重要
存储精度 权重在磁盘/显存中的表示 决定驻留与加载量
激活精度 参与矩阵乘的输入表示 决定 GEMM 走哪条内核
累加精度 中间累加的数值类型 决定长上下文的误差累积
KV/状态精度 缓存状态的表示 决定状态容量(第2章 2.4)

不能从权重文件的扩展名推断全部执行精度:量化制品可能带缩放因子、可能排除某些层、也可能在加载时被反量化成另一精度参与计算。判断依据是实际内核与配置回显(第1章 1.5.3)。

3.2.3 分块缩放、填充与额外字节

低精度格式大多带缩放元数据,因此"标称位宽"不等于"实际位宽"。以分块缩放格式为例(需按所选版本核验块大小):若每 16 个元素共享一个 8 bit 缩放因子,则

\[ b_{\text{eff}} = 4 + \frac{8}{16} = \mathbf{4.5\ \text{bit/元素}} \]

元数据占 \(0.5/4.5 \approx \mathbf{11.1\%}\)。相应地,块越大元数据占比越低,但量化误差通常上升——这是精度与开销的交换,不是一个可以只看标称位宽决定的问题 SOURCE_REGISTERED。

还有两类"额外字节"要计入:填充(形状对齐带来的浪费)与临时展开(某些结构在执行时把压缩表示展开成更大的张量)。第2章 2.4.2 已说明压缩存储与执行布局可能不同,本节只需记住:容量与带宽账要按执行时的实际张量算,而不是按磁盘上的字节数算。

3.2.4 有效算力:一步 GEMM 的完整演算

取第2章 Decode 中的一个隐藏层 GEMM:权重 \([K=4096]\times[N=4096]\),本步 \(M=8\) 个 Token,BF16(\(b=2\)):

\[ W = 2\times8\times4096\times4096 = \mathbf{268{,}435{,}456}\ \text{FLOP} \approx 0.268\ \text{GFLOP} \]

理想访存(权重 + 输入激活 + 输出,各按 \(b=2\) 字节):

\[ Q \approx 4096\times4096\times2 \;+\; 8\times4096\times2 \;+\; 8\times4096\times2 = 33{,}554{,}432 + 65{,}536 + 65{,}536 = \mathbf{33{,}685{,}504\ \text{B}} \]

运算强度:

\[ I = \frac{W}{Q} = \frac{268{,}435{,}456}{33{,}685{,}504} \approx \mathbf{7.97\ \text{FLOP/byte}} \]

(这与"权重主导时 \(I\approx 2M/b = M\)"的直觉一致:\(M=8\) 时约 8。)

对照本章设备的 ridge point \(500\) FLOP/byte:\(7.97 \ll 500\),这一步明显在访存受限区。它的时间下界取两者之大:

\[ T \ge \max\left(\frac{W}{F_{\text{peak}}},\ \frac{Q}{B_{\text{peak}}}\right) = \max\left(\frac{2.684\times10^{8}}{1.0\times10^{15}},\ \frac{3.369\times10^{7}}{2.0\times10^{12}}\right) = \max(0.268\ \mu\text{s},\ \mathbf{16.84\ \mu\text{s}}) \]

于是这一步在上述理想流量假设下的吞吐上界为

\[ F_{\text{roof}} = \frac{W}{T_{\min}} = \frac{2.684\times10^{8}}{16.84\times10^{-6}} \approx \mathbf{15.9\ \text{TFLOP/s}} \]

该带宽屋顶对应约 15.9 TFLOP/s,即所设计算峰值的 1.6%,并非已经测得的有效算力。对方阵 \(K=N=d\)、BF16、计入权重与输入输出激活的模型,有

\[I(M)=\frac{2Md^2}{2d^2+4Md}=\frac{Md}{d+2M}.\]

当 \(d=4096\)、目标 \(I=500\) 时,\(M=500\times4096/(4096-1000)\approx661.50\),所以最小整数是 662。忽略激活时得到的 \(M\approx500\) 只是权重主导近似;接近交点时必须恢复被忽略项。真实计算受限的切换点还受实际带宽、算力、布局与效率约束。

三个必须一并说明的限制:这是理想下界(忽略 L2/片上复用、内核效率、排队与同步);实际 DRAM 流量必须测量,缓存命中与重复加载可能使其低于或高于本近似(第2章 2.3.1);有效算力必须用与估算相同的计数约定才能与估算值比较。

依据与核验入口:R21 S10 S22


3.3 Roofline:计算与数据移动的约束

本节要回答的问题:什么时候值得优化算术,什么时候应该减少搬运?

3.3.1 运算强度

运算强度定义为针对某个存储层级统计的比值:

\[ I = \frac{W\ [\text{FLOP}]}{Q\ [\text{byte}]} \]

关键在于 \(Q\) 统计在哪一层:是设备显存(HBM)、还是片上缓存、还是跨节点网络。同一个算子在不同层级上强度不同,从而可能在不同层级分别受限。写作时必须写明层级,否则 Roofline 图无法解释。

3.3.2 两个时间下界取大,两个吞吐上界取小

\[ T \;\ge\; \max\left(\frac{W}{F_{\text{peak}}},\ \frac{Q}{B_{\text{peak}}}\right) \]

它给出的是不可逾越的下界,不是预测值。3.2.4 已演示:访存项(16.84 µs)比计算项(0.268 µs)大两个数量级,因此优化方向是"减少搬运或提高复用",而不是"让 GEMM 算得更快"。

同理,第2章 Prefill 的注意力项与线性项也可以用同一张图思考:大 \(M\)(长输入、大批)时强度高、趋近计算受限;Decode 的 \(M\) 小、强度低、趋近访存受限。两阶段落在 Roofline 的不同区域,这是它们优化手段不同的根本原因。

3.3.3 模型遗漏了什么

Roofline 只覆盖计算与某一层的数据移动,它不解释以下开销,报告时必须显式列出:

遗漏项 表现
排队与批组成 设备忙但单个请求的进展慢
同步与通信 时间线上出现非计算的等待
内核启动与小形状开销 短内核的执行时间远高于字节/带宽估算
片上复用与缓存命中 实际 HBM 流量小于原估算,应重算下界,而不是声称突破正确下界
padding 与对齐 实际搬运字节多于理论

还有一个常被颠倒的推论:库的 IO 复杂度更低,不自动保证端到端更快。算法的数据移动模型、内核库实现、以及引擎实际 dispatch 到哪条路径是三层不同的事;必须先在 trace 中确认调用了哪个实现,再把实际形状与精度放回强度模型 SOURCE_REGISTERED S09。同理,一个高利用率百分比不能证明"贴近上界"——3.2.4 的例子里设备很忙,有效算力却只有峰值的 1.6%。

依据与核验入口:R22 S08


3.4 显存预算:权重以外还有什么

本节要回答的问题:为什么权重能装下不等于服务能稳定运行?

3.4.1 逐项预算

沿用第2章的 7B BF16 模型、4096 上下文、批 8,把账本补齐(需按环境替换):

项 计算 容量
权重 \(7\times10^{9}\times2\) 14.00 GB
KV 状态 \(128\ \text{KiB}\times4096\times8 = 4\ \text{GiB}\) 4.29 GB
工作区与激活缓冲 假设 1.50 GB
通信缓冲 假设 0.50 GB
图捕获副本 假设 0.80 GB
小计 21.09 GB
分配器碎片与安全余量 10% 23.20 GB

(\(14+4.29+1.5+0.5+0.8 = 21.09\);\(\times1.1 = 23.20\) GB。)

在一张 24 GB 设备上余量只剩 0.80 GB,运行时很容易触到上限;在 40 GB 设备上则宽松得多。这张预算表只说明 24 GB 设备余量偏薄,不能推出组合一定装不下——容量是负载的函数,不是模型尺寸的函数。

3.4.2 初始化峰值与稳态不同

加载期、预热期与稳态期的峰值并不相同:加载时可能同时存在磁盘缓冲、权重暂存与转换中间量;预热时图捕获与形状桶会额外占用;稳态才收敛到可复用水平。因此预算要分阶段采样,而不是只报一个数。

某些内存被预留但并非全部活跃使用——因此必须区分三种口径:allocated(进程向运行时申请)、reserved(运行时缓存未归还设备)、设备占用(驱动视角)。用其中一个解释另一个,是容量争议的常见来源。

3.4.3 共享对象与常驻代价

跨层共享或多请求共享的状态按物理存储身份去重(第2章 2.4.4),不能按引用次数重复计入。反过来,权重常驻(快速启动一类机制)带来启动收益的同时,也带来常驻资源代价与失效恢复路径,收益与代价必须分列登记 SOURCE_REGISTERED。

依据与核验入口:R04 R31 S02


3.5 主机、互联与通信下界

本节要回答的问题:哪些等待不在加速器计算里?

3.5.1 主机供给与 NUMA

主机侧的三类成本常被算进"模型时间":预处理(分词、模板展开、媒体解码)、批组装与调度、以及输出序列化。此外还有 NUMA 与跨 socket 访存、容器 CPU 配额被限流、以及页错误/换页。判断方法见第9章 9.3:时间关联 + 干预,而不是资源占用率。

3.5.2 传输与集合通信

通信的最简模型是"启动开销 + 字节/带宽":

\[ T_{\text{comm}} \approx \alpha + \frac{M}{B_{\text{link}}} \]

其中 \(\alpha\) 为每次同步的固定开销(µs 级),\(M\) 为消息字节,\(B_{\text{link}}\) 为该链路的有效带宽。取参数 \(\alpha=6\ \mu\)s、\(M=256\) KiB、\(B_{\text{link}}=200\) GB/s:

\[ T_{\text{comm}} \approx 6 + \frac{262144}{2.0\times10^{11}}\times10^6 = 6 + 1.31 = \mathbf{7.31\ \mu s} \]

字节传输只占 1.31 µs,而固定开销占 6 µs——这说明小消息的通信由延迟主导,不是带宽主导。于是:

单步时间 每步 8 次同步的固定启动项占比 说明
500 µs(小模型/小批) \(48/500 = \mathbf{9.6\%}\) 同步开销显著
2000 µs(大批或长输出) \(48/2000 = \mathbf{2.4\%}\) 被摊薄

这就是"低字节量的频繁同步也可能拖慢单请求"的机制:频率比字节数更重要。批量增大后占比下降,也解释了为什么同一套并行配置在轻载与满载下的表现不同。

3.5.3 域内、跨域与远程状态

必须区分三类路径,它们的数量级差异很大:

路径 典型形态 记账方式
主机 ↔ 设备 PCIe/一致性互联上的拷贝 记入冷启动与每步输入
设备 ↔ 设备(域内) 同一 scale-up 域内的集合通信 记入每步同步
跨节点网络 以太网/RDMA 记入跨节点执行与远程状态访问

机柜级产品会给出"机柜内汇总显存"一类的指标,便于理解 scale-up 域的容量边界,但汇总容量不等于无代价的统一内存:跨域访问仍然要穿过网络,带宽与延迟相差数量级,并且可能有副本与一致性开销 SOURCE_REGISTERED。通信字节应按上述三条边界分别记账,不能合并成一个"网络流量"。

依据与核验入口:R08 R30 S14


3.6 端到端组合与局部优化上限

本节要回答的问题:一个算子加速后,整体最多能改善多少?

3.6.1 Amdahl 式估算

若某段在基线中占时间比例 \(p\),该段被加速 \(s\) 倍,且其余阶段耗时不变、各阶段互斥,则

\[ T_{\text{new}} = T_{\text{base}}\left[(1-p) + \frac{p}{s}\right] \qquad S_{\text{overall}} = \frac{1}{(1-p) + p/s} \]

算例:

占比 \(p\) 局部加速 \(s\) 整体加速比 总延迟下降
10% 2× 1.0526× 5%
40% 2× 1.25× 20%
5%(另设的实际暴露占比) 2× 1.0256× 2.5%

“速度提高 5.26%”与“延迟降低 5%”不是同一个百分比。第三行只演示暴露占比已经由依赖图确定为 5% 的情况,不能把任何“重叠一半”的活动都机械折半。若一个占关键路径 8% 的阶段提速三倍,总延迟下降 \(0.08(1-1/3)=5.33\%\),整体加速约 1.0563×。

3.6.2 两个假设必须写在结论里

  1. 各阶段互斥:若存在重叠,被优化的那部分可能根本不在关键路径上(第三行演示了占比减半的后果)。引用第1章的结论时尤其要注意:连续批处理让不同请求的工作在同一调度迭代中被组织,不等于它们的执行时间可以相互覆盖。
  2. 其余阶段不变:这在真实服务里往往不成立。局部变快会改变批组成、显存压力与排队行为;第9章 9.5.4 已说明,开环负载下服务时间下降通常带来端到端改善,而在闭环或自适应到达下收益可能被吸收。

因此这类估算的正确用法是给出上限与采用门槛,而不是给出承诺值。最终判断仍然回到端到端对照(第8章)。

依据与核验入口:R17


3.7 实验:从估算到校准

本节要回答的问题:偏差是参数错误、模型遗漏,还是测量问题?

3.7.1 先登记预测,再看结果

测试前写下三类预测:工作量(FLOPs、访存字节)、容量(逐项预算)、时间区间(下界与期望区间)。预测必须带上参数来源标签:规格 / 微基准 / 拟合 / 假设。先登记后跑是为了避免"看到结果再倒推参数"的拟合幻觉。

3.7.2 残差按来源分类

示例:3.2.4 那一步 GEMM 的理想下界为 16.84 µs,观测为 21.3 µs,残差 +4.46 µs(+26.5%)。残差只能归为三类并逐条取证:

类别 候选原因 下一步取证
参数错误 实际带宽低于标称、时钟/功耗限制 微基准实测带宽与频率
模型遗漏 额外拷贝、L2 未命中、padding、同步 计数器与 trace(第9章)
测量问题 计时点选错、异步未同步、跨域时钟 复测并核对时间域

残差不能通过"调参数"消化掉:只有能独立验证的参数才允许调整,否则就是把拟合当成校准。

3.7.3 只在覆盖区间内校准

校准点与验证点必须分开:用一部分负载点拟合,用留出的负载点检验预测能力。对高负载、不同拓扑、不同并行度外推时必须重新核验——几个贴合点不能证明模型普适。

无设备时的可行工作:校验单位与配置、用公开资料登记整系统资源账本、完成显存与工作量预算表;资料规格不作为校准点,也不把峰值规格当作容量结论。

依据与核验入口:R22 S21


3.8 深化:分层Roofline、逐算子下界与模型失配

3.8.1 先按依赖分段,再合成屋顶

对一个计算阶段,\(\max(F/P,Q/B)\) 表示理想情况下计算和搬运可重叠时的下界。对两个严格串行且性质不同的阶段,应先分别求界,再求和:

\[T_{serial}\ge\sum_j\max(F_j/P_j,Q_j/B_j).\]

把全部FLOPs和全部字节先相加,再求一次最大值,得到的仍可能是一个更松的乐观界,却丢掉了瓶颈交替。反例:阶段A的计算下界10 ms、访存下界1 ms,阶段B为1 ms和10 ms。逐段下界合计20 ms,聚合后却只有11 ms。差出的9 ms不是实现低效,而是聚合模型错误地允许两个串行阶段互相覆盖。

同理,HBM、L2、共享内存和跨设备链路不是同一份带宽预算。一个算子可以在HBM层复用良好,却受片上访存或指令发射限制;系统也可能在计算屋顶之下先遇到主机供给限制。只画一张HBM Roofline并不能完成诊断,但它能帮助排除一些不可能的解释。

图3-R1 GEMM的理想Roofline与精确交点

图中采用本章的设备参数,不对应具体产品。曲线由算术强度公式生成,不是采样点。配套CSV同时保存 \(M\)、FLOPs、字节和屋顶值,便于读者更换参数后重画。

3.8.2 峰值、可持续带宽与工作量效率必须分开

若使用厂商峰值带宽 \(B_{peak}\),\(Q/B_{peak}\) 是更乐观的界;若使用同种访问模式微基准的持续带宽 \(B_{sustained}\),则是校准模型的输入。两者不能混名。对不同访问粒度、读写比例或并发规模,持续带宽也可能不同;拿大块连续拷贝校准不规则分页读取,仍可能留下系统性误差。

计算端同样如此。\(2P\) 每Token是一种工作量近似,不是实际发射指令数。稀疏跳过、填充、重算、路由、归一化与输出投影会使真实工作量不同。为了发现差异,应同时记录“有用工作量”和“执行工作量”:前者衡量任务所需,后者解释资源实际消耗。低精度或稀疏硬件峰值只能与其适用的数据路径匹配,不能将稠密工作量除以稀疏峰值来制造更短时间。

3.8.3 用残差定位模型遗漏,不用一个效率系数掩盖一切

可以从 \(T=\alpha+F/P_{eff}+Q/B_{eff}\) 这样的校准式开始,但它并不自动描述计算搬运重叠。拟合前需声明这是串行近似、分阶段模型还是经验预测模型,避免把拟合参数再解释成物理峰值。

设预测误差 \(e_i=T_i^{obs}-T_i^{pred}\)。若残差随小批量增大,可能遗漏了固定启动开销;随上下文增长而增大,可能遗漏了状态读取或稀疏索引;只在特定形状突然跳变,优先检查内核选择、padding或图回退。若用一个“统一效率0.6”把所有误差压平,就失去了这种判别力。

校准还应保留训练形状与留出形状,分别报告误差分布。对留出的大上下文或不同并行布局,模型参数未必可迁移。真正有用的性能模型不是处处给出一个精确数字,而是能说明哪些区间已校准、哪些属于外推,以及一个新观测能推翻哪个假设。


3.9 实验图表补充:用官方规格校准Roofline的量纲

前面用的 1000 TFLOP/s、2000 GB/s 是便于推导的设备,不是某张卡的事实。本节补入NVIDIA官方SXM规格作为量纲锚点:A100 80GB SXM稠密BF16为312 TFLOP/s,HBM带宽2039 GB/s;H100 SXM官网1979 TFLOP/s带有“含稀疏”脚注,按两倍稀疏倍率换算的稠密近似值为989.5 TFLOP/s,HBM带宽3.35 TB/s。

统一十进制单位后,理想Roofline为 \(P(I)=\min(F_{peak},B_{peak}I)\)。两者交点约153.02与295.37 FLOP/byte。交点是标称资源屋顶的相交位置,不是Prefill/Decode的固定分界,更不是模型到这个长度就会自动切换瓶颈。

为什么不在图上补“实测点”:没有同精度、同形状的算子运行记录,就不知道实际访存量、有效带宽、launch开销和同步成本;凭利用率百分比构造点位会把假设伪装成证据。下一次实验应先选3—5个矩阵形状,固定累加精度、布局和预热,记录运行时间及字节口径,再把点叠在屋顶下方。

图3-V1 规格给出的只是屋顶:稠密BF16 Roofline

图3-V1 规格给出的只是屋顶:稠密BF16 Roofline

数据身份:SPEC_DERIVED。条件:A100 312TFLOP/s与2.039TB/s;H100约989.5TFLOP/s与3.35TB/s;十进制单位。

设备 BF16稠密 TFLOP/s TB/s 交点 FLOP/byte
A100 80GB SXM 312 2.039 153.02
H100 SXM 989.5 3.35 295.37

复算公式:roof=min(F_peak,B_peak*I)。

出处与边界:X03、X04;H100官网1979为含稀疏峰值;此图不能解释实机利用率或服务吞吐。

数据与绘图点 · PNG · 重绘代码

3.10 第三方数据补充:标称峰值的两个折扣:指令路径与访存层级

本章前面用官方规格算 roofline。这一节补第三方指令级微基准,把"标称峰值"再打两个折扣。

折扣一:用哪条指令(Hopper 架构剖析,arXiv:2402.13499,Table V/VII/VIII)。显存带宽普遍能到标称的 90%–92%(RTX 4090 92%、A100 90%、H800 91%),属于"标称可信"的量级。但 Tensor Core 的 mma 指令在 Hopper 上平均只到 62.9%,而同卡的 wgmma 指令超过 95%。同一个"峰值算力"数字,换一条指令路径就能差约 1.5 倍。用标称值画出的 roofline 屋顶,必须再乘一个"用哪条指令"的折扣。

折扣二:访存层级的代价(同文 Table IV,cycles)。Shared Memory 约 29–30、L1 约 38–43、L2 约 261–273、Global 约 466–541。从片上到显存,延迟差约 18 倍,其中 Global 比 L2 又高约 2 倍。这解释了 FlashAttention 这类 IO-aware 算法为什么收益巨大:它没有减少计算,而是把访存从 Global 挪到了片上。也说明"带宽够不够"和"延迟忍不忍得住"是两个独立问题。

一处必须披露的硬件身份。被测的 Hopper 卡是 H800 PCIe(NVLink 被削减、350 W 功耗墙),不是 H100 SXM;因此 62.9% 这个数不能读作"H100 的 Tensor Core 达成率"。A100 的 95% 原文是"超过 95%"的表述。

边界:单 SM/warp 级微基准;两篇论文测量方法不同,跨论文的 cycles 值不完全可比;cycles 与 ns 不可直接换算。

图3-T1 标称不等于可达:第三方微基准的达成率(第三方实测)

图3-T1 标称不等于可达:第三方微基准的达成率(第三方实测)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:Hopper 架构剖析(arXiv:2402.13499v1)Table V/III/VII/VIII;CUDA 12.1/12.2;指令级微基准。

项目 RTX 4090 A100 H800
显存带宽达成率/% 92 90 91
Tensor Core mma 达成率/% — 95 62.9

出处与边界:Z05;【重要】被测 Hopper 卡是 H800 PCIe(NVLink 削减、350W 功耗墙),不是 H100 SXM;未测 H100 SXM 的 HBM3 3352 GB/s 达成率;A100 的 95% 为"超过 95%"的表述。

数据与绘图点 · PNG · 重绘代码

图3-T2 存储层级的代价:四个数量级的延迟差(第三方实测)

图3-T2 存储层级的代价:四个数量级的延迟差(第三方实测)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:Hopper 架构剖析(arXiv:2402.13499v1)Table IV;另与 Ampere 剖析(arXiv:2208.11174,A100 Global 290 / L2 200 / L1 33 / Shared 23-19)互为交叉参考。

层级 RTX 4090/cycles A100/cycles H800/cycles
Shared Memory 30.1 29.0 29.0
L1 Cache 43.4 37.9 40.7
L2 Cache 273.0 261.5 263.0
Global Memory 541.5 466.3 478.8

出处与边界:Z05;单 SM/warp 级微基准;两篇论文的测量方法不同(Hopper 用 P-chase 变体),跨论文数值不完全可比。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 一个占总时间 10% 的算子提速两倍,串行假设下整体如何变化?

按 \(S=1/[(1-p)+p/s]\):\(p=0.10\)、\(s=2\) 时 \(S = 1/0.95 = \mathbf{1.0526}\),即端到端改善约 5.26%,而不是 10%。若这 10% 中有一半并不在关键路径上(与其它段重叠),有效占比降到 5%,改善只剩 2.56%。所以拿到"内核快 2 倍"这类结论时,第一件事是问它占端到端的多少、且是否互斥。

2. 权重除以 HBM 带宽何时只能当粗下界?

\(W/B\) 只对"权重必须整体读一遍、且完全无复用"的情形成立。它在以下情况会失真:批量较大时权重被多个 Token 摊薄(按 3.2.4,达到 ridge point 需要约 500 个 Token);存在片上缓存命中或预取;实际 DRAM 流量高于理想流量(额外拷贝、临时缓冲、未命中);以及并发排队与同步占据的时间未被计入。它是下界,不是预测值,用于判断"这个配置至少不可能快于多少"。

3. 机柜汇总显存为何不能直接当作无代价统一内存?

因为"汇总容量"描述的是可寻址的容量上界,而不是访问代价。跨出 scale-up 域的访问要穿过网络,带宽与延迟与域内相差数量级,还可能引入副本、一致性与传输开销 SOURCE_REGISTERED。所以正确做法是:把域内带宽/延迟与跨域带宽/延迟分别登记,把远程状态访问单独计时,容量账与传输账分开写。


本章验收清单

节 验收标准
3.1 资源账分额定/观测/假设三列;能指出是否独占、拓扑是否确认、哪些参数未验证
3.2 能用同一计数约定复算 FLOPs 与访存字节;四项精度分开登记;能审出稀疏峰值误用与精度不匹配
3.3 能说明数据移动统计在哪一层;能列出模型未包含的开销;不把高利用率当成贴近上界
3.4 容量预测含工作区与余量;分阶段(加载/预热/稳态)采样;区分 allocated/reserved/设备占用
3.5 能按主机—设备、域内、跨节点三条边界分别记账;能解释低字节量高频同步的影响
3.6 局部倍数不直接等同整体倍数;写明互斥与"其余不变"两个假设
3.7 预测先登记;残差按三类归因且只用可验证参数调整;留出验证点,外推区重新核验

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第4章 KV Cache 与容量管理

核心论点:KV Cache 是计算复用、内存分配和请求调度的交汇点;容量、命中和收益必须分开衡量。

本章贯穿的模型参数

参数 取值 来源
层数 \(L\) / 隐藏维度 \(d_{\text{model}}\) 32 / 4096 第2章
键值头数 \(h_{kv}\) / 头维度 \(d_{\text{head}}\) 8 / 128 第2章
常规 KV 每 Token 128 KiB 第2章 2.2.3
4096 Token 序列 512 MiB 同上

4.1 从模型结构计算状态规模

本节要回答的问题:一个请求究竟保留了多少状态?

4.1.1 建立状态字段,而不是套一个公式

第2章给出的 \(b_{\text{token}} = 2\,h_{kv}\,d_{\text{head}}\,L\,b\) 只适用于常规注意力。真实系统要按状态条目记账,每条记录八个字段:

字段 含义
state_id 物理存储身份(用于去重)
state_kind 主 KV / 压缩潜在 / 稀疏索引 / 递归状态 / 暂存张量
owner 属于哪个请求、会话或分支
growth_rule 随上下文增长 / 常数 / 按需
dtype 与 layout 精度与实际布局
shared_by 被哪些层或哪些请求共享
shard_rule 按什么维度分片、是否复制

按这八个字段逐项求值、按物理身份去重之后再加总,才能得到可核对的容量账。合并成一句"KV 多大"会丢掉共享、复制与压缩三件事。

4.1.2 分支生成改变状态数量

多分支(树搜索、并行采样、共享历史分支)是容量账最容易算错的地方。算例:共享前缀 \(H=2048\) Token,分出 4 个分支,每分支新增 \(U=512\) Token:

方案 状态总量 说明
共享前缀 + 4 份分支增量 \(128\,\text{KiB}\times(2048 + 4\times512) = 128\,\text{KiB}\times4096 = \mathbf{512\ MiB}\) 前缀只存一份
四个分支各自独立存储 \(4\times128\,\text{KiB}\times2560 = \mathbf{1280\ MiB}\) 前缀被存 4 次

共享节省 \(1280-512 = \mathbf{768}\) MiB,即 60%。注意前提:共享要求分支在分叉点之前的状态完全一致(同一模型、同一模板、同一位置处理),否则不能共享——这正是 4.3 的主题。

4.1.3 每 rank 实际分配不等于总量除以设备数

这是章末问题 1 的答案。KV 的分片通常按键值头维度进行,因此能否整除取决于 \(h_{kv}\) 与并行度 \(TP\) 的关系:

配置 KV 总量 每 rank 占用 是否为"总量 ÷ TP"
\(h_{kv}=8,\ TP=8\) 4 GiB 0.5 GiB 是(每 rank 1 个 KV 头)
\(h_{kv}=4,\ TP=8\) 4 GiB 1.0 GiB(1024 MiB) 否(头数不足,需复制 2 份)

第二种配置下每 rank 是"总量 ÷ 4"而不是"总量 ÷ 8",比按设备数均分多一倍。此外并行实现可能采用不同的切分维度(序列维、头维、或混合),具体行为必须按所选版本核验。

依据与核验入口:R02 R05 S11 S02


4.2 分页、块分配与碎片

本节要回答的问题:逻辑连续的上下文为什么不必物理连续?

4.2.1 逻辑块与物理块

分页管理把"逻辑 Token 序列"映射到"物理块序列":一个请求的上下文由若干块组成,块之间不必连续。这带来三个直接后果:按需分配(不为最长长度预留)、块级共享(相同前缀可指向同一物理块)、以及块级回收。

分页改善的是管理方式,不会消除状态本身所需的空间——这是最常见的误解:开了分页不等于显存够用。

4.2.2 块大小的权衡

块大小 \(B\)(每块的 Token 数)决定内部碎片与元数据规模。以 128 KiB/token 为例:

块大小 \(B\) 单块容量 每序列最大内部碎片 相对 4096 Token 有效量的最坏浪费上界
16 2 MiB \((B-1)\times128\,\text{KiB} = \mathbf{1.88\ MiB}\) \(15/4096 = \mathbf{0.37\%}\)
64 8 MiB \(63\times128\,\text{KiB} = \mathbf{7.88\ MiB}\) \(63/4096 = \mathbf{1.54\%}\)

上表最后一列是量级比较的最坏上界,不是长度恰为 4096 的实际碎片;4096 同时被 16 和 64 整除,实际尾块浪费均为零。一般式为 \(F(S,B)=[B\lceil S/B\rceil-S]b_{token}\)。例如 \(S=4097\) 时,两个块大小分别浪费 15 与 63 个 Token 槽。块越小,尾部浪费上界越低,但块数与管理开销越大。64 GiB 池、2 MiB/块对应 32768 块;每块 64 B 元数据时约 2 MiB。这些是逻辑管理量,不代表内核访问成本已经确定。

4.2.3 共享、引用计数与写时分离

共享块需要引用计数;当某个持有者要写入时,可能需要写时复制(copy-on-write)分离出私有块。因此"共享"并不总是净收益:在写入频繁的场景,复制开销可能抵消共享节省。评估时应分别报告:共享节省的字节、复制次数与开销、以及回收延迟。

论文中的收益是条件化的(取决于负载的共享结构与块大小),不能当作固定提升引用 SOURCE_REGISTERED。

依据与核验入口:R02


4.3 状态复用条件:精确前缀、非前缀与隔离

本节要回答的问题:看起来相同的提示词,为什么可能不能复用?

4.3.1 复用的前提是计算语义一致,不是字符串相同

前缀复用要求:从第一个 Token 起,模型、权重 revision、分词器、模板、位置编码处理、适配器、精度与布局全部一致,并且被复用的部分确实是逐 Token 相同的前缀。人类可读的"看起来一样"不构成依据——模板渲染后是否逐 Token 相同,要按实际 Token 序列判断(第1章 1.3.2)。

4.3.2 精确前缀复用与非前缀重用是两回事

精确前缀复用有明确的等价性:被复用部分具有相同的数学计算语义;具体数值是否逐位一致还依赖浮点顺序、批不变性与实现。非前缀重用(复用中间某段、或复用语义相近但 Token 不同的内容)不具备这个等价性,通常涉及选择性重算与质量恢复问题,不能继承精确前缀复用的等价性假设 SOURCE_REGISTERED S14。

4.3.3 缓存键、命名空间与失效

工程上的最小要求是把缓存键设计为身份的组合,而不是提示词文本:

cache_key = hash(model_revision, tokenizer_version, template_version,
                 adapter_ids, precision_and_layout, rope/position_config,
                 tenant_namespace, token_ids_of_prefix)

任何影响状态解释或访问授权的身份变化,都必须使原缓存键失效或进入显式兼容转换;仅不影响 Token 与数值语义的展示元数据不应无端降低命中。跨租户复用需要单独审查——不能为了提高命中率绕过隔离要求,敏感输入也不应进入公共缓存实验。

4.3.4 重启、版本切换与"存储还在"的陷阱

进程重启后外部存储里仍有数据,不代表这些状态仍然有效:权重、模板、适配器或布局可能已经变化。重新提供服务前必须验证身份与格式,并把验证失败按"未命中"处理而不是按"命中"处理。

依据与核验入口:R07 S13


4.4 命中率、复用量与节省时间

本节要回答的问题:命中很高,为什么不一定有相同比例的速度收益?

4.4.1 三个不同的"命中"

指标 定义 量纲
请求级命中率 发生命中的请求数 / 请求总数 比例
Token 复用率 被复用的逻辑输入 Token 数 / 全部逻辑输入 Token 数(同一观察集合) 比例
避免的计算量 因复用而省下的 FLOPs 或 Prefill 工作时间 FLOP 或 s

三者之间没有固定换算关系。下表另假设各请求输入等长、每个未复用 Token 的 Prefill 成本相同、查找开销忽略,仅演示线性近似,不是全注意力下的严格收益上界。算例:

情形 请求级命中率 平均复用长度占输入 Token 复用率 Prefill 占端到端 线性成本假设下的端到端粗估
A 50% 20% \(0.5\times0.2=\mathbf{10\%}\) 30% \(\approx\mathbf{3\%}\)
B 30% 80% \(0.3\times0.8=\mathbf{24\%}\) 30% \(\approx\mathbf{7.2\%}\)

A 的命中率更高但收益更小——"命中多"不等于"避免的工作多"。

4.4.2 查找开销与净收益

命中本身有成本:查找、可能的传输、以及排队等待。净收益 \(=\) 复用的重算成本 \(-\) 查找与传输成本。仍以上例:查找 0.5 ms、复用节省 2.0 ms 时净省 1.5 ms;若复用长度很短只省 0.2 ms,则净亏 0.3 ms。所以短前缀命中可能不值得——这也是为什么必须报告复用长度分布而不只是命中率。

4.4.3 分账与负面结果

按设备内、主机内存、远程内存、存储层分别报告:查找次数、命中位置、复用长度、传输字节、排队时间、重算时间、释放时间。两种必须作为正式负面结果保留的情形:

  • 命中后转移比本地重算更慢(等待从 Prefill 移到了网络或 I/O);
  • 命中率提高但 Goodput 下降(传输占用带宽、排队变长或质量退化)。

依据与核验入口:R01 R06 S14


4.5 分层与共享缓存:进程内、独立服务与存储池

本节要回答的问题:把状态移到主机或其他层级,何时值得?

4.5.1 两类卸载,别混为一谈

类型 访问频率 时延要求 典型目的
活跃 Decode 状态卸载 每步都要访问 极高 腾出显存放更多并发
可复用前缀的外部存储 命中时才访问 中等 跨实例/跨重启复用前缀

前者若放到慢层,每一步都会付出代价;后者只在命中路径上付出一次。用同一套分层策略同时服务这两类需求,往往是分层变慢的原因。

4.5.2 重算—传输—等待的决策账本

决定是否把状态放到外部层,比较三笔账。算例:复用 \(H=8192\) Token 的状态(每 Token 128 KiB)。

方案 计算 成本
重新计算 线性项 \(2\times7\times10^{9}\times8192 \approx 114.7\) TFLOP,加注意力项约 \(35.2\) TFLOP;按有效算力 300 TFLOP/s \(\approx\mathbf{0.50\ s}\)
从外部层传输(20 GB/s 链路) 状态 \(1\ \text{GiB}=1.074\ \text{GB}\) \(1.074/20 = \mathbf{53.7\ ms}\)
同上,但链路仅 2 GB/s 同字节数 \(\mathbf{537\ ms}\)

结论是条件化的:在 20 GB/s 链路上传输比值(53.7 ms ≪ 0.50 s)明显更划算;在 2 GB/s 链路上(537 ms)与重算相当,若还要排队等待则可能更差。因此决策必须连同链路有效带宽、排队时间与状态有效性一起记录,不能只报"显存占用降低了多少"。

4.5.3 组件角色:名称相近不等于可互换

实现分层时,把系统图上的四类角色分开:

实现层 资料案例 必须核验的边界
引擎及分层缓存 SGLang HiCache 具体后端、介质、缓存状态与引擎版本
独立缓存管理 LMCache 进程边界、存储身份、质量与生命周期
共享存储与传输组件 Mooncake Store / Transfer Engine 两类组件各自职责、拓扑与故障范围
点到点传输库 NIXL 支持后端、传输身份与完成语义;不替代调度器

以上为资料中的角色划分,不表示各版本与任意后端已互相兼容 SOURCE_REGISTERED S13 S15。一个组件能保存状态,不代表它同时负责全局路由、租户授权与整个请求生命周期——这些职责仍由调度与服务层承担(第12章)。

4.5.4 重启、取消、回收与质量验收

分层带来的新问题:进程重启后外部状态是否有效(4.3.4)、取消请求后各层的状态是否回收、以及非前缀复用的质量验收。验收要性能与质量分开:命中率与延迟属于性能账,复用后答案是否仍满足契约属于质量账,后者不能由前者推定。

依据与核验入口:R04 S13 S15


4.6 低精度、压缩表示与混合状态布局

本节要回答的问题:减少状态大小会带来哪些新约束?

4.6.1 三件事不能合并成一种"缓存压缩"

做法 机制 谁决定
对常规状态做量化 降低 KV 的存储精度 引擎配置
模型结构采用压缩表示 存潜在向量而非双份 K/V 模型设计(第2章 2.4.2)
混合模型的多类状态 注意力状态 + 递归状态并存 模型设计(第2章 2.4.4)

它们的质量风险、内核依赖与容量收益都不同,必须分别验收。

4.6.2 精度变化对容量的定量影响

沿用 128 KiB/token(BF16)的基准:

状态精度 每 Token 4096 序列 相对 BF16
BF16(2 B) 128 KiB 512 MiB 100%
FP8(1 B) 64 KiB 256 MiB 50%
分块缩放 4 bit(\(4+8/16=4.5\) bit) \(128\times4.5/16 = \mathbf{36\ KiB}\) 144 MiB 28.1%

注意最后一行的换算:标称 4 bit 之外还有缩放元数据(第3章 3.2.3),实际每位元素是 4.5 bit;若块大小不同,这个数字也会变,必须按所选版本核验块大小,不能用"4 bit"直接推出容量。

4.6.3 生产方与消费方必须一致

缓存布局要记录"谁生产、谁消费"。模型卡给出的设计,只有在引擎、精度与内核路径都对应时才能用于容量估算;跨引擎或跨实例传输可能需要布局转换与暂存,这部分字节与质量风险不得遗漏 SOURCE_REGISTERED S02。

4.6.4 长上下文质量的独立验收

减少状态字节后,还要核验:算子是否真正走低精度路径、转换成本(量化/反量化的耗时)、以及长上下文质量。用短输入的质量测试代替长上下文验收,是本类变更最典型的漏检。

依据与核验入口:R04 R26 S11 S02 S15


4.7 实验:缓存压力与容量边界

本节要回答的问题:如何找到缓存约束,而不是只测试是否 OOM?

4.7.1 冷暖与复用对照

三组基线必须分开跑:冷缓存(每次重置)、本地命中、有资源时的远程命中。只跑其中一组得出的结论无法迁移——例如"容量充足"可能只是因为命中率高。

4.7.2 活跃状态扫描与停止条件

同时扫描活跃请求数、输入输出长度与复用程度,记录块占用率、抢占次数、阶段延迟与终态。停止条件必须提前定义(例如"连续两个负载档位出现抢占"或"P95 超过契约"就停止加压),否则压测会一直跑到崩溃,只得到"OOM 阈值"这一个没有工程意义的数。

4.7.3 抢占、回落与恢复

系统可能在 OOM 之前就已经服务目标失守(延迟或达标率不达标)——容量结论应以预先定义的可用边界为准,而不是以"不崩溃"为准。测试结束还要降载观察状态是否回落,避免把累积未回收的状态当成稳态容量(第9章 9.4 的 allocated/reserved 口径在这里直接相关)。

依据与核验入口:R01 R04 S14


4.8 深化:缓存容量是物理状态的时间分布

4.8.1 从尾块浪费到准入预算

对每个请求 \(i\),设实际驻留长度为 \(S_i\)、块大小为 \(B\)、每Token状态为 \(b_i\)。不共享时,静态占用为 \(\sum_i B\lceil S_i/B\rceil b_i\);存在共享时,应改为对物理块集合求和,而不是先算每请求再直接相加。共享尾块产生写时分离时,短时间内还可能同时存在旧块与新块,容量压力的峰值高于稳态账。

准入因此不能只检查“本轮还能生成一个Token吗”。一个长输出请求刚进入时状态很小,之后会持续增长。可以使用最大输出长度做保守预留,也可以基于条件输出分布做风险预算;前者可能浪费容量,后者必须有抢占、拒绝或降级的清晰代价。所谓“容量利用更高”,往往只是把确定预留换成了可控风险,不是凭空增加显存。

对不同优先级,可以将状态预算和截止时间结合:给高优先级保留增长余量,低优先级只能使用可回收份额;但共享块被保留后未必能立即回收,不能把全部缓存占用都计作可抢占空间。决策表应区分已被活跃请求引用、可逐出但可复用、正在传输和等待设备完成四类字节。

4.8.2 平均请求长度不足以预测平均驻留

考虑稳定系统中每秒到达 \(\lambda\) 个请求,第 \(i\) 类请求从准入至离开的状态曲线为 \(M_i(t)\)。长期平均驻留量可以按“每个请求带来的面积”计算:

\[\overline M=\lambda\,\mathbb E\!\left[\int_0^{T_i}M_i(t)dt\right].\]

这比“平均在途数×平均最终KV”更准确,因为状态是逐步增长的,且长请求在系统里停留更久。输入长度、输出长度和服务时长之间的相关性会进入同一个期望,不能一般地拆成多个均值的乘积。

在一个普通Decode模型中,输入长 \(H\)、输出共 \(O\),第一输出由Prefill采样,此后需 \(O-1\) 步,每步间隔为 \(\tau\)。若忽略分页且仅看这些步骤的驻留,面积近似为

\[b\tau\sum_{j=0}^{O-2}(H+j)=b\tau\left[(O-1)H+\frac{(O-1)(O-2)}2\right].\]

这里出现二次项。因此,两批请求即使平均输出相同,输出方差不同,也可能有不同的平均状态驻留。这个推导不适用于把状态主动丢弃的滑窗、压缩变化或被抢占重算的情况;那些情况应直接积分实际块占用曲线。

4.8.3 公开分页案例应怎样引用

PagedAttention论文报告了其系统相对当时基线的条件化吞吐收益,并把核心问题定位为KV内存管理 N02。本书将它作为机制与实验设计案例,不把论文倍数作为2026年引擎之间的排序。当前引擎可能都已有分页管理,比较时需要控制缓存策略、负载共享、采样方式和版本。

这也解释了为何需要负面实验:若负载几乎没有共享前缀、容量本就充足、瓶颈在主机调度,进一步提高缓存命中可能几乎不改变Goodput。缓存的价值不是“命中率越高越好”,而是在其查找、复制、传输和保留成本之后,减少了多少关键路径工作与有效资源消耗。

4.8.4 缓存隔离是一种明确的共享策略

vLLM官方设计文档说明可用 cache_salt 将前缀复用限定在相同盐值的信任组内 N14。工程上仍须决定这个信任组由谁授权、如何轮换,以及模型、适配器和位置配置变化时如何失效。一个公开系统提示词可以被授权共享;包含租户私密内容的前缀则不能因为文本相同就跨越授权边界。哈希键只实现身份匹配,并不替代访问控制和安全审查。


4.9 实验图表补充:补入FP8 KV的真实公开负载结果

缓存字节减半与服务吞吐翻倍是两个不同命题。vLLM官方2026-04-22技术博客给出Llama-3.1-8B的对照:150个请求、并发8、约20k输入与2k输出,长度波动±15%。硬件H100、FA3及优化后v0.19.1来自文章上下文,原表没有逐行重复完整环境。

Table 4中,BF16到FP8的输出吞吐为450.3→517.5 Token/s,按原值复算增加14.92%;ITL中位数15.18→12.93ms,下降14.82%。TTFT中位数仅由763.6降至742.8ms,下降约2.72%。因此数据支持“该负载下Decode与总产出改善”,不支持所有阶段同比提速。

图中使用两个独立纵轴面板,避免把毫秒与Token/s混在同一量表。总时长672.6→585.2秒可复核吞吐方向,但不得再和吞吐增幅相加成总收益。两者不是两笔独立节省。

转成本地实验时:使用相同提示词、输出规则、质量集和到达方式,记录KV占用、抢占、吞吐、TTFT/ITL分布。仅有表中中位数无法填入P99或联合SLO达标率。

图4-V1 FP8 KV并不意味着吞吐翻倍:公开负载对照

图4-V1 FP8 KV并不意味着吞吐翻倍:公开负载对照

数据身份:PUBLISHED_RESULT。条件:150请求、并发8、约20k输入/2k输出、±15%;H100/FA3与v0.19.1由文章上下文给出。

KV格式 TTFT中位数/ms ITL中位数/ms 总时长/s 输出Token/s
BF16 763.6 15.18 672.6 450.3
FP8 742.8 12.93 585.2 517.5

出处与边界:X05;吞吐+14.92%是本书复算;没有逐请求数据,不制造P99或误差条。

数据与绘图点 · PNG · 重绘代码

4.10 实验深化补充:把缓存收益拆成“结构决定共享”和“层级决定搬运”

本章前面已用 FP8 KV 说明“字节减半不等于吞吐翻倍”。这一节再补两条更靠前的问题:共享到底省多少,以及把KV搬到CPU再搬回来要多久。

共享收益由生成结构决定。PagedAttention原始论文Figure 15给出OPT-13B、Alpaca trace、1×A100 40GB下的KV块平均内存节省:并行采样在输出数2/4/6时分别节省6.09%、8.53%、9.79%;Beam Search在Beam宽度2/4/6时分别节省37.56%、53.13%、55.16%。同为“共享”,宽度6时两者相差约5.6倍(55.16/9.79)。原因是Beam Search的分叉结构让大量Token前缀被同批请求复用,而独立并行采样只在公共提示词段共享。读法:这里衡量的是共享KV块的平均内存节省,不是整卡显存节省,也不代表两种算法的输出质量等价。

层级搬运别把Gbps读成GB/s。LMCache论文Table 5的CPU加载消融给出:原生vLLM CPU offload报告88 Gbps,LMCache报告400 Gbps。按1 Gbps=0.125 GB/s换算,分别为11 GB/s与50 GB/s;400/88≈4.55。这个4.55只是加载阶段之比,不是服务吞吐之比。逐page搬运与按chunk搬运的固定开销不同,实际采用还要计入等待、计算重叠、缓存命中率和被抢占的代价。原表未独立给出参与GPU数与模型,因此不补造端到端结论。单位提醒:该论文Table 1用“GBps”、Table 5用“Gbps”,两处标注不同;本书一律按字面单位换算,不假定二者等价。

图4-E1 共享收益取决于生成结构:并行采样与Beam Search

图4-E1 共享收益取决于生成结构:并行采样与Beam Search

数据身份:PUBLISHED_EXPERIMENT。条件:OPT-13B;Alpaca trace;论文1×A100 40GB配置;Figure15,分别按并行采样输出数/Beam宽度扫描。

输出序列数或Beam宽度 Parallel sampling节省/% Beam search节省/%
2 6.09 37.56
4 8.53 53.13
6 9.79 55.16

出处与边界:Y01;指标为共享KV块的平均内存节省,不是所有显存;两种生成算法不同,不作质量等价声明。

数据与绘图点 · PNG · 重绘代码

图4-E2 CPU KV加载:别把Gbps误读成GB/s

图4-E2 CPU KV加载:别把Gbps误读成GB/s

数据身份:PUBLISHED_MICROBENCHMARK。条件:LMCache v0.3.6;CPU加载消融基线vLLM v0.11.0;整体实验台单节点8×H100,Table5未独立给参与GPU数和模型。

方法 报告加载带宽/Gbps 换算GB/s
vLLM native CPU offload 88 11
LMCache 400 50

出处与边界:Y02;第三列为本书单位换算;加载约4.55倍不等于服务吞吐约4.55倍;表级完整配置缺失。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 为什么总 KV 除以 TP 度不一定等于每 rank 占用?

因为分片通常按键值头维度进行,能否均分取决于 \(h_{kv}\) 与 \(TP\) 的关系(4.1.3 的算例):\(h_{kv}=8,TP=8\) 时每 rank 是总量 ÷ 8;而 \(h_{kv}=4,TP=8\) 时头数不够分,需要复制,每 rank 是总量 ÷ 4,比按设备数均分多一倍。此外不同并行实现可能按序列维或混合维度切分,具体行为必须按所选版本核验,不能默认"总量 ÷ 设备数"。

2. 缓存命中 50% 能否推出 TTFT 减少 50%?

不能,中间至少隔了三层换算(4.4.1):请求级命中率 → Token 复用率(还要乘平均复用长度占比)→ 避免的 Prefill 工作量 → 端到端改善(还要乘 Prefill 在端到端中的占比)。算例中 50% 命中率、平均复用 20%、Prefill 占 30% 时,线性成本假设下的端到端粗估只有约 3%。此外命中本身有查找与传输成本,短前缀命中甚至可能净亏。

3. 远程缓存命中率提高但 Goodput 下降时,需要补哪些账本?

至少五本:①传输字节与链路有效带宽(等待是否被搬到了网络);②排队时间(传输是否加重排队);③重算时间对照(命中路径是否真比本地重算快);④状态有效性与质量(是否因复用而退化,尤其是非前缀复用);⑤释放与回落(取消/失败后各层状态是否回收)。命中率只是第一本账里的一个比例,用它解释 Goodput 变化本身就跳过了中间所有环节。


本章验收清单

节 验收标准
4.1 估算涵盖输入与已生成状态;按物理身份去重;能解释分片、复制与分支共享;不把总量 ÷ 设备数当每 rank 占用
4.2 能区分状态本体、内部碎片、元数据与共享节省;块大小权衡有量化对照
4.3 缓存键含身份组合;能列出必须失效的变更项;跨租户与敏感输入不进公共缓存
4.4 报告明确分子分母;区分命中率、Token 复用率与避免的计算量;保留负面结果
4.5 活跃状态卸载与前缀存储分开;重算/传输/等待三笔账齐全;组件职责与不兼容边界已登记
4.6 三种"压缩"分别验收;低精度换算含元数据并注明块大小待核验;长上下文质量独立验收
4.7 容量以可用边界定义而非不崩溃;停止条件提前定义;降载后观察状态回落

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第5章 批处理、排队与调度

核心论点:调度优化改变的是请求之间如何分享资源;吞吐提升必须同时检查尾延迟、公平性与拒绝成本。


5.1 静态批处理、动态批处理与连续批处理

本节要回答的问题:批次什么时候形成,又在什么时候改变?

三种策略的区别在于批次边界的更新时机。设四个请求 R1(短输出)、R2(长输出)、R3、R4,到达时刻略有先后(时间轴为构造数据):

静态批处理(整批完成才换批)
  t0 ────── batch#1: [R1][R2] ────── t5 ────── batch#2: [R3][R4] ────── t10
            R1 在 t1 就结束,但槽位空转到 t5

动态批处理(等待窗口内组批)
  t0 ── 窗口 ── batch#1: [R1][R2] ── t5 ── 窗口 ── batch#2: [R3][R4] ── t10
            窗口内到达的请求一起进批,窗口外的等下一批

连续批处理(逐迭代更新活跃集合)
  t0 ── [R1][R2] ── t1: R1 结束 → [R2][R3] ── t3: R4 加入 → [R2][R3][R4] ── ...
            每个迭代重新决定本轮批组成,结束即退出、就绪后可在资源与预算允许的下一轮加入

连续批处理提高的是资源共享的灵活性,但每次决策仍受三重限制:Token 预算(本轮最多处理多少 Token)、状态容量(活跃序列的状态放不放得下)、执行形状(能否命中已捕获的图或高效内核)。

由此可以直接回答本节验收的那个问题:批大小不总等于同时在线的请求数,因为

  • 容量不足时,部分在线请求本轮不参与(排队或等待块);
  • Token 预算用尽时,后来的请求即使已就绪也要等下一轮;
  • 长输入被分块后,一个请求可能在多个迭代里只贡献一部分 Token(5.4);
  • 抢占会让活跃请求暂时退出(5.6)。

抽象策略要对应到具体对象:用固定版本的引擎时间线解释调度器与 runner 的对应关系,并把每个开关的默认行为 / 显式开启 / 回退 / 不支持分成四类记录,避免沿用旧教程里的默认值 SOURCE_REGISTERED S03。

依据与核验入口:R02 S01


5.2 到达率、并发与排队

本节要回答的问题:固定并发和固定到达率为什么不是同一个实验?

5.2.1 开环与闭环

模式 机制 系统变慢时会发生什么
闭环 客户端维持固定并发,完成一个才补一个 到达率自动下降,过载被客户端吸收,延迟曲线看起来平稳
开环 按外部计划的到达时刻发出,不等返回 队列增长,暴露真实的排队与拒绝行为

第8章 8.4.1 已给出判别方法(计划 vs 实际发出、调度迟滞)。这里补充结论:闭环回答固定在途约束下的服务表现,不能直接回答外部到达不受反馈限制时的过载问题。它可能掩盖排队风险,但并非任何闭环实验都必然高估容量。

5.2.2 稳态关系要用对边界

Little 定律 \(L=\lambda W\) 描述的是同一边界内、长期稳定条件下的均值关系。它的适用前提必须逐条确认:

  1. 进入与离开的边界一致(\(L\) 数的是哪一段?队列内还是系统内?);
  2. 长期平均稳定(不是瞬时,也不是队列持续增长的阶段);
  3. 它只给均值,不能预测尾延迟。

用"设定并发 × 目标 RPS"直接推出 P99,是这条定律最常见的误用。

5.2.3 排队随负载的非线性增长

为建立直觉,用一个明确标注为模型的 M/M/1 算例(泊松到达、指数服务时间、单服务台、无限队列——真实系统不满足这些假设,此处只用于展示趋势)。设服务率 \(\mu = 50\) req/s(平均服务时间 \(1/\mu = 20\) ms):

到达率 \(\lambda\) 利用率 \(\rho\) 平均在途 \(L=\rho/(1-\rho)\) 平均停留 \(W=L/\lambda\) 其中等待时间
40 req/s 0.80 4 100 ms 80 ms
45 req/s 0.90 9 200 ms 180 ms
48 req/s 0.96 24 500 ms 480 ms

到达率提高 12.5%(40→45)让等待时间变成 2.25 倍;提高 20%(40→48)让等待时间变成 6 倍。这说明轻载延迟可作基础开销对照,却不能单独预测高负载的排队放大,也是为什么容量结论必须在目标负载区间内测量(第8章 8.6)。

报告必须分别记 offered、admitted、completed,并单列拒绝、取消、失败与窗口内在途增量。对同一边界,有 \(N_{end}=N_{start}+A-C-F-X\),其中 \(A\) 为准入,\(C/F/X\) 为完成、失败、取消等互斥离开事件。因而 admitted 与 completed 的差可能只是队列还在增长,不能全部解释为拒绝或放弃;拒绝通常属于 offered 与 admitted 之间的账。

依据与核验入口:R11 R25


5.3 Token 预算与执行形状

本节要回答的问题:限制序列数和限制批内 Token 数分别控制什么?

5.3.1 两个旋钮管两件事

限制 控制什么 典型副作用
最大序列数(max_num_seqs 一类) 本轮/引擎允许的活跃序列数及相关状态规模(不等于会话数) 设太小:并发上不去、排队变长
批内 Token 预算(max_num_batched_tokens 一类) 单轮前向的工作量上限 设太大:单轮延迟上升、ITL 变差;设太小:长 prefill 被切得更碎

两者不能互相替代:同样 32 个序列,输入从 512 改成 8192,本轮 Token 需求相差 16 倍,执行形状与算术强度完全不同(第3章 3.2.4:权重主导近似给出约 500;计入该方阵激活流量后理想交点约为 662 个 Token)。

5.3.2 与 runner、图模式的联动

改变预算不仅改变工作量,还会改变执行形状,从而影响:图捕获是否命中(第1章 1.5.2 的 padding/桶/降级链)、工作区大小、以及缓存压力。因此"调了预算、吞吐变了"不能直接归因到工作量——可能是换了执行路径。

5.3.3 必须记录实际形状

每次扫描都要同时保存:实际批内 Token 数、序列数、padding 量、图命中与否、回退原因。不能只靠启动参数推断形成了相同的计算负载——这正是"配置改了但没生效"或"生效方式与预期不同"的发现途径 SOURCE_REGISTERED S02。

依据与核验入口:R02 S01 S03


5.4 Chunked Prefill 与阶段干扰

本节要回答的问题:分块怎样改变首 Token 等待与生成间隔?

5.4.1 把长输入切成调度片段

长输入 8192、块 2048 → 被切成 4 个调度片段(\(8192/2048=4\))。分块的目的不是"让 prefill 更快",而是避免一个长请求独占一整轮,从而减少对同时进行的 decode 请求的干扰。

5.4.2 优先级与混合执行

调度策略上,待处理的 decode 请求通常被优先编排,预算内再安排 prefill 片段。官方优化文档给出的权衡方向是:块越小,decode 被 prefill 拖慢的程度越低(ITL 更好);块越大,单批能处理的 prefill Token 越多(TTFT 更好) SOURCE_REGISTERED。

算例(趋势示例,非实测):在长短请求混合流中开启分块(块 2048)后:

指标 关闭分块 开启分块 变化
长请求 TTFT 40 ms 70 ms +75%(被切成多轮、需等待预算)
短请求 ITL P99 120 ms 60 ms −50%(不再被长 prefill 连续阻塞)

这不是"更好"或"更差",而是把延迟在不同请求之间重新分配。因此结论必须覆盖两类请求:只在总体均值上看,很容易把"长请求被牺牲"掩盖掉。

5.4.3 概念边界

Chunked Prefill 与 Prefill Context Parallel 是不同概念:前者是把一个 prefill 切成多个调度片段;后者是让多个设备并行处理同一次 prefill。某一版本移除了旧的 Prefill CP 路径,不能据此推断分块 Prefill 的原理失效——功能可用性要按固定版本的支持矩阵界定,并确认 trace 中实际调用的是哪条路径 SOURCE_REGISTERED。

依据与核验入口:R09 S03


5.5 多租户、公平性与优先级

本节要回答的问题:怎样防止吞吐目标挤压某一类请求?

5.5.1 分层报告是公平性的前提

总体吞吐提高完全可以伴随某一类请求的长期等待。因此调度结果必须按业务类或租户类别分层,每类单独报告:达标率、拒绝率、等待时间分布、以及是否被抢占。"平均延迟下降"不能作为公平性证据。

5.5.2 饥饿的检测与处理

饥饿的可操作定义:在总负载上升时,某一类的等待时间单调增长且远超其它类,或其达标率跌破契约而总体仍达标。检测方式是分层时间序列,而不是看总体指标。

处理手段有三类,各有代价:

手段 机制 代价
资源配额 为每类保留容量或 Token 预算 资源利用率下降
优先级 高优先类先调度 低优先类可能长期饥饿
准入控制 超载时按比例拒绝 拒绝率上升(但要进分母)

公平性是需要明确选择的服务契约,不是一个"默认正确"的指标:先写清各类的权重与最低保障,再验证调度是否满足。

5.5.3 路由与本地调度会互相干扰

多副本场景下,若再叠加"缓存亲和路由"(把请求路由到持有其前缀副本的实例),队列均衡与状态局部性可能冲突:亲和提高了命中率,却可能让某个实例排队更长。因此要分别观察全局路由与单引擎调度,避免把租户饥饿全部归因到某一个批处理参数 SOURCE_REGISTERED S07。

依据与核验入口:R25 S06


5.6 抢占、准入、取消与背压

本节要回答的问题:资源不足时该排队、拒绝还是回收?

5.6.1 三种应对与各自的分母后果

手段 直接后果 统计后果
排队(准入后等待) 延迟上升 仍属于准入观察集合,未完成时记为在途而不是完成样本
抢占 已做工作可能作废,需重算 额外计算进成本;尾延迟恶化
拒绝(限流/丢弃) 请求未完成 进拒绝率,不得从分母删除

不能通过无限排队隐藏过载,也不能通过丢弃"困难请求"把成功请求的延迟曲线做得更漂亮——后者是最危险的一类"优化",因为它同时改善了吞吐与延迟,只牺牲了分母。

5.6.2 抢占的真实成本

抢占不只是"暂停"。按引擎指标文档的事件定义,被抢占的请求会被放回等待队列并在将来重新调度、重新执行其 prefill 阶段 SOURCE_REGISTERED。于是额外成本 = 已生成部分的状态重建工作量。

算例(构造数据,粗估):请求上下文 4096、已生成 2000 Token 后被抢占:

  • 已占用状态 \(128\ \text{KiB}\times(4096+2000) = 780{,}288\ \text{KiB} = \mathbf{762\ MiB}\),需要全部保留或重建;
  • 重放该 prefill 的额外工作按第4章 4.5.2 的量级线性缩放(8192 Token ≈ 0.50 s)作仅保留线性项的粗估:
\[ 0.50\ \text{s}\times\frac{6096}{8192} \approx \mathbf{0.37\ s} \]

(这是粗估:忽略了注意力项的平方增长与内核差异,实际值应实测。它的作用是说明量级——一次抢占的代价与一次完整 prefill 同量级,不是可以忽略的小事。)

5.6.3 重试预算与背压传播

背压要在三处分别记录:服务控制层的流控、引擎准入、客户端最大在途数。三者叠加时,拒绝可能发生在任意一层,而客户端看到的结果是相同的超时——因此必须逐层留痕才能定位。

重试还要记账:第1章 1.6.4 给出 \(\mathbb{E}[N_{\text{attempt}}]=(1-p^{r+1})/(1-p)\),实际运行中应保留重试放大系数作为运行数据,而不是只在报告里写"最终成功率" SOURCE_REGISTERED S18。

依据与核验入口:R25 S07


5.7 实验:建立调度的帕累托边界

本节要回答的问题:怎样找到适合业务的配置,而非唯一"最优参数"?

5.7.1 先定可行性,再谈优劣

先冻结一组约束(每类的 SLO、拒绝率上限、公平性下限),再用同一组混合负载比较少量有机制依据的配置。结果不是一条曲线,而是可行域:满足全部约束的配置集合。

5.7.2 一份四配置对照

配置 吞吐 P99 延迟 某租户达标率 结论
A(预算最大) 最高 超契约 达标 淘汰:尾延迟不达标
B(预算最小) 最低 达标 达标 保留但非最优
C(预算中等 + 分块) 中高 达标 达标 推荐(留出负载复核后)
D(预算最大 + 亲和路由) 高 达标 跌破契约 淘汰:某类饥饿,虽总量好看

这里最容易犯的错误是只展示 A 或 D 的那一点。推荐配置必须来自"满足全部门槛"的集合,并在留出负载上复核(不在用于调参的同一批负载上复核)。

5.7.3 保留淘汰原因

被淘汰的方案要连同原因一起归档:是尾延迟不达标、是某类饥饿、还是执行路径未生效。这些记录在下一次版本升级时会变成最便宜的回归基线——知道"为什么当初不选它"比"当初选了什么"更有价值。

依据与核验入口:R11 R24


5.8 深化:工作量方差、剩余服务时间与分块预算

5.8.1 为什么相同均值也会形成不同的排队

为解释方差,另设一个M/G/1模型:泊松到达、单服务台、先来先服务、服务时间独立同分布且二阶矩有限、\(\rho=\lambda E[S]<1\)。它不是连续批处理引擎的精确模型,只用于建立长短请求混合造成排队的直觉。

随机到达看到服务器正在执行某任务时,看到的往往是长任务。对时长为 \(s\) 的一次服务,它贡献的剩余时间面积为 \(s^2/2\);把面积按长期时间平均后,剩余服务时间的无条件均值为 \(\lambda E[S^2]/2\)。到达者还要等待队列中请求,平均等待满足

\[E[W_q]=\lambda E[W_q]E[S]+\lambda E[S^2]/2,\]

因而得到

\[E[W_q]=\frac{\lambda E[S^2]}{2(1-\rho)}=\frac{\rho}{1-\rho}\frac{1+c_s^2}{2}E[S].\]

\(c_s\) 为服务时间变异系数。固定平均服务时间20 ms、利用率0.8时,确定性服务(\(c_s^2=0\))平均等待40 ms,指数服务(\(c_s^2=1\))为80 ms;若 \(c_s^2=4\),则为200 ms。这不是引擎预测值,而是说明均值相同不足以保证排队相同。实际批处理会让服务时间与队列状态耦合,需要批组成和阶段事件进一步解释。

图5-R1 M/G/1模型中的方差与排队放大

5.8.2 Token预算不是固定的Prefill块大小

设单轮总预算为 \(T\),本轮普通Decode占用 \(D\) 个有效位置,其它验证或保留预算为 \(V\),则新Prefill可用预算至多是 \(\max(0,T-D-V)\)。当 \(D\) 随活跃请求变化时,同一个8192启动参数会产生不同实际Prefill块。若开启推测验证,\(V\)还随候选长度与接受行为变化。解释性能时应该画实际形状分布,而不是只写一个最大值。

官方vLLM优化文档描述了优先安排Decode、再用剩余预算调度Prefill的策略 N15。这里应把它理解为特定实现的调度规则,而非所有引擎的通则;预算大小对TTFT/ITL的影响,也需要结合实际负载和图执行路径核验。

5.8.3 分块是在让出服务机会,不是取消注意力依赖

一个长请求分成多个块后,每块新增查询仍需读取前序上下文,所以切块并没有删除query-key依赖。它能减少一次长计算对其它请求的连续阻塞,却也增加了调度次数、重复读取前序状态以及小形状低效率的可能。过小与过大都可能恶化某一层目标,最佳点随到达率、输出长度和状态压力变化。

因此可将候选调度问题写成一个约束优化:在每类请求的截止时间、拒绝上限和最低服务份额约束下,最大化合格完成量。这里拒绝不是“绝对不能用”的策略;在明确的准入契约下,及时拒绝可能优于无限排队。不能接受的是拒绝后从统计分母中删除这些请求,再宣称全部成功。

公平性检查也应考虑工作量归一化。一个拥有大量长请求的租户占用更多设备时间,不一定意味着调度偏袒;反之,各租户获得相同请求数也不意味着公平。应先选择按请求、Token预算、设备时间还是业务权重分配,再观测每类的实际兑现与尾部饥饿。


5.9 实验图表补充:用官方调度对照观察均值与尾部的分歧

SGLang v0.5.20发布说明在PR #32911相关条目中报告:基于GLM-5.2生产trace,HRRN相对FCFS使Mean TTFT下降69%,P99 TTFT下降8%。条目未披露绝对毫秒值、硬件和重复运行记录。因此本节只能把FCFS设为100,重绘相对延迟指数31与92。

这组结果适合回答“平均改善会不会自动带来尾部改善”:不会。均值和P99都朝好的方向变化,但幅度很不相同。也不能由Mean下降69%反推合格完成速率增加69%,因为失败分母、到达率和其他门槛未给出。

最小复现实验:以同一真实内容回放FCFS/HRRN,固定资源、Token预算、缓存预热与负载顺序,交错运行并做回退复测。除总体TTFT外,按长度类和租户分别记录等待、拒绝、抢占与达标率;避免总体平均值掩盖某类长请求饥饿。相对图提供的是值得测试的假设,不替你决定生产调度策略。

图5-V1 调度策略:均值改善与尾部改善不是一回事

图5-V1 调度策略:均值改善与尾部改善不是一回事

数据身份:PUBLIC_RELATIVE_RESULT。条件:GLM-5.2 production trace;同发布说明的HRRN vs FCFS条目;未披露硬件及绝对时间。

指标 FCFS指数 HRRN指数 公开降幅
Mean TTFT 100 31 下降69%
P99 TTFT 100 92 下降8%

复算公式:normalized=100*(1-reduction)。

出处与边界:X08;100是归一化基线,不是100ms;不由两个标量生成延迟分布。

数据与绘图点 · PNG · 重绘代码

5.10 实验深化补充:分页把“能同时装下多少请求”抬了一个量级

前面用 SGLang HRRN 说明了“怎么排”;这一节补上“能装多少”。PagedAttention论文Figure 13在OPT-13B、A100 40GB下比较了四种策略的平均组批请求数。

ShareGPT按2 req/s到达时,Orca Max/Pow2/Oracle与vLLM分别为7、9.81、13.62、30.42;Alpaca按30 req/s到达时为7、43.24、72.75、132.44。相对Orca Oracle,vLLM约2.23倍与1.82倍。这说明分页改善的是状态容纳与调度机会——更少碎片、更高并发,从而在同一个batch里塞进更多请求。它不等于端到端吞吐同倍提升,因为组批数受显存、计算与调度共同约束。

两种负载不要并排当同一场景。ShareGPT是2 req/s、Alpaca是30 req/s,到达率差一个量级,柱子高度不能直接横向比较,只能各自与同列的Orca基线比。平均组批请求数是占用/组批统计,不是req/s,也不是满足SLO的容量。把它与 5.9 节的相对延迟图配合看:一张回答“装多少”,一张回答“怎么排”,两者都不单独决定交付容量。

图5-E1 分页之后实际组入了多少请求?

图5-E1 分页之后实际组入了多少请求?

数据身份:PUBLISHED_EXPERIMENT。条件:OPT-13B,A100 40GB;ShareGPT 2req/s、Alpaca 30req/s;Figure13;两种负载分面显示。

策略 ShareGPT平均组批请求数 Alpaca平均组批请求数
Orca Max 7 7
Orca Pow2 9.81 43.24
Orca Oracle 13.62 72.75
vLLM 30.42 132.44

出处与边界:Y01;平均batched requests是占用/组批统计,不是req/s或SLO容量;不可把两种到达率的柱子当同一场景。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 固定并发压测为何可能低估过载排队?

因为闭环客户端在服务端变慢时会自动减少发出(5.2.1):并发 128、单请求停留变长时,到达率被动降到 \(\lambda\approx128/W\)。于是队列不增长、延迟曲线平稳,看起来"容量还行",而真相是客户端替服务端做了限流。要研究过载,必须用开环或轨迹回放,并同时报告 offered / admitted / completed 三种速率。

2. 相同 RPS 的长短请求组合为什么不同?

因为 RPS 只描述到达数量,不描述每请求的工作量。第2章已说明 Prefill 随输入线性与平方项增长、Decode 随输出逐步累加;第3章说明算术强度随批内 Token 数变化。因此"100 req/s 的长文档"与"100 req/s 的短问答"在 Token 预算、状态占用与执行形状上是三种不同的负载(第8章 8.3 的四档基线)。比较时必须给出长度分布,而不只是 RPS。

3. 旧版本 Prefill CP 路径撤回,为什么不能据此否定 Chunked Prefill 原理?

因为它们是不同机制:Chunked Prefill 把一个 prefill 切成多个调度片段以缓解对 decode 的连续占用;Prefill Context Parallel 是让多个设备并行处理同一次 prefill。某版本移除的是后者的旧实现路径,与前者是否成立无关。功能可用性要按固定版本的支持矩阵界定,并在 trace 中确认实际调用路径(5.4.3)。


本章验收清单

节 验收标准
5.1 能解释批大小为何不总等于在线请求数;抽象策略对应到具体对象,开关四类状态已记录
5.2 区分 offered/admitted/completed;Little 定律只用于均值与稳定边界;不将不稳定队列套入稳态公式
5.3 序列数限制与 Token 预算分别说明;保存实际批形状(Token/序列/padding/图命中/回退原因)
5.4 结论覆盖长请求与短请求两类;能区分 Chunked Prefill 与 Prefill CP;可用性与实际调用路径按版本确认
5.5 每类请求都有达标率与拒绝率;说明饥饿检测方式与公平性契约;路由与本地调度分别观察
5.6 排队/拒绝/抢占三者的分母后果明确;抢占成本有量级估算;背压三层分别记录
5.7 推荐配置来自满足全部门槛的集合并在留出负载复核;保留淘汰原因

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第6章 多卡并行、拓扑与 MoE

核心论点:多卡策略同时改变驻留方式、通信位置、执行粒度与副本数;四种影响必须在同一服务目标下一起比较,任何只看其中一项的结论都可能反向。


6.1 先区分容量并行与吞吐复制

本节要回答的问题:增加设备究竟是在装下模型,还是服务更多请求?

增加卡数只有三个目的,且它们互相竞争:

目的 机制 代价
装下模型 把权重与状态分片到多卡 每 rank 仍要留工作区与状态池;分片引入同步
缩短单请求 把一次前向的计算摊到更多设备 同步次数增加;单步工作量变小,固定开销占比上升
服务更多请求 增加副本数 每副本都要重复驻留权重,容量被权重吃掉

因此总设备数固定时,扩大并行度必然减少副本数。这是本节最关键的一句话:不存在"并行度越大越好"这回事,只存在"在当前负载与服务目标下哪一种划分更划算"。

6.1.1 布局候选表:先统一全模型量与每 rank 量

为单独检验分片算术,这里使用构造数据:权重总量 140 GB、每卡可用 80 GB、每 rank 固定开销 4 GB、全模型每 Token 的 KV 为 128 KiB,\(h_{kv}=8\),总设备数为 8。它把权重量与状态量作为独立输入,不是某个真实 70B 模型的配置。真实模型应按第2、4章重新提取层数与头数。

无复制且按头均分时,一条 4096 Token 序列的全模型 KV 为 \(K=0.536870912\) GB,每 rank 为 \(K/TP\)。所以

\[C_{replica}=\left\lfloor\frac{80-140/TP-4}{K/TP}\right\rfloor,\qquad C_{cluster}=\frac{8}{TP}C_{replica}.\]
布局 TP 副本数 权重/rank(GB) KV 池/rank(GB) 每序列 KV/rank(GB) 每副本路数 集群路数
A 8 1 17.5 58.5 0.067108864 871 871
B 4 2 35 41 0.134217728 305 610
C 2 4 70 6 0.268435456 22 88

先对每副本取整,再乘副本数,不能让不同副本共享小数路数。上述只是静态显存上限,未包含分块浪费、额外图工作区、状态增长及服务目标。

这次修正会改变原表结论:在这些假设下 A 的静态集群状态容量高于 B;B 是否有更高服务吞吐,要由两个副本各自的服务曲线判断,不能由容量表推出。C 可以运行较小并发,不是“模型不可运行”;若项目要求每副本至少 64 路,它仅因不满足该准入门槛而被排除。三种布局的单请求延迟均未知。

头数不足时,某些实现会复制 KV 头。对只采用均匀头切分或均匀复制的简化实现,\(h_{local}=\max(h_{kv}/TP,1)\) 只适用于整除关系成立的组合;任意不整除布局应读取实际分配,不能向上取整后假装已经确认实现支持。

依据与核验入口:R30


6.2 Tensor Parallel:分片与同步

本节要回答的问题:算子被拆开以后,增加了哪些通信,它们是否落在关键路径上?

6.2.1 切分位置决定通信类型

以线性层 \(Y = XW\) 为例,两种切法对应两种集合通信:

切法 数学形式 每 rank 持有 需要的通信
列切(按输出维) \(W = [W_1 \; W_2]\),\(Y = [XW_1 \; XW_2]\) 部分输出列 下游需要完整输入时需 all-gather
行切(按输入维) \(W = \begin{bmatrix}W_1 \\ W_2\end{bmatrix}\),\(Y = X_1W_1 + X_2W_2\) 部分和 需要求和 → all-reduce

经典 Megatron 式布局把两者配对使用:注意力与 MLP 内部先列切、输出投影再行切,于是每层产生 2 次 all-reduce;启用序列并行后,原本的 all-reduce 会被拆成 reduce-scatter 与 all-gather,每层变成 4 次集合通信。

这个"2 次还是 4 次"必须按实现取证,不能照抄。判据是 trace 中的集合通信内核名与调用次数(第9章 9.5),而不是文档里的示意图。

6.2.2 通信账本:Prefill 与 Decode 的主导项完全不同

设 \(d_{\text{model}}=4096\)、BF16(\(2\) B)、TP \(=8\)、\(L=32\) 层、每层 2 次 all-reduce(合计 64 次/步),互联有效带宽取 \(200\) GB/s、每次集合通信固定开销取 \(6\) µs(均为参数)。按 ring 算法,单次 all-reduce 每 rank 传输量约 \(2(N-1)/N \cdot M = 1.75M\):

阶段 张量形状 单次 \(M\) 每 rank 每次 64 次合计 带宽项 延迟项 延迟占比
Prefill(\(B{=}1,S{=}4096\)) \([1,4096,4096]\) 32 MiB 56 MiB 3584 MiB 18.79 ms 0.38 ms 2.0%
Decode(\(B{=}8,S{=}1\)) \([8,1,4096]\) 64 KiB 0.109 MiB 7 MiB 0.04 ms 0.38 ms 91.3%

这是本章最该记住的一张表。同样的层数、同样的并行度,只因为张量形状不同:

  • Prefill 由带宽主导(延迟只占 2%):优化方向是减少通信字节(序列并行、融合、降低精度传输)。
  • Decode 由延迟主导(延迟占 91%):每 step 要发起 64 次同步而每次只搬 64 KiB,优化方向包括减少必要的同步轮次、降低主机发起开销和改善拓扑;图捕获主要减少发起开销,并不会自动消除数学上必需的集合通信。

所以"TP 度提高有没有收益"这个问题在 Prefill 与 Decode 上答案可以相反,必须分开回答。也正因如此,不能把"all-reduce 微基准的带宽"当作 TP 收益的依据——Decode 侧压根不受带宽限制。

通信是否在关键路径上,要依据实际算子依赖。经典列切与行切配对可以让中间分片直接被下一算子消费,从而避免某次 all-gather;避免通信与把仍存在的通信隐藏在计算后面是两种机制。Reduce-scatter 也不是能任意替换 all-gather 的操作,必须满足下游对张量分片和求和结果的语义要求。

6.2.3 小批次与拓扑敏感性

TP 度提高会减小本地 GEMM 的工作量,但并不保证每次 all-reduce 的逻辑张量变小;经典行切输出的部分和常仍为完整输出形状,于是固定开销占比上升;同时增加 rank 数会让 ring 的跳数增加、跨域时每跳代价更高。因此存在明显的负载区间与互联条件依赖:

  • 批内 Token 数很小时(在线低并发 Decode),提高 TP 度可能净变差;
  • 长输入 Prefill 时,提高 TP 度通常划算,前提是分片后的状态与通信不跨域;
  • 一旦 TP 组跨越节点边界(而非高速互联域内),每次同步都要走慢链路,上面的 200 GB/s 假设直接失效。

可行结论的形式是区间而不是方向:"在本环境的 X 负载、Y 批形状、Z 互联下,TP 从 4 到 8 的收益是多少"——而不是"TP 越大越好"或"TP 超过 4 就劣化"。

依据与核验入口:R08 S02


6.3 Pipeline Parallel:阶段与气泡

本节要回答的问题:把层分到不同设备上,哪些时间会空转?

6.3.1 阶段切分与传递

流水并行沿层切分:把 \(L\) 层分成 \(p\) 个连续阶段,每阶段驻留在一组设备上,阶段间只传递隐藏状态张量。与 TP 的关键区别:通信频率低(每微批每阶段边界一次)但消息大,且通信是点对点而非集合。

它解决的是 TP 解决不了的问题——当模型大到 TP 组也装不下,或 TP 组内同步成本过高时,PP 提供了第二个切分维度。

6.3.2 气泡:为什么在线场景尤其吃亏

流水启动需要填充、结束需要排空,中间形成气泡。在经典 GPipe 式稳态模型下,若每阶段耗时相同为 \(t\)、微批数为 \(m\)、阶段数为 \(p\),则总时长 \((m+p-1)t\),理想时长 \(mt\),气泡占比为

\[ \text{bubble} = \frac{p-1}{m+p-1} \]
阶段数 \(p\) 微批数 \(m\) 气泡占比 场景
4 8 27.3% 中等批量
4 32 8.6% 离线大批量
2 8 11.1% 少阶段
4 4 42.9% 在线低并发

关键推论:同样 \(p=4\) 的布局,离线大批量(\(m=32\))气泡只有 8.6%,在线低并发(\(m=4\))却高达 42.9%。因此在训练里得到的流水效率结论不能搬到在线推理:

  1. 训练的微批数由全局批大小决定,可以堆到很大;推理的微批数上限由在途请求数决定(第5章),在线低并发时它天然很小;
  2. 推理还要处理 Prefill 与 Decode 混合、请求长度参差、随时到达与结束——这些都会破坏"每个微批等长"的假设;
  3. 训练可以重跑,推理的一次气泡直接付给用户。

因此 \((m+p-1)t\) 仅用于独立、等长、均衡阶段的理想微批流水;自回归反馈依赖、混合阶段与在线到达需另建模型,并用在线推理实验验证。

6.3.3 阶段平衡与可用性

气泡之外还有阶段不平衡:总时长由最慢阶段决定。设 32 层分 4 阶段、每阶段 8 层,但首尾阶段额外承担嵌入与输出投影,假设各阶段耗时 \([10.0,\,9.5,\,9.5,\,11.5]\) ms,则节拍为 \(11.5\) ms,阶段利用率

\[ \frac{10.0+9.5+9.5+11.5}{4\times11.5} = \frac{40.5}{46} = \mathbf{88.0\%} \]

也就是说,即便完全消除气泡,仍有 12% 因不平衡损失。改善办法是按实测耗时而非按层数切分。

可用性也必须写明:某阶段异常时,整条流水上的在途微批如何处理、是否需要整流水重启、恢复时间多长,这些属于运维契约,与效率分开记录(第12章展开)。

依据与核验入口:R30


6.4 Data Parallel:副本、路由与局部性

本节要回答的问题:请求平均分配,是否就能负载均衡?

6.4.1 副本容量与健康

数据并行复制整个模型实例,副本间不需要同步计算。它的代价是每副本都要完整驻留权重,因此并行度受 6.1 那张表的约束;它的收益是容量随副本数近似线性增长(前提是路由真的均衡)。

副本级必须分别记录:到达率、在途数、状态占用、达标率与拒绝率。聚合曲线会掩盖热点——把空闲副本与拥塞副本平均在一起,得到的是一条看起来健康的曲线和一批实际超时的用户。

6.4.2 请求成本估计:数量均衡 ≠ 工作量均衡

第5章已说明不同请求的成本差异极大。这里给出定量对照:4 个副本各收到 100 个请求(数量完全均衡),但生成的输出 Token 分别为

副本 请求数 输出 Token 相对集群均值
A 100 300 000 2.45×
B 100 80 000 0.65×
C 100 60 000 0.49×
D 100 50 000 0.41×
合计/均值 400 490 000 122 500

按请求数看完全均衡,按 Token 工作量看最忙副本是均值的 2.45 倍。结论:路由决策的输入应当是成本估计(输入长度、预期输出长度、是否命中前缀、状态占用),不是请求计数。轮询均衡的是计数,有限窗口内可能出现工作量偏斜;不能保证均衡实际成本。

6.4.3 缓存局部性与热点的取舍

缓存亲和路由(把共享前缀的请求送到同一副本)能提高命中率,但会制造热点。这是一次权衡而非优化,必须两边都测:

指标 无亲和 有亲和 变化
Token 复用率 20% 45% +25 个百分点
最热副本在途数 3 17 +14
该副本 P99 排队等待 40 ms 310 ms +270 ms
单请求省下的 Prefill — ≈120 ms −120 ms

省下的 120 ms 小于新增的 270 ms 排队等待,净效应为负。这个算例的价值在于它说明:命中率上升是局部收益,排队上升是全局代价,两者量纲相同(都是时间)却常常被分别汇报、从不放在一起比。

路由还必须在队列长度、缓存复用、故障规避三者间取舍,且要注意一个常见陷阱:缓存亲和会与副本故障规避冲突——把某前缀固定到某副本,该副本下线时命中率会骤降。因此要记录失效后的重建时间与命中率恢复曲线。

路由层的能力边界须按固定版本核验,不能假定控制面支持哪些策略 SOURCE_REGISTERED S07。

依据与核验入口:R07 R25 S07


6.5 Expert Parallel:dispatch、combine 与专家偏斜

本节要回答的问题:专家选择如何转化为跨设备通信与等待?

MoE 的三张清单已在第2章给出(计算按激活参数、存储按总参数、传输按路由量)。本节处理它落到多卡上的后果。

6.5.1 路由分布、专家容量与驻留

设 \(E=64\) 专家、分布在 \(W=8\) 个 rank(每 rank 8 专家)、全局批 \(4096\) Token、top-2 路由 → 共 \(8192\) 次"Token—专家"指派。均匀时每 rank \(1024\) 次。

6.5.2 dispatch、专家计算与 combine

一次 MoE 迭代拆成四段:

  1. 路由(Router):计算每个 Token 的专家选择,产生指派表;
  2. dispatch:按指派把 Token 发到持有目标专家的 rank(all-to-all);
  3. 专家计算:各 rank 在本地专家上做前馈;
  4. combine:把结果按原顺序聚合回 Token 所在 rank(第二次 all-to-all),并应用权重。

消息量按第2章的口径(\(d=4096\)、BF16、top-2):

阶段 全局 Token 一层单向总 payload 每 rank 发送 32 层累计(每 rank)
Prefill 4096 64 MiB 8.00 MiB 256 MiB
Decode 64 1 MiB 0.12 MiB 4 MiB

与 6.2 的表呈现同一个规律:Prefill 侧通信量以百 MiB 计(带宽主导),Decode 侧只有几 MiB 却每步都要发生(延迟主导)。所以 MoE 的多卡优化在两阶段上同样是两套思路。

6.5.3 通信—计算依赖与缓冲生命周期

dispatch 与 combine 之间夹着专家计算,形成严格的依赖链:combine 必须等所有目标 rank 算完,因此一次 all-to-all 的等待时间取决于最慢的接收方,而不是平均值。

缓冲生命周期也是一个常被忽略的成本项:dispatch 的接收缓冲要一直保留到 combine 完成,其大小由最坏情况(某 rank 收到的 Token 数)决定,而不是平均情况。因此容量规划必须按上界分配,否则会触发动态扩容或阻塞。

在可用实现上,应以所选提交的接口与依赖说明来解释通信与计算的耦合 SOURCE_REGISTERED;不要用 all-reduce 微基准代替专家通信的证据——两者的消息模式、同步结构与缓冲生命周期都不同。

6.5.4 偏斜:容量由最忙 rank 决定

一组用于偏斜分析的指派分布(8 个 rank):

\[[1536,\;1408,\;1152,\;1024,\;960,\;896,\;704,\;512]\]

和为 \(8192\),均值 \(1024\),最忙 rank 为均值的 1.50 倍。由此:

  • 该 rank 成为节拍,整步时间由它决定;
  • 若按均匀分配估算容量,会高估约 50%(\(1/1.5 = 66.7\%\) 的实际容量);
  • 平均值(1024)完全看不出这件事——平均专家负载不能反映最忙设备。

因此本章的硬要求:必须有逐 rank(或逐专家)的分布记录,而不是一份全局平均。没有分布就没有偏斜结论。

偏斜的来源要分开:路由本身的偏斜(某些专家天然更常被选中)、负载内容偏斜(某批请求都集中在同一领域)、以及拓扑偏斜(某些专家刚好跨了慢链路)。三者对策不同,混为一谈会导致"加专家/加设备"无效。

6.5.5 版本化说明与证据边界

关于专家并行的通信实现,公开仓库提供了弹性缓冲等机制以支持通信与计算的重叠 SOURCE_REGISTERED。这是"某仓库写了什么"层面的证据,不是本项目的实测;该仓库是浮动来源,正式引用前必须固定 commit 或存档快照。能写的是"该机制意图解决什么依赖",不能写的是"本环境启用后加速多少"。

依据与核验入口:R05 R30 S02


6.6 Context Parallel、混合并行与互联域

本节要回答的问题:长上下文为何需要新的切分维度,以及各维度组合有哪些硬约束?

6.6.1 长上下文切分

上下文并行沿序列切分:把长序列分到多个 rank,各自持有自己分片的状态。TP 可以通过头分片降低每 rank KV 容量,但收益会受 KV 头数、复制与内核布局限制;上下文并行提供沿序列切分的另一条容量和工作量扩展路径。代价是注意力需要跨分片访问,产生额外通信;且分片间存在依赖,实现上通常以环形或分块方式传递键值。

它可能带来状态容量扩展和注意力工作量分摊;是否缩短单请求还取决于新增通信——这一点必须与 6.1 的目的分类对应清楚。

6.6.2 五种维度的组合约束

维度 切的是什么 主要通信 主要代价
TP 张量(头/隐层维) all-reduce / all-gather 同步次数多,Decode 延迟主导
PP 层 点对点传递 气泡与阶段不平衡
DP 模型副本 无(计算内) 权重重复驻留
EP 专家 all-to-all 偏斜与缓冲上界
CP 序列 跨分片键值传递 依赖链与通信量

组合时必须逐维回答"这一维实际切了什么、产生了什么通信、复制了什么"。两个典型陷阱:

  • TP 与 CP 同时启用时,状态可能被复制(第4章的每 rank 占用不等于总量除以设备数);
  • PP 与 EP 组合时,专家组是否跨越阶段边界决定了 all-to-all 是否跨节点。

6.6.3 互联域边界

通信代价高度依赖它发生在域内还是跨域。机柜级高速互联域(如公开产品页所述的整机柜 NVLink 域 SOURCE_REGISTERED)与跨节点网络的带宽通常差一个数量级,因此:

  • TP 组必须在高速互联域内(同步次数最多);
  • PP 的相邻阶段尽量域内(消息大);
  • EP 组是否跨域是本布局最重要的单一决策(all-to-all 消息量大且对延迟敏感)。

产品页的带宽与倍数是厂商标称,不是本项目实测,不得作为性能结论的依据 SOURCE_REGISTERED。本环境必须自己测。

6.6.4 交叉支持矩阵

并行维度与推测解码、图模式、缓存布局之间存在组合约束,必须为每个模型与后端建立交叉矩阵,并按固定版本的能力表填写 SOURCE_REGISTERED:

组合 需确认的问题 记录方式
并行 × 推测解码 草稿验证是否跨越并行边界;接受率是否随并行度变化 支持 / 不支持 / 未验证
并行 × 图模式 图捕获是否覆盖集合通信;形状变化后是否重捕获 支持 / 回退 / 不支持
并行 × 缓存布局 状态分片后前缀复用是否仍成立;命中是否跨域 支持 / 受限 / 不支持
并行 × 量化 通信是否以低精度传输;缩放元数据是否随消息走 支持 / 精度受限

发布页记载某组合可用,只证明该版本声明了这条路径,不保证任意组合可用。未验证的格子必须写"未验证",不能留空(留空会被读成"应该没问题"),也不能写成"性能差"(那是另一类判断)。混合层结构的状态管理另有专门机制,不适用单一全注意力缓存公式 SOURCE_REGISTERED。不同后端的适配栈与版本依赖需按各自入口核验 SOURCE_ENTRY_ONLY。

依据与核验入口:R28 R19 S16


6.7 实验:并行扩展不只看加速比

本节要回答的问题:怎样在公平边界下比较不同设备数量和布局?

6.7.1 两种公平性,不能混用

公平性标准 固定什么 比较什么 回答的问题
固定任务(强扩展) 同一份负载计划 完成时间 / 单请求延迟 "加卡能让这一个请求快多少"
固定资源总量(弱扩展) 设备总数 同 SLO 下的合格完成速率 "这批卡怎么切分能服务最多请求"

两种标准不可混用,一次实验只能声明一种。用固定任务的加速比去论证"应该这样部署",是最常见的越界。

6.7.2 扩展效率的两个口径

  • 强扩展:同一单请求,4 卡 TP=4 时单步 \(18\) ms,8 卡 TP=8 时 \(11\) ms → 加速 \(18/11 = \mathbf{1.64}\),效率 \(1.64/2 = \mathbf{81.8\%}\)。
  • 弱扩展:设备与副本等比增加,吞吐 \(240 \to 470\) token/s → 加速 \(\mathbf{1.96}\),效率 \(\mathbf{97.9\%}\)。

两者相差 16 个百分点,这正好说明:弱扩展效率高是因为它没有要求单请求变快,只是复制了容量。报告扩展效率必须标明是哪一种。

同时必须给出同 SLO 下的合格完成速率(第7章三个量纲中的第二个)。只报"吞吐提升 96%"而不报"在 P99 TTFT ≤ 1 s 且质量达标下提升多少",无法支持部署决策。

6.7.3 没有单卡基线时怎么办

单卡装不下模型时,不能虚构单卡基线。正确做法是选最小可运行布局作为参照,并明确写出:参照布局是什么、为什么它是最小可行、加速比是相对它而非相对单卡。

证据身份与全书一致:来源快照、配置设计、合成/仿真与公开实测分别标识;没有证据支持的地方保持空白,不给倍数。

扩展报告还要附带:每 rank 内存、每 rank 执行与通信时间、副本分布、失败记录与资源数量;MoE 场景另需 per-rank 状态、路由分布、消息尺寸、最慢 rank 与互联域说明。

依据与核验入口:R08 S16


6.8 深化:并行度的转折点来自容量、通信和副本收益的竞争

6.8.1 通信模型中的“启动一次”需要定义

对一个 \(p\) rank 的ring all-reduce,若 \(\alpha\) 表示每个通信步的固定延迟、\(\beta\) 表示每字节时间,理想串行ring近似可写为

\[T_{AR}\approx2(p-1)\alpha+2\frac{p-1}{p}M\beta.\]

若微基准直接给出一次完整collective的启动截距,则应把它记作 \(\alpha_{coll}(p)\),不能又乘 \(2(p-1)\)。本章前面的6 μs是整次集合通信的截距,已包含在该配置下的固定项;它不是任意规模每跳延迟。

增加TP时,权重矩阵的本地工作量降低,但用于求和的逻辑输出张量可能不变,启动次数也可能基本不变。于是计算收益逐渐被通信下限吞没。在某些布局里消息算法会由ring改为tree或专用互联内核,斜率与截距同时变化,因此不能把一个两卡微基准的拟合参数直接外推到八卡。

6.8.2 先算静态可行,再测服务曲线

第6.1节修正后的871/610/88是独立模型的显存路数上限。它不能替代三种布局在同一SLO下的吞吐。设TP度为 \(p\) 时每副本合格完成能力为 \(g_p(\mathcal W)\),固定总卡数 \(G\) 的集群能力才近似为

\[G_p(\mathcal W)=\lfloor G/p\rfloor\,g_p(\mathcal W),\]

而这个乘法还要求路由均衡、公共链路和控制面不先饱和。\(g_p\) 是负载画像的函数,不是仅由权重大小决定的常数。并行度提高即使让某条请求更快,也可能因副本数减少而降低总能力;反过来,扩大TP释放足够KV、减少抢占后,单副本吞吐增幅也可能补回副本损失。必须测服务曲线才能判断方向。

图6-R1 正确计入KV分片后的静态容量

6.8.3 MoE要区分本地路由、远程路由与最慢专家

若全局有 \(T\) 个Token、每Token选 \(k\) 个专家、隐藏维 \(d\)、每元素 \(b\) 字节,则单向逻辑载荷为 \(Tkdb\)。它包括发给同rank专家的部分,不一定全部经过网络。假设专家在 \(p\) 个rank均匀分布且选择均匀独立,远程份额的期望约为 \((p-1)/p\);真实路由有亲和或复制时,这个比例需要实测。Dispatch和combine还应分别记录,不能只算一次就称为整层通信。

总字节也不是完整预测器。每个rank需要等相关输入和专家结果,最忙专家决定一部分节拍;小批时,单专家的Token数量不足以形成高效GEMM;大批时,热点专家又可能占满接收缓冲。优化可以改变专家复制、路由局部性或负载均衡,却会增加权重驻留和一致性管理成本。用“总参数大但激活参数小”概括这些取舍,不足以解释多卡效率。

6.8.4 长上下文并行要同时对账状态和位置

沿序列分片后,每rank持有的是不同位置的状态。注意力必须取得正确的全局位置、掩码及可访问分片,否则即使通信字节正确,也可能算出错误结果。推测验证的多个新位置、滑窗、稀疏索引和跨层共享会进一步改变访问集合。每个CP候选至少应验证数值或任务质量、每rank实际驻留、跨rank载荷、以及缺失或超时分片的失败路径。

训练中的序列并行、Prefill CP、Decode CP和KV分片未必是同一维度。读者在给布局命名时应写清“切谁、在哪个阶段切、谁复制、需要什么归约”,而不只是写一个TP×PP×CP乘法。这种布局描述才能在第14章的跨平台比较中保持可解释。


6.9 实验图表补充:把MoE偏斜从一句话变成rank负载图

本章6.5节设置4096个Token、top-2路由、8个rank,总共8192次Token—专家指派。用 6.5 节的负载向量作图后,可直接看到最忙rank为1536,平均1024,偏斜系数1.5。这里只核验指派次数与算术,不声称拿到了真实路由trace。

若每次指派的计算成本一致、同步步骤必须等最慢rank完成,那么用平均1024代替最忙1536,会把可服务能力高估约50%;等价地,最忙约束下的能力为均匀理想值的2/3。这不等于服务时延必然变成1.5倍,因为真实系统还有共享专家、不同内核效率、通信与计算重叠,以及不相等的专家Token分布。

从示意图升级为实测图:同一迭代保存每rank指派数、接收字节、dispatch/专家计算/combine时间与开始结束事件。对比均值、最大值和max/mean,并沿依赖链判断最忙rank是否真的位于关键路径。通信微基准正常与模型路由均衡是两种独立证据。

图6-V1 MoE的瓶颈是最忙rank,不是平均rank

图6-V1 MoE的瓶颈是最忙rank,不是平均rank

数据身份:TEACHING_CALCULATION。条件:4096Token、top-2、8rank;相同单位指派计算成本、无重叠的简化假设。

rank 指派数 相对均值
0 1536 1.5
1 1408 1.375
2 1152 1.125
3 1024 1.0
4 960 0.938
5 896 0.875
6 704 0.688
7 512 0.5

复算公式:skew=max(load)/mean(load)。

出处与边界:本书6.5;1.5倍偏斜不等于实际端到端慢1.5倍;通信、专家异构及重叠需另测。

数据与绘图点 · PNG · 重绘代码

6.10 实验深化补充:通信成本既随批大小变,也随通信域变

本章前面只有 rank 偏斜与官方规格 roofline。这一节补三组可核验的通信微基准,把“多卡通信很贵”拆成可测的两条轴:批大小与通信域。

轴一,批大小(TensorRT-LLM NVLink One-Sided AlltoAll,GB200 NVL72,EP8,BF16,DeepSeek-V3结构profile)。每rank batch从1增到2048时,dispatch从18.9μs升到311.8μs,combine从31.0μs升到322.6μs。batch1—32阶段时延近乎常数(dispatch 17.6—18.9μs),说明这是启动/固定开销主导;到batch128以后明显进入带宽项(逻辑GB/s由6.07升到753.28)。这解释了为什么Decode小批和Prefill大批走的是不同的瓶颈路径。

轴二,通信域(DeepEP历史低延迟RDMA内核,H800+每GPU CX7 400Gb/s,每rank 128Token、hidden 7168、top8)。EP由8扩大到256时,dispatch从163μs升到194μs,combine从318μs升到360μs。该表dispatch为FP8、combine为BF16,两列dtype不同,不能只看高低判断内核优劣。每rank负载固定,但EP扩大后全局数据量也随之增大,因此这是“固定局部负载下的通信扩展”,不是固定任务强扩展。

第三组(同NVLink配置,固定每rank batch=2048)。EP8→64时dispatch由311.8μs升到399.2μs、combine由322.6μs升到372.1μs。通信域扩大并非免费。

边界:以上都是通信微基准。“逻辑GB/s”包含本rank流量,不是NIC或NVLink的物理计数,不能当带宽上限;也不能据此推出同模型2/4/8卡的端到端吞吐扩展效率——那需要端到端扫描,尚未找到严格同条件的公开表,如实留缺。

图6-E1 DeepEP历史低延迟内核:EP扩大后的通信代价

图6-E1 DeepEP历史低延迟内核:EP扩大后的通信代价

数据身份:PUBLISHED_MICROBENCHMARK。条件:固定3b1045db43ed;H800,每GPU配CX7 400Gb/s;每rank128Token,hidden7168、top8、FP8 dispatch/BF16 combine。

EP ranks Dispatch/us Combine/us Dispatch报告GB/s Combine报告GB/s
8 163 318 46 46
16 173 329 43 44
32 182 350 41 41
64 186 353 40 41
128 192 369 39 39
256 194 360 39 40

出处与边界:Y03;每rank负载固定,EP增大全局量也变;不是固定任务强扩展。各EP专家总数未公开;图不外推当前DeepEP版本。

数据与绘图点 · PNG · 重绘代码

图6-E2 同一EP8配置:小批启动项与大批带宽项

图6-E2 同一EP8配置:小批启动项与大批带宽项

数据身份:PUBLISHED_MICROBENCHMARK。条件:GB200 NVL72;DeepSeek-V3结构profile:hidden7168/top8/256专家;BF16、EP8;每rank batch1—2048;理想路由。

每rank batch Dispatch/us Combine/us Dispatch逻辑GB/s Combine逻辑GB/s
1 18.9 31.0 6.07 3.7
2 18.4 31.1 12.47 7.36
4 18.1 31.4 25.34 14.59
8 17.6 31.4 52.22 29.23
16 18.5 32.6 99.34 56.31
32 18.2 32.7 201.72 112.25
64 22.3 34.1 328.68 215.18
128 31.7 38.1 462.4 384.96
256 50.8 53.0 578.18 554.44
512 89.5 91.7 656.24 640.56
1024 166.3 175.6 706.34 668.62
2048 311.8 322.6 753.28 728.2

出处与边界:Y04;每个标记为原表值;连线仅辅助阅读。逻辑带宽包括本rank流量,不能当NIC或NVLink物理计数。

数据与绘图点 · PNG · 重绘代码

图6-E3 固定每rank batch,扩大EP通信域并非免费

图6-E3 固定每rank batch,扩大EP通信域并非免费

数据身份:PUBLISHED_MICROBENCHMARK。条件:同Y04 BF16扫描,每rank batch=2048,EP8/16/32/64,256总专家,GB200 NVL72域内。

EP ranks Dispatch/us Combine/us Dispatch逻辑GB/s Combine逻辑GB/s
8 311.8 322.6 753.28 728.2
16 343.3 348.4 684.1 674.15
32 373.7 369.5 628.54 635.66
64 399.2 372.1 588.34 631.18

出处与边界:Y04;全局Token数随EP增加;这是固定局部负载的通信扩展,不是同模型2/4/8卡吞吐强扩展。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 单卡不能运行时,扩展效率应该如何定义?

答案方向:不虚构单卡基线,选最小可运行布局作参照并写明它是最小可行。加速比相对该布局而非相对单卡;效率 = 加速比 ÷ 设备倍数,但必须同时说明采用的公平性标准(固定任务还是固定资源)。若参照布局本身也是多卡,效率数字的含义是"相对于最小可行布局再投入设备的边际收益",要在结论里写清,不能简写成"扩展效率 81.8%"。

2. 多两个副本和扩大 TP 度如何公平比较?

答案方向:总设备数固定时两者互斥(6.1)。公平比较必须在同一总资源下,比同 SLO 的集群级合格完成速率,而不是单实例峰值吞吐。且要分三项分别给出:单请求延迟(倾向大 TP)、集群总容量(倾向多副本)、尾延迟与拒绝率(受路由与偏斜影响,见 6.4)。只报其中一项必然偏向某一种布局。另需重算每 rank 状态占用——分片可能引入复制(第4章),这会改变容量结论本身。

3. MoE 平均通信量不变而最慢 rank 变慢,扩展结论如何变化?

答案方向:容量由最忙 rank 决定,不由平均决定。6.5.4 的例子里均值 1024 而最忙 1536(偏斜 1.50),按均值估算会高估约 50%。因此:① 平均通信量不变不能推出扩展结论不变,要看分布与缓冲上界;② 增加设备未必改善——若偏斜来自路由分布本身(某些专家天然更热)而非容量不足,加设备只会把同样的偏斜复制到更多 rank 上;③ 报告必须给出 per-rank 分布与最慢 rank,而不是全局平均。判别方法是分别记录路由偏斜、负载内容偏斜与拓扑偏斜,看加设备后偏斜系数是否变化。


本章验收清单

节 验收标准
6.1 每个候选布局都注明实例卡数、副本数与比较目标;排除理由区分"装不下"与"性能差";每 rank 占用按分片规则重算而非均分
6.2 能说明切分位置对应的通信类型;通信结论区分带宽主导与延迟主导,并说明在 Prefill 还是 Decode;通信次数按 trace 取证而非照抄文档
6.3 气泡比例按在线实际微批数计算,不套用训练批量;给出阶段平衡利用率;写明单阶段异常的恢复边界
6.4 副本级到达、在途、状态占用、达标率分别记录;能识别请求数均衡而 Token 工作量不均衡;缓存亲和同时报告命中收益与排队代价
6.5 有逐 rank/逐专家分布;dispatch/combine 与依赖链分开描述;缓冲按上界而非均值规划;不把 all-reduce 微基准当专家通信证据
6.6 能画出 rank 映射并指出域内与跨域路径;五种维度各自说明切了什么、通信是什么、复制了什么;交叉矩阵未验证项写"未验证"
6.7 每个加速或效率数字都说明基准、公平性标准与 SLO 条件;无单卡基线时选最小可行布局并说明

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第7章 服务目标与性能指标

核心论点:性能指标首先是一份统计契约:事件、边界、单位、样本、分母和聚合方式缺一不可。

本章与指标字典的关系

book_support_v05/metrics_dict_v05.md 提供六部分基座:事件字典(§1)、指标定义(§2)、分母规则(§3)、工具字段映射(§4)、Sample-B0 样例(§5)、E0 单元测试断言表(§6)。本章在此之上做三件事:

  1. 把指标接回业务目标(7.1 的 SLO 契约)——指标不是目的,达成契约才是;
  2. 把 Sample-B0 的复算过程完整展开,并补齐异常样本的处理规则(7.2、7.7);
  3. 补足字典里只给了定义、没给统计方法的部分:吞吐(7.3)、分位数聚合(7.4)、Goodput(7.5)、质量验收(7.6)。

文中所有数值均为构造数据,程序复算范围见E0清单。

本章导航

读完本章,你应该能够:写出一份六要素齐全的 SLO 契约;在同一条事件流上复算 TTFT/TTFO/ITL/TPOT 并解释两个工具为何给出不同数字;说清一个"tokens/s"究竟算了哪些 Token;解释为什么两个实例的 P99 不能取平均;把联合达标率、合格完成速率与 SLO 约束容量分开定义,而不是拿两个分位数互相比较;以及给指标计算器写一组不需要 GPU 的单元测试。


7.1 从业务体验定义 SLI 与 SLO

本节要回答的问题:什么表现才算对目标用户可用?

指标是回答契约的工具,反过来就本末倒置。先定义"对谁、完成什么、在什么窗口内、算不算达标",再挑可观测的指标去度量它。

7.1.1 完成单位与服务边界

SLI(Service Level Indicator)是被度量的量,SLO(Service Level Objective)是对它的目标。二者都要先确定完成单位——沿用第1章的五类对象:任务、请求、模型调用、尝试、会话。同一个数字在不同完成单位下含义完全不同:一次任务可能包含三次请求,一次请求可能留下两个 attempt。

服务边界还必须写明链路范围:从哪个事件起、到哪个事件止、包含哪些外部依赖(第1章 1.1.3 的责任边界表)。接口延迟是否包含检索,取决于接口完成是否依赖检索;不能仅因它是下游服务就排除。用户任务时延则按完整任务边界计量。边界不写清,达标率就会变成一场各说各话的争论。

7.1.2 延迟、可用性与质量三类门槛

一份完整的契约至少覆盖三类门槛,缺任何一类都可能"达标但不可用":

类别 典型指标 失败表现
延迟 TTFT_client、TTFO_client、E2E、P99 分位 用户等待过久
可用性 成功率、取消率、超时率、拒绝率(按 attempt 计) 请求没结果
质量 任务通过率、格式合规率、关键字段正确率 有结果但不可用

质量门槛最常被省略,也最致命:一个把输出长度截断到 10 个 Token 的系统,延迟和可用性都会非常漂亮。因此任何性能优化的结论都必须带上质量门槛(7.6 展开)。

7.1.3 分层目标、观察窗口与异常处理

不同负载形态不该套用同一阈值。建议至少分三层,各自单独设阈值与窗口:

层 场景 主指标 窗口建议
交互 短输入短输出问答 TTFO_client P95、E2E P99 滚动 5—30 分钟
长文档/长输出 摘要、生成、代码 E2E P90、中断率 滚动 1—24 小时
异步/离线任务 批量处理、报告 单位时间完成任务数、任务成功率 批次窗口

契约六要素,缺一不可:

要素 内容 常见缺失
对象 哪一类请求/任务/租户 只写"服务"
指标 名称 + 起点/终点/时间域 只写 "P99 延迟"
阈值 数值与单位 无来源的"行业标准"
窗口 滚动窗口长度与更新频率 未写,导致用 5 分钟峰值对 24 小时承诺
分母 哪些 attempt 进分母 静默剔除超时与取消
异常处理 超时、取消、部分成功、数据缺失如何记 缺失值默认记 0

最后一条尤其要写死:缺失不等于零。未观测到首个可见 Token 时,TTFO 应记为缺失(null),不能填 0——填 0 会把"完全没输出"伪装成"极快"。同理,单 Token 请求的 TPOT 应记为不适用,而不是 0(7.2.3、7.7)。

还有一条组织层面的约定:内部工程目标与对外承诺必须分开登记。内部目标可以激进,用于驱动优化;对外承诺要留余量,并写明违约处理。本书出现的所有阈值都是演示值,不构成任何生产建议。

本节要点

  • 先定义完成单位与服务边界,再挑指标;边界不清则达标率无意义。
  • 契约需同时覆盖延迟、可用性、质量三类门槛,质量最易被省略也最致命。
  • 分层设阈值:交互、长文档/长输出、异步任务各有主指标与窗口。
  • 六要素齐全,且缺失值一律记为缺失而非零。

常见误区

  • 误:SLO 写"P99 延迟 < 2 秒"就够明确了。 正:缺对象、起点终点、时间域、窗口与分母,无法判断是否达标。
  • 误:超时和取消的请求不算失败,所以从分母里去掉。 正:去掉后成功率是被过滤过的;应单列一层报告。
  • 误:行业里都要求 2 秒,所以我们的阈值也是 2 秒。 正:阈值应来自业务体验与该系统的负载结构,引用外部值须给出来源。

本节验收

  1. 你能写出一份六要素齐全的 SLO 契约,并指出其中每个指标的起点、终点、时间域。
  2. 你能为交互、长文档、异步三类场景各设一套阈值与窗口。
  3. 你能说明本系统对"缺失值"的处理规则,并证明没有把缺失填成 0。

依据与核验入口:R24 S18


7.2 时间指标与工具迁移:TTFT、TTFO、ITL、TPOT

本节要回答的问题:名称相同的指标,怎样确认含义也相同?

四个缩写背后至少有十几种合法定义。本节不追求统一全世界,只要求一件事:任何一次报告,都要能把它用到的定义写在表格里。

7.2.1 首字节、首事件、首推理与首答案

一次流式响应里,"第一次出现某样东西"至少有四个候选时刻,它们可以相差数秒:

时刻 定义 是否用户可感知
\(t_{\text{first-byte}}\) 客户端收到第一个响应字节(可能只是响应头) 否
\(t_{\text{first-content-event}}\) 客户端收到第一个含内容的事件 取决于内容类型
\(t_{\text{first-reasoning}}\) 收到第一个推理/思考内容 通常否
\(t_{\text{first-visible}}\) 收到第一个可见答案 Token 是

第1章 1.4.1 已给出五类协议事件(控制、元数据、工具调用、内容、终端),这里的关键是:内容事件里还要再分"推理内容"与"可见答案"。当模型先输出推理内容时,\(t_{\text{first-content-event}}\) 与 \(t_{\text{first-visible}}\) 的差值就是用户"看到进度条动但看不到答案"的那段时间。

本书口径(与字典 §2 一致):

\[ T_{\text{TTFT}} = t(\text{engine.first\_token\_generated}) - t(\text{engine.queued}) \qquad T_{\text{TTFO}} = t(\text{首个可见内容写出}) - t(\text{engine.queued}) \]

两个都要报。只报 TTFT 会让推理先行模型的用户体验被系统性高估;只报 TTFO 又会丢掉"系统确实开始工作了"的信息。

7.2.2 客户端时间域与引擎时间域

同一个指标在两个时间域里是不同的量,不能混用:

口径 起点 含什么 不含什么
TTFT_server engine.queued 排队、prefill、首 Token 生成 连接、鉴权、网络、客户端解析
TTFT_client client.send 上述全部 + 网络与网关缓冲 客户端渲染(若终点取收到事件)

差值不是误差,是信息。以 Sample-B0 为例(字典 §5):TTFT_server = 634 - 21 = 613 ms,TTFT_client = 668 ms,差 55 ms 由"进入引擎前的 21 ms"与"服务端首 Token 生成后到客户端收到的 34 ms"构成;后者在带误差上界的关联下可进一步拆为生成完成到写出 7 ms(634→641)与传输/网关缓冲/客户端接收 27 ms(641→668)。若两个时间域未校准,则不能报告由跨域端点相减得到的 21 ms、34 ms 或 27 ms 分段;只能在各自域中报告时长,并将跨域分解标为不可识别。这 55 ms 若在跨地域部署下变成 300 ms,那么只优化引擎内部的人永远看不到它,而用户全部感受到它。

7.2.3 请求级 TPOT 与间隔级 ITL

这是本章最容易出错的一对指标,也是开篇场景的根源。

  • TPOT_req:按 Token 分摊,\((T_{\text{e2e}} - T_{\text{TTFT}})/(N_{\text{out}} - 1)\);
  • ITL_event:在本例观测能力下按 内容事件 采样,样本数 \(M - 1\),报告的是相邻输出事件间隔的分布。

Sample-B0 的复算(20 个输出事件、301 个输出 Token):

指标 算式 结果
TTFT_server \(634-21\) 613 ms
TTFO_server \(1180-21\) 1159 ms
TTFO_client \(1180+27-0\) 1207 ms
E2E_server \(4210-21\) 4189 ms
TPOT_req \(3576/300\) 11.92 ms
\(\overline{\text{ITL}}\) \(3569/19\) 187.84 ms
推理→可见答案间隔 \(1207-668\) 539 ms

三个必须一起检查的恒等式:

\[ 613 + 11.92 \times 300 = 4189 \quad\checkmark\qquad n_{\text{ITL}} = 19 \quad\checkmark\qquad \frac{\overline{\text{ITL}}}{\text{TPOT}} = \frac{3569}{19}\Big/\frac{3576}{300} = \frac{3569}{3576}\cdot\frac{300}{19} \approx 15.7586 \]

最后一项说明比值由两个因子组成:时间窗因子 \(3569/3576\approx0.998\) 与分母因子 \((N-1)/(M-1)=300/19\approx15.79\)。因此不能把比值无条件写成 \((N-1)/(M-1)\):只有当两者时间窗相同时(此处相差 7 ms,源于"首 Token 生成"与"首个事件写出"不是同一刻),分母因子才是唯一解释。跨请求聚合时还要写明请求权重与间隔权重——按请求平均的 ITL 与把全部间隔混合后的 ITL 也不是同一个量。把两者直接比较,或拿 A 工具(按事件计)的数字去比 B 工具(按 Token 计)的历史基线,会得出"性能退化 15 倍"的荒谬结论——这正是开篇场景的第二种成因。

官方也明确了这一点:当一个输出包含多个 Token 或聚合权重不同时,按事件采样的 ITL 与按请求计算的 TPOT 不相等 [SOURCE_REGISTERED] DOC-W07。

还有两类样本必须单独处理:

样本 正确做法 错误做法
输出 Token 数 \(N_{\text{out}} \le 1\) TPOT 记为不适用,从统计中排除;ITL 同样无样本 记 0,把"没输出"混入均值
未观测到首可见 Token TTFO 记为缺失 记 0,伪装成极快

某实现对 \(N_{\text{out}} \le 1\) 的请求把 TPOT 记为 0 且基准统计将其排除——"记为 0"是它的存储约定,"被排除"才是统计口径;照抄前者会得到被污染的分母 [SOURCE_REGISTERED] DOC-W07。

7.2.4 推理输出与可见输出的计数契约

"生成了多少 Token"至少要能回答四个不同的问题,四个数可以同时存在且互不相等:

  1. 生成总数:模型实际产生的 Token(含推理内容),来自服务端计数;
  2. 可见答案 Token:用户看到的正文,用于体验类指标;
  3. 推理 Token:可观测时单独统计,用于成本与"思考时长"分析;
  4. 拼接后重新分词:客户端对完整文本重新分词的结果,不等于生成总数。

计数契约必须写明:token_scope(全部/可见/推理/重算)、来源(服务端 usage / 事件 delta 累加 / 客户端重算)、以及块边界是否与分词边界对齐(通常不对齐)。第1章 1.4.2 已说明:按 delta 累加可以重建内容,但不能反推准确 Token 数或逐 Token 时刻。

规则:只能取得分块到达时刻时,就只报告"该观测层的事件间隔",不得称之为 Token 间隔。

7.2.5 工具原值、规范化值与迁移回归

同一条事件流,两个工具给出不同的 TTFT,往往不是谁错了,而是"首"的定义不同。

在复核资料描述的推理模型场景中,AIPerf 的 TTFT 指首个 Token(含推理内容),TTFO 指首个非推理输出;因此要对齐该迁移说明中的 GenAI-Perf 的 TTFT,应取 AIPerf 的 TTFO,而不是按字段名匹配 TTFT [SOURCE_REGISTERED, 继承] S17(与 DOC-W09 同址,尚未重新取正文,也未本地执行)。

用一个算例说明后果(延续 Sample-B0):

报告口径 数值 谁的体验
首 Token(含推理) 668 ms 系统开始产出
首可见答案 1207 ms 用户开始读到答案
差值 539 ms 用户"看着进度条等"的时间

换工具后 TTFT 从 1207 ms 变成 668 ms,不构成系统提速 539 ms——它只是换了"首"的定义。这类变化必须被识别为口径迁移,而不是性能收益。

为了让它可审计,每条指标的迁移都要登记七列:

字段 含义
raw_field 工具原始字段名与所在输出
normalized_field 本书口径名(如 TTFO_client)
event_scope 起点/终点事件
token_scope 计入哪一类 Token
unit 单位与量级
mapping_version 映射依据:工具版本 + parser 版本 + 查阅日
missing_reason 缺失原因(未观测/不适用/被排除)

最终报告保留三列:工具原值、规范化值、差异原因。不要把原值抹掉只留规范化值——下次工具升级时,原值是唯一能定位口径漂移的证据。

本节要点

  • 四个"首"时刻要分开;推理先行模型必须同时报 TTFT 与 TTFO。
  • 服务端域与客户端域是两个不同的量,差值是信息不是误差。
  • 本例 TPOT 按 Token 分摊,ITL_event 按事件取间隔;其比值还包含时间窗因子,只有窗口完全一致时才能简化为 \((N-1)/(M-1)\)。
  • \(N_{\text{out}}\le1\) 记不适用、未观测记缺失,都不记 0。
  • 工具迁移保留原值、规范化值与差异原因三列,并登记 mapping_version。

常见误区

  • 误:两个工具都叫 TTFT,可以直接比。 正:先看 event_scope 与 token_scope;推理先行时 TTFT 与 TTFO 可能差数百毫秒。
  • 误:单 Token 请求的 TPOT 是 0,所以系统极快。 正:应记为不适用并排除;记 0 会污染分母。
  • 误:块到达间隔就是 Token 生成间隔。 正:块 ≠ Token;只能报告观测层的事件间隔。
  • 误:换工具后 TTFT 变好了,说明优化生效。 正:先确认是不是口径迁移;用 Sample-B0 复算两边。

本节验收

  1. 你能在同一条事件流上复算 TTFT/TTFO/ITL/TPOT,并写出每个的起点、终点、时间域。
  2. 你能解释本系统中 ITL 与 TPOT 相差多少倍,以及倍数来自哪里。
  3. 你能说明 \(N_{\text{out}}\le1\) 与"未观测到首可见 Token"两类样本的处理规则。
  4. 你能为自己的工具链填出七列迁移登记表。

依据与核验入口:R01 R12 DOC-W07


7.3 吞吐:请求、输入、输出与有效完成

本节要回答的问题:一个 tokens/s 数字究竟算了哪些 Token?

吞吐是唯一一个"单位不写就等于没写"的指标:它可以是请求/秒、输入 Token/秒、输出 Token/秒、生成候选/秒,或有效完成任务/秒。五个数可以同时存在且互相矛盾。

7.3.1 完成请求与生成 Token

\[ R = \frac{N_{\text{completed}}}{T_{\text{window}}}\quad[\text{req/s}],\qquad X_{\text{out}} = \frac{\sum N_{\text{out}}}{T_{\text{window}}}\quad[\text{Token/s}],\qquad X_{\text{in}} = \frac{\sum N_{\text{in}}}{T_{\text{window}}}\quad[\text{Token/s}] \]

\(R\) 为请求完成速率,\(X\) 为 Token 速率,\(T_{\text{window}}\) 为观察窗口长度(s)。三者必须同时报告,否则无法判断一次变化的性质:输出长度分布变长时,\(X_{\text{out}}\) 可能上升而 \(R\) 下降——这时"吞吐提升了 20%"和"吞吐下降了 15%"说的是同一件事。

7.3.2 输入、输出、缓存与候选 Token 各归各账

至少四类 Token 不能混在一个总数里:

类别 是否计入吞吐分子 说明
输入 Token 逻辑输入速率与实际执行输入速率分列 前缀命中减少执行量,但被服务的逻辑输入仍可计入业务输入速率
输出 Token(最终保留) 计入 \(X_{\text{out}}\) 用户可见与推理内容建议分列
缓存复用省下的计算 不计入 省下的计算不是新产生的吞吐;只作为"节省量"单独报告
推测解码草稿候选 不计入有效输出 被拒绝的 Token 消耗了资源,但不产出内容;记入成本分子,不记入产出分子

两条容易踩的坑:

  • 混淆逻辑输入与执行输入。前缀缓存可以真实提高同资源下完成请求或服务逻辑输入的速率;错误在于把复用 Token 伪装成重新计算的 Token,或把“避免的 FLOPs”直接加到输出吞吐。报告应分别命名 logical_input_tokens/s、executed_input_tokens/s、replayed_tokens/s 与有效输出速率。

7.3.3 固定窗口与排空:两种方法差多少

同一套系统、同一负载,用两种方法测出的吞吐可以差 20% 以上。算例:

方法 定义 算例 结果
固定窗口法 开窗 60 s,数窗口内完成的请求 \(240/60\) 4.00 req/s
排空法 固定 300 个请求,测"首发→末完成"的时间 \(300/62\) 4.84 req/s

对应的输出 Token 速率:窗口法 \(240\times300/60 = 1200\) Token/s;排空法 \(300\times300/62 = 1451.6\) Token/s。

差异不是测量误差,是方法差异:完成事件窗口法计入所有在窗口内完成的请求,包括窗口开始前已经到达的请求;窗口末仍在途的请求单列。如果只计窗口内到达的队列,那是另一个 cohort 口径,须等队列排空或说明删失,排空法则包含爬坡与收尾阶段,且把"最后一个请求"的完整耗时摊进了分母。因此每次报告吞吐都必须写明方法、窗口长度、并发度与在途处理规则,否则两次实验的数字不可比。

本节要点

  • 请求速率、输入 Token 速率、输出 Token 速率必须同时报告。
  • 避免的计算不计作新执行量;已服务的逻辑输入可以进入逻辑吞吐。被拒候选不进入有效输出,实际消耗计入成本。
  • 固定窗口法与排空法的结果不可直接比较,报告必须写明方法、窗口与并发。

常见误区

  • 误:吞吐提升了 20%,系统变快了。 正:先看是哪个吞吐;输出变长时 Token/s 与 req/s 可以反向变化。
  • 误:命中缓存省下的计算可以计入吞吐。 正:服务的逻辑输入Token可以计入逻辑输入吞吐;避免的计算只能单列,不伪装成实际执行量。
  • 误:被拒绝的草稿 Token 不算数,所以不占成本。 正:它消耗了资源,必须进成本分子。

本节验收

  1. 你能给出三个吞吐数字(req/s、输入 Token/s、输出 Token/s)及其窗口定义。
  2. 你能说明本系统中缓存复用与草稿候选分别计入哪个集合。
  3. 你能说明一次吞吐对比实验用的是窗口法还是排空法,并给出并发度。

依据与核验入口:R11 R14 S18


7.4 分位数、分层与统计不确定性

本节要回答的问题:平均值和 P99 分别遗漏了什么?

7.4.1 分布与长尾

均值确实会掩盖长尾:1000 个请求中 999 个 100 ms、1 个 5000 ms,均值为 \((999\times100+5000)/1000 = 104.9\) ms。但"用高分位数就能抓住单个极端值"是错的——分位数看不看得见这个长尾,取决于分位算法与所选的 \(q\):

条件 结果 说明
最近秩法,\(q=0.999\) 取第 \(\lceil 0.999\times1000\rceil = 999\) 个样本 = 100 ms 极端值未被计入
线性插值(NumPy method="linear"),\(q=0.999\) \(100 + 0.001\times(5000-100) = \mathbf{104.9\ ms}\) 只反映插值位置
最近秩法,\(q=0.9995\) 取第 \(\lceil999.5\rceil=1000\) 个 = 5000 ms 极端值进入分位数

结论有两层:对这里的最近秩法,应根据 \(\lceil qn\rceil\) 是否落入极端值所在秩判断;插值法还受相邻两个秩的权重影响;选 P99 还是 P99.9,本身就是在决定"容忍多少比例的坏体验",必须与阈值一起写进契约。不同工具实现不同的分位算法(最近秩、线性插值、桶插值),同一份数据可以给出不同答案,因此必须选定并全程保持一致。

但分位数也不是万能的。它会被负载结构变化伪装成系统变化:某天 P95 从 800 ms 降到 500 ms,可能只是因为长文档请求占比从 30% 掉到 10%。因此分位数必须分层报告——按输入长度档、输出长度档、租户/模型、冷启动与否分层,每层单独给样本量与分位数。不分层就说"P95 改善了 37%",结论无法复核。

7.4.2 跨实例聚合:P99 不能取平均

这是本章最硬的禁止项。先看一个可手算的算例:

实例 样本构成 该实例 P99
A 98 个 100 ms + 2 个 1000 ms 1000 ms
B 100 个 500 ms 500 ms

(两个实例各 100 个样本,分位算法固定为最近秩法:\(\lceil 0.99\times100\rceil = 99\),即取第 99 个样本。A 的第 99、100 个样本都是 1000 ms,故 A 的 P99 为 1000 ms;若改成"99 个 100 ms + 1 个 1000 ms",同一算法下第 99 个样本是 100 ms,演示结论就会反过来。算法与数据必须同时写清。)

  • 对两个实例的 P99 取平均:\((1000+500)/2 = \mathbf{750}\) ms
  • 合并 200 个样本后重新计算:第 \(\lceil 0.99\times200\rceil = 198\) 个样本为 500 ms

750 ms 与 500 ms 相差 50%,而系统一毫秒都没变。平均出来的分位数是统计上无意义的值。

Prometheus 官方文档对此有明确结论(参见所列来源正文与版本记录):对 summary 预计算的分位数做聚合"rarely makes sense",在本例中"averaging the quantiles yields statistically nonsensical values",并直接把 avg(http_request_duration_seconds{quantile="0.95"}) 标注为 BAD [SOURCE_REGISTERED] R15。文档给出的可行路径是:用 histogram 表示分布,通过 histogram_quantile() 与 sum by (le) 聚合;其中 classic histogram 要求各实例的 bucket 边界一致,native histogram 的误差由配置的分辨率决定,classic histogram 的误差由分位数所在 bucket 的宽度决定 SOURCE_REGISTERED。文档还给出三条经验法则:能用 native histogram 就按其分辨率满足精度要求;不能用 native 但需要聚合时用 classic histogram 并谨慎设置 bucket 边界;只有在不需要聚合时才考虑 summary SOURCE_REGISTERED。

落到本书的操作要求:分位数必须从原始样本或可合并的分布摘要中计算;报告时必须写明样本量 \(n\)、分位算法(最近秩/线性插值/桶插值)与聚合层级。

7.4.3 重复实验与置信区间

三个决定结论可信度的因素:样本量、运行间波动、请求之间的相关性。

  • 样本量用区间说话,不用倍率口号。分位数有一种不依赖分布假设的区间估计:真值 \(\xi_q\) 落在第 \(r\) 到第 \(s\) 个顺序统计量之间的概率为 \(P(r \le Y \le s-1)\),其中 \(Y\sim\mathrm{Bin}(n,q)\)。取 \(q=0.99\)、覆盖约 95%:
样本量 \(n\) 秩区间 相对宽度 最近秩估计位置
1000 第 983—995 个 1.20% 第 990 个
20000 第 19770—19825 个 0.27% 第 19800 个

样本量扩大 20 倍,相对宽度从 1.20% 收窄到 0.27%(约 \(\sqrt{20}\approx4.5\) 倍)——这就是"尾部要更多样本"的定量形式,而不是"多两个数量级"这类无条件的口号。报告尾部指标时给出 \(n\) 与区间,而不是只给一个点估计。 - 运行间波动:同一配置重复 \(k\) 次运行,报告的是"运行"这个单位上的分布,而不是把所有请求混在一起。混在一起会低估不确定性(把相关样本当成独立样本)。 - 相关性:同一批请求在同一运行内彼此相关(共享排队、共享批组成)。用 bootstrap 时应以运行或会话为重抽样单位,而不是单个请求,否则置信区间过窄。

最低要求是:尾部指标附样本量与聚合方法;比较实验给出重复次数与置信区间或至少给出区间宽度;不把少量尾部样本包装成稳定结论。

本节要点

  • 均值掩盖长尾,分位数掩盖结构变化——两者都要,且分位数要分层。
  • 跨实例的分位数不能取平均,必须从原始样本或可合并摘要重算。
  • 分位数误差取决于表示方式:classic histogram 由 bucket 宽度决定,native 由分辨率决定,summary 不可聚合。
  • 尾部指标必须带样本量与聚合方法;重复实验以运行或会话为重抽样单位。

常见误区

  • 误:两个实例的 P99 平均一下就是集群 P99。 正:统计上无意义;合并原始样本或用可合并摘要重算。
  • 误:P95 从 800 降到 500,系统改善了 37%。 正:先看负载结构是否变化;分层后再判断。
  • 误:跑一次 5000 个请求,P99 就很准了。 正:尾部分位数需要远多样本,且要报告样本量与区间宽度。

本节验收

  1. 你能用手算例说明"平均 P99"错在哪里,并能给出正确的聚合方式。
  2. 你能说明本系统分位数使用的分位算法、样本量与聚合层级。
  3. 你能为自己的对比实验设计重复次数与置信区间,并说明重抽样单位。

依据与核验入口:R15


7.5 联合达标率、合格完成速率与 SLO 约束容量

本节要回答的问题:最快的配置是不是完成合格请求最多的配置?

7.5.1 用联合条件,而不是两个分位数并列

先澄清一个容易混用的词:本节原来的"Goodput"其实是比例,而分离式服务研究的经典文献把 goodput 定义为速率——"在给定 SLO 达成率目标下,每个已配置 GPU 能服务的最大请求速率" SOURCE_REGISTERED。两者量纲不同,不能互相替代。本书因此把它们拆成三个量:

名称 定义 量纲 用途
联合达标率(joint attainment) 同时满足延迟 \(\le\tau\)、协议成功、质量达标的观察对象数 / 观察集合总数 比例 判断一批流量里有多少是"真合格"
合格完成速率 在明确时间窗内完成且满足契约的请求或任务数 / 窗口秒数 req/s 或 task/s 容量与成本
SLO 约束容量 在联合达标率不低于目标、且系统满足所声明稳态条件时,可持续承载的最大到达率 req/s 上线决策与扩容

第一个量的定义式为

\[ \text{联合达标率} = \frac{|\{\,r \in \mathcal{A} : \text{延迟}(r)\le \tau \;\wedge\; \text{协议成功}(r) \;\wedge\; \text{质量}(r)\ \text{达标}\,\}|}{|\mathcal{A}|} \]

其中 \(\mathcal{A}\) 是观察集合。关键有两点:一是 \(\wedge\),三项必须同时成立;二是观察集合按层确定——attempt 层、逻辑请求层、任务层各有自己的集合,不能为了"保留失败"就把所有层的分母统一成 attempt(同一任务内的多次尝试不应变成多个任务),也不能反过来用"最后成功的那次尝试"代表整个任务。常见错误是把"P95 延迟达标"和"质量合格率 98%"并列展示,然后宣称达标率 95%——两个分位数不能相乘,也不能互相代替,因为命中的可能不是同一批请求。

7.5.2 分母:成功样本与全部尝试

本小节在 attempt 层使用全部符合契约的尝试作为分母,包含:超时、取消、被限流拒绝、客户端放弃、以及服务端仍在处理(orphaned)的请求。在成功样本里挑好看的延迟,再隐藏拒绝和超时,是最容易也最致命的自欺。

以字典 §5.4 的四条请求为例(构造数据):

口径 计算 结果
接口成功率(按返回 200 计) \(4/4\) 100%
请求层联合达标率(延迟 ∧ 质量) \(1/4\) 25%

两条都对——因为回答的是不同的问题。任何只报第一个数字的报告都应被视为不完整。

7.5.3 吞吐约束与质量门槛

质量门槛有两种落地方式,不能混为一谈:

方式 适用 报告要求
在线逐请求质量标签 有确定性校验(格式、JSON Schema、工具参数、单测断言) 可进联合达标率的分子
离线抽样质量评估 需人工或裁判模型评分 作为独立门槛单列,不能假装每个在线请求都有标签

分支任务还要区分两个层级:请求层达标率按 request_id 计,任务层达标率按 task_id 计,两者并列报告。业务契约需预先写明:哪些分支是必需的、哪些可容错降级、父任务终态如何判定(第1章 1.1.2)。不能把成功的几个子请求计成多个完成任务,也不能从分母里删掉导致任务失败的长尾分支 [SOURCE_REGISTERED] S18。

本节要点

  • 联合达标率是延迟、协议成功、质量三项的联合条件;它与合格完成速率(req/s)、SLO 约束容量(最大可持续到达率)是三个不同量纲的量,不可互相替代。
  • 分母按对象层级定义:attempt 不遗漏异常尝试,逻辑请求按 request_id 去重,任务按 task_id 聚合;未知终态按截止规则登记,不把一次重试计为一个新任务。
  • 在线质量标签与离线抽样评估分开报告,后者不得冒充逐请求标签。
  • 请求层与任务层达标率并列;任务终态由契约判定。

常见误区

  • 误:延迟达标率 96%、质量合格率 98%,所以达标率约 97%。 正:命中的未必是同一批请求;必须按联合条件计数。
  • 误:只在成功请求里统计延迟,因为失败请求没有延迟可言。 正:失败与超时正是要计入分母的部分。
  • 误:离线抽样的质量评分可以给每个在线请求打标签。 正:抽样结果只能作为独立门槛,不能回填到逐请求分子。

本节验收

  1. 你能写出本系统三个量(联合达标率 / 合格完成速率 / SLO 约束容量)的定义与观察集合。
  2. 你能分别给出请求层与任务层达标率,并说明分支任务的必需/容错集合。
  3. 你能说明质量标签是在线逐请求还是离线抽样,并在报告中如实标注。

依据与核验入口:R09 R24


7.6 正确性、输出质量与功能验收

本节要回答的问题:速度改善之后,系统还是在完成同一种任务吗?

性能提升最常见的副作用不是崩溃,而是静默变质:格式还合规,接口还返回 200,答案却不再可用。所以任何性能结论都必须携带质量门槛。

7.6.1 三层验收

层 检查什么 通过标准 能否自动化
Q1 接口与终态 协议合规、终态正确、无截断、取消可传播 终态字典全项匹配(第1章 1.6.1) 完全可自动化
Q2 数值或分布 必要时逐 Token/逐数值一致,或分布一致 取决于声明的可复现层次 可自动化(见第1章 1.3.4)
Q3 业务质量 任务是否真的被完成 契约中的质量门槛 常需抽样或裁判模型

三层质量验收记为 Q1—Q3,与第1章可复现性 L1—L4 是不同分类,不能按编号互相替代。格式正确不代表答案可用,任务质量相近也不代表输出逐位不变。“不改变输出”的主张应先声明是逐位、逐 Token、概率分布还是业务质量层面的等价,再提交相应证据。

7.6.2 格式、工具与停止行为

功能验收至少覆盖四项:

  1. 结构化约束:JSON Schema/语法约束下的输出是否仍可被解析,字段类型与必填项是否完整;
  2. 工具调用参数:模型返回的调用信息是否参数正确、可被下游执行——注意"返回了调用信息"与"调用成功"是两件事(第1章 1.4.2);
  3. 停止行为:max_tokens、stop 与 EOS 是否按预期生效;停止位置改变会改变输出长度分布,从而改变所有吞吐与延迟指标;
  4. 长上下文与多轮:长输入下是否出现截断、重复或失焦;多轮下上下文是否被正确复用或失效。

格式成功不等于任务正确:一个返回合法 JSON 但字段全为空的输出,能通过格式校验却完全不可用。质量门槛必须落在"任务是否完成"上。

7.6.3 优化变更的验收集

量化、非前缀缓存复用、推测解码是三类最容易"静默变质"的变更,它们不能共享同一份等价性假设——各自的质量风险不同,必须分别验收:

变更 主要质量风险 必查场景
低精度/量化 长上下文累积误差、极端激活值、特定层的敏感度 长上下文、数值/代码类任务、工具参数 SOURCE_REGISTERED R26
非前缀缓存复用 复用的状态与当前模型/模板/配置是否对应 模板或 revision 变更后的复用、跨实例复用 SOURCE_REGISTERED
推测解码 不是"草稿分布不同"本身——经典精确算法正是通过接受/拒绝与校正,在草稿分布不同的情况下保持目标分布 SOURCE_REGISTERED;真正的风险在校正实现、近似变体、数值路径与停止逻辑 校正路径的逐 Token 分布对照、结构化输出、停止条件、长输出 SOURCE_REGISTERED S02

数据集要三分,且不能用调过参的那一批宣称独立验证:

集合 用途 禁止
校准集 量化校准、阈值初调 不能作为最终验收集
调参集 选择配置与阈值 结论不得外推为独立验证
独立验收集 最终发布判定 不得在调优过程中被反复查看

(推测解码的算法正确性前提来自原始论文,实现变体必须另外核验 SOURCE_REGISTERED;低精度格式的存储与缩放表示参见厂商技术说明,其中宣传性倍数不得作为本书结果 SOURCE_REGISTERED。)

本节要点

  • 三层验收:接口终态、数值/分布、业务质量,证据不能互相替代。
  • 格式合规不等于任务完成;工具调用要验"能否执行",不只是"是否返回"。
  • 量化、非前缀缓存、推测解码分别验收,不共享等价性假设。
  • 校准集/调参集/独立验收集三分,禁止用调参集宣称独立验证。

常见误区

  • 误:输出格式 100% 可解析,所以质量没问题。 正:格式与内容质量是两件事;要验任务是否完成。
  • 误:质量分布稳定,说明优化没有改变输出。 正:那是 L3;声称逐位一致需要 L2 的证据。
  • 误:量化只影响速度,不影响质量。 正:低精度会改变长上下文与数值类任务的表现,必须实测。

本节验收

  1. 你能为一次优化变更列出三层验收项,并说明各自的通过标准。
  2. 你能为量化/缓存复用/推测解码分别设计质量风险检查场景。
  3. 你能证明自己报告的质量结果来自独立验收集,而非调参集。

依据与核验入口:R14 R27 S10 S02


7.7 实验:给指标计算做单元测试

本节要回答的问题:压测工具的报告,能否从原始事件重新算出来?

在信任任何工具输出之前,先用一组人工构造的事件序列检验"我的计算器"是否正确。这一步不需要 GPU,也不产生任何性能结论。

7.7.1 事件样本

样本沿用字典 §5 的 Sample-B0(20 个输出事件、301 个输出 Token、前 5 个为推理内容),复算结果与恒等式见 7.2.3。它的作用是给定义钉死一组可验证的数字:任何一次工具升级、解析器改动、字段改名之后,重跑这组断言,若结果不一致,变化一定出在口径而不是硬件。

7.7.2 边界条件与期望值

真正的 bug 都藏在边界样本里。下表给出必须覆盖的异常样本与其确定处理规则(不是虚构的测量结果):

# 样本 正确期望 错误做法
1 输出 \(N_{\text{out}} = 1\) TPOT = 不适用并从统计排除;ITL 无样本 记 0,污染分母
2 输出 \(N_{\text{out}} = 0\)(仅元数据) 首内容时间记缺失;不计入延迟分布 记 0 ms,伪装成极快
3 仅推理内容后超时 TTFT 有值、TTFO 记缺失;请求计入失败与超时层 用 TTFT 冒充用户可见首字
4 一个块含多个 Token 只报事件间隔;Token 数以服务端计数为准 捏造逐 Token 时刻
5 客户端中途取消 计入取消层;若未完成则不计入完成率分子 从分母删除
6 分支任务中一个分支失败 任务结果按契约判定(可能部分成功或失败) 把成功的子请求计成多个完成任务
7 重试(3 个 attempt,最后成功) 任务可能成功;attempt 层记 2 次失败 只记最后一次成功
8 跨主机时间未校准 只报本地间隔与因果顺序 输出伪精确的单向延迟
9 空流/连接中断无终端事件 记未完成并保留未知 按成功或按 0 处理
10 工具调用后无后续生成 编排层等待单列,不并入生成时间 混入模型生成耗时

7.7.3 反例:专门用来击中错误实现

只验证"正确输入得到正确输出"的测试,会把写死的常数当成真理。必须同时准备能击中错误公式与错误分母的反例:

# 错误实现 反例输入 期望断言(应失败/应报错)
1 把分位数当最近秩却对数据写成"极端值必进 P99.9" 1000 个样本:999×100 ms + 1×5000 ms 最近秩 P99.9 = 100 ms,不是 5000 ms;测试应捕获 5000
2 对实例分位数取平均 A(98×100+2×1000)、B(100×500) 平均 = 750 ms,而合并重算 = 500 ms;断言两者不等且以合并值为准
3 把 ITL/TPOT 比值写成 \((N-1)/(M-1)\) Sample-B0(窗 3569 vs 3576) 比值 = 15.7586,不是 15.7895
4 单 Token 请求的 TPOT 填 0 后进均值 含 1 条 \(N_{\text{out}}=1\) 的样本集 该条被排除;统计条数减少 1,不得出现 0 值参与均值
5 达标率分母用"成功返回的请求" 含超时/取消/失败 attempt 的集合 分母必须含全部已发出 attempt;断言与"仅成功集"结果不同
6 任务层用子请求数计数 一个任务含 3 个必需分支、1 个失败 任务结果 = 失败(按契约),完成计数不增加 3
7 跨时间域直接相减 客户端与服务端各带未校准时钟 应拒绝输出单向延迟,或标记 \(\varepsilon\) 缺失

第 7 条尤其重要:它不是"算错了",而是"这个数字根本不该被算出来"。测试应当检查结论,而不只是复现被写死的错误常数。

7.7.4 工具差异定位

对照真实工具输出时,最终保留三列:工具原值、规范化值、差异原因。差异原因只能来自以下几类:起点/终点事件不同、时间域不同、Token 范围不同、单位或量级不同、聚合方式不同、缺失值处理不同。不允许用"取平均"来消除差异。

流程是:手算样本 → 算逐请求指标 → 算聚合 → 加入边界样本 → 对照工具实现 → 记录差异 → 固化 mapping_version。工具升级后重跑字典 §6 的 12 条断言与本节表 7-2 的 10 条规则;无 GPU 的测试结果只能证明计算器正确,不能作为任何设备性能证据。

本节要点

  • 先用人工样本验证计算器,再处理真实压测输出;人工样本不证明后端性能。
  • 十类边界样本都必须有确定处理规则;缺失记缺失,不适用记不适用,都不记 0。
  • 工具对照保留原值/规范化值/差异原因三列,不用平均消除差异。

常见误区

  • 误:手算样本通过,说明压测工具的输出可信。 正:手算只验证计算器;工具口径仍需按 mapping_version 逐项核对。
  • 误:边界样本太少见,可以不处理。 正:它们正是分母污染与假性提速的来源。
  • 误:把示意数据保存进实测结果库没关系,反正标了 synthetic。 正:必须物理隔离;混库后无法区分哪些数字可对外。

本节验收

  1. 你能跑通 Sample-B0 的全部复算,并通过三条恒等式。
  2. 你能为表 7-2 的十类边界样本各给出一条处理规则与对应单元测试。
  3. 你能给出一次工具对照的三列记录,并说明差异原因。

依据与核验入口:R01 R12 S17


7.8 深化:联合达标、删失样本与指标计算器

7.8.1 两个P95达标,不等于95%的请求同时达标

设事件 \(A\) 表示请求TTFT达标,\(B\)表示生成间隔达标。即使 \(P(A)=P(B)=0.95\),联合达标率也只能由边际信息限制在

\[0.90\le P(A\cap B)\le0.95.\]

下界来自并集界,上界来自交集不大于任一事件。最差的5%首Token请求和最差的5%生成请求若不重合,联合只有90%;若完全重合,才可能95%。所以一个报告同时写“TTFT P95达标”“TPOT P95达标”,并没有计算请求级联合SLO。加入质量、错误、截止时间之后,必须在同一请求记录上逐项求交,不能把多个总体比例相乘或取最小值当成结果。

任务包含多个请求时,再做一次按任务契约聚合。尝试失败后重试成功,可能形成一个成功逻辑请求;任务超过总截止时间,则所有子请求最终完成也不能倒填为任务及时成功。层级、分母和观察窗口必须一起进入指标身份。

7.8.2 分位数定义会改变尾部读数

对999个100 ms和1个5000 ms的样本,最近秩/经验逆CDF的P99.9取第999个值,仍为100 ms;线性插值则在相邻秩之间给出约104.9 ms。NumPy明确将 inverted_cdf 与 linear 列为不同方法 N05。这不是性能变化,而是有限样本下的估计定义差异。

单个很慢的请求可能几乎不改变某个分位数,却明显增加均值、总资源面积或失败成本。因此长尾报告至少还应给出超过业务阈值的数量、最大值的出处以及时间分布,而不是只保留一个P99。跨实例聚合时也应合并可合并分布或原始样本;分位数本身不是可线性平均的统计量 N24。

7.8.3 超时样本既不能填零,也不能悄悄消失

一个请求在2 s被客户端放弃,只能证明用户未在2 s内得到结果,不能证明服务真实完成时长正好2 s。对“是否在2 s内交付”的二元SLO,它是不达标;对“最终完成时长分布”,它是右删失或缺失终态,需要另行处理。

如果只对完成请求画延迟分布,应明确命名为“完成条件下的延迟”,同时报告全量失败与删失比例。不能用该分布独立宣称总体用户体验变好。反过来,把所有超时都用一个固定巨大值填入直方图,也会引入人为分布;更稳妥的是保留原始终态、观察截止和缺失原因,分别计算二元达标率与可解释的完成分布。

7.8.4 可直接运行的最小计算器

book_support_v05/code/e0_reference.py 使用显式事件域计算持续时间;输出Token数≤1时TPOT返回空值;内容事件少于2个时,事件间隔样本为空;域不同或时间倒置直接拒绝计算。它把 ITL_event 与逐Token间隔分开,没有从分块大小猜测Token时间。

Sample-B0的服务器内容终点是4210 ms,终端事件是4213 ms,故两个E2E分别为4189 ms与4192 ms。对应TPOT必须选择同一个约定,不能在计算中混用。配套断言保留了11.92 ms的内容端点TPOT、19个事件间隔,以及不适用和跨域失败用例。

官方AIPerf迁移说明还区分推理模型的TTFT和首个非推理输出TTFO N16。本书计算器不是某个工具的兼容替代品;它是一组可审查的规范样例。接入工具时,原生字段仍须保留,并通过独立映射测试接到这些定义。

图7-R1 同一千条样本的两种P99.9

图中100与104.9 ms来自同一样本的不同分位算法;原始极端值5000 ms仍须单独报告。


7.9 实验图表补充:把联合SLO门槛画成可达边界

分别95%的TTFT与TPOT通过率,并不保证联合95%。取100个请求:若两项各失败5个且失败集合不相交,联合通过90个;若两项失败的恰好是同5个,则联合通过95个。图给出两个可以精确构造的端点,展示 \(0.90\le P(A\cap B)\le0.95\)。

实验报告应保存请求级两个布尔条件,先做AND再统计,不能把两个分位数通过当成同一批请求联合达标。若还加入质量、超时、取消或格式条件,继续按同一请求身份合并,失败和未终结请求也必须按预先定义的分母处理。

指标与网络案例的边界:第4章公开中位数无法推出本章联合达标率;第12章论文SLO门槛也不是本地阈值。Prometheus官方文档支持分位数聚合的注意事项,但不提供本书的逐请求数据。本图仍是一项集合论/E0示例,而非服务压测。

图7-V1 两个95%达标,不保证联合达标95%

图7-V1 两个95%达标,不保证联合达标95%

数据身份:TEACHING_CALCULATION。条件:100个请求;TTFT与TPOT分别95个达标;两种失败集合安排。

失败集合关系 TTFT通过数 TPOT通过数 联合通过数 至少一项失败数
不重叠 95 95 90 10
完全重叠 95 95 95 5

复算公式:max(0,pA+pB-1)<=P(A∩B)<=min(pA,pB)。

出处与边界:本书7.8.1;这是概率界的可达构造,不是对真实服务联合达标率的预测。

数据与绘图点 · PNG · 重绘代码

7.10 第三方数据补充:SLO 档位与分位数:门槛是规则,达成是另一回事

本章前面用联合 SLO 做了推演。这一节补两组第三方事实:门槛本身,以及实测分位与门槛的贴合程度。

门槛(MLCommons 现行 inference_rules.adoc 的 LLM 目标列)。同为 Llama2-70B,从 Conversational 切到 Interactive,TTFT 门槛由 2000 ms 收紧到 450 ms(4.4 倍),TPOT 由 200 ms 收紧到 40 ms(5 倍)。同一套硬件在两个档位下的"可交付吞吐"根本不是同一个数。报容量时若不说档位,数字就没有意义——这正是本章联合 SLO 讨论的现实依据。规则本身是"门槛",不是"达成值"。

达成(MLPerf Inference v4.1,H200-SXM-141GB×8 + TensorRT-LLM,llama2-70b-99.9,Server 场景,约束 TTFT≤2 s、TPOT≤200 ms,P99)。实测 TTFT 的 P99 为 510.35 ms,离 2 s 门槛还有近 4 倍余量;而 TPOT 的 P99 为 181.53 ms,距 200 ms 门槛只剩约 10%。真正把系统卡住的是 TPOT 这一侧。同一轮的 P99.9 是 200.53 ms,已越过 200 ms——这不矛盾,因为约束针对的是 P99;它说明的是:当约束卡在 P99,P99.9 就是下一个会被追问的指标。

边界:规则文本随轮次演进,与历史轮次可能存在模型与约束差异;该实测的提交方是硬件厂商(在 MLCommons 统一规则下运行并经合规审查),系统级结果不能拆成单卡;单一软件栈。

图7-T1 SLO 档位决定目标:Interactive 比 Conversational 紧一个量级(第三方规则)

图7-T1 SLO 档位决定目标:Interactive 比 Conversational 紧一个量级(第三方规则)

数据身份:THIRD_PARTY_RULES(第三方公开数据)。条件:MLCommons 现行 inference_rules.adoc 的 LLM TTFT/TPOT 目标列;Server 场景尾延迟取 P99,指标为约束下最大 Poisson 吞吐参数。

基准/模型 档位 TTFT约束/ms TPOT约束/ms
Llama2-70B (QA) Conversational 2000 200
Llama2-70b-interactive Interactive 450 40
Llama3.1-8B (摘要) Conversational 2000 100
Llama3.1-8B (摘要) Interactive 500 30
Llama3.1-405B (文本生成) Server 6000 175
Llama3.1-405B (文本生成) Interactive 4500 80
DeepSeek-r1 (推理) Server 2000 80
DeepSeek-r1 (推理) Interactive 1500 15

出处与边界:Z07;规则文本随轮次演进,与历史轮次(如 v4.1)可能存在模型与约束差异;规则本身不含实测数值;这是"门槛",不是"达成值"。

数据与绘图点 · PNG · 重绘代码

图7-T2 约束卡在 P99:实测分位数与门槛的贴合程度(第三方实测)

图7-T2 约束卡在 P99:实测分位数与门槛的贴合程度(第三方实测)

数据身份:THIRD_PARTY_BENCHMARK(第三方公开数据)。条件:MLPerf Inference v4.1,H200-SXM-141GB×8 + TensorRT-LLM,llama2-70b-99.9,Server 场景(Poisson 到达,P99 尾延迟约束 TTFT≤2s、TPOT≤200ms);原始单位 ns 已换算为 ms。

指标 min P50 P90 P95 P97 P99 P99.9
TTFT/ms 41.61 224.66 380.21 433.13 466.44 510.35 769.62
TPOT/ms 47.91 160.32 170.44 173.47 175.75 181.53 200.53

出处与边界:Z08;提交方为硬件厂商 NVIDIA(在 MLCommons 统一规则下运行并经合规审查);系统级结果不能拆为单卡;单一软件栈。

时效提示(2026-10-09):图7-T2 锚定在 MLPerf Inference v4.1;该数字对 v4.1 仍成立,但已不是最新轮次。截至检索日,套件已推进到 v5.0 / v5.1,两处需要一并更新口径: - 门槛侧:mlperf.conf(MLCommons inference 仓库 master)给出的 Server 场景约束仍为 Llama2-70B TTFT 2000 ms / TPOT 200 ms、Interactive 450 ms / 40 ms,与图7-T1 一致;新增 gpt-oss-120b 3000/80 与 Interactive 2000/20。门槛取自配置文件而非规则 PDF,是最可定位的一手来源。 - 实测侧:NVIDIA 官方博客公布的 v5.0 Closed / Datacenter 结果为 8×B200 98,443 token/s 对 8×H200 33,072 token/s(Server,3.0×;Offline 98,858 对 34,988,2.8×);每加速器记录(v5.0 + v5.1 Closed,Server)为 Llama2-70B 12,701、Llama3.1-405B 170、DeepSeek-R1 2,907 token/s/GPU。 - 两条边界:① 跨轮次数字不可混用——换轮次必须整表重取,不能只替换其中一格;② NVIDIA 原文注明"每芯片性能由总吞吐除以报告芯片数得出,不是 MLPerf 的主要指标",引用时须保留该限定。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 两个实例的 P99 能否取平均作为集群 P99?

不能。7.4.2 的算例中,两个实例 P99 分别为 1000 ms 与 500 ms,平均得 750 ms,而合并 200 个样本后重算为 500 ms——相差 50%,系统毫无变化。Prometheus 官方文档明确指出,对预计算分位数取平均"yields statistically nonsensical values",并把 avg(...{quantile="0.95"}) 标为 BAD [SOURCE_REGISTERED] R15。正确做法是从原始样本或可合并的分布摘要(histogram,按 le 聚合后用 histogram_quantile)重算,并写明分位算法、样本量与聚合层级。

2. 单 Token 请求应怎样处理 TPOT?

记为不适用并排除,不是记 0。公式的分母是 \(N_{\text{out}}-1\),单 Token 请求会让分母为 0;某些实现把它存成 0,但基准统计会将其排除——照抄存储值会把"没有输出"混入均值,污染分母。报告时须写明:此类请求有多少条、是否被排除、以及在达标率中如何判定——不能因为 TPOT 不适用就推断质量不合格:分类、判定、路由类任务的合法答案可能只有 1 个 Token,是否合格由任务契约判定。

3. 首推理 Token 为 0.30 秒、首答案为 1.20 秒,换工具后 TTFT 改善是否成立?

不成立,除非先证明口径未变。若新工具的 TTFT 统计的是"首个任意 Token(含推理)",而旧口径统计的是"首个可见答案",那么 1.20 s → 0.30 s 只是换了个"首"的定义,差值是用户"看着进度条等"的时间。判别方法:在同一条事件流上复算两边(Sample-B0 给出 613 ms 与 1159/1207 ms 的对照),并检查 event_scope、token_scope 与 mapping_version。口径迁移必须被识别为口径变化,不能记为性能收益。


本章验收清单

节 验收标准
7.1 每项 SLO 都有对象、指标(含起点/终点/时间域)、阈值、窗口、分母与异常处理规则;缺失值不记为 0
7.2 能在同一事件流上复算 TTFT/TTFO/ITL/TPOT;知道何时只能报"客户端分块间隔";未观测到的首答案记缺失;工具迁移有七列登记
7.3 任一吞吐值都能追溯到计数集合、时间分母与 Token 定义;写明窗口法或排空法与并发度
7.4 分位数附样本量与聚合方法;不对实例分位数取平均;对比实验给出重复次数与区间宽度
7.5 三个量纲分开定义;观察集合按层确定(attempt/请求/任务);分母含超时与取消
7.6 性能对比附带质量门槛与评估方法;量化/缓存复用/推测解码分别验收;结果来自独立验收集
7.7 十类边界样本各有确定处理规则;工具差异可解释而非强行平均;示意数据不进实测库

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第8章 业务负载与基准设计

核心论点:可信基准的关键不是运行某条命令,而是让负载、质量、计量和实验条件足以支持目标结论。

本章导航

读完本章,你应该能够:把生产记录整理成一张保留相关性与到达结构的负载卡;判断合成、公开、回放三类数据各自能检验什么;用"长度—到达—缓存—拓扑"四维矩阵选出最小实验集;证明自己的发压器没有成为瓶颈;声明冷启动与稳态的边界,并避免预热污染;用配对与重复把"配置带来的差异"从"运行间的漂移"中分离出来;最后交出一份第三方可复核的基准报告。


8.1 从业务记录构建负载画像

本节要回答的问题:生产负载中哪些分布必须保留?

8.1.1 输入输出联合分布

只统计"输入均值"和"输出均值"是不够的,因为决定资源占用的是二者的联合分布:Prefill 成本随输入增长,Decode 步数与显存占用随输出增长,两者同时长才是显存与并发的最坏组合。

可算例:设生产样本中"长输入"占 0.44、"长输出"占 0.50,而真实的联合分布里"长输入且长输出"只占 0.06(两者负相关——文档问答通常是长输入短输出,生成任务通常是短输入长输出)。若在合成负载中按边际分布独立采样,则

\[ P(\text{长},\text{长})_{\text{独立}} = 0.44 \times 0.50 = \mathbf{0.22} \]

真实值是 0.06,独立采样把它高估约 3.7 倍(\(=0.22/0.06\))。后果是把最消耗显存的组合人为放大了近四倍,测出的容量会显著低于生产,而吞吐也可能偏低。匹配边际不等于匹配联合分布。

因此负载卡至少要记录:输入长度分布(不只是均值,含分位与尾部)、输出长度分布、二者的联合分箱、以及长度与业务类别的交叉分布。

相关系数只能作为摘要,不能代替联合分布:它是一个标量,无法表达高阶依赖与尾部共同出现的形态。两组负载可以有相同的相关系数却在"长输入且长输出"这一最坏组合上差数倍——而上例已经说明这一格决定容量。所以校验合成负载与生产是否等价时,比较的是联合分箱(或多维分位),不是相关系数。

8.1.2 会话、复用与到达相关性

到达过程同样不能只用 QPS 描述:

维度 要记录什么 为什么影响结果
到达间隔分布 指数/固定/突发;自相关 排队延迟对突发最敏感
会话结构 平均轮数、轮间思考时间 决定输入增长与前缀复用机会
前缀复用率 共享前缀的请求占比与共享长度 直接影响 Prefill 成本(第4章)
并发在途数 同时未完成的请求/会话/任务 决定显存占用与批组成

注意在途数由到达率与延迟共同决定(\(L = \lambda W\),Little 定律):到达率 \(\lambda=200\) req/s、平均停留 \(W=1.2\) s 时,在途 \(L=240\)。只报 QPS 而不报在途数,读报告的人无法判断显存是否够用。

8.1.3 业务类别、时段与脱敏偏差

负载还要按业务类别与时段分层:白天交互型为主、夜间批处理为主时,用一套混合负载代表全天会两头不讨好。建议保留"类别占比 + 每类的长度与到达特征",而不是合成一个平均值。

脱敏会改变负载本身,这一点常被忽略:替换实体、截断文本、改写模板,都会改变 Token 化结果、前缀复用机会与内容难度。正确做法是记录脱敏方法并评估它对关键统计量的影响(长度分布、复用率、词表覆盖),把偏差写进负载卡,而不是假装脱敏无损。

无生产数据时可以构造负载,但必须写明:它代表什么(例如"长输入短输出的文档问答")、不能代表什么(例如不能代表质量、不能代表真实的复用率与突发结构)。

负载卡的推荐字段(可直接作为 JSON 模板保存,随每次实验归档):

字段 内容 缺失后果
source 生产/合成/公开/回放,含授权与许可 无法判断结论适用范围
length_joint 输入—输出联合分箱;相关系数仅作补充摘要 最坏组合被扭曲
arrival 间隔分布、突发模式、目标与实际 QPS 排队行为不可复现
session 轮数分布、轮间时间、共享前缀占比 复用收益被误估
topology 独立/顺序多轮/共享分支/子任务 并发定义含糊
category_mix 业务类别占比与时段 混合负载代表不了任何一类
concurrency 请求/会话/任务三级在途数 显存与批组成无法核对
desensitization 脱敏方法与偏差评估 静默改变 Token 化与复用
quality_gate 该负载的质量判定方法 只有速度没有可用性

本节要点

  • 负载画像要保留输入—输出的联合分布,匹配边际会把最坏组合放大数倍。
  • 到达过程要记间隔分布、会话结构、复用率与在途数,不能只报 QPS。
  • 按业务类别与时段分层;脱敏的偏差必须评估并写入负载卡。
  • 无生产数据时的合成负载须写明"代表什么、不代表什么"。

常见误区

  • 误:输入均值 2100、输出均值 340,照着造一批就行。 正:联合分布才决定资源占用;独立采样会显著扭曲最坏组合。
  • 误:峰值 QPS 就是负载画像。 正:还要有在途数、复用率与突发结构。
  • 误:脱敏只改了人名地名,不影响性能。 正:会改变 Token 化与前缀复用;必须评估偏差。

本节验收

  1. 你的负载卡包含联合分布(相关系数不能替代),而不只是两个平均长度。
  2. 你能给出在途数估计与到达间隔特征。
  3. 你能说明脱敏方法及其对关键统计量的影响。

依据与核验入口:R11 R18


8.2 合成负载、公开数据与真实回放

本节要回答的问题:三种数据分别能检验什么?

三类数据不是三选一,而是各管一段,且不能互相替代:

类型 能检验什么 不能检验什么 主要风险
合成负载 隔离长度、到达、并发等结构变量 内容相关机制(质量、接受率、格式) 把结构结论外推成业务质量结论
公开任务集 有语义的内容与质量;便于他人对照 本业务的复用率与突发结构 许可与版本漂移;数据集污染
授权回放 生产相关性:真实联合分布、复用、突发 新业务形态、负载漂移、闭环交互反馈、未记录的内部状态 隐私、许可、泄漏;脱敏偏差

("回放不能检验什么"一栏不能写"无":它记录的是历史,因此对新业务、负载漂移、需要闭环交互才能触发的行为,以及当时未被记录的状态,都不构成证据。)

8.2.1 可控变量的合成样本

合成负载的价值是可隔离:固定输出长度只改输入长度,能干净地测出 Prefill 的斜率;固定到达率只改会话轮数,能测出前缀复用的收益。它的边界也很清楚——随机 Token 不能代表内容相关机制。推测解码的接受率、结构化输出的合规率、工具调用的参数正确率,都依赖内容语义,用随机 Token 测出的数字不能写成业务结论(第7章 7.6)。

8.2.2 有内容语义的公开任务

公开任务集用于补齐质量与内容维度。使用时必须记录:数据集名称与版本、许可条款、抽样方法与样本量、以及是否与调优/训练数据存在泄漏。同一数据集的不同版本可能有不同的提示词格式,直接比较会引入口径漂移。

8.2.3 带授权的业务回放

回放是最接近真实的负载来源,也是责任最重的:需要授权、脱敏与留存期限约束;不得复制受限制的真实提示词到公开材料(第1章与本章授权与脱敏要求(原附录D尚未随本包提供))。回放还要保留时间结构——打乱顺序会破坏到达相关性与复用模式。

无论哪一类,负载文件都要带上来源、许可、生成方法、内容哈希与使用边界四件套,并在报告中注明它支撑哪一条结论。

本节要点

  • 合成管结构、公开管内容、回放管相关性,三者互补且不可互换。
  • 随机 Token 测不出接受率、格式合规率与工具参数正确率。
  • 公开数据集要记版本、许可、抽样与泄漏检查。
  • 回放须授权、脱敏、保留时间结构,并说明脱敏偏差。

常见误区

  • 误:合成负载测出的吞吐就是业务吞吐。 正:它只回答结构问题;质量类结论需要语义负载。
  • 误:用了公开数据集,质量结论就可以泛化到业务。 正:公开集代表它的分布,不是你的业务分布。
  • 误:回放只要留下文本就行,顺序无所谓。 正:顺序决定到达相关性与复用,打乱即失真。

本节验收

  1. 你能为每条结论指明它依赖哪一类负载,且不越界外推。
  2. 你的负载文件带来源、许可、生成方法与哈希。
  3. 你能说明回放数据的授权、脱敏与偏差。

依据与核验入口:R11 R27


8.3 多轮与分支负载:长度矩阵、会话和任务

本节要回答的问题:最小实验集如何覆盖主要变化,而不是穷举一切?

8.3.1 短长输入输出与独立请求基线

先建立四条基线,再往上叠加维度(长度为相对档位,实际取值按模型窗口与资源确定):

档位 输入 输出 主要受压资源
S/S 短 短 排队与调度开销
L/S 长 短 Prefill 计算与 KV 写入
S/L 短 长 Decode 单步成本与 KV 驻留
L/L 长 长 显存容量与在途上限

(上一版把 S/L 的两列写反了,本表已修正。)

L/S 与 S/L 必须分开测;但要注意"主要受压资源"一栏是待检验的预期,不是结论:L/S 通常更接近计算受限、S/L 更接近访存与驻留受限,这是在典型模型、典型批量与给定设备下的起点假设,会随并行策略、批大小、内核选择与负载结构而迁移(第1章 1.2.2、第2章 2.3.3)。把"L/S 必然算力受限、S/L 必然带宽受限"写成通则,与全书的条件化表述冲突。

四条基线之上,全书统一使用 W01—W08 负载族作为引用代号(相对档位,实际长度按模型窗口与资源确定):

代号 负载 主要验证问题 建议资源层级
W01 短输入/短输出交互 基础开销、排队与调度 E1
W02 长输入/短输出文档问答 Prefill 成本、前缀复用收益 E1
W03 短输入/长输出生成 Decode 带宽、KV 驻留、写出节奏 E1
W04 保留相关性的混合负载 真实联合分布下的容量 E1
W05 前缀与多轮会话复用 命中率、节省量与失效策略 E1—E2
W06 推理/结构化/工具流程扩展 TTFO 与可见输出、外部等待、任务完成 E1
W07 多模态扩展 额外编码路径与状态规模 E1(按模型可用性)
W08 突发、取消与恢复 过载行为、幽灵负载、恢复时间 E1—E2

引用时写代号而不是重述内容,可以让第 10—18 章的实验直接复用同一套负载定义;未启用的负载族要在结论边界里说明。

8.3.2 顺序多轮与共享历史分支

在长度矩阵之上加拓扑维度,四类结构在第1章 1.1.2 已定义,此处给出各自的测量重点:

拓扑 结构 测量重点 易错点
独立请求 无关联 单请求延迟与吞吐 用单请求结论代表会话场景
顺序多轮 \(n\) 轮依赖前 \(n-1\) 轮 逐轮输入增长、复用率、TTFT 随轮次变化 只测第一轮
共享历史分支 一前缀派生多后续 命中率、节省的 Prefill、失效策略 把命中率当成节省时间
独立子任务 任务拆成并行子调用 任务完成时间与成本,按 task_id 统计 把子请求成功数当成完成任务数

8.3.3 独立子任务、工具等待与取消

工具与外部等待必须单独计时,不计入模型生成时间:外部等待属于编排层,把它混进生成耗时会得出"模型变慢"的错误归因(第1章 1.4.2、第7章 7.7)。取消与超时也要作为负载的一部分——它们产生幽灵负载并改变容量(第1章 1.6)。

建议把外部等待设计成显式变量:注入固定延迟(如 0 ms / 200 ms / 800 ms)而不是调用真实外部服务,这样才能复现且可解释。

8.3.4 请求、会话与任务并发的区分

三种并发是三个不同的量,混用会让"并发 200"这个词失去意义:

并发类型 定义 谁受限
请求并发 同时未完成的 request_id 数 批处理与批组成
会话并发 同时活跃的 session_id 数 上下文驻留与复用
任务并发 同时未完成的 task_id 数 编排、外部依赖与分支

报告必须写明是哪一个,并给出其余两个的取值或其上限。

8.3.5 工具支持的拓扑与负载可解释性

工具能表达的拓扑是有限制的。以分支会话为例:相关资料描述了 FORK/SPAWN 一类操作与当前的树形拓扑,不能从"支持树形分支示例"推断支持任意菱形 DAG [SOURCE_REGISTERED] S18。工具不支持的图结构,可以用合成事件解析或自行实现,但必须标出实现来源,并在报告中说明"该拓扑由本书合成而非工具原生"。

选择实验组合的原则是:每个组合对应一个待验证的问题,未覆盖的区间写进结论边界,而不是假装覆盖。

本节要点

  • 四条长度基线分开;L/S 与 S/L 受不同资源约束,结论不可互换。
  • 拓扑维度至少覆盖独立、顺序多轮、共享历史分支、独立子任务。
  • 外部等待与取消要作为显式变量注入,不混进生成耗时。
  • 请求/会话/任务并发是三个量;工具的拓扑限制须声明,不支持的图结构要标实现来源。

常见误区

  • 误:分支压测跑通了,说明任意任务图都支持。 正:工具支持的是特定拓扑;推断更一般的图结构需要证据。
  • 误:子请求全部返回 200,任务并发就达标了。 正:任务并发按 task_id 与契约判定。
  • 误:外部工具延迟属于端到端,所以并进模型延迟。 正:编排层等待必须单列,否则归因错误。

本节验收

  1. 你能给出长度—到达—缓存—拓扑四维矩阵中选出的最小实验集,并说明每个组合验证什么。
  2. 你能区分三种并发并给出各自取值。
  3. 你能说明所用工具支持的拓扑边界,以及不支持部分是合成还是原生。

依据与核验入口:R04 R12 S18


8.4 发压模式与客户端自检

本节要回答的问题:是不是客户端先到极限,或者偷偷替服务器排队?

8.4.1 开环、闭环与轨迹回放

模式 定义 适用 风险
开环 按计划的到达时刻发出,不等前一批返回 研究排队、过载、真实到达相关性 客户端要能维持目标速率
闭环 维持固定并发,完成一个再发一个 测稳态吞吐与延迟 会替服务端排队:服务端慢时到达率自动下降,看起来"稳定"
轨迹回放 按记录的到达时刻与请求内容回放 最接近生产 需要授权数据;时间轴校准

闭环测试的最危险之处是它天然限流:当服务端变慢,闭环客户端发得更少,于是延迟看起来没有恶化、吞吐看起来只是略降——过载被客户端吸收了。要研究过载行为,必须用开环或轨迹回放。

8.4.2 计划到达与实际发出

必须同时记录"计划发出时刻"与"实际发出时刻",并报告调度迟滞(实际−计划)的分布。若迟滞长期为正,说明发压器跟不上目标,此时"目标 200 req/s"只是名义值。

可算例:目标 \(\lambda = 200\) req/s、平均停留 \(W = 1.2\) s,则需要同时维持在途

\[ L = \lambda W = 200 \times 1.2 = 240 \]

若客户端连接池上限为 128,并假设一条连接同一时刻只能承载一个在途请求、服务时间可取 \(W=1.2\) s 且该值稳定,则其能维持的最大到达率为

\[ \lambda_{\max} = \frac{128}{1.2} = 106.67\ \text{req/s} \]

也就是说,在这个假设模型下目标 200 req/s 根本达不到,实际只有约 107 req/s。若使用 HTTP 多路复用,或并发受其它因素(线程数、令牌桶、解析能力)限制,这个模型就要改写:连接池数不等于在途请求数,需要先确认实际的并发限制机制。若不记录这一条,报告会显示"系统在 200 req/s 下延迟稳定"——而真相是客户端把它限流到了 107。被客户端限流的请求不得从分母里省略。

8.4.3 客户端资源与连接限制

在给服务加压之前,先用受控回显服务(收到即返回,不做推理)验证发压器本身:能否维持目标速率、CPU/网络/文件描述符是否成为瓶颈、流式解析是否跟得上、分支调度是否正确。

清单至少覆盖:连接池与最大并发、TLS 握手成本(是否复用连接)、JSON 解析开销、时钟获取开销、事件写入 I/O、以及客户端所在主机与被测系统的网络路径。

工具迁移时还要保留旧、新版本对同一事件流的解析结果,用于确认字段变化不是服务容量变化(第7章 7.2.5 的七列登记)。

本节要点

  • 闭环会替服务端排队;研究过载必须用开环或轨迹回放。
  • 同时记录计划与实际发出时刻,报告调度迟滞。
  • 用 Little 定律核对客户端并发上限是否支持目标到达率。
  • 先用回显服务验证发压器,再测服务;被限流的请求必须计入分母。

常见误区

  • 误:闭环压测下延迟很稳,说明系统容量充足。 正:闭环会自动限流,把过载藏起来了。
  • 误:目标 200 req/s 就是实际 200 req/s。 正:要核对实际发出率与调度迟滞。
  • 误:客户端并发调大就行。 正:并发上限与延迟共同决定可达到达率;还要看连接与解析开销。

本节验收

  1. 你能说明本次用的是开环、闭环还是回放,并证明它匹配研究问题。
  2. 你能给出计划 vs 实际发出的对比与调度迟滞分布。
  3. 你能在加压前用回显服务证明发压器不是瓶颈。

依据与核验入口:R11 R13 S18


8.5 冷启动、权重常驻、预热与缓存污染

本节要回答的问题:预热了什么,又是否改变了被测负载?

8.5.1 容器启动、制品加载与转换

冷启动不是一个数,是一串阶段。每一段分别计时,否则无法判断优化落在哪里:

阶段 示例耗时 (s) 受什么影响
容器/进程启动 8 镜像体积、初始化
制品读取(权重分片) 46 存储带宽、网络、分片数
权重就绪(格式转换/量化预处理) 12 精度方案、是否发生转换
编译(如启用) 23 编译缓存是否命中、模型结构
图捕获/形状桶生成 11 模式与桶集合
缓存池热身 15 预热负载、目标容量
首次可接业务流量 115 上述全部

以上为构造数据 (\(8+46+12+23+11+15=115\))。关键要求是报告"首次可接流量时间",而不是只报"启动用了几秒"——前者才是运维真正关心的量。

8.5.2 编译、图捕获与缓存热身

编译与图捕获属于一次性成本,但它们的触发条件决定是否会重复付出:编译缓存失效(形状/配置变化)、图捕获遇到未覆盖的形状、以及运行时降级到 eager 都会让"第二次也很慢"。第1章 1.5.2 已给出机制,此处只需记住实验要求:在报告中记录编译与图模式的实际状态,而不是假设它们已完成。

缓存热身要独立设计:用独立样本预热,而不是拿测试集样本预热——后者会把测试前缀全部填满缓存,人为抬高命中率,这就是缓存污染。

8.5.3 权重常驻与实际接流量时间

把权重缓存在本地(或常驻内存/显存)可以显著缩短首次可接流量时间。资料示例:某版本发布说明包含 GPU 权重缓存类的快速启动机制 [SOURCE_REGISTERED] S02(页面快照,未本地执行)。沿用上表的构造数据:若制品读取由 46 s 降至 6 s,则首次可接流量时间从 115 s 降至 75 s(\(-40\) s),并另作假设:稳态吞吐保持不变。

因此登记此类机制时必须同时写清三项:命中前提(什么情况下走缓存)、常驻资源(额外占用的内存/显存/磁盘)、失效恢复(缓存失效或缺失时退回到哪条路径、耗时多少)。启动更快不等于稳态更快,把两者混在一句"性能提升 34%"里是最典型的误报。

8.5.4 冷暖分层、丢弃规则与失败恢复

正确做法是冷暖并列报告:分别给出冷启动后的首批结果、稳态结果,以及从冷到稳的时间(例如"第 200 个请求后 TTFT 进入稳态区间")。丢弃规则必须预先定义(丢弃前 \(k\) 个请求 / 丢弃前 \(t\) 秒 / 不丢弃但分层),而不是事后看着数据挑。

预热停止条件应依据稳定性判据(如连续若干窗口内指标变异系数低于阈值,或可用容量达到稳态),而不是照搬"预热 100 次"。

失败恢复也要计时:进程重启、缓存清空、编译缓存失效之后重新达到稳态需要多久,这直接决定扩容与故障场景下的容量(第16章)。

本节要点

  • 冷启动分阶段计时,报告"首次可接流量时间"。
  • 预热用独立样本,避免用测试集前缀污染命中率。
  • 权重常驻类机制要登记命中前提、常驻资源与失效恢复;启动更快 ≠ 稳态更快。
  • 冷暖并列报告,丢弃规则与预热停止条件预先定义。

常见误区

  • 误:启动耗时 12 秒,所以系统 12 秒后可服务。 正:可服务取决于"首次可接流量时间",包含加载、编译、图捕获与热身。
  • 误:预热跑一遍测试集最省事。 正:那是缓存污染;命中率被人为抬高。
  • 误:权重缓存让启动快了 40 秒,等于性能提升。 正:改善的是冷启动,稳态吞吐未变;且要付出常驻资源。

本节验收

  1. 你能给出冷启动各阶段的分项计时与首次可接流量时间。
  2. 你能说明预热样本的来源与停止条件,并证明没有用测试集污染缓存。
  3. 你能给出权重常驻机制的命中前提、常驻资源与失效恢复路径。

依据与核验入口:R11 R31 S02 S06


8.6 受控对照、重复与统计分析

本节要回答的问题:怎样知道差异来自配置,而不是顺序、温度或运气?

8.6.1 配对与分块实验

最有效的低成本手段是配对(paired)+ 交错(interleaved):同一份负载在两个配置上各跑一遍,且按 A/B/A/B 的顺序交错,而不是先跑完所有 A 再跑所有 B。这可降低部分顺序偏差,但不会自动消除单调漂移或残留效应;应优先使用随机化区组或 AB/BA 对称安排,并规定状态重置。

分析时看配对差值的分布,而不是两组的均值之差:差值序列能告诉你改善是稳定的还是被一两次异常运行拉出来的。

8.6.2 交互因素与重复

永远只改一个变量(OFAT)会漏掉交互:某开关在小批量时有利、大批量时有害,OFAT 会得出"总体略好"的错误结论。对明显存在交互的参数,用小型因子设计(如 \(2\times2\))比增加更多 OFAT 实验更省样本。

重复次数由波动与所需精度决定,不是固定三次。可算例:

配置 三次运行 (req/s) 均值 极差
A 4.02 / 4.18 / 3.96 4.05 0.22
B 4.31 / 4.22 / 4.40 4.31 0.18

把三轮按顺序配对,得到差值序列 \([4.31-4.02,\;4.22-4.18,\;4.40-3.96] = [0.29,\;0.04,\;0.44]\),均值 \(\bar{d} = 0.77/3 = \mathbf{0.257}\) req/s(约 6.3%),区间从 0.04 到 0.44。

正确的判断分两步,不能靠"极差 0.22 与均值差 0.26 同量级"这种比较——那不是有效的统计判断:

  1. 先定效应目标:本业务认为小于 0.15 req/s 的改善不值得付出迁移成本(这是业务决策,不是统计结论);
  2. 再看配对差值:三轮差值全为正(方向一致,3/3),但离散度很大(0.04–0.44),说明样本中方向一致,但只有三组时仍不足以把总体改善方向当成统计上确定的事实。

因此可写的结论是:B 优于 A 的方向在三次配对中一致,但效应幅度的区间(0.04–0.44)跨越了 0.15 的采用门槛,需要更多区组或更严格的对照才能给出采用决定;不可写的是"B 快 6%"或"三次不足以判断"这类没有效应目标与区间的断言。

还要注意交错不等于消除漂移:ABAB 只在漂移是随机且对称时才把它平均掉;若存在单调时间漂移、热状态累积,或 A→B 之间有残留效应(缓存、连接池、编译缓存未重置),顺序仍会与配置混淆。因此必须说明随机化或区组方式、必要的状态重置,并在报告里写明采用条件。

8.6.3 选择偏差、留出与仿真边界

  • 先定义主指标与停止规则,再查看结果。跑完一大堆配置再挑最好的一组,等于在选择噪声的最大值。
  • 留出验证:用于调参的负载与用于最终结论的负载要分开(第7章 7.6.3 的三分数据集同理)。
  • 仿真不能顶替实测:CPU-only 模拟器一类的工具可用于筛选实验与研究调度 [SOURCE_REGISTERED] S03;另有资料记录将配置器更名为仿真器 [SOURCE_REGISTERED] S06。仿真结果必须标 run_type = SIMULATED,写明仿真输入、模型假设与校准来源,不得与 MEASURED 混淆来源;校准图允许明确区分后并列展示,但仿真不承担模型数值质量验收。

本节要点

  • 配对 + 交错,看差值分布而不是两组均值。
  • 存在交互时用因子设计;重复次数由波动与精度决定。
  • 先定主指标与停止规则,再跑;调参与结论负载分离。
  • 仿真标 SIMULATED,来源必须清晰,不承担质量验收;但这不等于禁止与实测并列比较——在验证预测或校准模型时,把仿真与实测画在同一坐标系、用不同标识区分来源,恰恰是必要的对照。禁止的是混淆来源,不是并列展示。

常见误区

  • 误:A 跑了 4.31、B 跑了 4.05,所以 A 快 6%。 正:给出配对差值序列与区间,并对照事先设定的效应目标;不比较"均值差"与"极差"两个不同性质的量。
  • 误:改一个变量最干净,所以永远只改一个。 正:会漏掉交互;必要时用因子设计。
  • 误:仿真跑得快,可以用它出性能结论。 正:仿真只能筛选假设,结论仍需实测证据。

本节验收

  1. 你能说明实验是否配对、是否交错,并给出差值分布。
  2. 你能为存在交互的参数设计小型因子实验。
  3. 你能说明重复次数的依据,并把仿真与实测分开标注。

依据与核验入口:R12 S03


8.7 实验:交付一份可复核基准

本节要回答的问题:别人拿到报告,是否知道怎样重跑并检查结论?

8.7.1 实验清单与制品身份

一份可复核的报告至少包含以下 12 项,缺任何一项都会让结论无法追溯:

# 项目 说明
1 实验身份 run_id、时间、执行者、授权范围
2 制品身份 模型 revision、权重哈希、分词器、模板、精度方案
3 运行身份 引擎版本/commit、生效配置、执行路径、硬件与驱动
4 负载卡 来源、联合分布、到达结构、拓扑、脱敏偏差
5 发压方式 开环/闭环/回放、连接池上限、调度迟滞
6 预热与缓存状态 预热样本来源、停止条件、冷暖分层、丢弃规则
7 指标口径 起点/终点/时间域、mapping_version
8 质量门槛 判定方法、评估集、是否在线逐请求
9 分母与终态 成功/失败/取消/超时/未知各层的计数
10 统计方法 分位算法、样本量、重复次数、区间或差值分布
11 run_type MEASURED / SIMULATED / SYNTHETIC / CONFIG_ANALYSIS
12 失败与负面结果 失败运行、异常、排除理由

8.7.2 原始事件与汇总

图表必须由原始记录派生,不得手工改动数值;脚本与数据一同存档。工具接口随版本变化时,保留原生结果,再通过受控映射生成统一口径;禁止把多个版本的输出混作一套数据。

结果索引至少包含:逐请求原生记录、规范化结果、汇总表、环境日志、统计说明与图表生成脚本。这样即使第三方没有同款硬件,也能审查口径是否自洽。

8.7.3 结论范围与复现说明

报告的最后一节必须写明结论边界:本结论适用于哪些负载、哪些长度档、哪些拓扑、哪些版本组合;未覆盖的区间要明说。复现说明给出最短路径:拿这份 manifest 与负载卡,在何种资源层级下可重跑;在何种条件下只能审查口径而不能复现。

本节要点

  • 12 项报告清单齐全,第三方才能定位每个结论对应的运行与源数据。
  • 图表由原始记录派生;保留工具原值与映射版本,不混版本。
  • 结论边界写明适用范围与未覆盖区间,并给出复现或审查的最短路径。

常见误区

  • 误:报告里有性能表和结论就够了。 正:没有制品身份、口径与分母,结论无法被检查。
  • 误:多跑几个工具版本取平均更全面。 正:那是混数据;应保留原值并标注映射版本。

本节验收

  1. 你的报告包含 12 项清单,且每项可定位到具体文件或字段。
  2. 所有图表可追溯到原始记录与生成脚本。
  3. 结论边界写明了未覆盖的负载与版本区间。

依据与核验入口:R11 R13 S17 S03


8.8 深化:基准是一个采样与识别问题

8.8.1 同一负载文件不一定产生同一计算负载

同一份文本经过不同模板、分词器、媒体处理器或停止规则,会变成不同的Token序列和输出分布。即使输入Token一致,采样随机性、前缀冷热、推测接受和路由也会改变实际执行工作。报告应把三个层次分开:用户任务样本、引擎看到的逻辑Token、设备实际执行的位置和候选。

因此,回放文件除了文本或ID,还需保存到达计划、依赖拓扑和采样配置;执行后再导出实际Token统计。对“固定输出长度”的性能实验,要明确通过什么机制实现,是否禁止EOS,以及该机制是否改变业务任务。它可以作为控制变量的微基准,但不能直接代表自然生成的质量与成本。

8.8.2 计划时间和发出时间共同决定遗漏发生在哪里

在真正的开环实验中,计划到达与服务完成没有反馈关系。但客户端自身的连接池、协程数、流式解析或磁盘写入仍可能阻塞发出。此时若从实际发出开始计时,客户端等待被排除,可能再次把系统体验测得过于乐观。

可以并列报告两种时间:scheduled_to_complete含发压器迟滞,帮助审查是否忠实执行计划;sent_to_complete反映请求实际进入网络后的表现。前者不能全部归因于服务端,后者不能证明发压器没有漏掉外部到达。若迟滞持续增长,应停止扩压并标记客户端受限,而不是把名义目标速率写成实际速率。

8.8.3 三个正差值不是已经证明总体改善

第8.6节的配对差值为0.29、0.04、0.44 req/s,样本均值约0.257。样本最小值到最大值是观察范围,不是置信区间。在一个对称、独立且无平局的零效应假设下,三个差值恰好同号的双侧符号检验概率为 \(2/2^3=0.25\),并不构成常见5%水平下的显著结果。

这里不是要求机械追求某个p值,而是强调报告应同时回答:效应的业务门槛是多少,重复运行之间变动有多大,样本是否足以区分有价值的改善与噪声。若要估计总体效应的区间,采样单位应按运行、时间块或会话确定;对高度相关的逐Token记录做独立bootstrap,会把样本量夸大。

调参也会引入选择偏差。观察几十个配置后挑最快者,再用同一批数据估计提升,通常偏乐观。应预先限制候选、记录全部尝试,并用未参与选择的负载与新运行作最终确认。留出的是对决策的独立检验,不是简单换一个随机种子就算完成。

8.8.4 公开加载案例说明为何冷启动必须独立

SGLang 0.5.20发布说明记录了一个GLM-5.2、TP4、4×MI355X的加载优化案例:加载时间505.7 s变为40.4 s N18。这是项目方报告的加载阶段结果,不是本书测量,也不是在线Token吞吐。其价值在于提供了明确的阶段、模型和硬件身份,而不是给出一个可通用套用的加速倍数。

图8-R1 官方发布说明中的模型加载时间

图只重绘这两个公开数值,保留原指标和单位,没有补造误差条或重复次数。正式工程验收仍需补镜像与模型revision、冷缓存条件、存储路径和重复分布;若这些字段缺失,就不能把该图用作跨平台采购比较。公开数据能填补一个案例,但不能填满本地环境所有实验栏。


8.9 实验图表补充:补入论文真实负载长度,而不伪造分布

DistServe OSDI 2024正式论文Figure 7图例直接给出三组均值:ShareGPT输入755.5/输出200.3,HumanEval输入171.3/输出98.2,LongBench输入1738.3/输出90.7。本文核对了正式PDF第10页(印刷201页)的图中文字,而不是从曲线像素估计数值。

这些均值足以提醒基准不能混为一谈:论文中的摘要负载相对代码补全明显更偏输入。但它们不足以恢复联合分布,也不能推出“长输入且长输出”的概率。因此图只画均值柱,不生成散点、直方图或相关系数。

LongBench还受到OPT绝对位置嵌入的2048输入上限限制。不能把1738.3当作LongBench在现代128k模型上的天然平均长度,也不能据此设定今日业务负载。论文未披露完整tokenizer与三套数据的全部预处理规则,这些内容留在来源边界中。

实际使用:用本业务采样生成输入×输出联合分布、到达轨迹、缓存共享与多轮依赖,再判断哪个公开负载最接近;公开论文的均值只作对照点。505.7→40.4 秒那一组仍是 SGLang 特定模型加载实验,不与这里的OPT负载混合。

图8-V1 三类公开负载:均值已足以提醒不要混为一谈

图8-V1 三类公开负载:均值已足以提醒不要混为一谈

数据身份:PUBLISHED_RESULT。条件:OPT评测;LongBench输入被限制到2048;数值为原图图例avg标签;tokenizer未披露。

论文工作负载 输入均值/Token 输出均值/Token
ShareGPT 755.5 200.3
HumanEval 171.3 98.2
LongBench 1738.3 90.7

出处与边界:X07;只有边际均值,不是联合分布、样本直方图或现代长上下文数据集全貌。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 只匹配平均输入输出长度为何不足以复现业务?

因为决定资源占用的是联合分布。8.1.1 的算例中,边际独立采样把"长输入且长输出"这一最耗显存的组合从真实的 0.06 抬到 0.22(约 3.7 倍)。此外还有到达相关性、会话复用率与在途数——只给两个均值,既复现不了显存占用,也复现不了排队行为。正确做法是给负载卡:联合分箱、到达间隔分布、复用率、在途数。

2. 同一提示词重复运行是否一定是不公平测试?

不一定,取决于研究问题。若测的是缓存友好场景下的上限(例如前缀复用的最大收益、稳态单请求延迟),重复同一提示词是合法且可控的手段,但结论必须限定为"缓存全命中场景"。若研究的是生产容量或质量,它就是不公平的:除非生产确实具有同样的共享结构,否则不能假设真实负载也是 100% 复用,而重复负载会让 Prefill 成本被系统性低估、命中率被系统性高估。关键是声明它回答什么问题,而不是默认它代表业务。

3. 树形分支压测为何不能宣称已覆盖任意菱形 DAG?

因为工具支持的拓扑是具体的:资料描述了 FORK/SPAWN 一类操作与当前的树形结构限制 [SOURCE_REGISTERED] S18;"树形分支能跑通"只证明该拓扑可用。菱形 DAG 需要汇合(join)语义与依赖判定,属于另一类能力。若需要测试它,应自行合成事件或实现,并标明这是本书合成而非工具原生,同时把该限制写进结论边界。


本章验收清单

节 验收标准
8.1 负载画像含相关性与采样范围,不只是两个平均长度;脱敏偏差已评估
8.2 每个负载有来源和用途说明;随机负载结论未外推为业务质量结论
8.3 每个组合对应一个待验证问题;三种并发分别定义;外部等待与引擎等待分开;工具拓扑限制已声明
8.4 实际发压接近声明模式,达不到时明确标注客户端受限;被限流请求计入分母
8.5 预热范围与丢弃规则预先定义;正式测试的缓存状态可解释;启动加速与稳态吞吐分别报告
8.6 结果含重复分布、失败运行与选择过程;无法确认的改善不写成确定增益;仿真与实测分离
8.7 第三方能定位每个结论对应的运行及源数据;无环境时也能审查口径;同名指标同事件同分母才可比

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第9章 监控、日志与性能剖析

核心论点:观测的价值在于区分候选解释;采集更多数据并不自动形成更强证据。

本章导航

读完本章,你应该能够:按问题选择观测层级而不全量开采集;用标识把指标异常定位到具体请求与执行;检查主机侧是否拖累了设备;区分"设备忙碌率""显存容量占用""有效算力"三种语义;从系统时间线读出关键路径与空洞,并把执行路径落到可取证的身份;控制采集本身的扰动与合规风险;最后交出一个能让下一位工程师继续排查的最小诊断包。


9.1 观测分层与最小证据集

本节要回答的问题:回答不同问题,需要观察到哪一层?

9.1.1 四层观测

层 典型证据 能回答 不能回答
客户端 逐事件时间戳、终态、错误分类 用户体感、网络与网关缓冲 引擎内部发生了什么
服务端 请求级指标、队列长度、终态计数 排队与调度、成功率、缓存命中 设备在执行什么
运行时 引擎内部事件、执行路径、状态块 批组成、执行路径、回退、缓存命中来源 硬件计数器细节
设备 原生计数器、系统时间线、内核 trace 内核身份、通信、空洞、带宽 业务含义

层不是越深越好:越深的观测通常越贵、越易扰动系统、也越难与业务请求关联。先用便宜的服务端指标确定异常窗口,再针对假设向下采集。

9.1.2 权限决定可达边界

第1章定义的资源层级在这里直接决定能写什么结论:

资源层级 可观测 必须承认的边界
E1(只有接口权限) 客户端事件、终态、端到端分布 不能确认内核路径、排队内部、显存回收
E2(可访问主机与设备观测) 服务端指标、系统 trace、设备计数器 单设备不代表多卡或跨节点
E3—E5 节点内/跨节点拓扑与通信 未覆盖拓扑不得外推

E1 条件下写到内部机制的结论都是推测,必须标注为假设并给出验证路径。

9.1.3 假设驱动的采集计划

正确的顺序是:先列出候选解释,再决定采集什么,而不是"先把所有采集打开再说"。为每一项采集写明四列:目的(要区分哪两个解释)、频率或采样率、成本(时间/内存/存储/扰动)、以及停止条件。

一份最小采集计划通常长这样:

采集项 目的 频率 成本 停止条件
请求级指标 发现异常窗口 连续 低 常驻
服务端队列与终态 区分排队与计算 连续 低 常驻
系统时间线 trace 定位空洞与通信 异常窗口内 30—60 s 中高 拿到 3 个可解释窗口即可
内核级剖析 确认内核身份 单次、短窗、开关对照 高 取得一次路径证据即可

本节要点

  • 四层观测各有能回答与不能回答的问题,越深越贵越难关联。
  • 权限决定结论边界;E1 条件下的内部结论必须是标注过的假设。
  • 采集计划先列候选解释,每项写明目的、频率、成本与停止条件。

常见误区

  • 误:监控越全越好,全都开着最保险。 正:深度采集有扰动与存储成本,且与业务请求难以关联;应按假设开。
  • 误:有了设备 trace 就能解释业务变慢。 正:还需要请求级证据把设备行为与具体请求/负载对应起来。
  • 误:E1 也能通过端到端延迟推断内核问题。 正:推断是假设,不是结论;必须标注并给出验证路径。

本节验收

  1. 你能说出每个计划采集的数据对应哪一组待区分的解释。
  2. 你能按自己当前的资源层级列出可写与不可写的结论。
  3. 你的采集计划含成本与停止条件,而不是"一直开着"。

依据与核验入口:R01 R17


9.2 Metrics、Logs 与 Traces 的关联

本节要回答的问题:如何把汇总异常定位到具体请求和执行?

9.2.1 指标:发现变化,不解释原因

指标(metrics)是聚合量,用于发现变化与设定告警。两个约束必须记住:

  • 分位数不可跨实例平均(第7章 7.4.2);聚合要用可合并的分布表示,并写明分位算法与样本量;
  • 不要把 request_id、task_id 等高基数标识作为指标标签——它会把时间序列基数爆炸,也会把请求级信息错误地塞进聚合通道。请求级信息属于日志与 trace。

低基数标签的推荐集合:模型/版本、路由类别、长度档、终态类别、租户分层。

9.2.2 结构化日志:记录状态与细节

日志按事件而不是按文本行组织,每条至少带:时间戳 + 时钟域、request_id / attempt_id / task_id / session_id、trace_id / span_id、run_id、终态与原因。敏感提示词默认不入日志(9.6 展开),只保留哈希或脱敏摘要 + 私有索引。

9.2.3 Trace 与跨进程关联

trace 的价值是把调用与执行组织成因果关系。关联靠标识,不靠时间接近:

标识 关联什么
request_id / attempt_id 请求与重试
trace_id / span_id 跨进程调用链
run_id 一次实验或一次运行
transfer_id 状态/缓存传输(分布式场景)
model_call_id 具体到某次模型调用

三条使用规则:

  1. 记录采样率。trace 通常是采样的,覆盖率决定了能下什么结论。可算例:10000 个请求、采样率 1/100 → 100 条 trace;若异常只发生在 0.3% 的请求上(\(10000\times0.003=30\) 条),期望被采到的只有 \(30\times0.01=0.3\) 条——随机采样基本抓不到。这类尾部异常必须用定向采样(按终态、按长度、按租户条件采样)。
  2. 记录时钟域。跨进程、跨主机使用不同时钟时,不能直接相减伪造传输耗时(第1章 1.2.3、第7章 7.2.2)。
  3. 缺失片段不等于零耗时。未采集到的区间应标为缺失,不能填 0;把空洞填成 0 会让"没采到"变成"极快"。

分布式场景还要把路由决策、缓存查找、状态传输发起、接收确认、回收五类事件与 transfer_id 关联到同一 run_id [SOURCE_REGISTERED] S13 S06。

本节要点

  • 指标发现变化、日志记录状态、trace 组织因果,三者靠标识关联。
  • 高基数标识不进指标标签;请求级信息走日志与 trace。
  • 必须记录采样率与时钟域;缺失片段记缺失,不记 0。
  • 分布式事件(路由/缓存/传输/确认/回收)与 transfer_id、run_id 关联。

常见误区

  • 误:给指标加个 request_id 标签就能查单请求。 正:那会毁掉指标系统;请求级数据属于日志与 trace。
  • 误:trace 里没看到的耗时就是 0。 正:是未采集;标缺失,并说明覆盖率。
  • 误:按 1% 采样就能分析千分之一的异常。 正:千分之一在 10000 条里是 10 条,独立 1% 采样的期望命中只有 0.1 条; 若是 0.3%(30 条),期望命中 0.3 条,至少命中一次的概率约 26.03%(\(=1-0.99^{30}\))。 所以结论是"覆盖不足、不可依赖",而不是"绝对抓不到";尾部异常仍应改用定向采样。

本节验收

  1. 你能从一段指标异常回到对应的请求样本,并说明 trace 覆盖率。
  2. 你的日志事件带齐五级标识与时钟域。
  3. 你能说明分布式场景下的传输事件如何与 run_id 关联。

依据与核验入口:R01 R18 S15


9.3 CPU、内存、存储与网络观测

本节要回答的问题:设备空闲是否可能因为主机没有及时供给?

9.3.1 线程与进程调度

设备空闲的第一嫌疑往往是主机:预处理(分词、模板展开、图像解码)、批处理组装、采样与停止判定、输出序列化在多数部署中发生在主机侧,但采样与停止判定由哪一侧执行取决于实现(第1章 1.5.2 已要求按实现取证)。排查时应逐路径核验,而不是默认把它们都算在主机账上。检查项包括:进程的 CPU 配额与 cgroup 限制、线程是否被调度及时、是否存在锁竞争、以及 Python 层的 GIL 争用。

判据不是"CPU 高就一定是瓶颈",而是时间关联:设备空闲区间是否与主机繁忙区间重合?可算例:某窗口内设备空闲占 31%,其中 22 个百分点落在"等待主机数据/预处理"的时间线区间内;干预(提高预处理并发并预取)后空闲降到 9%(\(=31\%-22\%\))。31% → 9% 的改善来自时间关联 + 干预验证,不是因为看到 CPU 高就下结论。

9.3.2 内存与数据路径

主机内存压力(换页、OOM 风险)、容器内存上限、以及数据加载路径(本地盘/网络存储/对象存储)都可能成为隐性瓶颈。加载慢会延长冷启动(第8章 8.5),外部缓存的读取慢会抵消复用收益。

检查时要把"内存使用率高"与"发生了回收/换页"分开:前者可能只是缓存占用,后者才是性能事件。

9.3.3 连接与网络状态

网络问题常表现为流式返回的台阶、重传导致的间隔抖动,以及跨节点执行中的通信长条。检查:连接数与队列、重传率、MTU/分片、代理缓冲、以及客户端与服务端的路径。

不要孤立截取一张高占用截图。正确做法是:记录主机资源与设备行为的同一时间轴,做受控干预(例如增加预处理并发、调整连接池、改变数据位置),再复测。有时间关联 + 干预响应,才算候选解释;只有资源高低,不算。

本节要点

  • 主机供给不足是设备空闲的常见原因,但要靠时间关联与干预验证,不靠资源高低。
  • 内存要区分"占用高"与"发生回收/换页"。
  • 网络问题表现为流式台阶、重传与跨节点通信长条。
  • 同一时间轴 + 受控干预,才构成候选解释。

常见误区

  • 误:CPU 用了 90%,所以 CPU 是瓶颈。 正:要看它与设备空闲是否在时间上重合,并用干预验证。
  • 误:内存占用接近上限,所以内存不足。 正:关键是是否发生回收/换页,而不是占用率。
  • 误:设备空闲说明设备没问题。 正:可能是主机没喂上数据,属于主机侧问题。

本节验收

  1. 你能给出主机资源与设备行为的同一时间轴,并标出重合区间。
  2. 你能设计一次干预来验证主机侧假设。
  3. 你能区分"资源占用高"与"发生了影响性能的事件"。

依据与核验入口:R17


9.4 设备计数器与利用率语义

本节要回答的问题:"利用率高"究竟是在统计忙碌、带宽,还是有效计算?

9.4.1 忙碌率不等于有效算力

"利用率"在不同工具里至少有四种分母:时间忙碌率(采样窗口内设备非空闲的时间占比)、容量占用率(显存被占用的比例)、带宽利用率(相对峰值带宽)、有效运算吞吐(实际达成的 FLOP/s 占峰值的比例)。把它们统称为"利用率",是性能诊断里最普遍的错误。

先看一个容易算错的对照:5 分钟(\(=300\) s)窗口内设备忙碌 288 s(96%),某类矩阵活动的记录量为 126 s。

126 s 是什么,决定了能推出什么:

126 s 的含义 可以推出 不能推出
多个流/内核的累计时长 它可能超过窗口长度(跨流重叠) 不能用 \(300-126\) 得到"非矩阵时间"
活动区间的并集,且包含于 288 s 忙碌但非该活动 \(=288-126=\mathbf{162}\) s,空闲 \(=300-288=\mathbf{12}\) s 不能推出"总共浪费 174 s"

也就是说,只有在第二种解释下才能做减法,而且结果是 162 s 与 12 s 两笔账——绝不是"174 s 全是浪费"。此外,"矩阵活动时长占比"也不是"有效 FLOP/s 占峰值比例":前者是时间,后者是速率,两者的分母不同,只能通过各自原生字段分别取得。

结论:96% 忙碌不代表 96% 的有效算力;但要追问"那 162 s 花在哪",必须先确定 126 s 的口径。只看一个百分比无法区分这两类。

必须落到具体原生字段。以 nvidia-smi 为例,它的两个"利用率"定义完全不同(原生定义见本书 N17)SOURCE_REGISTERED:

字段 官方定义要点 常见误读
GPU 利用率 "Percent of time over the past sample period during which one or more kernels was executing on the GPU." 当成"算力用满了"
Memory 利用率 "Percent of time ... during which global (device) memory was being read or written." 当成"实际字节/s ÷ 峰值带宽"

此外该文档还给出三条使用边界:采样周期随产品在 1 秒到 1/6 秒之间(读数不可假定为 1 秒平均);MIG 启用时不支持查询 gpu/memory 等利用率;ECC 初始化期间可能出现虚高读数。因此正确做法是先选定原生字段、写明其定义与采样,再谈分析——不能先把所有 "utilization" 概括成一个"设备忙碌率"。

9.4.2 显存容量占用与显存带宽

这是另一组常被混用的量:

量 含义 高值意味着 不等于
容量占用率 权重 + 状态 + 临时缓冲占显存的比例 并发与长度接近上限 带宽压力
带宽利用率 实际读写速率 / 峰值带宽 访存密集(典型如 Decode) 容量不足
有效运算吞吐 实际 FLOP/s / 峰值 计算密集(典型如大批量 Prefill) 设备忙碌

典型形态是"容量占用高但带宽空闲"(长上下文、小批量)与"带宽饱和但容量有余"(大批量 Decode)。二者对应的优化方向完全不同:前者要减状态或分离,后者要提访存效率或调批。

9.4.3 采样窗口、工具差异与厂商边界

同一名称的计数器,在不同工具与不同厂商之间不一定可比:采样粒度不同(毫秒级采样会把短内核平均掉)、分母定义不同、以及是否计入等待。使用前的三条纪律:

  1. 先查定义,再引用数字:记录指标名、单位、分母、采样窗口、原生工具及版本;
  2. 用已知工作量或微基准校验:把计数器读数与可独立推算的量对照(例如已知的输入 Token 数与理论运算量);
  3. 不跨厂商对齐同名百分比:不同架构的计数器语义可能完全不同。

【版本化补证】S24/S25 的特定网页入口不足以支持基于它的命令结论。本次另取得 CANN 8.5.0 的离线推理性能分析正文 N08,已能核对基本 msprof 调用和导出文件类别,见第15章 15.8。它是独立来源,不能将其版本替换成 msprof 26.1.0 或 CANN 9.1.0,也不能据此认证未经验证的硬件组合。

推荐的计数器语义登记字段:

字段 内容
metric_name 原生指标名
unit 单位(%、GB/s、GiB/s、FLOP/s…)
denominator 分母定义(时间/容量/带宽/峰值)
sampling 采样粒度与窗口
tool_version 原生工具与版本
scope 适用硬件与驱动范围
cross_vendor_comparable 是否可跨厂商比较(默认否)

本节要点

  • 利用率至少有四种分母;"忙碌率 96%"不等于"有效算力 96%"。
  • 显存容量占用与显存带宽利用率是两件事,对应不同优化方向。
  • 引用计数器前先查定义、用已知工作量校验、不跨厂商对齐。
  • 原生工具字段按版本核验;正文未取得的入口保持未知,不写命令与字段语义。

常见误区

  • 误:利用率 96%,算力已经榨干了。 正:可能大部分时间在等数据或等批;要看有效运算量与空洞构成。
  • 误:显存用了 90%,所以显存带宽是瓶颈。 正:容量与带宽是两个量;需要带宽类计数器判断。
  • 误:两个平台的"利用率 80%"可以直接比较。 正:分母与采样可能不同;跨厂商默认不可比。

本节验收

  1. 你能说清所用利用率指标的分母与采样窗口。
  2. 你能区分容量占用、带宽利用与有效运算吞吐三个量。
  3. 你能为自己的原生工具登记语义表,并标明未取得正文的入口。

依据与核验入口:R17 R21 S23 S25


9.5 时间线剖析:执行路径、同步、传输与回退

本节要回答的问题:时间线上的空白和长条意味着什么?

9.5.1 系统关键路径与设备空洞

正确顺序是先系统、后算子:先看系统级时间线找出阶段关系(排队 / 计算 / 传输 / 同步 / 输出),确认关键路径,再挑少量区间做算子级剖析。直接在设备时间线上找"最长的那个内核",很容易优化掉一段根本不在关键路径上的工作。

设备空洞(gap)至少有三类来源,处理方式完全不同:

空洞来源 特征 下一步
数据未就绪 空洞前有主机侧活动、输入拷贝 查主机供给与预处理(9.3)
调度未排入 空洞位于批与批之间,随负载变化 查批组成与排队(第5章)
依赖未满足 空洞紧邻通信或同步操作 查通信与并行拓扑(第6章)

9.5.2 通信等待、数据移动与计算重叠

通信长条往往包含等待:集合通信的完成时间取决于最慢的参与者,因此一条长条可能是"传输久",也可能是"等别人"。判别需要结合 rank 视图与请求上下文,但观测到的先后差异只提供候选解释,不能直接定性根因:要确认"谁在等谁",还需要时钟映射、消息身份(哪条消息属于哪次集合通信)、队列与依赖信息,以及协议层面的完成语义。一个常见的误导来源是:开始时间不齐本身可能就是时钟未对齐的产物,而不是调度问题。

集合通信的语义可由官方文档确定,但性能取决于拓扑、消息大小与具体实现,不能由语义推出耗时 SOURCE_REGISTERED。计算与传输是否重叠,需要用时间线证据而不是配置开关来判定。

9.5.3 runner、graph 与实际内核身份

配置项只表示"请求使用某条路径",不等于实际生效。证据分三档,强度递增:

档 证据 能证明
源码阅读 分支条件与 dispatch 逻辑 可能走这条路径
启动日志/配置回显 后端选择、模式、开关 声明了什么
运行 trace 内核名、调用次数、形状 这一次实际走了哪条

运行期 trace、精确插桩或可核对的执行计数等证据,才能支撑“本次实际启用”的结论;仅有内核名称也未必足以区分全部配置(第1章 1.5.3)。内核库与注意力实现的具体能力范围需按所选版本核验,不能把仓库主页的描述当作当前生效行为 SOURCE_REGISTERED S09。

9.5.4 采样开销与跨时钟关联

异步执行带来一个陷阱:CPU 侧调用返回的时刻不等于设备完成的时刻。以调用返回时间当完成时间,会把排队与执行颠倒。判别依据是设备时间线上的完成事件,或显式的同步/等待事件。

剖析本身也有开销(9.6 量化),并且跨进程/跨主机的关联需要时钟映射(9.2.3)。因此局部剖析的结论必须写明:采样率、时间域、以及是否做了开关对照。

最后一个值得警惕的现象:微基准里内核变快,端到端却没变。可能的原因有两类:一是该内核不在关键路径上(优化了空闲);二是排队反馈——但这只在特定条件下成立。在一个到达过程固定、其余条件不变的开环队列模型里,服务时间下降会降低利用率与等待时间,端到端应当改善;而在闭环负载、自适应到达或存在其它瓶颈接管时,节省的时间才可能被吸收。所以正确做法是写成两条可区分的假设,并用改变负载模式的实验去判别,而不是宣布"优化无效""测量不准"或把"排队会吸收一切"当成普遍规律。

9.5.5 一次完整判读(trace)

前面讲的是纪律,这一小节给一份可跟做的样例。以下为构造数据的合成 trace,与 Sample-B0 同一时间轴(服务端时钟域,ms),并假设采样在主机完成、输入拷贝可在最终调度确认前预取(实际需按实现核验):

泳道 区间 (ms) 时长 事件
主机 0–6 6 分词与模板展开
引擎 21 — engine.queued
主机 18–21 3 批组装
引擎 140 — engine.scheduled
设备 134–141 7 输入拷贝 H2D
设备 141–600 459 Prefill 线性层
设备 600–620 20 Prefill 注意力
主机 620–634 14 采样与停止判定(首 Token)
主机 634–641 7 写出首个响应块
设备 641–900 259 推理内容生成(E1—E5)
(无设备活动) 900–1180 280 待解释的空洞
设备 1180–4210 3030 可见答案生成(E6—E20)

三步判读:

  1. 算并集与关键路径。设备活动并集 \(=7+459+20+259+3030 = 3775\) ms;观测窗口(入队到最后内容事件)\(=4210-21 = 4189\) ms; 故设备空闲 \(=4189-3775 = \mathbf{414}\) ms。关键路径 \(= \)调度等待及交接 \((21\to141) + \)Prefill \((141\to620) + \)采样 \((620\to634) + \)写出 \((634\to641) + \)生成 \((641\to4210) = 4189\) ms,与 E2E_server 一致。
  2. 定位最大可解释项。414 ms 的空闲里有 280 ms 集中在一个连续区间(900–1180),占空闲的约 67.6%——这是最值得解释的一块,而不是那些分散的内核间隙。
  3. 提两个竞争解释,并给出下一项最便宜的取证。候选:H1 编排层在等待外部工具返回;H2 调度未排入(等待下一轮批组成或其它请求);H3 依赖/同步未满足(等待通信或内存回收)。 区分它们的最便宜证据依次是:查该请求在引擎侧的调度事件与批组成记录(若期间无调度活动,倾向 H1;若有等待排队的记录,倾向 H2);查是否存在对应的通信或传输事件(区分 H3);以及注入固定的外部延迟看这段空洞是否随之等长变化(直接检验 H1)。

这份 trace 能教授判读方法,不能证明任何硬件的性能。剖析工具本身也会区分 CPU wrapper、launch latency、memory overhead 与设备工作排队等不同成分,判读时必须先分清长条里是哪一种,再谈原因 SOURCE_ENTRY_ONLY。

本节要点

  • 先系统级关键路径,后算子级剖析;空洞要分类(数据/调度/依赖)。
  • 通信长条可能含等待;需结合 rank 与请求上下文。
  • 源码、启动声明与运行期证据分层;trace 是运行期证据的一种,需与形状和调用关联。
  • 异步执行下调用返回 ≠ 设备完成;微内核提速不等于端到端改善。

常见误区

  • 误:找到最长的内核,优化它就能提速。 正:先确认它在关键路径上;否则优化的是空闲。
  • 误:通信条很长,说明网络慢。 正:可能是等最慢的参与者;要看 rank 视图。
  • 误:内核快了 30%,端到端应该也快 30%。 正:看关键路径占比与排队反馈;不在关键路径上就没有端到端收益。

本节验收

  1. 你能指出一段窗口的关键路径,并给出至少一个替代解释。
  2. 你能把执行路径结论落到三档证据中的最高档。
  3. 你能说明剖析的采样率、时间域与是否做了开关对照。

依据与核验入口:R16 R08 S09 S15


9.6 观测开销、权限与数据保护

本节要回答的问题:采集行为是否改变了我们想测的系统?

9.6.1 采样与剖析扰动

详细剖析会增加时间、内存与存储开销,也会改变批组成与缓存行为。基线性能与诊断采集必须分别标识,不能拿"开着 trace 跑出来的数字"当作系统基线。

可算例:同一负载下,关闭采集时端到端 P50 为 1.82 s,开启详细 trace 后为 2.31 s,增加 0.49 s(约 27%)。因此:

  • 任何"开启采集"的运行都要在 run_id 上标记采集配置;
  • 性能实验与诊断实验分开存档,报告时不得混用;
  • 采集开关要做对照:先测关闭、再测开启、最后再测一次关闭,确认扰动可复现且系统已恢复。

降低扰动的常用手段:只对异常窗口采集、降低采样率、只采必要的流与计数器、避免全量请求级日志落盘。

9.6.2 权限与环境控制

采集前要确认:谁授权、采集范围(进程/主机/设备)、是否允许改变配置(例如临时开启调试开关)、以及采集数据的留存期限与存放位置。配置变更本身要记录——它会成为下一次实验的混淆因素。

对生产环境的采集还应写明停止条件与回滚方式:出现异常时如何立即关闭采集、如何恢复到采集前的配置。

9.6.3 脱敏、保留与发布

敏感字段默认不公开:原始提示词、用户标识、凭据与密钥、业务数据。日志里保留哈希或脱敏摘要,原始内容存放在受控的私有索引中。

公开材料采用脱敏摘要 + 私有原始索引的结构,而不是直接上传全部日志:

层 内容 可见范围
公开包 指标口径、脱敏事件样本、结论与证据索引 读者
私有索引 原始日志、trace、请求内容 授权内部

发布前用一张审查表逐项确认:无凭据、无未授权内容、无生产提示词、图表可追溯到原始记录与脚本、以及所有数字标了来源状态。

本节要点

  • 剖析有扰动:基线采集与诊断采集分别标识,并做开关对照。
  • 采集要有授权、范围、留存期限与停止/回滚方式。
  • 敏感字段默认不公开;公开包是脱敏摘要 + 私有索引。

常见误区

  • 误:开个 trace 不影响结果,可以顺便测性能。 正:扰动可达数十个百分点;性能与诊断实验必须分开。
  • 误:日志里留着提示词方便排查,反正不对外。 正:公开或共享时会泄漏;默认不写原文,只留哈希。
  • 误:脱敏就是把名字换成 X。 正:还要处理可重识别的组合字段与结构信息,并评估对负载统计的影响(第8章 8.1.3)。

本节验收

  1. 你能给出采集开销的对照数字,并证明基线运行未被混入。
  2. 你的采集有授权记录、停止条件与回滚方式。
  3. 你的公开包不含凭据、密钥或未授权内容,且保留私有索引。

依据与核验入口:R16 R18


9.7 实验:建立最小诊断观测包

本节要回答的问题:哪些证据足以让另一位工程师继续排查?

9.7.1 异常窗口定位

先选一个可稳定复现的问题窗口(时间区间 + 负载条件 + 版本组合),再开始采集。但"无法稳定复现"不等于只能留下模糊印象:要区分两条路径——事后证据分析(用当时已保存的日志、指标、trace 与终态记录做现场取证,结论标为假设并说明证据强度)与受控复现验证(重建条件后重跑,用于确认因果)。前者即使不能复现也常常能得到可检验的结论,后者才需要可复现性。

窗口要写成一条记录:起止时间(含时区与时钟域)、负载卡代号(第8章 W01—W08)、目标与实际到达率、版本与配置摘要、观察到的异常指标及其口径。

9.7.2 聚焦采集与事件关联

按 9.1.3 的计划,先取端到端与服务指标,再采集短时系统 trace,最后(只在必要时)做内核级剖析。每一步都把新证据与已有的 request_id / run_id 关联起来,不给孤立的时间线。

关联的最小集合:请求事件(客户端 + 服务端)、服务指标窗口、系统 trace 片段、设备计数器片段、以及采集配置与开销记录。分布式场景再加上路由、缓存查找、状态传输(transfer_id)与回收事件。

9.7.3 证据分级与交接

观测包的产出不是一堆图,而是一张事实 / 假设 / 待验证三分表,每项配上请求索引与运行索引:

分级 判据 例子
事实 有原始记录与口径,可复核 "该窗口 P95 端到端为 3.9 s(样本 n=1240,最近秩法)"
假设 与证据一致但未被干预验证 "空闲疑似由主机预处理供给不足导致"
待验证 缺证据或需更高资源层级 "内核身份未取证,需服务端 trace"

接手者应当能立刻回答三个问题:这些数字的时间口径是什么、采集有哪些缺口、以及下一项最有价值的实验是什么。观测包的价值在于减少猜测,而不是生成最多的图。

本节要点

  • 先锁定可复现的异常窗口,再采集。
  • 采集按层级推进,并与请求/运行标识关联,不留孤立时间线。
  • 产出是事实/假设/待验证三分表 + 索引,目标是减少猜测。

常见误区

  • 误:把所有能采的都放进包里,接手者自己挑。 正:包要有分级与索引,否则等于把排查成本转嫁给别人。
  • 误:结论写"疑似""可能"就够了。 正:要区分假设与待验证,并写明验证所需的资源层级与实验。

本节验收

  1. 你的观测包含可复现的窗口定义与负载/版本摘要。
  2. 每条证据都有来源、时间域与完整性说明。
  3. 三分表标出了下一项最有价值的实验。

依据与核验入口:R01 R17 S13 S24


9.8 深化:从“看到活动”到“识别阻塞”的证据距离

9.8.1 活动并集、累计时长和关键路径是三个运算

设两个GPU流分别有区间[0,10] ms和[5,15] ms。累计活动时长为20 ms,活动并集为15 ms,而某个请求的关键路径取决于它是否需要等待这两个活动。直接把20 ms放进15 ms的窗口作“忙碌率”,会得到超过100%的值;这不一定是采集错误,而是用了累计时间而非并集。

即使算出并集,也不能把所有活动都归给某个请求。共享批、通信流和后台预取可以同时服务多个请求。请求级归因需要调用ID、迭代ID、批组成或可追溯的运行插桩;没有这些连接,只能报告窗口级活动,而不能宣称某个用户请求有多少设备计算时间。

随包 E0 程序提供区间裁剪与并集计算,覆盖重叠、相邻、窗口外和倒置区间。第9.5节的trace在入队至最后内容窗口内得到3775 ms设备活动、414 ms未观测到设备活动;“未观测到”仍可能包含采集缺口,真实运行不能不加检查就命名为空闲。

9.8.2 采样设计决定能否见到尾部

若存在30个异常请求,每个独立以1%概率被采样,则至少看到一个异常的概率是 \(1-0.99^{30}\approx26.03\%\)。提升到5%时约78.54%,仍非必然。定向采样可以提高覆盖,但会改变样本分布:按慢请求采到的trace不能再用于估计总体慢请求比例。

因此应分开两个目的:持续低开销指标用于估计异常发生率,定向trace用于解释异常机制。报告还需记录采样策略改变的时刻,否则“今天异常trace变多”可能只因采样率提高,而不是系统退化。

9.8.3 内核名是定位入口,不是完整的执行证明

同名内核可能处理不同形状、不同精度或不同分支;一个图中存在某个内核,也不证明全部请求都走该图。比较时应至少关联设备、流、形状或批桶、调用次数、执行区间和回退原因。对编译融合后的代码,源码中的多个算子可能只对应一个运行内核,反向逐名匹配也会失效。

NVIDIA对 nvidia-smi GPU利用率的定义是采样窗口内存在内核执行的时间比例,Memory利用率也是相应时间比例,而非字节带宽比 N17。因此“100%”只能提示持续活动;若要判断是否接近带宽屋顶,必须取得实际字节计数或有明确语义的带宽指标,并与同层模型比较。

9.8.4 剖析开销可能改变被研究的机制

采集不仅增加固定时间,还可能改变调度。一次本来短于批等待窗口的操作,因同步采集变长后会改变下轮批组成;多租户竞争、缓存命中和推测接受也可能随之变化。此时不能简单从剖析时长减去一个平均开销得到“真实值”。

更可靠的做法是把无深度采集基线、低开销观测和短窗口剖析分为不同运行类型。用第一类确认用户可见差异,用后两类支持机制;当机制证据与无采集表现不一致时,应先检验采集是否改变了路径。发布的时间线应明确标为,公开厂商trace应标注其来源,本书没有采集的现场不应画成仿真的工具截图。


9.9 实验图表补充:一张公开拟合图,一张采样漏报图

vLLM官方博客给出单H100、FA3、Llama-3.1-8B、并发1、输出128Token的上下文扫描,输入从256到125k。它报告的ITL线性拟合参数为BF16斜率 \(4.37\times10^{-5}\) ms/Token、截距6.44ms;FP8斜率 \(2.37\times10^{-5}\)、截距6.58ms。这里每个点由公式重建,不是取回的原始测量点,因此全部画为虚线。

较低斜率意味着新增缓存Token的增量时间降低,较高截距意味着固定开销增加。使用正文舍入参数求交点约7000Token;Table 3报告7010Token。二者差异是精度/舍入层面的信息,不应修改原表去强求一致,也不应把交点当成容量边界。

另一张图针对观测盲区:窗口内固定30个异常,每请求独立1%采样时,至少捕获一次的概率为 \(1-0.99^{30}\approx26.03\%\)。图展示的是独立伯努利采样假设,不是某个trace平台实测;错误请求定向采样、相关采样和限额策略需要另外建模。

两类证据应分开:拟合参数帮助形成瓶颈假设,采样概率帮助解释为什么没有看到异常不等于异常不存在。真正归因仍要请求日志、调度事件与设备区间相互印证。

图9-V1 从公开拟合识别固定开销与随长度增长的成本

图9-V1 从公开拟合识别固定开销与随长度增长的成本

数据身份:PUBLIC_FIT_RECONSTRUCTION。条件:单H100、FA3、并发1、输出128、输入256到125k;采用文中舍入斜率和截距。

格式 斜率/ms每Token 截距/ms
BF16 4.37e-05 6.44
FP8 2.37e-05 6.58

复算公式:ITL=slope*input_len+intercept。

出处与边界:X05;虚线非原始点;舍入参数给约7000Token,原表精确交点7010,两者不强行等同。

数据与绘图点 · PNG · 重绘代码

图9-V2 只采样1%,会漏掉多少罕见异常?

图9-V2 只采样1%,会漏掉多少罕见异常?

数据身份:TEACHING_CALCULATION。条件:窗口内恰有30个异常请求;每次独立伯努利采样;采样不依赖异常。

采样概率/% 至少捕获一次/%
1 26.03
2 45.452
5 78.536
10 95.761
20 99.876

复算公式:P=1-(1-p)^30。

出处与边界:本书9.2、9.8;真实请求/采样若相关,公式不成立;此图不是采样工具实测。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 如何证明 CPU 供给不足而非设备故障?

靠时间关联 + 受控干预,不靠资源高低。做法:把主机资源与设备行为画在同一时间轴上,确认设备空闲区间与主机繁忙/等待区间重合(9.3.1 的算例中,31% 的空闲里有 22 个百分点落在等待主机数据的区间);然后做一次干预(提高预处理并发或预取、调整线程/配额),复测看空闲是否按预期下降(该例降到 9%)。若干预后没有变化,也不能直接判定假设被排除:可能干预未真正生效、力度不足、测量精度不够,或另一个瓶颈接管了。下一步应先验证"干预确实作用到了目标机制"(例如确认预处理并发真的提高了、确实命中了预取路径),再决定是否放弃该假设——这比"CPU 高所以是 CPU 问题"强得多。

2. 为何平均设备利用率不能用于直接诊断?

因为它的分母通常是时间忙碌,而忙碌包含等待、数据搬运与低效执行。9.4.1 的算例中,忙碌率 96% 而矩阵运算只占 42%,若 126 s 为忙碌窗口内的活动并集,则应分成忙碌但非该活动的 162 s 和空闲的 12 s;174 s 不能统称浪费。此外还有三类同名量(容量占用、带宽利用、有效运算吞吐)会被混为一谈,且不同工具的采样粒度与分母定义不同、跨厂商不可比。利用率是筛选信号,不是诊断结论。

3. 只有原生工具入口而没有正文时,哪些字段必须保持未知?

必须保持未知的有:采集命令与参数、字段名与其语义(分母、单位、采样窗口)、以及由此推出的能力或性能结论。已登记的情况是部分昇腾入口(性能采集命令行文档、集合通信库文档)只取得站点框架,因此标 SOURCE_ENTRY_ONLY,书中不写命令也不解释字段 SOURCE_ENTRY_ONLY S25。另有历史版本平台文档只可用于其标明范围,不能作为整套栈的证据 SOURCE_REGISTERED R21。可写的只有"存在该入口、状态待核验",以及把它补正的验证计划。


本章验收清单

节 验收标准
9.1 任何计划采集的数据都能对应一个待区分的解释;采集项写明目的、频率、成本与停止条件;结论不超出资源层级
9.2 能从指标异常回到对应样本并说明 trace 覆盖率;高基数标识不进指标标签;缺失记缺失不记 0
9.3 主机候选瓶颈有时间关联与下一步检验,而不是只凭资源高低判断
9.4 能区分容量占用、时间忙碌与有效运算吞吐;计数器有语义登记表;未取得正文的入口保持未知
9.5 能指出关键路径与替代解释;执行路径结论落到 trace 档证据;不把不同流的累计时间直接相加
9.6 说明采集影响与缺失指标;公开包不含凭据或未授权内容;基线采集与诊断采集分开
9.7 接手者能辨认时间口径、采集缺口与下一项最有价值的实验

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第10章 瓶颈假设与受控实验

核心论点:诊断不是给现象贴标签,而是提出可被区分的解释,再用最小干预逐步缩小原因范围。


10.1 把"慢"改写成可检验问题

本节要回答的问题:相对于什么、在哪类请求上、从何时开始变慢?

"系统慢了"不是诊断输入,是投诉。可检验的问题陈述必须包含六项——这就是问题卡:

字段 要求 常见缺失
现象 具体指标 + 分位 + 时间窗口 只写"变慢",不写哪个指标哪个分位
比较基准 相对哪个版本/哪次运行/哪个负载 与印象对比,没有基线数据
影响群体 哪类请求、哪个租户、哪个长度档 用总平均掩盖了"只有长输入变慢"
变化窗口 从何时开始,是否与某次变更对齐 不知道起点,无法缩小候选
目标指标与门槛 达到什么算解决 没有停止条件,优化无止境
已知事实 / 未知事项 分开列 把猜测写进"已知"

10.1.1 先排除负载混杂,但不能用两点证明没有回归

观测仍为:升级前输入中位数 1250、TTFT P50 为 620 ms;升级后输入中位数 1650、TTFT P50 为 780 ms。两点能拟合出 \(T=120+0.4S\),但这条线由这两个观测本身确定,不能作为“负载变化已经完整解释恶化”的独立证据。

更重要的是,版本和输入长度同时变化,且两个中位数不必来自同一组请求;一般也不能从输入中位数推出延迟中位数。即使补第三个长度点,版本效应仍可能混杂。

有效的下一步是建立交叉对照:旧、新版本分别运行同一份短输入和长输入负载,固定到达、缓存、输出策略,得到四个单元;在每个长度档内比较版本差值,再检查交互。真实业务可通过请求配对、分层重加权或预先指定的回归模型实现类似控制,但外推到没有共同覆盖的长度区间仍需标明。当前两点支持的结论只有:“观察到版本与负载同步变化,尚不能区分负载效应和软件回归。”

10.1.2 影响群体必须分层

总平均会掩盖分层差异。三类最常见的分层维度:输入/输出长度档(第8章 W01—W08)、业务类别(推理型 vs 生成型)、租户/路由目标(第6章 6.4)。

一个典型形态:总 TTFT P50 恶化 8%,分层后发现——短输入档无变化、长输入档恶化 22%。这时应优先检查长输入特有的路径;分块和 Token 预算等调度参数仍可能直接影响它,因为问题只在长输入上,而它的机制(第2章 2.2 的 \(S^2\) 项、第5章 5.4 的分块)与短输入不同。

10.1.3 问题陈述必须与技术无关

最后一条硬规则:问题卡里不能出现你打算采用的优化手段。写成"推测解码没开导致慢",会把后续所有证据引向确认它;写成"长输出档的 TPOT 在 X 之后恶化 22%,SLO 为 ≤12 ms",则任何人都能独立复算。第9章的最小诊断观测包也要求这一点。

依据与核验入口:R01 R24


10.2 构造瓶颈假设树

本节要回答的问题:哪些不同原因都能解释当前现象?

10.2.1 四个主干分支

把候选解释按机制而不是按组件分成四支(组件会重复出现,机制不会):

现象:TTFT P50 恶化
├─ ① 需求变化     输入变长 / 到达率上升 / 复用率下降 / 负载结构迁移
├─ ② 等待增加     排队 / 限流 / 权重或图未就绪 / 外部工具等待 / 状态等待
├─ ③ 执行变慢     runner 回退 / 形状变化命中另一内核 / 图未命中 / 精度路径变化
└─ ④ 测量变化     TTFT↔TTFO 映射变化 / 工具口径变化 / 时钟域未对齐 / 分母变化

第 ④ 支最容易被跳过,但它常常是答案,而且成本最低(第7章 7.7 的工具迁移登记表就是为此准备的)。

10.2.2 每个节点必须配三类东西

假设树不是决策口诀。只有在有观测或有干预能区分分支时,它才有诊断价值。因此每个叶节点必须写三列:

列 内容 作用
支持证据 已有的、指向它的观测 决定优先级
可能推翻它的结果 什么观测出现后应放弃它 没有这一列的假设没有诊断价值
下一项最便宜的检验 区分它所需的最小证据 决定成本

以"TTFT 恶化"为例:

分支 支持证据 可能推翻它的结果 最便宜的下一项检验
① 输入变长 输入中位数 1250→1650 同长度档内 TTFT 也恶化 按长度分层重算(E0,纯计算)
② 排队增加 在途数上升、P99/P50 拉大 降低并发后 P50 不改善 降并发到 1/4 复测 P50/P99
③ 执行变慢 版本变更日志提到执行路径 低并发下 P50 回到基线 低并发复测 + trace 内核名
④ 测量变化 同期升级了测量工具 工具原值与规范化值一致 保留三列对照(原值/规范化值/差异原因)

10.2.3 多瓶颈并存与迁移

多个瓶颈可以同时存在,且随负载迁移。这意味着"找到根因"往往不是找到一个点,而是找到当前负载区间下的主导项。第5章 5.2.3 的排队非线性表已经说明了这一点:ρ 从 0.8 到 0.96,等待时间增长 6 倍,主导瓶颈会从"执行"迁到"排队"。

版本化候选(runner 回退、形状触发另一内核、远程缓存命中但关键路径等待增加、工具口径变化)只能作为候选机制提出;发布说明不能替代当前环境的证据 SOURCE_REGISTERED S03 S17。

依据与核验入口:R17 R25 S03 S17


10.3 从相关性到受控干预

本节要回答的问题:两个指标同时变化,怎样检验它们之间有因果关系?

10.3.1 相关性不能判定因果

"排队长度与设备忙碌同时升高"是典型的两可局面:可能是负载上升同时推高了两者(共同原因),也可能排队导致批更大进而设备更忙(因果),也可能设备变慢导致排队(反向因果)。三者对策完全不同,靠看曲线无法区分。

10.3.2 最小变更 + 事前预测 + 回退复测

受控干预的三件套:

  1. 对照:固定负载,只改一个可控条件;
  2. 事前预测:干预前写清"若 H1 成立则观察到 X,若 H2 成立则观察到 Y"——没有预测的事后解释不算证据;
  3. 回退复测:改回去,确认指标回到基线。这可降低自然漂移这一替代解释的可信度,但仍需随机化、重复与状态重置支持。

算例。候选:H1 排队主导 vs H2 单请求执行变慢。干预:并发从 32 降到 8(负载降至约 1/4)。

量 干预前 若 H1(排队主导)预测 若 H2(执行变慢)预测 观测值
TTFT P50 780 ms ≈200 ms(接近服务时间下界) ≈700 ms(基本不变) 210 ms
P99/P50 比 4.2 ≈1.8 ≈3.5 1.8

这组观测更支持排队机制,但不能仅因降并发有效就排除执行机制:批形状和执行路径也可能同时变化。注意这里的判别力来自两个量同时符合预测——只报 P50 的话,"降到 210 ms"在两种假设下都可能被事后圆过去。

10.3.3 干预的副作用:机制的一部分 vs 混杂因素

干预很少只动一件事。例如"把批内 Token 预算从 4096 改到 8192"会同时改变:批形状、图命中率、状态占用、抢占概率、padding 量。

区分标准是:这个联动是机制链条的一部分,还是与问题无关的附带变化?

  • 若是机制的一部分(例如扩大预算本就是为了摊薄权重访存,第3章 3.2),把它写进机制预测,一起验收;
  • 若是混杂因素(例如同时触发了图重捕获,导致前若干步走 eager),必须隔离——通常是先预热到稳态再开始测量。

一次同时改多项后,不能声称确定了单一根因。三个参数同时改动且性能提升 18%,若要无混叠地估计三个二水平因素及全部交互项,可以采用 \(2^3=8\) 个组合并加入重复;这不是所有诊断任务都必须执行的唯一方案;退而求其次也至少要逐个回退,观察收益是否保持。

生产环境只采用已授权且可回滚的变更;故障注入只在隔离环境进行。

依据与核验入口:R17


10.4 四类常见现象的诊断路径

本节要回答的问题:面对不同症状,第一轮证据应该怎样选?

下表提供的是候选与下一项检验,不是"症状 → 根因"的一对一绑定。每条路径都必须包含非 GPU 解释与测量自身出错的可能。

10.4.1 首 Token 慢(TTFT / TTFO)

先排除 候选分支 下一项最便宜的检验
口径:TTFT 还是 TTFO?起算点是 engine.queued 还是客户端发出?(第7章 7.2) 测量:工具迁移改了字段映射 保留工具原值与规范化值三列对照(E0)
负载:输入长度分层后是否仍慢 需求:输入变长 / 复用率下降 按长度档重算 + 缓存命中率分层
等待:排队、限流、权重或图未就绪 降并发复测;看首次请求与稳态请求的差
执行:prefill 路径变化、分块粒度、图未命中 trace 内核名 + 批形状记录
主机/网络:分词、模板展开、网关缓冲 客户端—服务端双流水(第1章 1.2)

10.4.2 生成间隔慢(ITL / TPOT)

先排除 候选分支 下一项最便宜的检验
口径:ITL 按事件间隔、TPOT 按 Token 间隔,两者分母不同(第7章 7.2.3) 测量:混用了两个指标 同时报两个并说明分母
等待:被同批长请求拖慢、抢占、写出节奏 按批组成分层;看是否被抢占过
执行:decode 带宽/延迟主导变化、状态读取增大 第6章 6.2 的通信主导项判别
主机:采样与停止判定在哪一侧执行 按实现取证(第1章 1.5.2),不默认
写出:流式分块策略导致台阶 记录写出时刻 vs 生成时刻

10.4.3 吞吐平台与尾延迟恶化

先排除 候选分支 下一项最便宜的检验
口径:实际输出量是否变了(第7章 7.3 窗口法 vs 排空法) 测量:产出分子定义变化 复算两种口径
需求:到达率接近饱和(ρ→1) 开环复测,看拒绝与队列增长
等待:排队非线性、抢占重放 第5章 5.2.3、5.6
执行:路径变化、并行维度通信 运行身份 + trace
任务拓扑:外部工具等待进了模型时间 依赖图重建(第1章 1.1)

10.4.4 OOM 与长运行退化

长运行退化的算例:运行 12 小时后 P99 从 800 ms 涨到 2400 ms(3 倍),重启后回到 850 ms。

候选 判别证据
请求侧泄漏(状态未归还) 活跃序列数持续增长;块表可用数下降
分配器缓存(非泄漏) reserved − allocated 从 6 GB 涨到 17 GB,而 allocated 稳定在 62 GB
状态碎片 先区分分页 KV 的尾块浪费与底层分配器/工作区碎片;分页上下文本身不要求物理连续(第4章 4.2)
连接池 / FD 耗尽 连接数与句柄数曲线
时钟漂移 跨主机时间差随时间增大(第1章 1.7.2)
日志 / 磁盘堆积 主机侧观测(第9章 9.3)

上表第二行是关键:显存读数上升不等于泄漏。reserved − allocated 增长可能包含可复用缓存,也可能包含碎片和不可复用块;需结合内存快照、活跃对象与压力后的回收行为判断,不能直接判定泄漏或直接排除泄漏。

缓存相关异常要分别考察身份失效、传输排队、清理未完成三种情形,不能把"命中率下降"直接当成原因已经确定 SOURCE_REGISTERED S13。

依据与核验入口:R01 R17 S17 S01


10.5 优化优先级与实验价值

本节要回答的问题:下一步应该做最容易的调整,还是最能排除疑问的实验?

先分清两类动作,它们的目标不同、排序规则也不同:

整改项 诊断实验
目标 直接改善服务 缩小候选范围
排序依据 影响面 × 证据强度 × 成本 × 回滚风险 信息量 ÷ 成本
失败的价值 低(浪费工时) 高(排除了一支假设)

10.5.1 诊断实验按"信息量/成本"排序

四个候选的排序示例:

# 实验 能排除的分支 成本 信息量/成本 顺序
1 工具口径三列对照(E0,纯计算) ④ 测量变化(整支) 0.5 h,无设备 极高 1
2 按长度分层重算历史数据(E0) ① 需求变化 1 h,无设备 高 2
3 降并发复测(E1) ② vs ③ 的判别 2 h,需隔离环境 中 3
4 全链路 trace 采集(E2) 细分执行路径 4 h+,有扰动 低(前三项未做时) 4

前两项都不需要 GPU,却能淘汰两支假设。这正是第1章 1.7.1 强调的"E0 先把纯计算口径锁住"——昂贵的实验在口径未验证前只会产出不可信的数据。

10.5.2 排序是决策工具,不是评分公式

四列(影响面、证据强度、工程成本、回滚风险)的组合不能机械加权成一个分数。业务权重由项目填写,且必须允许"证据不足时先补测"这一选项——它常常比"先做收益看起来最大的改动"更便宜。

同样地,仿真只用来决定下一项实测是什么;仿真速度不是后端速度,不得写进性能结论 SOURCE_REGISTERED S06。

依据与核验入口:R24 S03


10.6 负面结果、反例与结论边界

本节要回答的问题:没有提升的实验,怎样变成可复用知识?

10.6.1 失败有五种,必须分开记

类型 含义 记录要求
无证据 缺资源或缺授权 保持空白,不填推测值
运行失败 配置错误、OOM、不兼容 保留错误与环境
运行后无收益 跑了,指标没变好 保留干预前后的完整数据
只在某类负载有效 分层后有效/无效并存 写明有效区间与无效区间
有不可接受副作用 变快但质量或稳定性退步 写明副作用与判定门槛

把后三类写成"参数没调好"是知识损失。它们是最有价值的负面样本——发布稿不能只留下最好的那一次。

10.6.2 一个完整的质量否决案例

推测解码关闭 开启 变化
TPOT P50 11.9 ms 9.3 ms −21.8%
任务层达标率 91% 84% −7 个百分点

每 Token 时间确实降了 21.8%,但任务质量跌了 7 个百分点。结论:不采用。这不是"没调好",而是一次有效的排除——它只说明这个假设候选未通过质量门槛。经典精确拒绝采样在算法条件满足时保持目标分布;若实际观测到退化,应检查近似接受、数值差异、实现缺陷或评估波动,而不是把质量下降归为推测解码的必然代价。

10.6.3 结论可以是"尚无法区分"

允许这样的结论存在:

已排除 ① 需求变化与 ④ 测量变化;② 排队主导与 ③ 执行变慢在本负载区间下无法区分,因为降并发实验同时改变了批形状。下一项证据:在固定批形状下改变到达率。

必须写的是下一项证据,而不是用确定口吻补齐未知。证据等级按第9章的三档(源码只证明可能性、日志只证明声明、trace 证明本次事实)标注。

依据与核验入口:R12


10.7 实验:完成一次闭环诊断

本节要回答的问题:怎样从问题陈述走到有限度但可执行的结论?

闭环的七个动作,顺序不能颠倒:

① 登记症状(问题卡六项)
      ↓
② 列出竞争假设(四主干 + 可推翻条件)
      ↓
③ 排序并选择区分实验(信息量/成本)
      ↓
④ 写事前预测(若 H1 → X;若 H2 → Y)
      ↓
⑤ 采集证据(固定负载、单变量、记录运行身份)
      ↓
⑥ 回退 + 复测(确认系统回到基线)
      ↓
⑦ 写条件化结论(已证实 / 已排除 / 未解决 + 下一项证据)

评分重点是证据完整性与归因边界,不是猜中预设答案。第 ⑥ 步最常被跳过,而它决定了第 ⑦ 步能不能成立——不复测就无法区分"干预起效"与"系统自己恢复了"。

可选诊断对象:指标工具迁移(④ 支)、内核路径回退(③ 支)、缓存等待(② 支)。无论选哪个,结论都必须来自回退复测,不能照抄发布说明 SOURCE_REGISTERED S03。

依据与核验入口:R16 R25 S03


10.8 深化:用交叉对照识别回归,而不是用故事填平残差

10.8.1 一个可以区分版本和长度的四格实验

第10.1节的两点观察缺少交叉单元。可以另设计如下表:

版本 短输入1250 Token 长输入1650 Token
A 620 ms 780 ms
B 660 ms 900 ms

在相同短输入上,版本差为40 ms;在相同长输入上为120 ms;两种版本的长度增量分别160 ms和240 ms,因此版本与长度的交互为80 ms。这里每个数字都是设定,不是对原先两个观测补造的真实结果。

这张表能提出更具体的问题:为什么B的长输入路径多出80 ms?候选包括分块阈值、注意力后端、位置相关状态布局和图形状覆盖。下一轮实验可以固定版本、逐步扫描长度,寻找连续斜率变化或阈值跳变,再对执行路径取证。与“版本后输入更长,所以没有软件问题”的叙事相比,这种设计保留了可反驳的结构。

10.8.2 干预作用于机制,还是改变了整个问题

减少并发经常同时减少排队、改变GEMM形状、降低KV压力和避免图回退。因此“降并发变快”能证明系统对并发敏感,却不能唯一识别排队根因。要进一步区分,可保持离线批形状不变测执行,再在在线场景控制到达;也可以只改变准入位置,观察设备阶段是否保持不变。

类似地,减少输入长度、关闭某类工具或截短回答会改变任务工作量。这些干预有助于识别依赖路径,却不能全部计入“完成同一任务更快”的优化收益。诊断实验可以故意改变问题以识别机制;最终收益验收必须回到原契约。

10.8.3 可识别性比“找到一个相关指标”更重要

设两种解释H1、H2在现有观测边界上预测完全相同。例如只有客户端端到端数据时,网关缓冲增加200 ms与引擎开始前排队增加200 ms可能不可区分。继续增加同样的端到端样本,可以更准确地估计总差异,却不会自动识别内部原因。

这时应改变观测边界:增加服务端入队事件、旁路代理的对照或同机房客户端。诊断的核心不是总样本越多越好,而是新增证据是否让两个假设给出不同预测。若权限不足,就明确停在“内部等待增加、位置未知”,并把所需观测交给拥有权限的一方;这比用设备利用率猜一个内部组件更有价值。

10.8.4 实验退出条件同时包含成功和失败

对每个候选至少预登记一个机制条件和一个业务条件。例如“预算调整有效”的机制条件可以是实际Prefill块增大、图路径不变、抢占不增加;业务条件则是合格完成速率改善且关键租户尾延迟不退步。只看到业务改善而机制证据不符,应保留收益观察但撤回单一机制归因。

失败也要区分:参数未生效、证据不足、机制发生但被其它瓶颈接管、局部有益而整体不划算。四种情况产生不同的下一步。最需要避免的是看到无收益就不断扩大参数,最终挑出一次偶然更好的运行,并把前面的失败全部删除。


10.9 实验图表补充:将版本回归假设拆成可反驳的对照

第一张图严格沿用10.8.1的四格取值,而不是把原来两个观测缺失的格子补成“实测”。相同1250Token输入下B比A慢40ms;相同1650Token下慢120ms,差中之差80ms。这提示版本和长度存在交互,但没有重复样本就不能写统计显著,也不能唯一归因某个内核。

第二张图引入独立的真实公开版本例:vLLM博客Table 3中,Llama-3.1-8B的FP8/BF16 ITL交点从v0.10.2的24889Token移动到v0.19.1的7010Token;相对BF16的FP8斜率由63%变为54%。图只重绘同模型的这两行,不把别的模型741565Token的外推交点混入。

这是一组整版本比较,不是“只改一个开关”的因果实验。若要判断本服务某次回归,应固定制品、长度、并发、模板、缓存状态和到达轨迹;对照版本执行相同负载,再对候选机制做单独干预。机制变化、质量变化和服务指标变化要分别留证,而不是看总体吞吐上升就宣布回归修复。

图10-V1 四格交叉对照:版本效应会随长度改变

图10-V1 四格交叉对照:版本效应会随长度改变

数据身份:TEACHING_CALCULATION。条件:四格数据均为本书设定;每格待以匹配负载、多次运行替换。

版本 1250Token/ms 1650Token/ms 长度增量/ms
A 620 780 160
B 660 900 240
B−A 40 120 80

复算公式:interaction=(900-660)-(780-620)=80ms。

出处与边界:本书10.8.1;80ms是差中之差,不是统计显著性;没有原始重复记录,不计算置信区间。

数据与绘图点 · PNG · 重绘代码

图10-V2 版本变化会移动FP8的收益交点

图10-V2 版本变化会移动FP8的收益交点

数据身份:PUBLISHED_RESULT。条件:Llama-3.1-8B;H100/FA3单请求上下文;before与after比较;交点由原文拟合得到。

版本 公开拟合交点/Token FP8斜率为BF16的/%
v0.10.2 24889 63
v0.19.1 7010 54

出处与边界:X05;是整版本比较,不可把全部收益归因于单个开关;交点不是最大可用上下文。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 何种实验最能区分"缓存污染"与"模型执行真的变快"?

答案方向:两者都表现为"第二次跑更快",区分点在复用身份。做法:① 用独立于测试集的预热样本重跑——若提速消失,说明先前是预热样本与测试集内容重合造成的缓存污染(第8章 8.5);② 在同一负载下清空缓存测冷态——若冷态仍有提速,才是执行路径的真实改善;③ 同时记录命中率与命中来源层级(协议层会话 / 引擎前缀 / 外部缓存服务),因为"命中"本身不等于原因已定(10.4.4)。关键点:预热的收益必须在不复用测试内容的前提下重测。

2. 改了三个参数性能提升,能证明哪一个有用?

答案方向:不能。三个参数同时开启只有"全开 vs 全关"两组数据,无法归因。要完全分离主效应与交互效应需要 \(2^3 = 8\) 组全因子;最小可行方案是逐个回退:保持另外两个开启,把目标参数改回基线,观察收益是否保持。还要注意 10.3.3 的两点:一是一次改多项后不得声称单一根因;二是若某参数的收益依赖另一个参数(交互),单项对照会显示"都无用"而组合有效——这时必须做组合实验并写明依赖关系,而不是各自判死刑。

3. 发布说明指出默认 runner 变化,为什么还不能认定它就是当前回归根因?

答案方向:发布说明只提供候选机制,不是本环境证据。要认定需要四步:① 确认本环境版本确实落在该变更之后,且生效配置回显显示走的是新 runner;② 用 trace 或日志确认实际走了哪条路径——"声明默认"不等于"实际启用"(第1章 1.5.3、第9章 9.5.4);③ 用回退(版本回退或强制旧路径)复测,观察指标是否回到基线;④ 排除同期的其它变更与负载变化(10.1.1 的负载归一化)。缺任一步都只能写"runner 变化是候选机制之一",不能写成根因 SOURCE_REGISTERED。


本章验收清单

节 验收标准
10.1 问题卡六项齐全;比较基准明确;结论与拟采用技术无关;负载归一化已做并写明假设
10.2 四主干分支齐全(含"测量变化"支);每个节点有支持证据、可推翻条件与下一项检验
10.3 干预有对照、事前预测与回退复测;区分了"机制的一部分"与混杂因素;一次改多项不声称单一根因
10.4 每条路径含非 GPU 解释与测量自身出错;长运行退化区分请求侧泄漏与分配器缓存
10.5 整改项与诊断实验分开排序;诊断按信息量/成本;仿真不写成后端速度
10.6 失败五分类分别记录;负面样本进案例库;未区分时写明下一项证据
10.7 七步闭环完整,回退复测未跳过;结论分已证实/已排除/未解决三栏

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第11章 配置、执行与内存优化

核心论点:每项优化都必须说明改动什么机制、在什么条件下成立、质量风险是什么、怎么回退、端到端怎么验收——而不是默认把所有功能都打开。


11.1 主机路径、调度与缓存参数优化

本节要回答的问题:哪些低风险改动可以先验证?

11.1.1 参数不是清单,是假设

每项参数调整都必须对应第10章的一条瓶颈证据。没有证据的调参不是优化,是抽奖。推荐字段:

字段 内容
参数与取值 改前 → 改后
预期机制 它改变了什么(供给?批形状?复用?)
观测项 用什么指标验证机制确实发生
生效证据 如何确认实际生效(配置回显/trace),而非只写了命令行
质量影响 哪个质量门槛需要复测
回退方式 怎么改回去,回退后要复测什么

11.1.2 释放显存 ≠ 自动增加容量

一个反复出现的误判:权重变小了,就说"容量变大了"。算例:将 70B 权重量与独立指定的状态量组合,非特定真实 70B 架构;单卡可用 80 GB,每 rank 固定开销 4 GB,状态账沿用第4章(4096 上下文每路 0.537 GB)。

权重格式 位宽/元素 权重 状态池 可容纳 4096 路数
BF16 16 140.0 GB — 装不下
FP8 8 70.0 GB 6.0 GB 11
分块 4-bit(含缩放) 4.5 39.4 GB 36.6 GB 68

三个读法:

  1. 位宽减半不等于容量翻倍。16→8 bit 只换来 11 路,8→4.5 bit 却换来 68 路——因为存在"能否装下"的阈值效应与固定开销。用线性比例外推容量是最常见的错。
  2. 可用的路数还受调度与尾延迟约束,不只是显存。扩大批预算后吞吐可能上升而 P99 恶化(见下)。
  3. 若 \(h_{kv}\) 与并行度不满足分片条件,实际占用还会高于这里(第4章 4.2、第6章 6.1)。

11.1.3 一个"吞吐涨、尾延迟超标"的案例

批内 Token 预算 4096 → 8192:

指标 4096 8192 变化
吞吐(排空法) 1200 token/s 1368 token/s +14.0%
P99 ITL 120 ms 185 ms +54.2%

若 SLO 规定 ITL P99 ≤ 150 ms,这个改动应当被拒绝,尽管吞吐涨了 14%。这正是第7章"三个量纲"的用途:吞吐是速率,SLO 是约束,两者不能互相抵扣。

依据与核验入口:R01 R30


11.2 低精度推理:格式、执行路径与质量

本节要回答的问题:位宽降低以后,存储、计算、状态、任务质量分别发生了什么变化?

11.2.1 四类精度必须分开说

精度 影响什么 常见误判
权重 驻留体积、权重访存量 用权重位宽代表整个模型的精度
激活 计算内核选择与数值路径 认为权重量化后激活也自动低精度
累加 数值稳定性、长序列误差 完全忽略累加类型
KV 状态驻留、长上下文容量 用权重量化的节省比例推 KV

11.2.2 分块缩放:为什么"4 bit"不等于四分之一

分块缩放格式在数据位宽之外还要存缩放因子。以每 16 个元素配一个 FP8 缩放为例,实际位宽是

\[ b_{\text{elem}} = 4 + \frac{8}{16} = \mathbf{4.5\ \text{bit/元素}} \]

对一个 \([4096, 4096]\) 权重矩阵:

格式 数据 缩放 合计 相对 BF16
BF16 32 MiB — 32 MiB 100%
FP8 16 MiB — 16 MiB 50.0%
分块 4-bit 8 MiB 1 MiB 9 MiB 28.125%

相对 BF16 是 28.125%,不是 25%。差的 3.125 个百分点全部来自缩放因子(占 0.5/4.5 = 11.1%)。所以这条硬规则必须守住:不能用 4 bit 推出四倍速度,也不能用模型体积下降代替实际显存或成本下降。

不同格式必须分别核对。官方技术说明 N04 中,NVFP4 为每 16 个数据元素配一个 E4M3 FP8 缩放,并有张量级 FP32 缩放;MXFP4 为每 32 个元素配一个 E8M0 缩放。忽略张量级常数和对齐时,二者平均位宽分别约为 \(4+8/16=4.5\) 与 \(4+8/32=4.25\) bit。前者对大矩阵约为 BF16 的 28.125%,后者约 26.5625%;这是格式字节推导,不是端到端显存或速度保证。

FP8 也可能有缩放、分块、排除层与布局转换。上表 FP8 一行是仅计主体数据的基准,不应写成任意 FP8 制品无缩放元数据。

11.2.3 四列对照:制品、张量、执行、验收

观察对象 必填字段 禁止的替代推断
模型制品 权重格式、缩放、校准集身份、摘要 文件里写 FP4 就代表完整执行是 FP4
运行张量 权重/激活/累加/KV 各自的 dtype 与布局 用权重位宽代替全部张量精度
实际执行 GEMM / attention / MoE 后端、设备、形状、回退原因 参数被接受就代表原生低精度内核生效
输出验收 任务分层、质量阈值、失败样本与性能 只保留更快或更短的答案

量化制品、加载后的张量、实际执行的内核三者必须分别存档。第9章 9.5.4 的三档证据(源码 / 日志 / trace)在这里直接适用:需要 trace、运行插桩或可核对执行计数证明本次路径;内核名称本身还需结合 dtype 和形状。

11.2.4 质量验收:校准集与独立集分开

七项对照表,每项候选都要填(模板):

列 说明
模型体积 制品字节数(含缩放元数据)
物理显存 实测 allocated / reserved,不是理论体积
Prefill / 生成时间 分阶段,不合并成一个"延迟"
合格完成速率 第7章三量纲之一,带 SLO
质量 分层达标率 + 失败样本
恢复成本 出问题后回退所需时间与风险
支持状态 支持 / 不支持 / 未验证

校准集用于产生量化参数,独立集用于验收。用同一份数据既校准又验收,等于没有验收。质量门槛必须覆盖业务分层、长上下文、结构化输出三类——只在短问答上测质量是常见的漏项。

未支持的组合标为能力缺口,不要人为给一个"性能较差"的分数(第6章 6.6.4 同理)。缩放格式或激活精度的细节未从来源固定前,不发布精确节省比例。

依据与核验入口:R26 R14 S09


11.3 执行器、编译、图执行与内核选择

本节要回答的问题:减少调度开销和数据移动,什么时候能改善整体?

按三个层次分开讲,因为它们解决的是不同问题,也常常被混为一谈:

层次 解决什么 代表来源
算法 减少数据移动本身(IO 感知的注意力) SOURCE_REGISTERED
内核库 提供具体实现与后端 SOURCE_REGISTERED S09
引擎调度 决定这一次走哪条路径、是否回退 SOURCE_REGISTERED S03

11.3.1 从算法到被调用,中间隔着两道门

FlashAttention 的原始论文提供的是 IO 感知的原理 SOURCE_REGISTERED;某个内核库提供了实现 SOURCE_REGISTERED S09;但服务是否真的调用了它,是第三件事。这三者的关系是"可能 → 提供 → 实际",不能从库名推断它被服务真正调用——必须绑定所选 commit、设备、形状,并用 trace 内核名确认。

同理,引擎侧的执行器代际变化、旧路径移除、RC 版本的入口变更候选 SOURCE_REGISTERED S03 只说明"该版本声明了什么",不说明本环境走了哪条。没有实际环境时,交付的是代码路径图,不是伪造的 trace。

11.3.2 冷启动与稳态必须分开验收

编译与图捕获的一次性成本要用稳态收益去摊。算例:编译 23 s + 图捕获 11 s = 34 s;稳态每步节省 0.8 ms。

\[ \text{回本步数} = \frac{34\ \text{s}}{0.8\ \text{ms}} = \mathbf{42\,500\ \text{步}} \]

若单步 20 ms,回本需要 \(42\,500 \times 0.02 = \mathbf{850\ s}\)(约 14.2 分钟)的连续服务。

推论:在短任务、频繁重启、或按需伸缩的场景里,编译/图捕获可能是净亏的。所以验收必须分冷启动与稳态两栏,并且把常驻工作区开销与预热收益一起算(第8章 8.5)。

11.3.3 图命中率:平均值会掩盖尾部

形状不在已捕获的桶里时会回退到逐次执行。算例:图内每步 1.4 ms、回退后 3.2 ms、命中率 96%。

量 值
加权平均单步 \(1.4\times0.96 + 3.2\times0.04 = \mathbf{1.472\ ms}\)
相对"全命中"慢 5.1%
回退迭代相对图内迭代 \(3.2/1.4 = \mathbf{2.29\ 倍}\)

只看均值只看到 5.1%,但这是迭代级命中率,不能推出恰有 4% 的请求回退;一个请求可跨越许多迭代。这就是"回退"必须从能力表里单独记录、并进入尾延迟分析的原因。

11.3.4 单内核收益不外推整个服务

第3章的 Amdahl 式上限在这里直接适用:某内核占关键路径 8%,提速 30%,整体加速

\[ \frac{1}{(1-0.08) + 0.08/1.3} = \mathbf{1.0188} \;(\text{仅 } +1.88\%) \]

所以"微基准里快 30%"与"服务端到端改善"之间隔着三道门槛:是否在关键路径上、占比是否足够、节省是否被排队反馈吸收(第9章 9.5.4 已给出两条可区分假设与判别实验)。单内核结果不外推整个服务,这是本节的硬要求。

每份性能记录必须带:runner、graph mode、compiler、attention/GEMM/MoE backend、device target、实际形状、fallback_reason。

依据与核验入口:R03 R23 S01 S03 S08


11.4 推测解码:草稿、验证与当前实现组合

本节要回答的问题:接受率高,为什么仍可能没有速度收益?

11.4.1 两个计数不能混

第2章已给出:草稿接受率 \(\alpha\)、每轮候选数 \(k\) 时

\[ \mathbb{E}[\text{接受的草稿数}] = \sum_{i=1}^{k}\alpha^{i}, \qquad \mathbb{E}[\text{本轮推进量}] = \sum_{i=0}^{k}\alpha^{i} \]

后者包含被拒后的校正 Token(或全接受时的额外 Token),比前者多一个量级为 1 的项。\(k=4\)、\(\alpha=0.7\) 时是 1.7731 与 2.7731。算加速比必须用推进量。

11.4.2 整轮成本,不是验证成本

每轮耗时必须拆成四段并记录,因为只算目标验证会系统性高估收益。算例 (相对普通一步 = 1.00):

段 成本
草稿生成 0.30
目标验证 1.00
接受处理 0.15
同步等待 0.20
整轮 1.65

\(\alpha=0.7\)、\(k=4\):每 Token 相对成本 \(= 1.65/2.7731 = 0.595\),加速 1.68×(而不是按"验证成本 1.0"算出的 2.77×)。

11.4.3 接受率上升而加速比下降

这正是本节标题问题的答案:

\(\alpha\) 本轮推进量 整轮成本 每 Token 成本 加速比
0.70 2.7731 1.65 0.595 1.68×
0.75 3.0508 1.95 0.639 1.56×

接受率从 0.70 提到 0.75(+7%),两行仍固定 \(k=4\);另假设更昂贵的草稿器或额外处理让整轮成本从 1.65 涨到 1.95,加速比反而从 1.68 降到 1.56。原因是分母(推进量)只涨了 10%,分子(整轮成本)涨了 18%。

所以:接受率是中间量,不是收益。最终要报的是第7章的三个量纲——目标延迟、合格完成速率、质量。

11.4.4 组合支持与质量回归

运行矩阵需记录:模型、草稿或预测头身份、TP/PP/EP/CP、图模式、缓存布局、支持状态(支持/不支持/未验证)。当前实现的研究入口包括 MTP、EAGLE3、DFlash、DSpark 等 SOURCE_REGISTERED,但本章按草稿来源与验证方式组织,不把名称清单当作机制解释;未固定机制或版本时只作候选资料,不承诺精确采样性质。

经典精确采样算法的分布性质依赖算法条件 SOURCE_REGISTERED;近似变体、校正实现与数值路径必须另行验证,不能把经典性质直接外推。

验收要求:同一真实内容分布做关闭/开启对照,随机 Token 不能替代内容相关的接受率测试(第8章 8.2);拒绝的候选消耗资源但不计入有效输出。接受率上升而服务达标率下降的结果必须保留(第10章 10.6.2)。

依据与核验入口:R27 S02


11.5 上下文、结构化输出与应用边界优化

本节要回答的问题:"少做工作"与"更快完成同样工作",如何区分?

11.5.1 语义保持 vs 信息裁剪

这是本章最容易越界的一节。判定标准很简单:改动之后,交付给用户的是不是同一个任务?

算例:上下文从 8000 Token 裁到 3000 Token。

指标 裁剪前 裁剪后 变化
TTFT P50 780 ms 320 ms −59.0%
任务层达标率 88% 71% −17 个百分点

TTFT 改善了 59%,但达标率掉了 17 个百分点。这不是系统优化,是改变了任务。正确做法:若业务确实同意缩短上下文,必须另立一条对照轨道并单独签署,不能混进"纯系统提升"的收益里报告。

三条明令禁止的"制造加速":删除必要证据、减少必需分支、缩短回答要求。共享历史分支、上下文裁剪、非前缀缓存三类改动都必须分别做语义验收 SOURCE_REGISTERED S18。

11.5.2 结构化约束:净收益怎么算

约束生成(语法/格式约束)的收益与代价在不同层,必须放在一张表上算净账:

层次 变化
每步成本 合法 Token 掩码计算使单步 +8%
任务层 格式错误率 12% → 1%,重试减少
任务层达标率 81% → 92%(+11 个百分点)

单 Token 慢了 8%,但任务达标率涨了 11 个百分点——联合表现有改善迹象,仍须核对资源成本、截止时间与分层 SLO 后决定是否采用。判据是任务层而不是单步时间;只看单步会得出相反结论。

11.5.3 输入结构与模板版本

输入侧的改动(模板版本、检索片段组织、共享前缀布局)会改变 Token 化与复用命中率(第8章 8.1 的脱敏偏差同理)。因此每次改动必须记录:输入差异、模板版本、格式校验结果、任务样本集。敏感业务只用授权测试集 SOURCE_REGISTERED。

依据与核验入口:R06 R14 S18


11.6 组合优化、交互与收益消融

本节要回答的问题:单项有效的改动,叠加后为何可能相互抵消?

11.6.1 资源格局会被改写

三项改动各自释放/占用的资源不同:量化释放容量、图执行占用工作区、推测解码改变批形状。组合之后资源格局不是三者之和。

11.6.2 一个完整的消融表

基线吞吐 1000 token/s、P99 ITL 120 ms、质量达标率 92%;A = 量化、B = 图执行、C = 推测解码:

组合 吞吐 相对基线 P99 ITL 质量达标率
基线 1000 — 120 ms 92%
A 1120 +12.0% 118 ms 91%
B 1080 +8.0% 115 ms 92%
C 1180 +18.0% 131 ms 92%
AB 1150 +15.0% 116 ms 91%
AC 1240 +24.0% 142 ms 90%
BC 1210 +21.0% 138 ms 92%
ABC 1210 +21.0% 145 ms 90%

四条结论:

  1. 单项收益不能直接相加。12%+8%+18%=38%,表中的ABC只有21%。
  2. 添加优化可能降低产出。ABC为1210 token/s,低于AC的1240;具体机制仍需实测区分。
  3. 吞吐、尾延迟和质量必须联合判断。AC虽吞吐最高,却把ITL P99推到142 ms、质量降到90%。
  4. 推荐取决于联合门槛,而不是只看质量。质量至少 92% 时,基线、B、C、BC 都在质量可行集。若再要求 ITL P99≤135 ms,则 BC 被排除,在其余表中候选里 C 吞吐最高;若上限放宽为 140 ms,BC 的 1210 token/s 高于 C 的 1180。若允许质量至少 90% 且 ITL≤150 ms,表中 AC 的 1240 高于 ABC 的 1210。以上仅是对表的确定性筛选;真实采用还需重复运行的不确定性与独立质量验收。

禁止把各单项最高提升简单相加 SOURCE_REGISTERED S06;只运行有支持依据且能回答问题的组合,不做无意义全排列。

11.6.3 维护成本与适用边界

采用决策还要算三笔账:新增依赖的成熟度、回退方式、撤回接口是否还在。一个收益 20% 但依赖 RC 版本、且没有关闭开关的优化,其风险远高于收益 12% 但可随时回滚的优化。第10章 10.5 的可逆性列在这里直接适用。

依据与核验入口:R26 R31 S06


11.7 实验:提交一份可上线的优化证据

本节要回答的问题:什么时候可以把"实验更快"升级为"值得采用"?

11.7.1 顺序不能颠倒

① 质量与正确性门槛   → 不合格就直接淘汰,不进性能比较
② 同负载性能对照     → 基线 vs 候选,配对设计(第8章 8.6)
③ 组合消融           → 只做有依据的组合
④ 留出负载复核       → 用未参与调参的负载复测
⑤ 长运行与异常恢复   → 12h+ 稳定性、OOM/重启恢复
⑥ 采用 / 拒绝 / 回滚 → 三选一,写明条件与触发线

"先测性能再补质量"是错误顺序,会导致为了保住性能数字而放宽质量门槛。

11.7.2 报告必须写明区间而不是一个数

合格报告要回答:在哪些区间改善、在哪些区间退步、退步多少、维护成本是什么、什么条件下回滚。只有实验结果支持时才建议采用;"无收益"或"风险过高"同样是合格结论。

11.7.3 决策卡模板

字段 内容
候选与机制假设 改动什么、预期改变什么
生效证据 配置回显 + trace(证明实际启用,非仅声明)
质量结果 校准集与独立集分别;失败样本
性能结果 三量纲 + 分位 + 样本量 + 区间
组合消融 与已采用项的交互
负面与副作用 退步区间、稳定性问题
采用/拒绝/回滚条件 明确触发线与回退步骤
证据等级 源码 / 日志 / trace 分别标注

部署本身需要另行授权,本书不默认执行生产变更。执行路径身份与质量回归列为报告附件;资料支持但没有 trace 的项目,不填"启用已验证"。

依据与核验入口:R14 R25 S09


11.8 深化:精确推测采样、图形状成本与优化交互

11.8.1 接受率公式背后是概率校正

经典推测解码方法要求目标分布 \(p\) 与草稿分布 \(q\) 在同一候选空间和相同条件上下文下可比较。草稿Token \(x\sim q\) 以 \(\min(1,p(x)/q(x))\) 的概率接受;拒绝后从与 \([p(x)-q(x)]_+\) 成比例的剩余分布采样 N03。

其正确性的核心可直接验证:直接接受给 \(x\) 带来的概率质量为 \(q(x)\min(1,p(x)/q(x))=\min(p(x),q(x))\);拒绝后补回正差部分 \([p(x)-q(x)]_+\),二者相加为 \(p(x)\)。因而接受率低不意味着目标分布退化,主要意味着资源浪费增大。近似接受、数值实现、不同的logits处理或停止语义则需要各自证明与测试,不能沿用这一等价性结论。

设第 \(i\) 个草稿在前面全部接受的条件下,以概率 \(a_i\) 被接受,则期望草稿接受数是 \(\sum_{i=1}^{k}\prod_{j=1}^{i}a_j\)。只有各位置条件接受概率都可近似为同一个 \(\alpha\) 时,才化成 \(\sum\alpha^i\)。推测轮的推进量还包含校正或全部接受后的额外Token;临近EOS、输出预算或停止串时,需要截断这个理想公式。

11.8.2 净收益阈值可以在实验前写出来

普通一步耗时为 \(t_0\),推测完整一轮耗时为 \(t_{round}\),期望实际推进量为 \(m\),则理想速度收益要求 \(t_{round}<m t_0\)。增加候选长度会增加 \(m\),也会增加草稿、目标验证、KV暂存和同步成本;当后几个候选很少被接受时,边际收益可能转负。

图11-R1 固定候选数下的理论推测收益条件

图中 \(k=4\),整轮相对成本分别取1.3、1.65、1.95;曲线不是实测接受率,也不考虑EOS截断。读者可以先用它排除明显不可能的成本组合,再在真实语义负载上测位置接受分布与整轮时间,而不是只看一个全局接受率。

11.8.3 图命中和形状桶也有资源优化问题

假设覆盖更多形状可以减少回退,但每个形状桶增加捕获时间、保留缓冲或编译制品。于是图配置也不是“捕获越多越好”:应最大化长期节省的发起开销,减去启动、存储和被挤出的状态容量成本。对短生命周期实例,更多捕获可能直到下线都未回本。

迭代命中率也不等于请求命中率。若一个请求经历100步、每步独立回退概率为4%,那么至少回退一次的概率为 \(1-0.96^{100}\approx98.31\%\);实际回退往往由形状成簇出现,并不独立,这个计算只是反例,说明不能把“96%迭代命中”翻译成“只有4%请求受影响”。报告应分别给出迭代级、请求级和回退事件连续长度。

11.8.4 交互项决定能否叠加

对第11.6节吞吐表,用同一基线尺度计算两因素交互:\(I_{AB}=Y_{AB}-Y_A-Y_B+Y_0\)。A、B分别提高到1120、1080,而AB为1150,故交互为−50 token/s。ABC比AC还低,说明在该表里添加B并非单调有益;它不能被“图执行本来应该加速”这一先验否定。

真实运行要用重复与区间判断交互是否超过噪声,并检查容量、实际批形状和路径变化。这个公式只是描述差异结构,不会自动指出机制;要把−50归因于图工作区挤占KV,仍需要分配记录与隔离干预。

11.8.5 公开初始化案例与测量边界

vLLM v0.30.0发布说明报告,在H200上于图捕获期间冻结GC,图捕获由12秒降至2秒、引擎初始化由28.9秒降至8.2秒 N06。这是初始化案例,不是稳态请求性能;摘要未给出的模型、形状与重复条件保留未知。

图11-R2 公开图捕获与初始化结果

初始化可能包含图捕获,不能把两组时间加总计算节省;本书未复现该运行。


11.9 实验图表补充:补齐推测解码、混合精度与质量失败三个公开案例

推测解码。EAGLE-3论文v2的Table 4给出SGLang团队在1×H100、LLaMA-Instruct 3.1 8B、MT-Bench、bs=1上的原值:无推测158.34、EAGLE-2 244.10、EAGLE-3 373.25 Token/s。EAGLE-3相对基线约2.357倍。原表列标题写Latency,但单位为Token/s;本书据单位称“单请求生成速率”,不当作毫秒时延,也不与表3的batch64吞吐混用。

混合注意力。同一vLLM FP8博客的gpt-oss-20b Table 5说明一刀切量化不是唯一选项。保留滑窗层BF16的skip-SW策略输出871.8 Token/s,相比全BF16的831.6增加约4.83%,相比全FP8的853.0增加约2.20%。有限滑窗的缓存规模受限,量化固定开销可能难以回收;但这个机制解释仍受具体模型和内核路径约束,不能预设所有滑窗模型都该跳过量化。

负面质量证据。博客Figure 1的Hopper/FA3、128k needle-in-a-haystack案例报告91%→13%→89%,对应BF16、修复前FP8、两级累加修复后FP8。文章未给该三值的具体模型与样本数,因此不补标准误,也不把89%解释为与91%统计等价。这里最有价值的不是“修复后提升多少”,而是提醒低精度路径的累加与实现可以造成严重质量回归。

采用门槛:速度、质量、资源峰值、异常率和回退能力同时验收;没有可复核的质量结果时,不接受仅凭吞吐的上线结论。

图11-V1 推测解码的公开单请求生成速率

图11-V1 推测解码的公开单请求生成速率

数据身份:PUBLISHED_RESULT。条件:LLaMA-Instruct 3.1 8B;SGLang团队实验;batch size=1;同一H100。

方法 Token/s 相对基线/倍
无推测 158.34 1.0
EAGLE-2 244.1 1.5416
EAGLE-3 373.25 2.3573

出处与边界:X06;原表标题Latency但单位Token/s;不把此单请求速率当并发服务容量,未给误差条。

数据与绘图点 · PNG · 重绘代码

图11-V2 混合注意力:有选择地量化比一刀切更合理

图11-V2 混合注意力:有选择地量化比一刀切更合理

数据身份:PUBLISHED_RESULT。条件:150请求、并发8、约20k输入/2k输出±15%;H100/FA3上下文;skip-SW保留滑窗层BF16。

KV策略 TTFT中位数/ms ITL中位数/ms 总时长/s 输出Token/s
BF16 468.9 8.09 364.2 831.6
FP8 451.7 7.9 355.1 853.0
FP8 skip-SW 456.4 7.7 347.4 871.8

出处与边界:X05;这不是Llama的数据;不得把两个模型的柱高比较解释成优化优劣。

数据与绘图点 · PNG · 重绘代码

图11-V3 质量回归:更低精度并不自动保持正确性

图11-V3 质量回归:更低精度并不自动保持正确性

数据身份:PUBLISHED_RESULT。条件:Hopper、FA3 FP8长上下文路径;两级累加修复;具体模型、样本数未披露。

配置 公开准确率/%
BF16基线 91
FP8修复前 13
FP8修复后 89

出处与边界:X05;91→13→89是不同配置的公开结果,不是时间序列;89不能证明与91统计等价。

数据与绘图点 · PNG · 重绘代码

11.10 实验深化补充:量化省的是字节,不是等比例的时延

本章前面已补齐 EAGLE-3 推测解码、混合注意力与长上下文质量负例。这一节补一条常被忽略的通信侧账:把权重/激活压小,省下的字节和实际时延并不是等比例。

TensorRT-LLM同一篇NVLink AlltoAll博客的Post-Quant Dispatch表(batch2048)给出三种dispatch格式:BF16为14336 byte/Token、311.8μs、报告逻辑753.3 GB/s;MXFP8为7392 byte/Token、172.2μs、703.4 GB/s;NVFP4为4032 byte/Token、101.7μs、649.3 GB/s。

按原值复算:NVFP4的payload是BF16的4032/14336=28.125%(约3.56倍压缩),但dispatch时延为101.7/311.8=32.617%(约3.06倍加速)。少搬的字节更多,省下的时延更少,差额来自固定开销与协议成本。因此不能把局部3.06倍写成模型整体3.06倍。

采用门槛不变:这里只覆盖dispatch一步,不含combine与专家计算,也不含量化质量验收。低精度路径必须先过质量与数值稳定性,再谈通信收益;图像alt标B200而章节文字标GB200 NVL72,本书不做更细的硬件保证。

图11-E1 通信前量化:少搬字节与实际时延并非等比例

图11-E1 通信前量化:少搬字节与实际时延并非等比例

数据身份:PUBLISHED_MICROBENCHMARK。条件:Post-Quant Dispatch表,batch2048;性能章节写GB200 NVL72,EP8按同一BF16基线和复现命令对应,表未单独标EP。

dispatch格式 byte/Token Dispatch/us 报告逻辑GB/s BF16字节数/当前字节数
BF16 14336 311.8 753.3 1.0
MXFP8 7392 172.2 703.4 1.94
NVFP4 4032 101.7 649.3 3.56

出处与边界:Y04;图像alt标B200、章节写GB200 NVL72,本书不作更细硬件保证;未含量化质量验收;仅dispatch,不是整轮。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 量化后模型体积降到 28%,为什么不能说显存和成本也降到 28%?

答案方向:三件事量纲不同。① 体积是制品字节,物理显存还含分配器开销、工作区、图捕获缓冲与碎片(第4章 4.3、第11章 11.3.2);② 分块缩放的元数据占 11.1%,且不同格式缩放规则不同,不能互推(11.2.2);③ 成本由完成速率决定,不由驻留体积决定——若低精度路径走了回退内核或代价更高的布局,速率可能不升反降。所以正确写法是分别报"制品体积 / 实测 allocated-reserved / 同 SLO 下合格完成速率"三项,并说明各自口径。

2. 图执行开启后单步从 3.2 ms 降到 1.4 ms,服务端到端为什么可能不变?

答案方向:三道门槛(11.3.4)。① 是否在关键路径上——若该步不在关键路径,优化的是空闲;② 占比是否足够——按 Amdahl,占 8% 提速 30% 只改善 1.88%;③ 排队反馈——但这只在特定条件下成立:开环固定到达下服务时间下降应改善等待,闭环或自适应到达才可能吸收(第9章 9.5.4 给了两条可区分假设)。此外还有 11.3.2 的冷启动摊薄:34 s 的一次性成本需要 42500 步回本。正确做法是写出这几条假设,用不同负载模式的实验判别,而不是直接判"优化无效"。

3. 接受率从 0.70 提到 0.75,为什么服务反而更慢?

答案方向:因为收益的分母是本轮推进量,分子是整轮成本,而接受率只是中间量(11.4.3)。候选量变大会同时抬高整轮成本:本例中推进量从 2.7731 涨到 3.0508(+10%),整轮成本却从 1.65 涨到 1.95(+18%),于是每 Token 成本从 0.595 升到 0.639,加速比从 1.68 降到 1.56。另外还要检查:更大的验证批是否改变了批形状与图命中率(11.6)、拒绝候选是否消耗了资源却不计入产出(第2章 2.1)、质量是否退步(10.6.2)。最终判据是三个量纲,不是接受率。


本章验收清单

节 验收标准
11.1 参数与瓶颈证据一一对应;有生效证据;释放显存不自动等于容量增加;尾延迟超标的改动被拒绝
11.2 四类精度分开;分块缩放的位宽与元数据占比已算;四列对照齐全;校准集与独立集分开;未支持组合标能力缺口
11.3 算法/内核库/引擎调度三层分开;冷启动与稳态分开验收并算回本步数;图命中率与回退倍率分别记录;单内核不外推服务
11.4 接受草稿数与推进量分开;按整轮成本算加速比;真实内容分布做开关对照;拒绝候选不计产出
11.5 语义保持与信息裁剪分开;任务变更另立对照轨道;结构化约束按任务层算净收益
11.6 不叠加单项收益;组合实测;推荐方案能对应到具体质量门槛;维护成本与回退方式已记
11.7 质量先于性能;有留出负载复核与长运行检查;决策卡含采用/拒绝/回滚条件与证据等级

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第12章 分布式推理服务:P/D 分离、状态传输与控制平面

核心论点:架构价值必须在模型数据面与服务控制面共同验证——阶段独立配置可以减少干扰,但状态转移、路由、流控、版本变化与恢复会新增成本。


12.1 数据面、控制面与分离架构的采用条件

本节要回答的问题:阶段独立调度能解决当前哪个已证实的问题?

12.1.1 先把两个面分开

组成 关心的问题
数据面 执行器、缓存、状态传输、内核 一次前向走了哪条路径、状态搬了多少字节
控制面 服务发现、路由、准入、版本管理、扩缩容决策 请求交给谁、谁的版本、失败后谁接管

具体进程可以跨越这两个边界——但分析口径不能混淆。把"控制面路由延迟"记进"模型慢",是分离式架构里最常见的记账错误(第1章 1.1.1 的依赖假设在这里直接适用)。

12.1.2 分离不是天然优于共置

共置(Prefill 与 Decode 在同一实例)的问题是阶段间干扰:一次长 prefill 会占用批位与 Token 预算,让正在 decode 的请求被拖慢(第5章 5.4)。分离把两阶段放到独立资源池,可减少实例内执行干扰;共享网络、主机、存储和控制面仍可能产生跨池干扰,但引入:

  • 状态传输(12.2);
  • 两个池的配比与瓶颈迁移(12.3);
  • 版本感知路由与状态身份管理(12.4);
  • 新的故障域与恢复路径(12.5)。

因此采用条件必须是可测试的三条,而不是"感觉有干扰":

  1. 存在已证实的阶段间干扰(有分层数据:某类请求的 ITL/TPOT 因另一类请求而恶化);
  2. 两阶段的最优资源形状不同(例如 P 更受算力约束、D 更受带宽与容量约束,第2章 2.3、第3章);
  3. 状态传输与协调成本小于分离带来的收益(有量级估算)。

12.1.3 一个完整的共置/分离对照

构造数据。负载为混合负载,长输入(4096 Token)占 20%,短输入占 80%,平均输出 300 Token;总资源 8 卡。

指标 共置(TP=8 ×1) 分离(P 池 4 卡 + D 池 4 卡) 变化
长输入首个 D 侧可见输出 P50 420 ms 330 ms −21.5%
短请求 ITL P99 195 ms 78 ms −60.0%
集群合格完成速率 4.00 req/s 5.20 req/s +30.0%

本协议规定 P 端首 Token 暂不对客户端写出、D 端首步后才出现可见输出;该端点不是所有分离架构的 TTFT。其时间构成:\(285\)(P 池 prefill)\(+ 26.8\)(状态传输)\(+ 18\)(D 池首步)\(= \mathbf{329.8\ ms}\)。

注意这里的关键点:传输的 26.8 ms 已经包含在 330 ms 里,占 8.1%。分离之所以更快,是因为 P 池专精且无传输部分的设定时间从 420 ms 变成 303 ms,减少约 117 ms;扣去新增传输 26.8 ms 后,净减少约 90.2 ms。如果只报"分离让 TTFT 从 420 降到 330",而不拆出这 26.8 ms,读者就无法判断换个网络或换个序列长度会怎样。

12.1.4 反面情形:什么结果支持继续共置

同样是分离架构,换成短输出负载(平均输出 30 Token):

指标 共置 分离 变化
合格完成速率 6.10 req/s 6.25 req/s +2.5%

传输成本(26.8 ms)没变,但 prefill 在总时长里的占比更高,隔离带来的收益更小。若项目的工程收益门槛高于 2.5%,或新增成本超过可兑现收益,则支持维持共置;仅凭这个百分比本身不能证明分离不值得。

本节验收要求的正是这一点:能说明什么结果支持继续共置,而不是先选架构再找理由。

12.1.5 新增成本清单

新增项 本例
状态传输延迟 26.8 ms/请求(逻辑量)
控制面组件 3 个(路由、服务发现、传输协调)
启动时间 +38 s
新增故障域 2 个(路由器、传输/缓存服务)

固定集成栈的资料案例:Dynamo 1.5.0 与 llm-d 0.9 SOURCE_REGISTERED S07。选择一套完整固定集成解释请求路径,另一套只比较设计职责——不把两者的组件混搭。原论文的实验倍数只作原论文结论引用 SOURCE_REGISTERED,不是本环境的预期。

依据与核验入口:R09 S06


12.2 状态传输:布局、字节、链路与"可用性"

本节要回答的问题:一次请求从 P 端到 D 端,到底要搬哪些状态,搬完是否就能用?

12.2.1 三种口径,不能直接互相替代

口径 本例(4096 Token) 说明
逻辑状态大小 512 MiB(0.5369 GB) \(4096 \times 128\) KiB,第4章状态账
线上字节数 逻辑 + 分块头/对齐 需实测,通常略高于逻辑量
设备端读写量 可达逻辑的 2—4 倍 D2H + 网络缓冲 + H2D;若需布局转换再加一读一写

用逻辑大小 ÷ 标称带宽得到的传输时间是乐观下界,不是预测值。

12.2.2 五段计时:重叠只省下 8.2%

段 时长
注册与准备(内存注册、连接建立) 3.0 ms(可提前到热身完成)
D2H 拷贝 6.7 ms
网络传送 26.8 ms
H2D 拷贝 6.7 ms
校验与可用确认 1.8 ms
串行合计 45.0 ms
设定的端到端 41.3 ms

重叠只省下 8.2%。这条数据的用途是反驳一个常见假设——"传输会被计算完全隐藏"。这个例子没有完全隐藏;真实可隐藏比例必须由路径依赖和运行记录确定,既不能预设完全隐藏,也不能预设一定无法隐藏。

有效带宽 \(= 0.5369\ \text{GB} / 41.3\ \text{ms} = \mathbf{13.0\ GB/s}\),即标称 20 GB/s 的 65.0%。所以本节验收的硬要求是:传输结论基于真实路径与有效带宽,不得把标称网络带宽当成应用吞吐。

12.2.3 传输库与 Store 是两个组件

点到点传输库解决"字节怎么搬" SOURCE_REGISTERED;分层存储解决"状态放哪、能留多久" SOURCE_REGISTERED(Transfer Engine 与 Store 是不同组件)。传输库不自动承担状态语义验证、请求调度或租户授权。

12.2.4 "搬完了"不等于"可以安全参与解码"

在消费方确认之前,必须先核对四元组:

项 为什么
模型 revision 不同 revision 的状态语义不同
状态布局 布局不同则同样的字节被解释成不同的东西
dtype(含缩放元数据) 低精度状态的缩放规则必须一致(第11章 11.2)
分词器与模板版本 影响 Token 序列与位置编码的含义

这条尤其危险的地方在于:字节搬过去了但布局不同,可能校验失败、运行报错,也可能静默产生错误答案。因此必须有消费端的显式校验,且校验结果要进事件记录。

非前缀复用与分层缓存服务的兼容性入口 SOURCE_REGISTERED 只解决"能不能复用",不解决"这次复用是否安全"。

依据与核验入口:R09 R08 S15


12.3 资源配比、路由流控与阶段容量

本节要回答的问题:两个资源池如何避免一端闲置、一端排队?

12.3.1 配比由负载结构决定,不是由输入输出 Token 比例直接换算

设 P 池单卡 prefill 能力 \(2400\) Token/s(输入)、D 池单卡 decode 能力 \(480\) Token/s(输出)。每请求所需的"卡·秒"为 \(S_{\text{in}}/2400\) 与 \(S_{\text{out}}/480\):

负载 P 需求(卡·秒) D 需求(卡·秒) P 池占比
输入 1200 / 输出 300 0.500 0.625 44.4%
输入 1200 / 输出 900 0.500 1.875 21.1%
输入 3600 / 输出 300 1.500 0.625 70.6%

P 池占比在 21.1% 与 70.6% 之间变化,跨度 3.3 倍。所以固定配比只适用于某一个负载分布;输出长度变化或输入长度变化都会让瓶颈迁移。这也解释了为什么不能只用输入/输出 Token 比例直接换算卡数——两阶段的单卡吞吐能力不同,且各自随形状变化。

12.3.2 配比扫描:瓶颈迁移可见

配比 P:D P 池利用率 D 池利用率 合格完成速率
2:6 96% 62% 4.35 req/s
3:5 88% 78% 5.05 req/s
4:4 71% 94% 5.20 req/s
5:3 58% 99% 4.80 req/s

最优出现在 D 池接近饱和(94%)而 P 池有余量(71%)处。这个形态说明:当前负载下 D 是瓶颈端;若负载变成长输入短输出,在其它条件不变时,通常需要增加 P 侧资源,表中配比应向更大的 P:D 方向移动。

12.3.3 路由开销与反馈延迟也要监测

控制层按工作量、队列与状态分布做决定时,它自己也有成本:

  • 路由决策延迟:查状态位置、判版本、算成本估计的时间;
  • 反馈延迟:控制层看到的队列与状态分布是多久之前的(陈旧的控制信号会导致震荡);
  • 缓存亲和 vs 排队均衡:第6章 6.4.3 已给出权衡算例——命中收益可能被新增排队吃掉,这里同样适用,只是现在跨了阶段。

12.3.4 组合变化后必须重测

推测解码、专家路由、并行度任一变化都会改变两阶段的能力曲线,此前的固定配比不再有效。第11章 11.6 的消融在这里同样成立:不能沿用旧配比直接推荐。

依据与核验入口:R09 S06 S02


12.4 版本感知路由、状态身份与一致性

本节要回答的问题:请求如何到达持有正确状态的正确版本实例?

12.4.1 四类身份,缺一不可

身份 谁分配 解决什么
task_id 业务/编排层 一次业务任务的聚合
request_id 客户端或网关 逻辑请求
attempt_id 重试产生 区分物理尝试(第1章 1.6)
state_id(cache_id) 缓存/传输层 状态块属于谁、属于哪个版本

分离架构把这些身份拆到了不同组件:命中、重试、取消可能发生在不同的进程里。错误的身份或错误的回收时机,会造成错误结果或资源泄漏——这属于正确性问题,不是性能问题。

12.4.2 状态生命周期状态机

CREATED ──▶ TRANSFERRING ──▶ READY ──▶ IN_USE ──▶ RELEASED
   │             │              │
   └──▶ FAILED   └──▶ FAILED    └──▶ STALE(版本失效/布局不匹配)
                                 └──▶ ORPHAN(生产方完成、消费方从未接收)

关键规则:"传输成功"不等于"状态可安全使用"。只有进入 READY 且消费方完成四元组校验(12.2.4)后,才能参与解码。

12.4.3 版本路由与滚动更新

把服务版本、模型 revision、缓存格式三者同时作为路由约束。旧请求排空与新请求准入必须分别定义:

  • 排空(drain):不再给待退役实例分配新请求,但允许在途请求完成;
  • 准入(admit):新请求只发给新版本实例。

算例:4 个 D 实例逐个滚动更新,单实例排空窗口 45 s(长请求最大 30 s + 余量 15 s),总切换窗口 \(4 \times 45 = \mathbf{180\ s}\),期间稳态容量降为 75%。

切换流量不代表旧缓存可以给新模型复用。模型 revision 变化会让全部旧状态失效:命中率从 45% 掉到 0,TTFT P50 从 330 ms 涨到 512 ms(+55.2%),直到新状态重建完成。这段"版本切换悬崖"必须写进容量计划,否则上线即跌 SLO。

遥测字段必须带版本登记 SOURCE_REGISTERED,不能假设字段语义已稳定。

12.4.4 租户隔离与回收

状态 key 必须含租户标识。未经授权的跨租户状态复用属于安全问题;经过授权的公共前缀或信任组共享可以另立隔离契约——因此隔离校验不能为了省一次传输而省略。失效与回收要有明确的触发条件与终态记录。

依据与核验入口:R07 R18 S07


12.5 故障、重试、滚动升级与退化路径

本节要回答的问题:P 端完成而 D 端失败时,系统怎么收场?

12.5.1 四类故障分开定义终态

故障点 已完成的工作 重试语义 成本
P 执行失败 部分 prefill 重跑 prefill(可能部分复用) ≈1× prefill
传输失败 完整 prefill 状态 优先重传,状态不在才重算 重传 26.8 ms vs 重算 285 ms
D 执行失败 已生成 \(N\) 个 Token 重传后重生成,或整体重算 按策略定
路由/控制面失败 无 换实例重发 重跑全部

关键对比:重传 26.8 ms 与重算 285 ms 相差 10.6 倍。这组成本提示优先评估重传,但策略还必须检查状态身份、保留租约、剩余截止时间、幂等性与目标实例是否可用;不满足时应重算、拒绝或结束,而不是无条件重传。

12.5.2 孤儿状态:被遗忘的成本

P 完成但 D 从未接收的状态,会一直占着 P 池的状态池。算例:故障注入 5 分钟产生 240 个孤儿状态:

\[ 240 \times 0.5369\ \text{GB} = \mathbf{128.8\ GB} \]

P 池状态池总量 \(4 \times 41.0 = 164\) GB → 孤儿占 78.6%。也就是说,一次 5 分钟的控制面故障,可以让 P 池在恢复前基本失去承载能力。孤儿状态的回收策略(超时扫描、生产方主动释放、消费方确认驱动的回收)必须与超时时间一起设计。

12.5.3 重试放大与失败分母

故障期间 P 失败率 30%、重试上限 2 次,按第1章 1.6.5 的公式:

\[ \mathbb{E}[N_{\text{attempt}}] = \frac{1-0.3^{3}}{1-0.3} = \mathbf{1.39} \]

尝试次数放大 39%,而这些额外尝试产生的工作不会被计入任何"成功请求"统计。报告必须包含失败分母与重算预算,只报稳态吞吐会低估故障期的真实负载。

12.5.4 滚动升级的"健康"定义

健康判定必须包含实际质量与 SLO,而不只是容器进程存活。还要求:

  • 检查不可用实例是否被路由避开(而不是继续被分配);
  • 长请求是否被正确排空(而不是被硬杀);
  • 恢复期间的重试是否放大了负载;
  • 退化路径是否可用:控制面失效时能否退回共置模式——这必须预先验证并演练过,不能假定"到时候可以切回去"。

依据与核验入口:R10 S06 S13


12.6 异构资源、共享状态与多阶段服务

本节要回答的问题:分离架构怎样扩展到不同硬件和多模态?

12.6.1 异构 P/D:兼容性是前提

异构资源可以让 P 池用算力更强的设备、D 池用带宽/容量更合适的设备。但状态格式、精度、布局与传输协议必须兼容——这一条比性能更重要,因为不兼容既可能显式失败,也可能静默出错。

本节只把它们写成设计与验证问题,不因为框架有功能入口就声称任意硬件组合可工作。

12.6.2 分层缓存:重算 vs 从哪一层读

(读取 512 MiB 状态):

层 有效带宽 读取耗时 vs 重算 prefill(285 ms)
设备显存 — 不需额外载入;正常访问时间仍非零 取决于是否可直接消费
主机内存 20 GB/s 26.8 ms 优(10.6×)
本地 NVMe 3 GB/s 179.0 ms 优(1.6×)
远端存储 0.5 GB/s 1073.7 ms 劣(3.8× 于重算)

结论是分层的:本地 NVMe 划算,远端存储不划算。所以"要不要下沉到远端"不能一概而论,必须按本环境的重算成本与链路带宽计算。分层缓存的设计入口 SOURCE_REGISTERED S13,介质、后端与部署限制需另行锁定。

12.6.3 编码器分离:关键路径变长

多模态可以把编码器作为独立阶段,于是关键路径变成

\[ 45\ (\text{图像编码}) + 285\ (\text{prefill}) + 26.8\ (\text{传输}) + 18\ (\text{首步}) = \mathbf{374.8\ ms} \]

多一个阶段就多一个排队点、多一个故障域、多一次状态交接。每加一个阶段都要重新做关键路径与瓶颈分析,不能只在原有路径上"加一项"。

12.6.4 集成栈依赖与迁移风险

固定集成栈是维护者给出的版本化集成组合;本书未本地重建,不能称为作者验证通过:Dynamo 1.5.0 固定集成 vLLM 0.28.0、SGLang 0.5.18、TensorRT-LLM 1.3.0rc25 SOURCE_REGISTERED。

由此产生三条硬规则:

  1. 不能把其中的引擎分别换成各自 latest 后,仍称"同一集成"——那是新的、未验证的组合;
  2. 该快照把 KVBM 标为弃用并计划在 1.6 移除——"计划移除"不是已发生,本稿不预判后续发布状态;
  3. 相关名称(如 AISimulate 更名)按该版本登记,不用新名称回推旧版本的行为。

昇腾等其它后端的适配栈与版本依赖另行核验 SOURCE_ENTRY_ONLY。

依据与核验入口:R10 R32 S13 S15


12.7 实验:固定集成栈的服务能力与恢复对照

本节要回答的问题:什么对照能回答"架构变化是否值得"?

12.7.1 至少声明一种公平性

与第6章 6.7.1 同一规则,但这里更严格:

公平性 固定什么 比较什么
固定资源 设备总数与型号 服务能力(同 SLO 下的合格完成速率)
固定 SLO 目标与门槛 达成它所需的资源成本

不能混用,也不可把不同硬件数量与不同质量的结果当作纯架构对比。

12.7.2 主实验只选一套可获得的集成

完整保存镜像、后端、路由与 connector 配置(含版本与 commit)。除稳态的共置/分离比较外,还要检验:

  1. 控制面失败(路由不可用、传输服务不可用);
  2. 模型版本切换(状态失效与恢复曲线);
  3. 状态清理(孤儿回收是否生效);
  4. 可服务时间(从启动到能接流量,含控制面就绪)。

另一套平台完成设计对照;没有证据的格子保持未知,不填推测值。

12.7.3 最后把成本放回结论

稳态性能之后,必须把传输成本、运维成本、回滚成本放回结论里。一个稳态 +30% 但需要 3 个新组件、2 个新故障域、且回滚需 180 s 的方案,与 +2.5% 的共置方案相比,未必更划算——这是项目决策,不是技术优劣。

依据与核验入口:R09 R24 S07


12.8 深化:分离的收益边界、首输出协议与状态交接

12.8.1 从公开实验读出问题,而不是只摘最大倍数

DistServe的OSDI 2024论文图1使用13B模型、单张A100 80GB、512输入/64输出的合成负载。该图及相邻正文报告,共置系统在给定TTFT/TPOT约束下约1.6 req/s,单独Prefill与单独Decode分别约5.6和10 req/s N01。

图12-R1 DistServe公开的共置与阶段隔离动机实验

后两根柱是阶段隔离实验,不是两套各自完成端到端任务的服务。论文用它们讨论理想资源组合;本书不把10/1.6算成同资源端到端加速,也不从图中补造未公开的逐请求数据、置信区间或2026版本表现。

12.8.2 转移是否值得,先比较暴露成本

设隔离及资源匹配减少的关键路径时间为 \(\Delta T_{saved}\),额外状态交接的暴露时间为 \(T_{handoff}\),新增控制等待为 \(T_{control}\),则这条请求延迟改善要求

\[\Delta T_{saved}>T_{handoff}+T_{control}.\]

这只是单请求条件,容量还取决于两个池的排队与共享网络。例如在设定状态128 KiB/Token、8192前缀下,需转移1 GiB。若隔离净省100 ms且其它暴露开销20 ms,那么仅按连续传输下界就需有效带宽高于 \(1\text{ GiB}/80\text{ ms}\approx13.42\) GB/s。低于此值不能满足这组时间预算;高于它也不代表保证满足,因为排队、注册和布局转换尚未全部计入。

不同请求长度对应不同转移量,远端缓存命中还可能替代重算。一个“开了分离就快”的固定判断,应改为按状态大小、当前队列、链路和期限选择共置或分离路径;但动态选择本身也有控制开销,需保留简单基线。

12.8.3 首Token到底由谁发出,改变计量结果

一种协议让P端完成Prefill后立即发首Token,同时把状态发给D端。此时TTFT可以不包含完整转移,但首Token到第二Token之间可能出现明显停顿。另一种协议由D端确认就绪后统一开始流式输出,转移就进入首输出时间。两者都可以正确,却不能用同名TTFT直接比较。

因此分离验收应同时记录:P端首Token生成、客户端首内容、D端状态可消费、D端首次前向完成,以及首两个客户端内容事件的间隔。仅测TTFT可能把等待从“首Token之前”搬到“首Token之后”,用户感知未必改善。原 330 ms的算式已限定为D端首步后开始可见输出的协议,不再当成所有P/D系统的通则。

12.8.4 状态所有权需要处理迟到完成

考虑传输超时后P端已释放缓冲,旧的网络完成回调又迟到。如果控制面只按 request_id 认领状态,就可能把旧字节当成新尝试的状态。一个更稳健的设计应把状态身份写成 {model_revision, layout_version, attempt_id, transfer_id, generation},消费端校验当前generation,过期结果只做清理,不能重新进入READY。

释放也应依赖安全条件:生产者不再写入、传输没有引用、消费者不再计算、缓存策略允许回收。租约或引用计数可以实现部分条件,但不替代设备完成语义。故障恢复要验证超时重传、重复确认、迟到确认、取消与版本切换的交错,而不只是验证一次成功传输。

12.8.5 固定集成不是把组件版本逐个取最大

Dynamo 1.5.0发布表分别固定了vLLM、SGLang和TensorRT-LLM后端,并按后端列出不同NIXL版本 N10。本书将其录入来源矩阵,而不是生成一个未经运行验证的通用安装脚本。尤其应保留KVBM“本版弃用、目标在后续版移除”的时间语义;迁移目标是否等价,要按所需能力另行核验。


12.9 实验图表补充:把状态传输成本与论文SLO契约并排核对

本节第一张图是明确的下界:512MiB状态以0.5、3、20GB/s有效带宽传输,仅payload项分别约1073.74、178.96、26.84ms。与285ms重算时间比较,慢层级可能输给重算,快层级才值得继续核算其他成本。图中的0.5/3/20是设定带宽;排队、注册、复制、序列化与重传必须再加回真实依赖图。

第二张图来自DistServe正式论文Table 1,展示TTFT/TPOT的目标门槛而非达成的时延。OPT-13B、66B、175B聊天TTFT分别0.25、2.5、4.0秒;代码OPT-66B为0.125秒,摘要为15秒。不同任务的门槛差异本身就会改变“最佳布局”与可交付容量。

版本勘界:arXiv v2的前两组聊天TTFT是0.2、0.4秒,66B TPOT也不同;全书统一用 OSDI 正式版。Figure 1的P-only约5.6、D-only约10 req/s不是完整服务。论文“2P1D可达10 req/s、约3.3 req/s/GPU”以Ideally引出,属于理想资源配比推演,本书不将它画为已测的端到端结果。

实验落点:同时固定首Token产生位置、线上状态字节与实际传输路径;分离前后使用相同质量和联合SLO,再比较合格完成速率,而非取某阶段最大值相除。

图12-V1 状态转移:带宽足够才有讨论分离的前提

图12-V1 状态转移:带宽足够才有讨论分离的前提

数据身份:TEACHING_CALCULATION。条件:S=512MiB=536870912byte;带宽0.5/3/20GB/s均为假设;重算285ms。

有效带宽假设/GB每秒 理想payload/ms 285ms与传输时间之比
0.5 1073.742 0.265
3 178.957 1.593
20 26.844 10.617

复算公式:T_ms=S_bytes/(B_GBs*10^9)*1000。

出处与边界:本书12.5、12.6;仅payload理想时间,不含排队、序列化、注册、复制和重传;不代表真实链路。

数据与绘图点 · PNG · 重绘代码

图12-V2 同一论文,不同任务采用不同服务目标

图12-V2 同一论文,不同任务采用不同服务目标

数据身份:PUBLISHED_RESULT。条件:OPT-13B/66B/175B;FP16;论文全局测试床4节点32×A100-80GB;不同实验使用相应子集。

任务/模型 TTFT门槛/ms TPOT门槛/ms
对话13B 250 100
对话66B 2500 150
对话175B 4000 200
代码66B 125 200
摘要66B 15000 150

出处与边界:X07;图中是门槛不是测得的延迟;不混用arXiv v2的0.2/0.4s聊天TTFT。

数据与绘图点 · PNG · 重绘代码

12.10 实验深化补充:真实系统与模拟器:校准的是模型,不是容量

本章前面明确了“阶段速率≠端到端”,并采用DistServe OSDI正式版的SLO契约。这一节补上该论文最能说明方法论的一张表:Table 2把真实系统结果与模拟器结果并排校准。

平台为4节点、32×A100-80GB、FP16。到达率由1.0升到4.0 req/s时,vLLM真实/模拟由97.0%/96.8%降到23.6%/24.1%;DistServe-Low真实/模拟由100%/100%降到70.0%/68.9%。逐点复算绝对差:vLLM最大1.8个百分点,出现在2.0 req/s(52.8对51.0);DistServe-Low最大1.1个百分点,出现在3.0 req/s(83.0对84.1)与4.0 req/s(70.0对68.9)。这是“模拟器能多准地复现真实”的校准证据,不是“DistServe比vLLM快多少”的加速证据。偏差是绝对百分点,不是相对误差<2%。

读图纪律:实线为真实系统、虚线为论文模拟器,两者不可混画为一条曲线。有限速率网格只提供已测点,不能外推精确容量边界;本表未单列模型/负载/每行分配卡数,因此不据此计算公平的卡均容量倍率。也不把论文Figure 1以“Ideally”引出的2P1D理想推演当作端到端实测。

图12-E1 P/D系统证据:真实结果与模拟器必须分开

图12-E1 P/D系统证据:真实结果与模拟器必须分开

数据身份:PUBLISHED_REAL_AND_SIMULATED。条件:OSDI正式版Table2;整体平台4节点/32×A100-80GB、FP16;本表模型/负载/每行分配卡数未单列,附近图11为OPT-66B/ShareGPT,仅作上下文。

到达率/req每秒 vLLM真实/% vLLM模拟/% DistServe-Low真实/% DistServe-Low模拟/%
1.0 97.0 96.8 100.0 100.0
1.5 65.5 65.1 100.0 100.0
2.0 52.8 51.0 99.3 99.3
2.5 44.9 46.1 87.3 88.3
3.0 36.7 38.3 83.0 84.1
3.5 27.8 28.0 77.3 77.0
4.0 23.6 24.1 70.0 68.9

出处与边界:Y05;实线是真实系统、虚线是论文模拟器。缺表级配置,不据此算公平的卡均容量倍率;不得混入Figure1的理想2P1D。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. P/D 分离改善了 TPOT,却恶化了 TTFT,应该如何判断?

答案方向:用业务目标与联合达标率判定,而不是挑好的那个指标报。先确认两个前提:① 两类指标分别落在哪些请求上(分层,不是总平均);② TTFT 与 TPOT 各自的契约阈值是什么。然后按第7章的联合达标率算总账——TTFT 恶化会减少达标请求数,可能完全抵消 TPOT 的改善。对照:TTFT 420→455 ms(阈值 450 ms,超限请求增多)、ITL 195→78 ms,联合达标率 82% → 79%,此时应当不采用。只有在 TTFT 仍在阈值内、且达标率与合格完成速率同时改善时,分离才成立;若 TTFT 已接近阈值,则应改进传输路径或调整配比后重测,而不是接受这个权衡。

2. 状态可传输,是否等于跨后端兼容?

答案方向:不等于,而且这是最危险的一类误解。传输库解决"字节怎么搬",兼容还要求四件事同时成立:模型 revision、状态布局、dtype(含缩放元数据)、分词器/模板版本。字节搬过去了而布局不同,可能校验失败、运行报错,也可能静默产生错误答案——所以这不是"性能差",是静默的正确性故障。因此必须在消费端做显式校验(12.2.4 的四元组),校验结果进入事件记录,且未通过时状态进入 STALE 而不是 READY。跨后端组合要先验证再使用,不能因为某框架提供了连接器就认为任意两端可互操作。

3. 为什么不能给固定 Dynamo 集成中的引擎分别换成各自 latest 后保留同一兼容结论?

答案方向:因为官方集成记录针对的是具体版本组合,实际可用性还需复现验证,与任一组件单独的最新状态无关。分别换成 latest 之后:① 组件间的接口与协议可能变化(连接器、控制面接口、状态格式约定);② 原集成声明的版本依赖矩阵不再成立;③ 已被验证过的行为(例如状态传输对 dtype 与缩放元数据的处理)在新组合下未经验证。所以得到的是一个新的、未验证的组合,必须重新验证,不能沿用原结论 SOURCE_REGISTERED。同理,"某组件被标记为弃用并计划移除"是计划,不是既成事实,不得据此预判后续版本的行为。


本章验收清单

节 验收标准
12.1 采用条件三条可测试;对照同时给出支持分离的与支持继续共置的结果;新增成本列全(传输、组件、启动、故障域)
12.2 逻辑/线上/设备端三种口径分开;五段计时与有效带宽已测;重叠比例实测而非假设;四元组校验已定义
12.3 配比注明负载区间;给出配比扫描与瓶颈迁移;路由开销与反馈延迟被监测;组合变化后重测
12.4 四类身份齐全;状态机含 STALE/ORPHAN;排空与准入分别定义;版本切换的容量跌落已计入计划
12.5 四类故障各有终态与成本;重传 vs 重算的策略写死;孤儿状态占用已量化;重试放大与失败分母已报;退化路径已演练
12.6 兼容矩阵明确;分层缓存按本环境重算成本决策;多阶段关键路径重新计算;集成依赖三条规则遵守
12.7 公平性原则明确且未混用;主案例锁定一套集成;传输/运维/回滚成本放回结论

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第13章 软件栈与硬件适配

核心论点:兼容性是具体版本、模型、算子与功能组合的属性,不是某个框架名称或 API 声明的属性。


13.1 驱动、运行时、框架与服务层

本节要回答的问题:同一个故障属于哪个软件层?

13.1.1 分层与依赖方向

层 组成 典型故障表现
设备驱动 / 固件 内核态驱动、固件 设备不可见、ECC 报错、时钟异常
运行时 设备运行时、内存与流管理 初始化失败、上下文丢失
算子库 / 通信库 计算内核、集合通信 某形状走另一条路径、同步超时
模型框架 张量、图、dtype 语义 精度路径不一致、布局转换开销
推理引擎 调度、状态管理、执行器 排队、抢占、回退
服务封装 协议、流式、取消 首字节延迟、取消未传播

在此之外还有三个独立组件:独立缓存服务、状态传输、服务控制层 SOURCE_REGISTERED S13。

关键纪律:能独立安装的组件,不等于可以组合使用。依赖图要标出调用方向与版本约束,而不是把各组件的 latest 拼在一起。

13.1.2 同一表象可以来自不同层

案例:驱动从 535 升到 550 后,TTFT P50 从 320 ms 涨到 480 ms(+50.0%),吞吐不变、无报错。

候选层 支持证据 可能推翻它的结果
驱动/固件 变更与恶化时间对齐 回退驱动后不恢复
算子库 该版本可能换了内核 trace 中内核名未变
引擎配置 同期可能改过开关 配置回显与基线一致
负载 输入长度可能变了 长度分层后同层也恶化

区分的实验顺序(按信息量/成本):先看长度分层(E0,纯计算)→ 再看 trace 内核名(E2)→ 最后回退驱动复测(需变更授权)。回退复测是提高归因可信度的重要步骤,但不是唯一证据,也不能单独排除时间漂移和同时发生的变更(第10章 10.3)。

13.1.3 责任边界

记录每层的版本、来源、依赖与回退方式,目的不只是排查,更是把问题交给正确的维护者。升某一层破坏另一层是常态,不是异常。

依据与核验入口:R19 R23 S13


13.2 支持矩阵:模型、功能组合与发布成熟度

本节要回答的问题:支持某模型,是否意味着支持它的所有运行方式?

13.2.1 组合数量:为什么"支持模型"是很弱的一句话

把可独立开关的功能维度列出来——精度、并行方式、上下文长度、缓存、推测解码、多模态、结构化输出——共 7 个维度。若每维取"开启/关闭"两态,组合空间是

\[ 2^{7} = \mathbf{128} \]

若把全部 128 个都定义为有效候选、实际只验证其中 11 个,则形式覆盖率为 8.6%。实际工程应先去除无意义或互斥组合,再报告需求覆盖率,不能把独立二元开关模型当成真实支持空间。

这就是"支持某模型"不能推出"支持量化 + 多卡 + 长上下文"的定量形式。模型支持列表回答的是"至少有一个组合能跑",不是"所有组合都能跑"。

13.2.2 四态矩阵,未知不能写成支持

每一格必须落到一个状态,且附证据来源:

状态 含义 证据要求
支持 该组合经过验证 官方条目
实验性 可用但不承诺稳定 标记与已知限制
未支持 明确不可用 官方声明或实测失败
未知 没有证据 不得留空(留空会被读成"应该没问题")

四态不可合并:"未支持"与"未知"是不同信息——前者是负面证据,后者是没有证据。

13.2.3 两个轴:发布成熟度 与 功能状态

成熟度 含义 常见误读
正式维护线 有维护承诺 误读为"所有模型都支持"
模型限定 RC 只面向特定模型 误读为"通用升级"
预览 / 实验 可能变更或撤回 误读为"可用即可上线"
主干 未发布 误读为"最新即最好"

"正式维护版本 ≠ 全部功能可用","模型专项 RC ≠ 通用升级"——这两条是本节最容易出错的地方 SOURCE_REGISTERED S20。

每行矩阵要标出:模型 revision、硬件、依赖版本、并行/精度/缓存/推测组合、来源正文状态、本地运行状态。

13.2.4 三个证据维度不可互换

维度 回答什么
来源支持 官方说支持什么
发布成熟度 该版本的维护策略

官方支持状态与实际验证状态相互独立:官方可以为“支持”,而实际仍为“未验证”。不将二者压成一个含义含混的状态。

依据与核验入口:R19 R26 S19 S12


13.3 适配器、算子后端与回退路径

本节要回答的问题:表面接口相同,实际执行为什么可能不同?

13.3.1 抽象层降低的是调用差异,不是执行差异

硬件插件与抽象层让调用形式统一,但算子实现、图模式与回退路径仍可能不同。算例:

后端 A 后端 B
同一 API 调用的实际路径 原生内核 通用回退
单步耗时 1.4 ms 4.6 ms(3.29 倍)
输出内容 一致 一致

输出一致而性能差 3.29 倍——这正好说明"只验证 API 返回了文本"是不够的。适配章节必须保留路径确认步骤。

13.3.2 实际调用图

engine release ──▶ Model Runner ──▶ graph mode ──▶ 内核/算子后端 ──▶ 设备
        │                │               │                │
        └─ 版本 ─────────┴─ 回退 ────────┴─ 形状/桶 ─────┴─ 能力等级

四层各有版本化事实,且各有独立的回退可能。要分别记录:引擎 release 与 commit、Runner 代际与是否走了回退、图模式与命中 key、内核后端与设备能力等级 SOURCE_REGISTERED S04。

不要把旧教程的类名与默认参数继续当成通用入口。

13.3.3 回退是结果,不是失败

回退本身没错,错在不知道发生了回退。因此报告必须说明:实际后端是什么、关键功能是否真正生效、未生效的原因是什么。未知内部路径不作具体归因——写"未取得该层证据"优于猜测。

依据与核验入口:R19 R31 S04


13.4 制品、集成依赖与版本冻结

本节要回答的问题:怎样避免"同一个镜像标签"指向不同实验?

13.4.1 制品链

原始权重(revision) + 校准数据 + 转换配置 → 量化/转换制品 → 构建参数与本地补丁 → 容器镜像(digest) → 运行配置(hash)

浮动标签(latest、分支名)不足以保证复现。只有把这一整条链固定住,升级后结果变化时才可能区分:是权重变了、算子变了,还是运行配置变了。

manifest 必备字段:model_revision / quant_config / calibration_id / image_digest / commit / config_hash / patch_list / maturity。不包含凭据或受限权重本体。

13.4.2 集成版本 ≠ 各组件最大版本号

反例:Dynamo 1.5.0 固定集成 vLLM 0.28.0、SGLang 0.5.18、TensorRT-LLM 1.3.0rc25 SOURCE_REGISTERED。

集成中的引擎版本不等于每个项目的最大版本号。把集成中的组件分别升到各自 latest,得到的是另一个未验证的组合,不是"更好的同一个集成"。

13.4.3 驱动、固件与补丁

驱动与固件也要进 manifest,它们影响内核选择与时钟行为(13.1.2 的案例)。本地补丁必须记录,否则"同一镜像"在不同机器上不是同一环境。

13.4.4 生命周期三态

状态 含义 能否推荐
已弃用(deprecated) 仍可用,不建议新用 仅存量
计划移除 尚未发生 不得写成已移除
已移除 路径不存在 不可推荐

不根据版本号大小推断兼容性 SOURCE_REGISTERED。

依据与核验入口:R19 R29 S19


13.5 服务部署、健康、路由与隔离

本节要回答的问题:可启动、可接流量、可稳定服务分别如何确认?

13.5.1 三个状态,不是一个

沿用第8章的冷启动分解(容器 8 + 制品 46 + 权重就绪 12 + 编译 23 + 图捕获 11 + 热身 15 = 115 s):

状态 时刻 能否放流量
进程存活 8 s 否
模型就绪 66 s 否(未预热、未编译)
可接流量(可服务) 115 s 是

若健康检查只看进程存活,会比真正可服务早 107 s 放流量。这 107 s 内的请求会撞上权重加载与编译,产生一批被误判为"系统慢"的坏样本。

健康检查要与业务流量分开:存活探针用于重启决策,就绪探针用于是否接入。

13.5.2 控制面与数据面拓扑

在主案例范围内检查:路由更新、独立缓存服务、状态转移的 readiness 条件。只完成模型加载不能标为满足业务 SLO SOURCE_REGISTERED S07。共享状态服务不可用时的退化路径必须可描述,不能假定它永远在线。

13.5.3 排空与缓存失效

长请求排空窗口(第12章 12.4.3 用 45 s)与版本切换时的缓存失效要在部署方案里预先定义,旧请求必须有可解释的终态。

13.5.4 隔离

权限、资源与租户隔离既影响稳定性,也影响测试公平性——共享主机上的两个被测实例会互相污染测量结果(第8章 8.4)。

依据与核验入口:R25 R24 S07


13.6 跨后端指标与工具适配层

本节要回答的问题:如何统一报告,又不抹平底层差异?

13.6.1 统一的是数据契约,不是指标集合

适配层保留四件事:原生名称、单位、来源、版本,再映射可比部分。统一报告的每个字段都要能回溯到原始字段与映射版本。

13.6.2 缺失必须留空

适配器必须能表达"不存在"或"不可观察",而不是为凑统一表格填零。对照:

字段 后端 A 后端 B
设备忙碌率(时间占比) 96% 留空(该后端无同语义计数器)
若填 0 的后果 — 把"没采到"当成"完全空闲"

这与第7章"缺失记缺失,不记 0"是同一条规则。

13.6.3 不比较同名百分比

不同厂商的"利用率"定义不同(第9章 9.4.2 已给出原生定义):有的是"采样周期内一个或多个内核执行的时间占比",有的是字节/s 与峰值之比。跨后端不直接比较同名百分比 SOURCE_REGISTERED R21。

指标迁移(AIPerf 与 GenAI-Perf 的 TTFT/TTFO 差异)作为适配层的回归测试 SOURCE_REGISTERED:工具升级时必须重跑指标口径单元测试。

依据与核验入口:R01 R19 R21 S24


13.7 实验:交付最小可复现后端

本节要回答的问题:怎样证明环境不是一次性的"装成功了"?

三步:

  1. 固定制品与启动:冻结完整依赖(不是各组件 latest),保存 manifest 与启动日志;
  2. 功能冒烟与异常:短/长请求、流式、停止、取消、必要的结构化校验,以及异常路径(超时、错误输入、取消后资源);
  3. 观测及重建验证:核对指标口径与环境档案,最好由全新环境重建一次;无法重建时记录依赖缺口。

目标是可审查与有条件复现,不是宣称所有平台通用。随环境提交:来源正文、依赖矩阵、版本成熟度、弃用清单、实际路径身份。未选定主环境时不提供看似可运行的占位命令。

依据与核验入口:R19 R29 S19


13.8 深化:兼容性不是一个版本号,而是一组可检验的约束

13.8.1 把“能安装”拆成四个层次

适配失败至少有四种不同机制。第一是接口不匹配:调用参数、返回对象或协议不同。第二是二进制不匹配:扩展库、运行时和驱动所依赖的符号或设备目标不同。第三是语义不匹配:接口调用成功,但布局、缩放、位置处理或停止规则不等价。第四是观测不匹配:服务可以运行,却无法提供诊断所需的事件与字段。

四种问题不能都用“换一个镜像”处理。接口问题需要最小调用;二进制问题需要装载错误和构建身份;语义问题需要已知输入的数值、格式或任务检查;观测问题需要证明确实存在相应事件,而不是把空缺字段填成零。一个后端可以通过前三层、却不满足本项目的可运维要求。

依赖清单因此应写成有方向的约束图。例如“引擎→框架扩展→运行时→驱动”是加载关系,“生产方缓存格式→消费方缓存格式”是语义关系,“原生事件→解析器→指标字典”是观测关系。同一个版本升级可能同时影响三条边,单纯比较包版本列表发现不了这些变化。

13.8.2 官方支持与本地验证必须保留为两个坐标

官方状态 本地状态 可写的结论
声明支持 官方声明支持,实际未验证
声明支持 失败 本环境复现失败;先核对完整条件,不能直接改写官方支持范围
未声明 成功 本组合局部测试成功;不能升级为官方支持
明确不支持 偶然成功 存在超出支持边界的运行;不宜据此承诺维护和稳定性

这张二维表解决“官方支持而作者未测就一律未知”的概念冲突。未知的是本地运行结果,不是已经存在的官方声明。反过来,一个成功响应也不等于取消、长上下文或量化组合全部通过。

对没有定义或无法达到的组合,分母也要调整:七个二元开关的128个组合只是取值空间,其中有些配置可能互斥。真正的测试覆盖率应是“已测试的有效组合/项目要求覆盖的有效组合”,不能用没有业务意义的笛卡尔积制造精确的覆盖百分比。

13.8.3 制品链要允许重建,也要允许比较

建议把环境清单写成机器可读的结构,并保存原生输出与人工确认来源。模型revision、量化制品摘要、校准集身份、镜像digest、依赖版本、本地补丁、运行参数和观测映射各自独立编号;不要把所有差异压进一个“实验版本”。

当A、B两个运行表现不同,先生成制品差异表。若只改变引擎而权重、模板、负载和指标均固定,才能把引擎列为主要干预;若校准集或模板也变了,则需要拆分实验。这个过程可以完全在E0执行:它检查记录是否一致,不需要GPU,但不能替代功能测试。

完整性校验只证明文件未被意外改动,不自动证明下载来源可信、许可证允许发布或镜像安全。公开包宜保存摘要和来源,不应把受限制权重、访问令牌或内部镜像凭据直接分发。

13.8.4 冒烟测试应覆盖状态变化,而不只是输出文本

最小测试序列可以围绕状态迁移组织:冷启动→就绪→短请求完成→长请求→主动取消→再次短请求→正常排空。每一步既检查协议结果,也检查是否影响下一步。取消以后还能回复短请求,不能证明所有状态已释放;但若服务再也无法接流量,就已经发现生命周期异常。

对量化、图执行、推测解码和远程缓存,优先选择一个会触发边界的形状,再记录配置声明与实际运行证据。不支持的项明确跳过并说明原因,不能把跳过计成通过。后续再扩成固定版本的能力矩阵,逐项复用已有证据,而不是每次升级都从“装好了”重新开始。


13.9 实验图表补充:把官方支持声明变成有边界的覆盖矩阵

本章补入vLLM-Ascend v0.26.0rc1测试总结中实际列出的四模型部署组合。矩阵只转写Model Validation表:A3的节点数与EP/DP、1P1D都保留;Ascend 950仅在Kimi K3行出现,其余格记“未声明”。这比画一大片“支持”更接近原始证据。

时效提示(2026-10-09):上述四模型组合是 v0.26.0rc1 的声明。截至检索日 vLLM-Ascend 已到 v0.27.1rc1(2026-09-24),其声明的完整验证模型收缩为 MiniMax-M3 与 DeepSeek-V4-Flash 两项,并明确"Other models and hardware combinations…were not sufficiently validated for this release"。引用本节矩阵必须写明版本与检索日;它不是能力上限,也不是项目支持模型全集。

“未声明”不等于不支持,官方声明过的蓝格也不代表本书本地验证过。尤其不能从某模型W4A8在A3和Ascend950被验证,推导其他W8A8C8模型在两种平台所有并行度也可用。矩阵没有附成功率、吞吐或成熟度评分,因为报告未提供这些逐格量。

X09把原N09的浮动分支引用加强为固定提交 f2f74a16c3c50a76f4349d807918e83edec1e35c,但不覆盖旧记录当时的核验状态。运行身份仍需要驱动、固件、CANN、torch_npu、vLLM、插件、镜像digest与模型revision,缺任何一环都不能把源代码声明当作可重建的完整环境。

本地接入表的规则:每一组合分别增加声明、启动成功、数值通过、性能通过、长期稳定五项,不用一个“支持”布尔值把它们压平。

图13-V1 官方验证过哪些组合?空白不等于不支持

图13-V1 官方验证过哪些组合?空白不等于不支持

数据身份:DOCUMENTED_COVERAGE。条件:仅保留报告列出的模型、精度、节点数、EP/DP和1P1D组合。

模型/量化 A3部署 Ascend950部署
GLM-5.2 · W8A8C8 4节点 EP / 1P1D 未声明
Kimi K3 · W4A8 8节点 EP / 1P1D 4节点 EP / 1P1D
DeepSeek-V4-Flash · W8A8C8 4节点 DP / 1P1D 未声明
DeepSeek-V4-Pro · W8A8C8 2节点 EP / 1P1D 未声明

出处与边界:X09;未声明≠不支持;蓝格也不代表本书已验证;不得展开为任意功能笛卡尔积。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 为什么支持模型不等于支持"量化 + 多卡 + 长上下文"组合?

答案方向:因为"支持"是属性集合上的命题,而验证只在笛卡尔积的一个子集上进行。7 个可独立开关的维度、每维两态就是 128 个组合,实测 11 个只覆盖 8.6%。模型支持列表回答的是"至少有一个组合能跑通",能力矩阵才回答"这个具体组合能不能跑"。因此每个要用的组合都要单独进矩阵并标注四态(支持/实验性/未支持/未知),未知不得留空。

2. 统一 API 能够保证跨后端输出和指标一致吗?

答案方向:都不能完全保证。API 统一的是调用形式;执行路径可能不同(13.3.1 中同一调用 1.4 ms vs 4.6 ms,输出一致而性能差 3.29 倍)。至于指标,统一的是数据契约(原生名称、单位、来源、版本 + 映射规则),不是指标集合——某后端没有同语义计数器时必须留空,不能填 0,也不比较同名百分比。所以正确说法是:"统一 API 降低了调用差异与报告口径差异,不保证执行路径、性能与可观测字段相同。"

3. 正式维护版、模型限定 RC 与来源支持为什么是不同维度?

答案方向:它们回答三个不同的问题——发布成熟度(该版本有没有维护承诺)、适用范围(是通用还是只面向特定模型)、来源支持(官方声明支持什么)。一个正式维护版也可以不支持某些模型或组合;一个模型限定 RC 不是通用升级;官方声明支持但未经验证的组合,登记为“官方支持/未验证”。因此能力矩阵的每一行必须把这三项分别记录,不能互相替代或合并成一个"支持"标记 SOURCE_REGISTERED S20。


本章验收清单

节 验收标准
13.1 能定位每个组件的版本、来源、依赖与回退方式;能举出同一表象来自不同层的案例与区分顺序
13.2 矩阵四态齐全且每格附证据;发布成熟度与功能状态分开;来源支持/成熟度/实测三维度分别记录
13.3 说明了实际后端与关键功能是否真正生效;保留路径确认步骤;未知内部路径不强行归因
13.4 制品链完整可定位;集成版本不被替换为各组件 latest;生命周期三态不混用
13.5 进程存活/模型就绪/可接流量三态分开;健康检查与业务流量分开;排空与失效有定义
13.6 统一列可回溯到原生字段与映射版本;缺失留空不填 0;不跨后端比较同名百分比
13.7 环境文档足以重建已测试组合;未测试功能保持未知;异常路径已覆盖

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第14章 跨平台比较与迁移验收

核心论点:跨平台比较必须先确定比较对象——硬件、软件实现,还是完整交付方案;无法控制的差异不能被隐去。


14.1 定义比较对象与公平性

本节要回答的问题:比较的是单卡、实例、机柜,还是完整服务方案?

14.1.1 比较单位决定结论方向

算例。两平台各 8 卡,单卡吞吐与整机扩展效率如下:

平台 A 平台 B
单卡吞吐 1200 token/s 980 token/s
单机扩展效率 0.78 0.95
整机吞吐 \(1200\times8\times0.78 = \mathbf{7488}\) \(980\times8\times0.95 = \mathbf{7448}\)
相对差 — A 仅高 0.5%

单卡层面 A 快 22.4%,整机层面只差 0.5%。如果把单卡结论写成"平台 A 快 22%",就是一次比较单位错误——扩展效率的差异(互联、主机供给、软件栈)把优势吃掉了。

所以比较单位必须显式声明,并且可以有多层:卡 → 服务器 → 互联域 → 完整服务资源池 SOURCE_REGISTERED S22。

14.1.2 匹配条件与不可控差异

同模型同精度可以减少一部分差异,但仍可能使用不同内核与并行布局;同质量不同精度则属于方案比较,不是硬件比较。

差异账本要逐项列出三类:

类别 例子 处理
已匹配 模型 revision、负载卡、SLO 写入契约
可匹配但未匹配 并行度、批预算 说明为什么保留差异
不可控 内核实现、驱动、固件、主机 必须披露,不得隐去

14.1.3 结论归属

报告结论属于被测组合,不能把完整软件栈结果归因给单一芯片。不能从整套方案表现反推单卡优劣——方向反了。

依据与核验入口:R14 R30 S22


14.2 迁移前的功能与负载盘点

本节要回答的问题:目标平台必须真正支持哪些使用方式?

14.2.1 从业务要求倒推,不从平台功能正推

这是本节最重要的一条纪律:先写需求,再看平台;反过来做会静默删减要求。

三类清单:

类别 处理 例子
必需 不支持则迁移不成立 流式输出、取消传播、结构化约束
可替代 有等价实现即可 某种缓存策略换成另一种
非必需 可暂缓 某项调试接口

14.2.2 非演示功能必须进清单

取消、超时、日志、观测能力这些"不演示就看不见"的功能,最容易在迁移后被发现缺失。而观测缺失本身就是迁移风险——它影响后续诊断与运维,不是"以后再补"的小事。

14.2.3 迁移成功的定义在部署前确定

验收标准要在部署前写死。典型失败形态是:迁移到一半发现某功能不支持,于是悄悄把它从需求里删掉,最后报告"全部通过"。

依据与核验入口:R19 R29


14.3 模型、数值与任务质量对齐

本节要回答的问题:如何判断迁移后仍满足原业务目标?

14.3.1 先对齐身份,再看数值

顺序不能颠倒:权重 revision → 模板版本 → 停止规则 → 采样设置,全部对齐之后才谈数值差异。身份没对齐就比数值,得到的差异没有解释价值。

14.3.2 逐字相同不是质量指标

算例:

指标 来源平台 目标平台
输出逐字相同率 — 78%
任务层达标率 91% 90%
└ 分类任务 96% 96%
└ 长文档摘要 84% 82%

逐字相同率只有 78%,但任务达标率只差 1 个百分点。不同执行顺序、不同内核的浮点累加顺序都会造成文本差异,这不等于质量下降。反过来,只看平均任务得分也会漏掉分层退步(长文档摘要掉了 2 个百分点)。

所以本节验收要求三条同时做:身份对齐、数值分布检查、独立任务质量验收并分层。保留关键业务分层与失败样本,量化变体单独验收。

14.3.3 同名称不代表同路径

同称 FP4 / FP8 不保证存储、激活、累加或 KV 路径相同(第11章 11.2)。量化格式、缩放规则与实际内核必须在报告中显式披露 SOURCE_REGISTERED S09。

依据与核验入口:R14 R27 S10


14.4 两条性能比较轨道

本节要回答的问题:是要求相同配置,还是允许各平台合理调优?

14.4.1 两条轨道,分别报告

轨道 定义 回答的问题
约束匹配轨 同精度、同并行、同批形状等尽量一致 "在相同约束下谁更快"
平台优化轨 各平台在同质量与同 SLO 下合理调优 "可交付能力谁更强"

算例:

轨道 平台 A 平台 B 结论
约束匹配轨 5.20 req/s 4.60 req/s A 高 13.0%
平台优化轨 5.80 req/s 6.10 req/s B 高 5.2%

两条轨道的结论方向相反。只报告一条并非天然错误,但必须把结论严格限定到该轨道;以下是越界表述:

  • 只报约束匹配轨 → 宣称"A 更快",但掩盖了 B 调优后更强;
  • 只报平台优化轨 → 宣称"B 更快",但把工程投入伪装成了硬件差异。

14.4.2 调参预算必须披露

上例中 A 用 2 人日、B 用 10 人日。这个数字必须写进报告,因为"合理调优"不是免费的,而且两侧投入不等时,完整方案差异中包含调优投入;可以用于交付方案选择,但不能全部归因于硬件。

14.4.3 命名与分开报告

每个结果都要有完整命名:{平台, 精度路径, 并行布局, 轨道, 调参预算, 负载, SLO}。不要求不存在的跨平台相同参数——强行统一参数会让某一侧处于不合理劣势。

依据与核验入口:R14 R23


14.5 差异诊断与不可比项处理

本节要回答的问题:发现性能差距后,哪些解释有证据?

14.5.1 先排除再解释

按顺序排除:输入与 Token 口径 → 并行布局 → 客户端与网络 → 执行路径(内核/图/回退)→ 指标映射。软件默认值、内核回退、集成依赖与 API 指标迁移都在排除清单里 SOURCE_REGISTERED S17。

某项能力在指定版本已移除时,应记录为迁移缺口,而不是继续扫一个不存在的参数。

14.5.2 只用共同观测边界作差

不能拿一侧的内部时间与另一侧的端到端时间作差。若平台 A 能取到引擎内部排队时刻而平台 B 不能,则排队这一层在跨平台比较中不可比,只能在共同边界(如客户端端到端 + 服务端终态)上比较。

14.5.3 无法隔离时如实报告

无法隔离硬件与软件影响时,正确写法是"方案差异",并说明需要什么证据才能进一步拆分。归因粒度必须与证据粒度一致——不能用不可比指标填补结论。

依据与核验入口:R01 R20 S01 S20


14.6 上线验收、双跑与回滚

本节要回答的问题:基准通过以后,迁移还需要证明什么?

14.6.1 灰度与影子验证

从实验流量走向真实服务,需要独立检查:长运行、突发、错误恢复、资源配额、观测覆盖。

影子流量与双跑必须经过授权并控制数据与资源影响——不能把双倍请求无条件发向生产。

14.6.2 回滚不只是恢复软件

算例:

回滚组成 耗时
软件回滚(进程重启) 15 min
旧缓存/状态制品重建 42 min
合计 57 min

完整时长是只算重启的 3.8 倍;按完整时长作分母,漏计比例为 73.7%。若旧状态格式不可读,就必须重新构建,这部分成本属于迁移成本的一部分,应在评估阶段就计入 SOURCE_REGISTERED。

正式维护线与模型限定 RC 要分别建立回滚路径 SOURCE_REGISTERED S20。

14.6.3 变更治理

迁移报告包含:回滚触发条件、旧环境保留策略、旧状态兼容性说明。本书交付设计,不默认实际执行生产切换。

依据与核验入口:R24 S19 S13


14.7 实验:编写一份可决策的迁移报告

本节要回答的问题:怎样在不夸大差异的情况下给出采用建议?

14.7.1 逐项归纳,不给综合分

用同一任务集完成两侧证据收集,按功能、质量、性能、稳定性、运维逐项归纳。不要强行给每个平台一个综合分——综合分会掩盖不可比项与未知项。

14.7.2 允许分负载的差异化结论

合格的结论形态包括:

  • "某类负载适合迁移,另一类保留原平台";
  • "仍缺关键证据,暂不决策";
  • "功能缺口 2 项,补齐后再评估"。

14.7.3 未完成的项不写成已通过

每条建议都要能回到验收数据。未完成的项目保持未完成状态。

依据与核验入口:R14 R24 S20 S22


14.8 深化:质量非劣、配对比较与迁移风险预算

14.8.1 先规定“允许差多少”,再看结果

迁移验收不应只有“目标平台质量约90%”一句话。设来源与目标在同一独立任务集上的通过概率差为 \(\Delta=q_B-q_A\),业务预先允许的最大退步为 \(\delta\)。一个非劣判据可以写为:差值置信区间的下界高于 \(-\delta\)。这里的 \(\delta\) 来自业务风险,不由观察到的差值倒推。

例如两侧都得到90%的通过率,可能是同一批任务失败,也可能是完全不同的任务失败。总体百分比相同不能说明关键任务被保住。因此按同一任务配对,保留四格:两侧都通过、只有A通过、只有B通过、两侧都失败。真正影响差异判断的是两个不一致格,而不是只保存两侧独立的均值。

对于开放式生成,评审需要明确盲评方式、裁判版本、评分规则与重复生成次数。固定seed可能降低部分波动,但不同执行路径未必逐字一致;逐字一致性与任务质量是两条验收轴。长上下文、工具参数和结构化任务的严重错误可以设单独否决门槛,不能用其它简单任务的高分抵扣。

14.8.2 两条性能轨道各有可回答的问题

约束匹配轨用于控制差异,平台优化轨用于比较可交付方案。两者都不自动隔离纯硬件因果:即使模型、精度和设备数量相同,内核、编译、驱动和互联仍可能不同。报告应明确结论单位是“这个完整组合”,不能把方案差距直接缩写成芯片差距。

调参预算可以按预先限定的人员工时、候选配置数或实验设备小时约束。较大的调参预算不必然使结果无效,但它是可交付成本的一部分:一侧需要持续专项维护才能保持优势,另一侧默认配置即可满足契约,这本身就是采购与迁移的决策信息。

不要为了表面公平强迫两侧使用语义不同的同名参数。例如两个后端的批预算可能统计不同Token集合,或一个参数控制活跃序列、另一个控制调度轮次。先对齐控制对象,再决定参数是否有可比值。

14.8.3 性能差异要有运行级配对

把相同负载轨迹分别用于A、B,并按区组记录环境、顺序和冷暖状态。对第 \(j\) 个区组计算性能差 \(d_j\) 或对数比 \(\log(Y_{B,j}/Y_{A,j})\),再汇总区组分布。不能因为一次运行里有十万条请求,就把十万条都当成独立的跨平台重复实验。

若业务关注SLO约束容量,比较对象是各自满足同一质量、延迟、拒绝和稳定条件的最大已测到达率,而不是分别挑选各自最高tokens/s。没有覆盖可行边界时,结论应写成“至少支持当前档位”,不能输出未经测量的精确容量。

14.8.4 把迁移风险写成可停止的窗口

迁移期间至少有三种负担:双跑增加资源消耗,缓存变冷增加每请求工作量,回滚保留环境增加固定成本。它们发生在不同时间段,应分别计入而不是用一个固定百分比代替。

设灰度阶段允许增加的失败请求预算为 \(B_f\)、额外设备小时预算为 \(B_h\)、最长回滚恢复时间为 \(T_r\)。每个扩流阶段都检查累计预算和关键分层门槛;达到停止条件就不再扩流,先恢复或补证据。这样的验收比“跑三天没报错”更可执行,因为它明确了运行期间可以承担多少风险。

本章 57 分钟的回滚示例属于构造数据,只在软件恢复15分钟和状态重建42分钟串行时相加。若两者可并行,应该重新画依赖图;若旧实例仍保持就绪,回滚路径可能完全不同。任何一个数字都不能脱离实际部署结构成为通用承诺。


14.9 实验图表补充:保留双轨比较,而不是制造硬件排行榜

检索 MLCommons 官方比较规则后,本书没有把不同模型、精度、可用性分类、场景和设备数量的公开结果拼成芯片排行榜。那样可以很容易填满一张图,却会违背本章的可比性契约。这里把14.4.1的双轨数据画清楚,数字仍保持原有证据身份。

约束匹配轨A/B为5.20/4.60 req/s,A相对B高13.04%;平台优化轨为5.80/6.10,B相对A高5.17%。方向反转不是矛盾:前者回答在限定条件下的差异,后者回答允许各自优化后交付方案的差异。两轨不能只挑有利的一轨写结论。

真实迁移实验需补齐:固定任务集、质量门槛、输入输出分布、并发/到达轨迹、预热与缓存状态;列清调参人日、精度变更、功耗和系统边界。质量使用相同样本配对评估,性能按独立运行重复,不能把一次运行的几千个请求假装几千次独立复现实验。

本书没有本机双平台资源,也没有与本章 A/B 一一对应的公开运行记录,因此不以别人的数字替换A/B。

图14-V1 约束匹配轨与平台优化轨,结论可以反向

图14-V1 约束匹配轨与平台优化轨,结论可以反向

数据身份:TEACHING_CALCULATION。条件:数字沿用本章设定;轨道内需相同质量/SLO/负载;不混用卡、节点、资源池边界。

轨道 A req/s B req/s 该轨道结论
约束匹配轨 5.2 4.6 A较B高13.04%
平台优化轨 5.8 6.1 B较A高5.17%

出处与边界:X12;不能映射为任何真实厂商排名;调参预算、精度、功耗和软件差异均需披露。

数据与绘图点 · PNG · 重绘代码

14.10 第三方数据补充:跨平台比较:先确认条件等价,再谈数字

本章前面讲迁移验收的口径。这一节补一组同轮次、同规则的跨平台第三方条目,并刻意放入一个不可比的反例。

可比的三个(MLPerf Inference v4.1,Closed division,llama2-70b-99.9,Server 场景,约束 TTFT≤2 s 且 TPOT≤200 ms)。同为 8 加速器、同模型、同精度档、同场景、同可用性时:H200×8 为 32,789.74、H100×8 为 21,605.79、MI300X×8 为 21,028.23 tokens/s。

不可比的一个。同轮次还有 B200-SXM-180GB×1 的 10,755.59 tokens/s。它不能与前三个排名:加速器数量是 1 而不是 8,可用性分类是 Preview 而不是 Available。跨平台比较的第一条纪律不是"选哪个指标",而是"条件是否等价"。

吞吐相当 ≠ 体验相当(同轮次条目的延迟分位)。MI300X×8 与 H100×8 的吞吐很接近(21,028 对 21,606),但 TTFT 的 P50 是 990.2 对 272.95 ms——差 3.6 倍,P99 差 3.5 倍。前者反映稳态产能,后者反映每个请求要等多久才开始出字。同时 MI300X 的 TPOT P99 已达 197.87 ms,逼近 200 ms 门槛,余量远小于 Hopper 平台。

边界:提交方为各硬件厂商(运行于 MLCommons 统一规则并经受审收录);仅覆盖 NVIDIA 与 AMD 两家具备可比条目的 LLM 加速器;软件栈不同;系统级结果。

图14-T1 同规则同条件下的跨平台吞吐:可比的三个与不可比的一个(第三方实测)

图14-T1 同规则同条件下的跨平台吞吐:可比的三个与不可比的一个(第三方实测)

数据身份:THIRD_PARTY_BENCHMARK(第三方公开数据)。条件:MLPerf Inference v4.1,Closed division,llama2-70b-99.9,Server 场景,统一约束 TTFT≤2s 且 TPOT≤200ms;各系统 performance/run_1/mlperf_log_summary.txt。

系统 加速器数 可用性 tokens/s 是否可直接比较
H200-SXM-141GB ×8 8 Available 32789.74 可(同轮次/同模型/同场景/同加速器数)
DGX-H100 H100-SXM-80GB ×8 8 Available 21605.79 可
8×MI300X + 2×EPYC-9374F 8 Available 21028.23 可
B200-SXM-180GB ×1 1 Preview 10755.59 不可(加速器数=1 且可用性=Preview)

出处与边界:Z09;提交方为各硬件厂商(NVIDIA/AMD),运行于 MLCommons 统一规则并经受审收录;仅覆盖 NVIDIA 与 AMD 两家具备可比条目的 LLM 加速器;软件栈不同。

数据与绘图点 · PNG · 重绘代码

图14-T2 跨平台不只看吞吐:尾延迟的形态差得更远(第三方实测)

图14-T2 跨平台不只看吞吐:尾延迟的形态差得更远(第三方实测)

数据身份:THIRD_PARTY_BENCHMARK(第三方公开数据)。条件:同图14-T1 的三个可比条目;约束同为 TTFT≤2s、TPOT≤200ms(P99)。

系统 TTFT P50/ms TTFT P99/ms TPOT P50/ms TPOT P99/ms
H200 ×8 224.66 510.35 160.32 181.53
H100 ×8 272.95 461.28 156.42 175.35
MI300X ×8 990.2 1604.17 143.94 197.87

出处与边界:Z09;提交方为各硬件厂商;软件栈不同;系统级结果。

时效提示(2026-10-09):图14-T1/T2 锚定在 MLPerf Inference v4.1,数字对该轮次成立,但已非最新轮次。截至检索日的最新可比锚点为 v5.0 / v5.1:NVIDIA 官方博客公布的 v5.0 Closed / Datacenter 结果为 8×B200 98,443 token/s 对 8×H200 33,072 token/s(Server 场景,3.0×)。换轮次必须整表重取——v4.1 的 H200×8 = 32,789.74 与 v5.0 的 H200×8 = 33,072.00 分属两轮,不能混在同一张表里比排名。

另有一条可独立复核的佐证,用来支撑"吞吐相当 ≠ 体验相当"这一节结论:AMD ROCm 官方发布的 MLPerf 复现文档中,一次 Server 场景性能跑给出 completed 29,522.56 token/s,TTFT P50 950.5 ms / P99 1,761.0 ms,TPOT P50 132.6 ms / P99 165.0 ms,约定门槛为 2000/200 ms。它达标了,但 P99 TPOT 距门槛仅剩 17.5% 余量,而 P99 TTFT 距门槛尚有余量——约束卡在 TPOT 侧的形态与图14-T2 一致。引用边界:该日志片段未标明具体平台与加速器数,用作论据时必须补注这一项,不能当作某型平台的官方成绩。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 同模型不同精度可以比较,但该如何命名结论?

答案方向:命名为方案比较,不是硬件比较。结论必须带完整限定:{平台, 精度路径, 并行布局, 轨道, 调参预算, 负载, SLO},并显式披露精度细节——同称 FP4/FP8 不保证存储、激活、累加与 KV 路径相同(第11章 11.2),分块缩放的实际位宽也不同(NVFP4 为 4.5 bit/元素)。若两侧精度路径不同,报告中应写"在 A 走 FP8、B 走分块 4-bit 的方案下……",而不是"平台 B 比 A 快 X%"。

2. 一方经过两周调优、另一方默认配置,报告必须披露什么?

答案方向:三件事。① 调参预算(人日)与调优项清单;② 两条轨道分别报告——约束匹配轨 5.20 vs 4.60(A 高 13.0%)、平台优化轨 5.80 vs 6.10(B 高 5.2%),只报一条时必须限定其结论,不能外推为另一轨道的结论;③ 结论归属——平台优化轨的差异包含工程投入,不能写成硬件能力差异。若只报平台优化轨,等于把 10 人日的投入伪装成平台优势。

3. 回滚恢复软件后旧缓存无法读取,迁移成本应如何补记?

答案方向:回滚成本 = 软件回滚 + 状态/缓存制品重建 + 兼容配置恢复,三者都要进迁移评估,不能只算进程重启(本例 15 min vs 合计 57 min,完整时长为原估计的 3.8 倍)。因此:① 迁移前就要确认新旧状态格式是否可读,不可读时把重建时间写进回滚预案;② 灰度期间保留旧环境与旧制品,使回滚路径真实可用;③ 报告里把"回滚时间"作为一项独立的验收门槛,而不是运维细节。


本章验收清单

节 验收标准
14.1 比较对象明确(卡/服务器/互联域/资源池);差异账本列出已匹配、可匹配未匹配与不可控三类;结论归属到组合而非单一芯片
14.2 需求在部署前写死;必需/可替代/非必需分列;取消、超时、观测等非演示功能已入清单
14.3 身份对齐先于数值比较;质量验收分层且用独立集;同名称精度路径显式披露
14.4 两条轨道分别报告;调参预算披露;结果命名完整
14.5 排除顺序已执行;只用共同观测边界作差;无法隔离时写"方案差异"
14.6 双跑经授权;回滚含状态制品重建;正式维护线与 RC 分别有回滚路径
14.7 建议按负载分列;未完成项不写已通过;不给综合分掩盖未知

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第15章 国产加速器推理实践

核心论点:国产实践采用具体软件栈与具体实验说话:先验证功能与质量,再诊断执行、通信与服务边界。


15.1 建立国产平台研究对象

本节要回答的问题:本章验证的是哪款硬件、哪个版本、哪种部署?

15.1.1 不存在抽象的"国产卡"

第一条纪律:不能以"国产卡"作为统一测试对象。必须记录到:具体硬件型号、卡数与服务器拓扑、模型与完整软件版本、部署形态。

案例身份卡字段:

字段 内容
硬件 型号、卡数、拓扑(同域/跨节点)
软件栈 驱动、固件、CANN、框架适配、推理后端及各自版本
模型 名称、revision、精度方案
部署 单实例/多副本/是否分离
资源层级 E1—E4 中实际具备哪一级
证据分层 官方说明 / 第三方结果 分别标记

15.1.2 研究问题与能力范围

先写清要回答的问题(功能是否齐全?质量是否达标?性能边界在哪?),再说明本次能验证到哪一层。没有设备的部分可以完成资料研究与实验设计,但不能填写性能结果。

15.1.3 不沿用未经核验的产品参数

不沿用传闻、二手评测或未经核验的规格。公开官方能力与第三方结果分开标记,不得混写。

15.1.4 结论绑定组合

所有结论绑定明确组合,不从一个案例外推整个国产生态。

依据与核验入口:R19 R33


15.2 国产栈依赖:正式版、模型限定 RC 与原生路径

本节要回答的问题:如何把驱动、CANN、框架与推理后端组合起来?

15.2.1 版本化依赖对照

以下表是资料登记,不是部署推荐或已跑通的环境 SOURCE_REGISTERED S20:

路径 来源身份 已补齐的官方依赖 本地状态与限制
vLLM Ascend 维护版本条目 0.23.0.post1(S19) 保留历史身份;不与 RC 的依赖混搭 本书未重建
模型限定 RC 0.26.0rc1;短提交 f2f74a1(N07) vLLM 0.26.0;Python≥3.10、<3.13;CANN 9.1.0;PyTorch 2.10.0;torch_npu 2.10.0.post4 限定 Kimi K3、GLM-5.2、DeepSeek V4 Flash0731/Pro0813;不是通用升级推荐
原生服务路径 MindIE 等 仍需其自身兼容矩阵 不继承 vLLM Ascend 结论

该 RC 发布说明还给出 Triton Ascend 3.2.2 的硬件范围,以及镜像内 Mooncake 0.3.11.post1。驱动、固件、镜像 digest 和完整部署启动条件仍需按所选硬件补齐。第15.8节使用其测试报告做公开证据阅读案例,不把维护者验证当作独立验证。

15.2.2 原生路径独立核验

MindIE 与其它原生服务路径单独建立矩阵,不套用 vLLM Ascend 的结论。现有复核对 MindIE 入口未取得实质正文 SOURCE_ENTRY_ONLY,因此只保留研究入口与需要核验的字段,不给最新版本、支持能力或性能结论。

15.2.3 至少一套组合完成实际重建后才可声明可复现

仅资料快照不构成兼容认证。本节验收要求:至少一套组合完成实际重建,其余保持"设计/待验证"。

依据与核验入口:R19 S19 S26


15.3 模型部署与功能质量基线

本节要回答的问题:第一份国产平台基线应该先证明什么?

15.3.1 先对齐输入

沿用第13、14章规范:模板版本、分词器、停止规则、采样设置先对齐,再谈性能。模型与输入没对齐,后面所有数字都不可比。

15.3.2 功能边界测试结果优先于性能

匿名后端的否决示例(不是对昇腾或该 RC 的功能测试结果):

检查项 结果
功能冒烟 12 项 通过 10 项
不支持 2 项 取消传播、结构化输出约束
质量达标率 87%(目标 90%)

结论:质量未达标,性能数据不能作为合格迁移能力。这两项不支持的功能也不是"以后再补"——取消传播缺失会直接产生幽灵负载(第1章 1.6),结构化约束缺失会让下游解析失败率上升。

15.3.3 基线选择标准

基线不需要一开始用最大模型或最大并行度。优先选择可长期访问并能重复测试的组合——可重复比规模更重要。

依据与核验入口:R19 R14


15.4 原生剖析与指标语义

本节要回答的问题:怎样从国产工具中取得可解释证据?

15.4.1 入口与正文是两个状态

所引特定入口(msprof 26.1.0)的复核只取得站点框架,body_verified = false SOURCE_ENTRY_ONLY R20。

这意味着:不能按该入口宣称具体命令已核验。正确做法是——执行前先取得对应版本的工具文档,保存字段列表、权限要求、采集开销、时间域,然后才开始。该特定入口的空缺继续保留;CANN 8.5.0 工具正文另见 15.8.3,示例有版本边界,不把两者视为同一环境。

15.4.2 禁止按名称猜测指标等价

厂商原生计数器不必与其它平台的工具同名,也不必同义。典型形态:

原生计数器 不能直接等价于 原因
计算核心占用率 另一平台的"GPU 利用率" 分母可能是时间占比、容量占用或带宽比(第9章 9.4)
内存占用率 显存字节/s ÷ 峰值带宽 定义不同

统一报告应保留原始语义(第13章 13.6),并为每个字段记录映射规则与版本。

15.4.3 可追溯性

本节验收要求:读者能从一条分析结论追溯到原生记录、时间域、转换规则三者。缺任一项,该结论不可发布。

依据与核验入口:R20 S24


15.5 执行、内存优化与已撤回配置核对

本节要回答的问题:怎样判断问题来自模型适配、算子还是资源调度?

15.5.1 竞争假设,不套用其它平台的参数

复用第10章的方法,先列候选:输入或版本不匹配 / 实际执行回退 / 状态压力 / 主机供给 / 通信等待。然后按信息量排序取证。

硬纪律:不默认套用其它平台的参数名与取值。匿名反例(不代表某个已核验的国产引擎存在该行为):把某个在 CUDA 侧常见的融合开关直接迁移过来配置 —— 无效,因为该版本没有这条路径,而且不报错。

改用本版本支持的图模式后:单步从 4.6 ms 降到 2.9 ms(−37.0%),另假设独立质量评估从 87% 降到 85%,则需回退复核;不能把这段构造示例解释为图执行必然损害质量。

15.5.2 已移除配置不能照旧教程推荐

复核快照中 0.26.0rc1 已移除 FlashComm2 等旧优化路径 SOURCE_REGISTERED。

旧教程中的预取或融合开关不能直接成为新 RC 的建议。每次实验先查本版本支持、替代方式与实际执行。

15.5.3 不把差距笼统称作"生态问题"

两个本节明令禁止的写法:

  • "只调通参数"不算归因成功——需要机制解释与回退复测;
  • 不把全部差距笼统称作生态问题——那是不归因,不是结论。

15.5.4 每次调整后回到同一业务负载

优化后必须回到同一负载,验证质量、延迟与稳定性三者(第11章 11.7 的顺序:质量先于性能)。

依据与核验入口:R19 R26


15.6 多卡通信、集群与混合部署

本节要回答的问题:通信异常与模型计算问题怎样分离?

15.6.1 三类结论分开

结论类型 测什么 能推出什么
通信微基准 链路带宽与延迟 链路健康
模型性能 端到端与阶段耗时 该模型在此布局下的表现
业务容量 同 SLO 下的合格完成速率 可服务能力

三者互不替代。微基准健康不保证模型端到端高效;模型通信时间长也不一定代表链路故障(可能是消息尺寸分布或 rank 偏斜,第6章 6.5)。

15.6.2 原生通信库入口的状态

HCCL 的历史 alpha 入口在复核中未取得实质正文 SOURCE_ENTRY_ONLY R33。

它不能证明当前部署的通信兼容性。选定运行环境后必须补:原生通信文档 + 小规模健康测试,再讨论真实模型的 dispatch/combine 或其它消息。不能以旧网页标题替代验证。

15.6.3 示意形态

典型观察(构造示例):某通信微基准带宽设为 190 GB/s(取值;未给消息、拓扑与合格基线,不能仅据此判健康),但模型端到端中通信段占比 24%,且最慢 rank 比均值慢 38%。

→ 三个数字分别提示传输速率、通信活动与 rank 差异;链路是否健康及通信是否暴露在关键路径,仍需进一步证据。微基准健康与模型高效是两件事。

15.6.4 混合部署

跨节点或混合部署需分别验证:状态兼容性、失败恢复、服务目标。集群实测必须有实际资源,无资源时只提交设计。

依据与核验入口:R33 R20 S20


15.7 实验:形成国产平台工程案例

本节要回答的问题:一章完整案例应当交付哪些证据?

15.7.1 同一证据链

最终案例按同一链条编写:

环境身份 → 任务与契约 → 质量基线 → 性能基线 → 竞争假设 → 受控干预 → 回退复测 → 结论与边界

15.7.2 允许三种结论

  • 采用:功能、质量、性能、稳定性全通过;
  • 继续验证:部分证据不足;
  • 不采用:质量不达标或代价过高。

不需要预设宣传方向。能证明某项配置不值得采用,同样是一份合格案例。

15.7.3 分层不能混

官方说明、第三方结果与待验证判断三层清楚分开。不用第三方数字冒充官方声明。其它国产后端沿用相同案例模板,只有证据完整后才加入比较。

依据与核验入口:R19 R33 S20 S25


15.8 深化:用官方测试报告建立一个可审查的国产栈案例

15.8.1 已补齐的证据是什么

本节依据 vLLM Ascend v0.26.0rc1 发布说明及其链接的测试总结 N07。它们分别提供依赖/支持边界和测试覆盖记录;没有提供本书自己的运行日志。因此本节是一份官方案例审读,不把它改写成作者部署成功的经历。

时效提示(2026-10-09):本节锚定在 vLLM-Ascend v0.26.0rc1;截至检索日最新为 v0.27.1rc1(2026-09-24),其声明的完整验证模型已收缩为 MiniMax-M3 与 DeepSeek-V4-Flash 两项(两节点 1P1D 与 KV Cache Pool 部署),并明确其余组合"未充分验证、不属于本次发布声明范围"。读官方报告时必须区分"覆盖场景"与"场景结果"、区分实测与按接受率折算——这两条纪律在版本更替时同样适用。

测试总结列出Kimi K3 W4A8在A3八节点EP、1P1D,以及Ascend 950四节点EP、1P1D等组合。质量表给出Kimi K3的GPQA报告得分92.42与OCRBench报告得分0.88 N09。不同基准的得分量表不同,本书保留原值,不将两者放在同一百分比柱图上,也不据此计算跨平台优劣。

证据对象 官方报告实际提供 本书不能补造
环境与覆盖 具体模型、量化、平台及部分并行/P/D组合 完整驱动固件、镜像digest和全部运行参数
性能场景 P-only、D-only、端到端和不同长度/前缀比例 页面未给出的逐项吞吐、延迟分布、误差条
质量 部分基准原始分数 样本级标签、配对差异或统计区间
稳定性 报告声明的测试覆盖 本书环境的长期稳定保证

这些缺失不是“可以按相近型号估算后填入”的空白。实验条件不足的公开结果可以用来选择测试问题,不能直接拿来填本地容量表。

15.8.2 一个真实的负面案例:长输入下草稿并未带来有效收益

同一官方测试总结的已知问题记录指出,Kimi K3长输入场景存在性能未达预期的问题,并讨论DSpark草稿在较长输入下接受效果不足、未取得有效收益,修复被列入后续版本计划 N09。这比构造“国产平台某功能不支持”的故事更有价值:它给出了版本、模型、场景和待改进方向。

这条记录支持的结论是该报告中的特定组合存在已知限制,不是“国产平台不适合推测解码”,也不是“草稿接受率低就是全部根因”。本地复查应固定真实内容、输入长度、并发与输出规则,记录接受长度分布、草稿时间、目标验证时间和合格完成速率。后续版本的修复是否完成,须另查对应发布记录并复测;计划本身不等于修复结果。

15.8.3 原生剖析正文已经取得,但版本范围不能串用

另据 CANN 社区版 8.5.0 离线剖析指南 N08,指南给出的基本形式是 msprof --output=<输出目录> <用户应用>,并说明可导出时间线与算子统计。以下是按该形式编写的条件化启动包装,仅做本地shell语法检查,未在昇腾设备执行;使用前必须核对已安装工具的帮助与权限。

#!/usr/bin/env bash
set -euo pipefail
: "${OUT:?请设置隔离的输出目录}"
: "${APP:?请设置待剖析应用的路径}"
command -v msprof >/dev/null || { echo "未找到 msprof" >&2; exit 1; }
[[ -f "$APP" ]] || { echo "APP 文件不存在" >&2; exit 1; }
mkdir -p "$OUT"
# 示例仅适用于已经验证依赖的 Python 应用。
# 其余应用形式、参数和采集开关应按所用版本指南改写。
msprof --output="$OUT" python3 "$APP"

该指南列出的产物包括 msprof_*.json 时间线、op_summary_*.csv 算子明细、op_statistic_*.csv 算子汇总和 task_time_*.csv 任务调度记录 N08。先确认实际产物,再按任务身份、时间域和调用关系解释;列名相似不等于可与CUDA工具直接比较。

8.5.0剖析指南与RC所列CANN 9.1.0不是同一环境。本节只是补全了一个以前未取得正文的工具路径,不能把两份资料拼成已验证的安装组合。

15.8.4 下一步实验必须能区分机制

若模型通信耗时很高,先记录消息类型、尺寸、rank启动和结束时刻,再在相同布局下做通信健康检查。通信微基准正常只能排除它覆盖的故障模式,不能排除模型负载中的偏斜、同步频率或计算—通信依赖问题。

若图执行前后质量变化,应先检查模型、精度、模板、采样和停止条件是否真正一致,再判断是否存在数值路径问题。不能把一次质量下降当成图执行的固有性质。国产平台实践的深度来自这种可区分的证据链,而不是多列几个品牌名称或没有出处的性能倍率。

15.8.5 一个可重绘的官方负面数值

同一测试总结的已知问题还记录了Qwen3-32B W8A8在A2、P/D共置pooling测试中,开启pooling后原生TPS由2443降至2313,约下降5.32% N09。它是报告中的历史问题记录,不代表该RC当前对该模型作全面支持承诺。

图15-R1 pooling官方负面案例

完整长度、并发和重复条件未提供,所以本书只重绘两项原值,不补造性能曲线,也不将它解释成所有缓存池都会退化。


15.9 实验图表补充:补齐官方质量表与测试场景范围

X09固定提交的Accuracy表给出GLM-5.2、Kimi K3、DeepSeek-V4-Flash、DeepSeek-V4-Pro的GPQA Score分别92.165、92.42、91.67、91.92,另给Kimi K3 OCRBench Score 0.88。图将GPQA和OCRBench分成两个面板,完整保留报告原值与模型名。

原文只写Score,未在这张表中完整交代GPQA子集、样本数、提示模板及统计区间。本书不因分数相差 0.255 就判断模型显著更优;也不把OCRBench的0.88直接转成“880道正确”,因为报告未披露足够的计分和归一化链路。正文相应用语改为“报告得分”,避免与基准原生量表混淆。

该报告还列出9种性能场景:P-only与D-only分别覆盖16k→1k、128k→1k、40k—80k变长→2.5k;端到端覆盖16k→1k且前缀0%、128k→1k且前缀90%、1M→1k且前缀比例未注明。覆盖场景不等于给出了场景结果,逐项吞吐、P99、误差条仍缺,不能用相近型号推算补入。

图 15-R1 的 pooling 由 2443 降到 2313 TPS,按原值复算下降 5.32%。它仍是特定Qwen3-32B/W8A8/A2/P-D共置单前缀负例,不扩大成所有缓存池结论;DSpark长输入无收益也只保留为该组合限制。

图15-V1 国产推理栈公开质量数据:先保留量表,再谈结论

图15-V1 国产推理栈公开质量数据:先保留量表,再谈结论

数据身份:PUBLISHED_RESULT。条件:报告4模型GPQA与1模型OCRBench原值;对应部署见图13-V1;量表分开。

基准 模型 报告Score
GPQA GLM-5.2 92.165
GPQA Kimi K3 92.42
GPQA DeepSeek-V4-Flash 91.67
GPQA DeepSeek-V4-Pro 91.92
OCRBench Kimi K3 0.88

出处与边界:X09;报告未给GPQA子集/题数及逐样本数据;保留Score,不由0.88反推880个正确,也不做显著排名。

数据与绘图点 · PNG · 重绘代码

15.10 实验深化补充:昇腾公开数据的两种口径:阶段实测与接受率折算

本章前面固定了 vLLM-Ascend 的部署覆盖与质量原值。这一节补入华为《昇腾服务器 DeepSeek V3/R1 推理部署最佳实践》的阶段性性能数据,并严格区分两种口径。

口径一,阶段实测(Prefill,Atlas800I A2、2节点16卡、MLA TP16、A8W8C16)。报告表3给出卡均输入Token/s:2K×8为1622,1K×16为1650。两者总Token量相近但请求数不同(8对16),说明“相同总Token不等于相同请求数”。

口径二,接受率折算(Decode,4节点32卡、MTP)。在TPOT≤100ms约束下,按默认70%接受率折算,2K+2K与1K+2K分别为卡均723与784输出Token/s;若按80%接受率折算为765与830;按90%折算为808与876。这六个数值全部是按接受率折算得到的,不是六次独立的实测。723→808来自接受率假设的变化,不能当作已验证的生产提速;90%接受率也不是任意业务都能达到的值。

不要相加。Prefill的1622是卡均输入吞吐,Decode的723是卡均输出吞吐,二者维度不同,不能相加成端到端吞吐,也不能据此推整机服务能力。具体芯片型号、权重revision与软件精确commit报告未完整披露,本书不补造。

图15-E1 昇腾Prefill:先把单阶段实测与服务能力分开

图15-E1 昇腾Prefill:先把单阶段实测与服务能力分开

数据身份:PUBLISHED_STAGE_EXPERIMENT。条件:Atlas800I A2,Prefill2节点/16卡,MLA TP16;总输入16K;DeepSeek V3/R1报告A8W8C16路径;表3。

输入×总batch Prefill卡均输入Token/s
2K × 8 1622
1K × 16 1650

出处与边界:Y06;卡均输入吞吐不是输出吞吐或完整PD能力;具体芯片、权重revision、软件精确commit未披露,不补造。

数据与绘图点 · PNG · 重绘代码

图15-E2 昇腾Decode:接受率折算不是六次独立实测

图15-E2 昇腾Decode:接受率折算不是六次独立实测

数据身份:PUBLISHED_ACCEPTANCE_ADJUSTED。条件:Atlas800I A2;Decode4节点/32卡;DeepSeek V3/R1、A8W8C16、MTP;TPOT≤100ms;报告默认70%接受率折算,80/90%为其他折算。

MTP接受率/% 2K+2K、卡均72并发/输出Token每秒 1K+2K、卡均80并发/输出Token每秒
70 723 784
80 765 830
90 808 876

出处与边界:Y06;全部点标为报告折算值,不称六组真实内容实验;不能把90%接受率当作任意业务可达值。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 为何不能把其它平台的参数名称直接迁到国产后端?

答案方向:参数名对应的是具体内核与执行路径的存在性,不是通用语义。路径不存在时,开关要么被忽略、要么报错,而"被忽略"最危险——你以为开启了优化,实际什么都没发生(15.5.1 的反例)。正确做法:先查本版本的能力矩阵与实际执行路径(trace 内核名),确认路径存在后再调,并且每次调整后回到同一业务负载验证质量、延迟与稳定性。另外,同一名称在不同版本上的默认值与生效条件也可能不同(第1章 1.5 的四态记录)。

2. 通信微基准通过为何仍可能有模型同步问题?

答案方向:微基准测的是链路的带宽与延迟,模型侧还受四件事影响:消息尺寸分布(Decode 侧消息很小,延迟主导,第6章 6.2)、rank 偏斜(容量由最忙 rank 决定,6.5.4)、依赖链与缓冲生命周期(6.5.3)、以及图模式与批形状是否让通信落在关键路径上。本节的构造示例中,微基准的读数为 190 GB/s(不足以单独判定健康),但通信段仍占端到端 24%、最慢 rank 慢 38%。所以三类结论(微基准 / 模型性能 / 业务容量)必须分开报告,互不替代。

3. 特定 RC 已移除的优化,能否照旧教程继续推荐?

答案方向:不能,而且要区分三种生命周期状态。已移除意味着路径不存在——旧教程的开关在新版本上直接失效(或静默无效);已弃用意味着仍可用但不建议新用;计划移除尚未发生,不得提前写成事实 SOURCE_REGISTERED S06。正确做法:每次实验前先查本版本的支持情况与替代方式,用 trace 确认实际执行路径,并把"已移除"记为迁移缺口而不是继续扫描一个不存在的参数。


本章验收清单

节 验收标准
15.1 结论绑定到具体硬件/版本/部署;官方与第三方证据分层;不从一个案例外推整个生态
15.2 依赖一致且有来源;正式维护线与模型受限 RC 分开;原生路径不继承其它服务栈结论;仅快照不构成兼容认证
15.3 输入身份先对齐;功能与质量结果优先于性能;质量不达标时性能数据不作合格能力
15.4 入口与正文状态分开;原生计数器不按名称等价;结论可追溯到原生记录、时间域与转换规则
15.5 竞争假设完整;不套用其它平台参数;已移除配置不照旧推荐;不把差距笼统称作生态问题
15.6 通信微基准、模型性能、业务容量三类结论分开;原生通信库入口状态如实登记
15.7 案例沿同一证据链;允许"不采用"结论;官方说明与第三方结果不混写

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第16章 容量规划与扩容决策

核心论点:容量是一组负载、质量与服务目标约束下的可用边界,不是设备数量乘以单卡峰值。


16.1 容量对象与需求分层

本节要回答的问题:需要支撑的是请求数、Token 工作量,还是业务完成量?

16.1.1 日均相同不等于容量相同

算例:日均 100 万请求,两种到达形态:

情景 计算 情景中的窗口到达率
A 均匀(日系数 1.5) \(10^6 / 86400 \times 1.5\) 17.4 req/s
B 集中在 4 小时(占 60%) \(10^6 \times 0.6 / (4\times3600)\) 41.7 req/s

日均相同,两个所设时间窗口的到达率约差 2.4 倍。4小时的均值不是短窗口峰值;若窗口内不均匀,还需叠加突发轨迹。这就是"平均每日请求量不足以定义瞬时容量"的定量形式。

16.1.2 三个容量对象

对象 单位 适用问题
请求数 req/s 准入与排队
Token 工作量 Token/s 显存与批预算
业务完成量 任务/s 真正的交付目标

只报 req/s 会掩盖长度结构差异:100 req/s 的长文档与 100 req/s 的短问答是两种不同的负载(第5章 5.5.3)。

16.1.3 增长用情景不用单点

增长预测分成低/中/高情景,每个情景都绑定负载画像与服务目标,便于将来用实际数据更新。增长假设与观测事实分开记录。

依据与核验入口:R24


16.2 建立容量包络与服务曲线

本节要回答的问题:系统在哪个负载区间持续满足全部门槛?

16.2.1 包络来自联合约束

扫描到达率与长度混合,按延迟、拒绝、质量、稳定性四项找可用区域 (构造数据;联合达标率门槛设为 90%、TTFT P99≤1000 ms;另假设已标可用行的队列稳定且拒绝率满足约定):

到达率 长输入占比 排空法吞吐 P99 TTFT 达标率 可用?
20 20% 19.8 req/s 780 ms 96% 是
35 20% 34.2 req/s 890 ms 95% 是
45 20% 43.1 req/s 1180 ms 92% 否:TTFT 超门槛
50 20% 44.0 req/s 1650 ms 84% 否
45 50% 41.8 req/s 1420 ms 86% 否

关键读法:可用边界不是最高吞吐点。44.0 req/s 那一行的吞吐最高,但达标率 84% 已跌破 90% 门槛——它不是容量,它是过载点。35 req/s 是这张稀疏网格中最高的可行已测档位,不是已经精确识别的容量边界;35与45之间需加密扫描。

16.2.2 不稳定的判据

以下任一出现即限制边界:队列持续增长、资源累积(显存/句柄)、失败率上升、达标率下降。某次峰值表现不能代表持续容量。

16.2.3 分负载类别保留各自曲线

长输入档与短输入档各自的服务曲线要分开保存,因为瓶颈机制不同(第5章 5.4)。未测区间明确标记为外推。

依据与核验入口:R09 R24


16.3 突发、长尾和会话累积

本节要回答的问题:峰值需求与平均需求之间应留多少缓冲?

16.3.1 缓冲取决于三个量,不是一个百分比

算例:突发 3 倍、持续 90 s,队列积压峰值 620 个请求,回落后消化耗时 340 s。

\[ \frac{340}{90} = \mathbf{3.8} \]

积压消化时间是突发时长的 3.8 倍。所以缓冲 = f(突发强度, 突发时长, 恢复速率),三者缺一不可。

"固定留 20% 余量"不是通用答案——同一个 20%,在突发持续 90 s 与 15 min 的场景下可能都不够,且在其它条件相同时,更长突发不会凭空减少缓冲需求。

16.3.2 长任务与状态驻留

长输出与多轮会话会延长状态驻留,改变后续请求的可用空间(第4章)。共享历史分支与独立子任务要分别测并发与状态驻留 SOURCE_REGISTERED。

分支完成依赖决定任务长尾——不能用各子请求平均耗时相加代替真实任务时序(第1章 1.1.1)。

16.3.3 取消也要检验回收

任务取消后,子请求与状态能否及时回收要单独检验;否则取消只会把积压变成幽灵负载。

依据与核验入口:R04 S18


16.4 故障、权重热状态与实际可用容量

本节要回答的问题:少一个节点或正在升级时,服务还能维持什么水平?

16.4.1 三种容量分别报告

情景 可用容量 说明
正常 40 req/s 全部实例在役
维护中(滚动更新,1/4 下线) 30 req/s 75%
单节点故障 32 req/s 80%,但状态重建慢

未演练的恢复部分列为风险,不能假设冗余自动生效。故障期间允许的降级必须预先定义,不能在事故后修改成功标准。

16.4.2 冷启动进入容量账

新实例从创建到可接流量 = 115 s(第8章分解)。要维持故障期容量,扩容必须提前于需求至少 115 s 发起——这直接决定了扩容信号的提前量。

vLLM 0.30.0 的 Fast Start 由常驻 GPU 权重进程与 CUDA IPC 映射组成 N06。容量核算应按底层物理权重身份去重,而不是把 daemon 的权重和映射进程中的同一权重重复相加。若另设每卡 4 GB 非共享常驻开销,8卡共32 GB,只能标为假设;它不是该机制的官方固定开销。启动收益、实际增量显存与失效恢复分别测量。

冗余容量应扣除恢复过程的网络、存储与常驻状态成本,不是"加一台设备就完成" SOURCE_REGISTERED。

16.4.3 重新核验触发

缓存冷失效、驱动变更或模型更新后,启动时间与常驻收益都要重新核验。

依据与核验入口:R25 S02


16.5 扩缩容信号、控制回路与仿真边界

本节要回答的问题:什么信号比单一设备利用率更接近真实压力?

16.5.1 四类信号

信号 说明
等待 排队时间、P99/P50 比
工作量 Token/s、在途 Token
状态压力 状态池占用、块可用数
SLO 失守风险 达标率趋势

不依赖单一平均利用率——第9章 9.4 已说明"96% 忙碌"与真实压力是两回事。

16.5.2 控制回路要按真实时序相加,而不是累加所有配置数

设采样周期为 15 s,需要连续 3 次超阈值观测,创建实例至 readiness 为 115 s。阈值刚好在一次采样前越过时,第三次确认约在 30 s 后;刚好错过一次采样时,约在 45 s 后。因此,没有额外限速时,从越阈值到新实例可服务的区间为 145—160 s。

环节 时长/区间 是否必然串行累加
等待第一次命中采样 0—15 s 是
再取得两次确认 30 s 是
实例启动与 readiness 115 s 是
300 s 冷却/稳定窗口 取决于方向、上次动作与策略 不能默认全额加入本次扩容

若一个自定义策略明确规定扩容前还必须等待剩余 \(c\) 秒,\(0\le c\le300\),则上述区间再加 \(c\);若等待与采样确认重叠,应取依赖图的最大值而非再次求和。“15 s 采样、3 周期却为 180 s、合计 610 s”的计算不能成立。Kubernetes 的稳定窗口与扩缩容行为还区分方向,不能把缩容稳定窗口当成固定的扩容阻塞 N19。

缩容另需处理在途请求排空、缓存损失和回滚,不与扩容等待混成一个常数。

16.5.3 仿真只用于筛选

历史轨迹回放或受控仿真可以用来测试触发逻辑(防振荡、无效扩容),但仿真与实测必须有不同 run_type SOURCE_REGISTERED S06。模型质量与真实通信恢复仍需实际验证。

16.5.4 防振荡

触发、冷却、排空与回滚条件都要明确。算法不会把反复扩缩当成正常成功——反复扩缩本身是要被监控的失败模式。

依据与核验入口:R25 R30 S07


16.6 优化、加副本、扩大并行还是分离

本节要回答的问题:增加预算之前,怎样比较可选路径?

四条路径按同一质量与 SLO比较可用增量:

路径 增加什么 代价 证据成熟度要求
优化现有配置 单位资源产出 工程投入、质量风险 需第11章的消融与回归
加副本 容量近似线性 权重重复驻留 需路由与偏斜检查(6.4)
扩大并行度 单请求延迟 副本数减少、同步增加 需第6章布局表
阶段分离 抗干扰 传输、控制面、新故障域 需第12章对照

三个纪律:

  1. 控制层与常驻权重资源一并计入 SOURCE_REGISTERED S13;
  2. 不能把已被固定集成栈弃用的组件无条件推荐为新方案;
  3. 应用侧减少重复工作也是一条独立选项,但要检查任务是否被改变(第11章 11.5)。

至少保留一个低复杂度基线——方案推荐应来自对照,而不是技术偏好。

依据与核验入口:R09 R24 S13


16.7 实验:形成有条件的扩容建议

本节要回答的问题:报告应该怎样表达推荐数量与风险?

16.7.1 数量必须可复算

从实测容量和业务情景推导资源范围,再考虑故障、加载与负载漂移。任何资源数量都要可复算,并附带:假设、敏感参数、后续验收条件。

16.7.2 不写成绝对保证

推荐数量的正确写法是区间 + 前提:

在负载画像 X、SLO Y、维护窗口 1/4 实例下线的前提下,需 12—14 台;若长输入占比超过 35%,需重新测量(该区间未覆盖)。

16.7.3 尚未具备规模实验时

可以给规划区间并明确"扩展效率仍需确认",而不是填一个保证值(第6章 6.7)。

依据与核验入口:R24 R30 S07


16.8 深化:从有限窗口账本到扩容控制

16.8.1 瞬时积压可以守恒,稳态容量不能凭空外推

在一个固定系统边界中,定义 \(A(t)\) 为累计准入请求数,\(C(t)\) 为累计完成数,\(D(t)\) 为累计取消或其它离开数,\(N(t)\) 为在途数,则

\[N(t)-N(0)=A(t)-C(t)-D(t).\]

这是有限窗口计数关系,不要求稳态。拒绝在准入之前发生,另记offered与admitted之差;同一个取消请求只应按定义离开一次。若守恒式对不上,应先找丢失终态、重试混计或窗口边界错误,而不是立即拟合排队模型。

相反,Little定律里的均值需要稳定边界与长期平均条件。一个不断增长的队列可以满足计数守恒,却没有可以外推的稳态平均等待。容量报告应同时给出完成率与在途变化,否则“吞吐不再增长”可能只是已经进入积压阶段。

16.8.2 突发缓冲取决于净流入面积

在简化流体模型中,净积压近似为正的到达需求减去可服务速率的时间积分。若基础到达率20 req/s、可服务率25 req/s,突发变为40 req/s并持续60 s,则新增积压约 \((40-25)\times60=900\) 个;回到20 req/s后,净消化能力只有5 req/s,需要约180 s排完。这些数字是模型,尚未包括拒绝、期限超时与长度变化。

这个算例说明,增加25%的稳态能力不等于能够轻松吸收两倍突发。恢复速度取决于“服务能力减基础需求”,接近零时消化时间会很长。若积压请求在恢复前就超过业务期限,单纯允许更大队列并不能保住有效完成量。

16.8.3 扩容是一条有延迟的依赖链

第16.5节修正后的自定义策略是:每15 s采样,需要连续三次满足条件,启动到就绪115 s。阈值越过至首个命中采样需要0—15 s,随后两次确认需要30 s,所以无其它等待时为145—160 s。不是“3×15=180”,也不能将300 s冷却窗口无条件加到每一次扩容。

图16-R1 自定义扩容策略的生效区间

Kubernetes的HPA文档区分扩容和缩容行为,并允许分别配置稳定窗口 N19。本书的三次确认策略是设定,不是宣称HPA默认采用该逻辑。真实控制器还要记录采样陈旧度、调度等待、镜像/权重加载、探针和路由收敛;可重叠的等待按依赖处理。

16.8.4 整数副本数与故障约束一起计算

若已测每副本在目标负载下可持续服务 \(g\) 个合格请求每秒,规划需求为 \(\lambda\),并要求任意一个副本离线后仍满足需求,则同质、独立且路由均衡的简化条件为 \((n-1)g\ge\lambda\)。因此 \(n\ge\lceil\lambda/g\rceil+1\)。这不是任意集群通用公式:跨节点TP组的故障单位可能是一整组,而不是一张卡。

例如 \(g=8\)、\(\lambda=35\) 时需至少6个副本满足上述单副本故障条件。这里的8必须来自相同质量与SLO下的已测能力;若仅为假设,则副本数也只是条件化规划。不能用峰值tokens/s代入,再把答案写成生产保证。

容量扫描中,补齐的门槛是TTFT P99不超过1000 ms、联合达标率至少90%。35 req/s是表中最高已测可行点,45 req/s已超过延迟门槛;35到45之间仍需细扫。可行点、真实极限和推荐运行点是三个不同概念,运营余量还要覆盖漂移、故障和突发。


16.9 实验图表补充:用完整参数补出突发积压与恢复曲线

为避免把不同案例拼在一起,本图只沿用16.8.2:初始无积压,服务能力25 req/s;0—60秒到达40 req/s,之后恢复20 req/s,忽略取消、拒绝和重试。突发期净增长15 req/s,共积压900;恢复后净消化5 req/s,需要180秒,从突发开始算到第240秒才排空。

这解释了为什么“突发只有1分钟”不代表影响也只持续1分钟。图中没有发生扩容,容量恒定;如果在中途新增副本,就需要把服务率改为分段函数,并计入加载、预热、健康检查和状态就绪,而不是把新卡算力从触发时刻就加进去。

16.3.1的620积压/340秒恢复来自另一组轨迹,两者互不换算,不用一张图冒充两者。原图145—160秒扩容生效区间是自选采样/确认/启动策略;Kubernetes文档只支持区分扩容与缩容控制规则,不能给这组时延背书。

真实实验采集:计划到达、实际到达、接收、拒绝、在途、完成、超时与新增副本ready按同一窗口对账,逐时间段验证积压变化等于到达减离开,保留故障和维护两种容量情景。

图16-V1 一次60秒突发,会留下180秒恢复尾巴

图16-V1 一次60秒突发,会留下180秒恢复尾巴

数据身份:TEACHING_CALCULATION。条件:服务25req/s;0—60s到达40,其后20;无取消、拒绝、重试,初始积压0。

时点/s 积压 该时点后到达率/req每秒 服务率/req每秒
0 0 40 25
60 900 20 25
120 600 20 25
180 300 20 25
240 0 20 25

复算公式:Qpeak=(40-25)*60=900; recovery=900/(25-20)=180s。

出处与边界:X11;与16.3.1的620/340s属于不同模型;不作为生产恢复时长保证。

数据与绘图点 · PNG · 重绘代码

16.10 第三方数据补充:突发与长度分布:容量规划的两个现实输入

本章前面用算例演示扩容决策。这一节把两个输入换成真实 trace 的复算值(BurstGPT,KDD\'25,data/BurstGPT_1.csv;本次复算 61 天、1,429,737 条请求、平均到达率 0.2713 RPS)。

输入一:突发是尺度相关的。峰值/均值比只统计非空窗口时为:1 s 窗口 49.5 倍、10 s 窗口 53.0 倍、60 s 窗口 53.2 倍、300 s 窗口 56.3 倍、3600 s 窗口 33.3 倍。"系统有 50 倍突发"这句话如果不写窗口就没有意义。容量规划真正要问的是:在你的扩容生效时间尺度上,突发倍数是多少。这正是算例里"突发只持续 1 分钟"那个假设的现实版本。

输入二:请求与响应不是同一个分布。输入长度均值 610.6、p50 251、p90 1796、p99 3387、最大 29665 tokens;输出长度均值 123.3、p50 34、p90 272、p99 1491、最大 7315 tokens。输入是长尾重的(均值≈2.4×p50),输出相对集中在低位(均值≈3.6×p50)。这意味着容量规划不能只用一个"平均长度":Prefill 侧被 p99 输入主导,Decode 侧被输出长度分布主导,两者要用不同的分位数。

边界:BurstGPT 为单一区域服务商子集(会话与 API 混合),非全行业代表;原始平均到达率只有 0.2713 RPS,用于容量规划必须按目标 RPS 缩放;失败请求未逐条区分原因。

图16-T1 突发是尺度相关的:换一个窗口,倍数就变了(第三方真实 trace)

图16-T1 突发是尺度相关的:换一个窗口,倍数就变了(第三方真实 trace)

数据身份:THIRD_PARTY_TRACE(第三方公开数据)。条件:BurstGPT(KDD'25)data/BurstGPT_1.csv,本次复算:61 天、1,429,737 条请求、平均到达率 0.2713 RPS;峰值/均值比仅统计非空(active)窗口。

统计窗口 峰值/均值比 口径
1 s 49.5 BurstGPT 非空窗口
10 s 53.0 BurstGPT 非空窗口
60 s 53.2 BurstGPT 非空窗口
300 s 56.3 BurstGPT 非空窗口
3600 s 33.3 BurstGPT 非空窗口

出处与边界:Z10;单一区域服务商子集(会话与 API 混合),非全行业代表;原始 RPS 很低(0.2713),用于容量规划须按目标 RPS 缩放;失败请求未逐条区分原因。

数据与绘图点 · PNG · 重绘代码

图16-T2 请求与响应不是同一个分布:长度决定容量怎么算(第三方真实 trace)

图16-T2 请求与响应不是同一个分布:长度决定容量怎么算(第三方真实 trace)

数据身份:THIRD_PARTY_TRACE(第三方公开数据)。条件:BurstGPT data/BurstGPT_1.csv 本次复算;1,429,737 条请求;长度单位为 token。

类型 均值/tokens p50/tokens p90/tokens p99/tokens 最大/tokens
请求(输入) 610.6 251 1796 3387 29665
响应(输出) 123.3 34 272 1491 7315

出处与边界:Z10;单一区域服务商子集;未按模型或服务类型分层;最大值受长尾极端样本影响。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 平均日请求量相同,为什么实例数可能相差很大?

答案方向:因为容量是瞬时约束,取决于情景中的窗口到达率与长度结构,而不是日总量。本节算例中,日均同为 100 万,均匀到达峰值 17.4 req/s、集中 4 小时峰值 41.7 req/s,相差 2.4 倍——对应的实例数也近似差这么多。另外还要看:长输入占比(改变每请求的工作量)、突发时长(决定积压消化时间)、以及维护与故障情景(容量分别降到 75% 与 80%)。所以规划输入必须是到达轨迹 + 负载画像 + 服务目标,不是日均数字。

2. 为什么固定留 20% 余量不是通用答案?

答案方向:余量要覆盖的是突发期间的积压,而积压 = 突发强度 × 突发时长 − 期间的消化能力。本节算例中,3 倍突发持续 90 s,积压 620 个请求,回落后消化要 340 s(突发时长的 3.8 倍)。同一个 20% 余量,在持续 90 s 时可能不够;其它条件相同时持续 15 min 的积压只会更多;在示例扩容滞后 145—160 s(不含另有明确策略的等待)的系统里,20% 更是不够——因为新实例还没上线,积压就已经形成了。所以余量应按"突发强度 × 时长 / 恢复速率 + 扩容滞后内的需求增长"计算,并分别给出正常、维护、故障三种情景的余量。

3. Fast Start 缩短启动但增加常驻资源,容量与成本如何同时验收?

答案方向:两栏一起报。① 恢复期容量:启动从 115 s 降到 75 s(−40 s),意味着故障与扩容情景下可更早恢复,这要折算成"故障期可服务容量随时间的曲线",而不是只报一个启动秒数;② 常驻成本:若另外确认每卡存在 4 GB 非共享的增量常驻开销,则8卡合计32 GB减少状态池;只有非共享增量会进一步减少可容纳并发路数,共享映射的同一物理权重不能重复扣减(第6章 6.1 的表要按新的状态池重算)。同时还要核验:缓存冷失效、驱动变更或模型更新后收益是否仍在,以及稳态吞吐不变(第8章 8.5)——启动收益不能代替稳态收益;是否接受启动与稳态容量之间的交换,取决于故障恢复和成本契约。


本章验收清单

节 验收标准
16.1 容量目标有单位、时间尺度与负载分布;增长分情景;假设与观测事实分开
16.2 容量来自四项联合约束;可用边界不是最高吞吐点;不稳定判据已定义;外推区间已标记
16.3 缓冲按强度×时长×恢复速率计算;长任务与分支分别测;取消后回收已检验
16.4 正常/维护/故障容量分别报告;冷启动时间进容量账;常驻资源扣除;未演练项列为风险
16.5 信号来自四类而非单一利用率;控制回路滞后已量化;仿真与实测 run_type 分开;防振荡条件明确
16.6 四条路径按同 SLO 比较;控制层与常驻资源计入;保留低复杂度基线
16.7 推荐数量可复算且带前提;写成区间而非保证;扩容后的验收条件已定义

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第17章 成本、效率与优化收益

核心论点:成本必须与同一质量和服务目标下的有效完成量对应;资源释放、能力增加与现金节省是三种不同结果。


17.1 成本边界:现金、折旧与机会成本

本节要回答的问题:我们准备比较哪一种成本?

17.1.1 三个口径不能混加

口径 含义 常见错误
现金支出 本期实际付出去的钱 与分摊混加
管理分摊 购置成本在使用期内分摊 与现金重复计入同一口径
机会成本 占用存量资源而放弃的其它用途 被当成零成本

购买款与折旧不能在同一口径中重复计入。租赁价格还要说明是否包含主机、存储与网络——不包含的话,整机成本被系统性低估。

17.1.2 先定义视角与时间区间

成本卡必须注明:币种、日期、包含项、来源、时间区间。价格随日期变化,没有日期的成本数据无法复核。

本书采用管理决策账本,不预设设备一定失去使用价值,也不固定折旧轨迹。

依据与核验入口:R24


17.2 从设备小时到每个有效完成单位

本节要回答的问题:应该除以所有 Token,还是成功且达标的任务?

17.2.1 分母是有效完成量,不是产出量

算例。8 卡服务器,设备小时成本 ¥96/h(假设,需按实际报价替换):

项 值
设备小时成本 ¥96/h
一小时内发出的 attempt 1200
达标完成的任务 1000
单位有效完成成本 \(96 / 1000 = \mathbf{¥0.096/任务}\)

关键在分子分母的覆盖范围:分子包含全部 1200 次 attempt 的计算(它们都消耗了资源),分母只计 1000 个契约规定的有效任务。

17.2.2 一个会低估的错误算法

若只把"成功 attempt 消耗的资源"计入分子,而忽略失败与超时的部分:

\[ \text{错误值} = \frac{1000 \times (96/1200)}{1000} = \mathbf{¥0.080/任务} \]

比正确值 低估 16.7%。这类错误很常见,因为失败尝试常常不进任何"成功"统计,于是看起来"没花钱"。

分支任务的成本分子包括所有子调用、失败尝试、重算与工具资源,分母只计有效任务 SOURCE_REGISTERED。不能用隐藏失败或缩短任务来制造单位成本下降。

17.2.3 时间分母与需求利用

潜在峰值不是实际产量。无业务需求的空闲时段仍然可能产生费用——所以单位成本要用"实际完成量"而不是"理论产能"作分母。

四个口径分别报告:任务、请求、推理 Token、可见输出。Token 单价只是特定负载下的派生指标,不能跨负载比较。

依据与核验入口:R09 R24 S18


17.3 利用率、缓存与需求匹配

本节要回答的问题:性能提升什么时候可以转化为可实现的收益?

17.3.1 三类收益必须分开记

收益类型 含义 是否已兑现
资源释放 单位资源产出提高 否(只是潜力)
可交付能力 同资源下能服务更多 否(需有需求承接)
可执行财务动作 退租、合并实例、承接新业务 是

算例:优化释放 30% 容量,但需求利用率只有 55%。可执行动作是合并实例减少 2 台(16 台中减 2):

\[ \text{实际节省} = \frac{2}{16} = \mathbf{12.5\%} \]

释放 30% 容量,实际只省 12.5% 现金。不能把30%技术容量与12.5%现金节省直接相减,二者分母不同;未兑现部分应以设备小时、可服务任务量或可退租实例数单独计量。

17.3.2 缓存收益要看真实复用

缓存提升要结合真实复用率与存储成本,不能把压测上限当成全年平均 SOURCE_REGISTERED。权重常驻降低某类延迟,但增加保留资源 SOURCE_REGISTERED——这两笔要分别记账。

17.3.3 建立"技术变化—运营动作—收益兑现"链

没有第二步(运营动作),第一步(技术变化)就不是收益。未知需求用情景而非确定增长表示。

依据与核验入口:R07 R25 S13


17.4 能源、主机、网络与运维

本节要回答的问题:除了加速器,还有哪些成本不能漏掉?

17.4.1 系统边界必须一致

算例:

测量位置 功耗 占比
8 张加速卡板卡 5.6 kW 57.1%
整机设定值 9.8 kW 100%

只测板卡会漏掉 42.9%。额定功耗也不等于实际能耗——必须注明测量位置、采样方式与时间积分。

17.4.2 年化示例

按整机 9.8 kW、全年 8760 h、电价 ¥0.8/kWh(均为假设):

\[ 9.8 \times 8760 \times 0.8 = \mathbf{¥68{,}678/年} \]

若只测板卡,年电费会被算成 ¥39,245,低估 43%。

17.4.3 其它不能漏的项

主机、网络、缓存存储、运维人力、控制面组件都要按同一边界记录,并避免重复分摊。机柜级互联、共享存储与服务控制层纳入同一预算边界 SOURCE_REGISTERED S22——不把机柜汇总资源当成零成本共享池。

依据与核验入口:R17 R21 S22


17.5 优化收益与工程投入

本节要回答的问题:提高效率的项目是否值得做?

17.5.1 反事实对照

比较三种方案的可执行成本变化:继续现状 / 优化 / 扩容。不能只算优化一项。

17.5.2 完整投入账本

算例 (金额均为假设):

项 值
单位成本变化 ¥0.096 → ¥0.082(−14.6%)
年任务量 \(1000 \times 8760 = \mathbf{8{,}760{,}000}\)
年节省 \(8{,}760{,}000 \times 0.014 = \mathbf{¥122{,}640}\)
一次性开发验证 18 人日 × ¥2000 = ¥36,000
持续维护 4 人日/年 × ¥2000 = ¥8,000
第一年净收益 ¥78,640
回本周期 \(36{,}000 / [(122{,}640-8{,}000)/12] = \mathbf{3.77\ 个月}\)(按净持续节省)

17.5.3 投入与风险分列

一次性开发、验证、持续维护、失败风险分开列。收益未验证时用范围或情景,并说明什么证据能支持继续投入。

新增缓存/传输/控制组件的开发、回归与升级维护都要计入 SOURCE_REGISTERED S13。仿真节省的是研究筛选成本,不是已证明的生产节省。

依据与核验入口:R24 S02 S13


17.6 敏感性、盈亏边界与不确定性

本节要回答的问题:哪些假设一变,方案结论就会反转?

17.6.1 盈亏边界

按上例,年投入 ¥44,000(36,000 + 8,000)、单位节省 ¥0.014:

\[ \text{盈亏平衡年任务量} = \frac{44{,}000}{0.014} = \mathbf{3{,}142{,}857\ 任务/年} \]

低于这个量级,优化项目不划算。这是比"回本约 3.77 个月(净持续节省口径)"更有用的数字——因为它给出了结论反转的条件。

17.6.2 敏感参数扫描

参数 变化 对结论的影响
需求利用率 55% → 30% 不能由利用率单独推断;需求下降可能增加缩容空间,也可能受最低副本数、故障冗余或已付承诺费用限制
长度混合 长输入占比 +30% 单位成本上升,节省缩小
SLO 收紧 P99 阈值降低 达标率下降,有效完成量减少
价格 设备小时 +20% 节省绝对值放大
维护成本 4 → 12 人日/年 首年投入由 ¥44,000 增至 ¥60,000,盈亏边界上移 36.4%

17.6.3 稳健方案

优先选择在多数情景下都成立的方案,而不是在乐观情景下收益最大的那个。无法确定的外部条件保留区间,不用单点掩盖风险。

依据与核验入口:R24


17.7 实验:从性能报告走向成本决策

本节要回答的问题:怎样把一次可信测试转换成管理上可用的说明?

17.7.1 输入身份与核算口径

引用已验证的性能基线与质量结果(不是压测峰值),用项目真实成本或明确标注的假设完成成本模型。价格输入保留来源日期。

17.7.2 四类输出分别写

报告要分别写出:当前支出、单位有效完成成本、潜在容量、可兑现节省。四者量纲不同,不能互相替代。

17.7.3 没有真实报价时

交付的是可填写模型与决策方法,不宣称"能省某个确定金额"。每个数字都要有来源或假设标签。

依据与核验入口:R09 R24 S13


17.8 深化:把技术改善换成可兑现的成本变化

17.8.1 “吞吐提高30%”与“资源节省30%”的分母不同

若优化使单位资源能力从 \(g\) 提高到 \(1.3g\),在相同需求、完全连续可缩放且其它条件不变时,所需资源比例为 \(1/1.3\),减少的是

\[1-\frac{1}{1.3}=23.08\%,\]

不是30%。真实实例必须取整数,还受最低副本、故障冗余、合同和已付费用约束,现金节省可能更小,也可能暂时为零。反过来,服务改善也可能降低失败和超时,使有效任务成本下降,而不是单靠减少机器。

这应落实为三张独立表:技术能力变化、可执行运营动作、现金支出变化。不要把不同分母的百分比相减后称为“剩余收益”。

17.8.2 整数实例会产生阶梯式收益

设需求为35个有效请求每秒,基线每副本8个,候选每副本10个,均要求一副本故障余量。基线需 \(\lceil35/8\rceil+1=6\) 个,候选需 \(\lceil35/10\rceil+1=5\) 个;可减少一副本,比例16.7%。在另一个需求点,两个方案可能都需要相同实例数,技术提升暂时只转化为余量。

图17-R1 同一需求与故障约束下的整数副本数

图中每副本能力均为假设,不是任何硬件的测量。图的用途是解释为什么资源价格相同、技术提速相同,现金收益仍会随需求区间呈阶梯变化。把平滑吞吐曲线直接转换为平滑现金曲线,会漏掉这个关键约束。

17.8.3 回本计算要扣除持续支出

沿用第17.5节账目:年毛节省122,640元、持续维护8,000元、一次性投入36,000元。若毛节省和维护均匀发生,不考虑折现与资金时间差,则年净持续节省为114,640元,简单回本周期为 \(36000/114640\times12\approx3.77\) 个月。首年净收益仍是78,640元。

盈亏平衡的首年任务量以44,000元总投入除以每任务0.014元节省,约314.3万任务。若维护由4人日增至12人日、单价仍2,000元,则首年投入变为60,000元,盈亏边界增加36.4%,不是57%。这些只是同一模型的算术结果,不能代替实际报价、需求预测和合同安排。

更重要的是,单位成本降低是否真的带来现金下降。已购买设备的管理分摊减少、额外业务的机会价值与当期退租节省应分列;不存在退租动作时,不应把管理分摊的变化写成银行账户已少付的金额。

17.8.4 能耗用时间积分,并保持同一边界

对离散功率采样,能量可用梯形法近似:\(E\approx\sum_i (P_i+P_{i+1})(t_{i+1}-t_i)/2\)。单位要统一,例如kW与小时得到kWh。丢失的采样区间应披露,不能默认功率为零;额定功率也不能替代运行采样。

若已测的是设施总电量,就不能再乘一次设施开销系数;若只测IT设备,设施附加能耗要单列并说明分摊口径。对共享缓存、网络和控制面,比较前后应采用同一边界,避免把设备省下的功耗转移到另一个没计费的组件。

有效任务能耗是总能量除以同一窗口的有效完成数。优化可能降低平均功率却延长执行时间,也可能提高平均功率但更快完成;仅比较某个瞬时瓦数,无法判断总能耗。第17.4节全年功率不变的年化示例明确保留为假设,不作为设备全年真实运行画像。


17.9 实验图表补充:将优化收益落到带维护支出的回本曲线

沿用本章账目:每年毛节省122640元,一次开发验证36000元。若每年维护8000元且收支均匀,年净持续节省114640元,简单回本约3.77个月,首年净收益78640元;维护升至24000元时,回本约4.38个月,首年净收益62640元。

两条线都在第0月先支出36000元,再按净持续节省累计。它们并不预测真实合同的付款时间,也没有纳入折现、税费、故障损失或业务量波动。曲线使用本章自设金额,不是当前云厂商报价或电价调查。

不能跨越的三步:技术提升先转成满足质量/SLO的可用容量,再确定是否跨过整数副本门槛,最后确认能否取消租赁、少采购或避免支出。没有执行的资源动作,只能记容量余量或效率改善,不能登记为现金节省。

真实实验报告应保留单位有效任务成本的分子分母,失败、重试和闲置资源不能从成本分子删掉;维护时间和回退投入也应在接受优化前预估,并在上线后复核。

图17-V1 技术收益要扣掉维护,才是回本曲线

图17-V1 技术收益要扣掉维护,才是回本曲线

数据身份:TEACHING_CALCULATION。条件:年毛节省122640元、一次投入36000元;维护8000/24000元;均匀发生、未折现。

年维护/元 年净持续节省/元 简单回本/月 首年净收益/元
8000 114640 3.768 78640
24000 98640 4.38 62640

复算公式:cash=-36000+(122640-maintenance)*month/12。

出处与边界:本书17.5、17.8;经济模型,不代表当前采购、电价、云价格或必能兑现的现金节省。

数据与绘图点 · PNG · 重绘代码

17.10 第三方数据补充:价格不是成本:先把口径对齐

本章前面讲优化收益怎么算。这一节补第三方价格与成本口径,核心是一句话:标价、租金、成本、电费是四个不同的数,混用会系统性算错。

标价下降很快,但那是价格(a16z《LLMflation》;Epoch AI 数据洞察)。以 MMLU 为等效性能锚,最便宜达标模型的标价从约 60 美元/百万 token 降到约 0.06 美元,约 1000 倍。但 Epoch 对"不同任务"的下降率给出 9×–900× 的极宽区间。这两个数字都不能直接写进成本模型:标价下降同时混合了硬件进步、模型变小、竞争定价与补贴。用标价当成本,会系统性高估节省。

租金口径:两个"H100 一小时"差 37.5%(Epoch AI《Inference economics of language models》,arXiv:2506.04645;Silicon Data SDH100RT 指数,2026-10-01 读数)。成本模型里的假设是 2.00 美元/GPU-hour,市场租赁价指数读数是 2.75 美元/GPU-hour——差 37.5%,而两者都还不含折旧、电费、PUE、运维与闲置。做回本测算时,这个差异比大多数优化收益都大。

电费不是零头(LBNL《2024 United States Data Center Energy Usage Report》)。美国数据中心用电从 2023 年的约 176 TWh,到 2028 年的情景区间约 325–580 TWh。这是国家/行业级情景估算,不能直接换算成单次推理电费,但它说明:规模化推理的成本结构里必须给电留位置。

边界:以上全部是第三方分析、测算与情景;标价含利润率变化与选择偏差;Epoch 成本模型作者自称 crude;Silicon Data 是租赁价指数而非 TCO;LBNL 2028 为跨度极大的情景区间。

图17-T1 标价下降很快,但那是价格不是成本(第三方分析)

图17-T1 标价下降很快,但那是价格不是成本(第三方分析)

数据身份:THIRD_PARTY_ANALYSIS(第三方公开数据)。条件:a16z《LLMflation》(价格取自 Internet Archive 历史标价,MMLU 为等效性能锚,输入输出价取平均);Epoch AI 数据洞察(以"达到某基准分数的最便宜模型"标价做对数线性回归)。

口径 起点 终点 降幅 单位
a16z LLMflation(MMLU 门槛最便宜模型) 60 0.06 约 1000× 美元/百万 token(API 标价)
Epoch AI:任务间下降率区间 — — 9×–900× 区间(对数线性回归)

出处与边界:Z12;【口径警示】两者都是 API 标价(list price),不是厂商成本,含利润率变化;样本仅覆盖部分厂商与第三方托管,存在选择偏差;MMLU 锚点受基准污染影响;Epoch 的区间极宽(9×–900×),不宜作为单一预测常数。

数据与绘图点 · PNG · 重绘代码

图17-T2 成本口径必须先对齐:租金假设与能耗情景(第三方测算)

图17-T2 成本口径必须先对齐:租金假设与能耗情景(第三方测算)

数据身份:THIRD_PARTY_MODELED_COST(第三方公开数据)。条件:Epoch AI《Inference economics of language models》(arXiv:2506.04645,H100 3.3 TB/s 显存带宽);Silicon Data SDH100RT 指数(2026-10-01 读数);LBNL《2024 United States Data Center Energy Usage Report》。

项目 数值 单位 口径
H100 租金(Epoch 成本模型假设) 2.00 美元/GPU-hour 作者选取的市场租金假设
H100 租金(Silicon Data 指数读数) 2.75 美元/GPU-hour 市场租赁价指数(含 neocloud 与私人租赁)
美国数据中心用电(2023) 176 TWh 历史估算
美国数据中心用电(2028 情景) 325–580 TWh 情景区间

出处与边界:Z14;【测算非实测】Epoch 成本模型作者自称 crude,口径只含 GPU 租金,未系统包含折旧、电费、PUE、运维与闲置;Silicon Data 为租赁价指数而非 TCO;LBNL 为国家/行业级情景估算,不能直接换算单次推理电费;2028 区间跨度大。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 优化释放 30% 容量,是否等于节省 30% 现金?

答案方向:不等于,这是本章最重要的一条。释放的是技术容量,要变成现金需要两个条件:存在可承接的新需求,或存在可执行的资源缩减动作(退租、合并实例)。本例中释放 30% 容量、需求利用率 55%,实际能合并掉 2 台(16 台中减 2),只省 12.5%。技术容量变化与现金变化不能直接相减;未兑现能力以设备小时或可服务任务量单列,不能记作现金节省。同理,缓存与权重常驻带来的延迟改善也要扣除其保留资源成本(第16章 16.4.2)。

2. 单卡功率下降是否能证明整套服务能耗下降?

答案方向:不能。两层理由:① 边界不一致——板卡只占整机的 57.1%(5.6 kW / 9.8 kW),只看板卡会漏掉 42.9%;② 可能存在功耗转移——主机、网络、存储与控制组件的能耗可能上升(例如更激进的预处理、更大的缓存层、更多的传输)。正确做法是:固定系统边界(整机或机柜),注明测量位置、采样与时间积分,用额定功耗之外的实测能耗做前后对照,并在同一边界下同时报主机与网络项。

3. 任务失败但多数子请求成功,其计算是否保留在任务成本分子中?

答案方向:保留。资源已经消耗,无论任务是否成功。成本分子包括全部子调用、失败尝试、重算与工具资源;分母只计契约规定的有效任务 SOURCE_REGISTERED。本章算例中,一小时内发出 1200 次 attempt、达标 1000 个任务,单位成本 = ¥96 / 1000 = ¥0.096;若错误地只把成功 attempt 的资源计入分子,会得到 ¥0.080,低估 16.7%。这也意味着:降低失败率本身就是降成本,而不是"只是稳定性问题"。


本章验收清单

节 验收标准
17.1 成本口径单一且不混加;成本卡含币种、日期、包含项与来源
17.2 分子含全部 attempt 的资源;分母只计有效任务;四个口径分别报告;不用隐藏失败制造成本下降
17.3 资源释放 / 可交付能力 / 可执行财务动作三类分开;有"技术变化—运营动作—兑现"链
17.4 能耗与成本的系统边界一致;测量位置与采样已注明;未测组件明确排除
17.5 反事实对照完整;开发/验证/维护/风险分列;未验证收益用情景表示
17.6 给出盈亏边界与结论反转条件;敏感参数已扫描;不用单点掩盖风险
17.7 每个数有来源或假设标签;成本、产出与时间范围完全对应;无报价时标为模型

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

第18章 端到端案例、回归测试与版本治理

核心论点:工程能力的终点不是得到一次最好结果,而是形成可复核、可回退、能随版本持续更新的证据与决策体系。


18.1 贯穿案例:从业务问题到决策

本节要回答的问题:十八章的方法怎样连接成一次完整工程工作?

18.1.1 一条链,八个环节

沿用全书同一个主案例,串成完整档案:

① 任务与环境登记(第1章:SUT 边界、完成单位、制品清单)
        ↓
② 指标与服务目标(第7章:三量纲 + 起点/终点/时间域)
        ↓
③ 负载与可信基线(第8章:负载卡 W01—W08、发压器自检、重复)
        ↓
④ 观测与剖析(第9章:四层观测、三档证据、 trace 判读)
        ↓
⑤ 瓶颈假设与受控干预(第10章:四主干、事前预测、回退复测)
        ↓
⑥ 优化与消融(第11章:决策卡、组合消融、质量先于性能)
        ↓
⑦ 容量与扩容(第16章:包络、情景、控制回路滞后)
        ↓
⑧ 成本与治理(第17章 + 本章:单位有效成本、回归矩阵、版本台账)

每一步引用已有运行而不重复造数据。主案例尚未实测时保留可执行设计;实测到位后替换对应证据层。

18.1.2 展示"哪条事实推动了下一步"

档案的价值在于可追溯:最终建议能追溯到原始运行、适用负载与质量条件。不是"我们优化了 X,提升了 Y%",而是"由于 ⑤ 中排队主导被证实,⑥ 才选择改批预算而非换内核;若 ⑤ 的结论反转,⑥ 的选择应当改变"。

依据与核验入口:R24


18.2 三类支线案例:没有唯一胜利路径

本节要回答的问题:如何用不同结局训练工程判断?

三类模板(写作结构,不是预设结果):

类型 结构 训练的判断
配置改进有效 低复杂度调整获得改善 先做便宜的对照,别急着上架构
架构调整不值得 收益小于新增成本与风险 能说"不采用"也是成果
迁移部分适用 某类负载适合、某类保留 结论按负载分列,不给综合分

三条纪律:

  1. 结果栏保持待验证,实验后按事实选用;
  2. 至少保留一个无收益或不采用的真实案例,不能伪造反例以显得全面;
  3. 作者应服从证据,而不是为了故事完整而修改结论。

依据与核验入口:R09 R25


18.3 回归矩阵与发布门槛

本节要回答的问题:哪些变化值得重新跑哪些测试?

18.3.1 三层回归

层 内容 成本 触发频率
低成本冒烟 功能、输出格式、指标口径单元测试 分钟级 每次变更
代表负载基线 W01—W08 中的代表档 + 质量集 小时级 每日/每版本
条件触发扩展 长运行、故障恢复、容量扫描 天级 按变更类型

18.3.2 变更 → 范围矩阵

变更 必须触发
模型 revision 全套(质量、性能、容量)+ 制品清单更新
模板 / 分词器 质量集、输出格式与停止条件、长度分布与 TTFT/TPOT 基线、前缀复用命中率
驱动 / 固件 全套 + 执行路径取证(trace 内核名)
引擎版本 / runner 默认切换 全套 + 图模式与回退取证 SOURCE_REGISTERED
指标映射 / 工具升级 指标口径单元测试必须重跑 SOURCE_REGISTERED
上游弃用 / 移除 受影响配置与实验清单
并行布局 / 配比 容量扫描与阶段能力

18.3.3 阈值要考虑波动

避免把噪声当回归,也避免因为总体平均不变而忽略关键场景退步。推荐做法:

指标 建议触发线(示例,按业务定)
P50 延迟 变化 > 5%
P99 / P99.9 变化 > 12%(尾部方差更大,第7章 7.4.3)
质量达标率 下降 > 1 个百分点即触发
单位有效成本 上升 > 5%

关键补充:分层看,不只看总平均——总平均不变而某一档退步是常见形态(第10章 10.1.2)。

需要设备证据的执行回归,不得被"配置解析通过"替代。

依据与核验入口:R14 R29 S17


18.4 版本、来源命题、实验与结论的关联

本节要回答的问题:怎样知道某个判断对哪些版本仍然成立?

18.4.1 六类身份分别保留

模型 revision、制品链、软件栈、负载画像、指标映射与图表脚本——各自保留身份,结论绑定完整实验组合。

18.4.2 source_record:把"命题"登记下来

在 run_manifest 之外建立 source_record,每条来源命题记录:

字段 说明
具体命题 这句话在说什么(不是"某文档支持 X"这种笼统说法)
产品 / 版本 / commit 身份
模型 revision、硬件范围 适用边界
事件日 / 发布日期 / 查阅日 三个日期分开
正文定位 章节、段落或原文摘录
发布成熟度 GA / RC / 预览 / 主干
两个独立布尔 body_verified(是否取到正文)与 runtime_verified(是否本地运行)
复核触发条件 什么情况下需要重新核验

找到入口不等于核验了正文 SOURCE_REGISTERED S23 S25。三条特定入口保留历史正文状态。本次另增加 CANN 8.5.0 离线剖析正文(N08)与 Ascend RC 测试报告(N09),它们可以支持各自范围内的新内容,不覆盖旧入口记录;HCCL/MindIE 尚缺所选部署版本的完整证据。

18.4.3 升级不覆盖旧数据

后续版本升级新增运行与比较,不覆盖旧数据。旧结论可标为"待复核"或"不再适用";修订记录解释变化来自版本变化、负载变化,还是纠正错误——这三者必须分开。

18.4.4 合并稿不产生新的验证

本合并稿的来源日期与正文状态承接各章登记与复核报告,不因编辑合并而自动产生一次新的技术验证。来源仍浮动且未固定 commit 的地方已明确标注;后续网页更新新增记录而不覆盖旧结论。

禁止用浮动 latest 作为唯一身份。

依据与核验入口:R01 R19 S06 S24


18.5 失败案例、弃用清单与技术时效性更新

本节要回答的问题:一次故障怎样变成以后少走弯路的资产?

18.5.1 失败案例卡

字段:症状、环境、证据、处理、反例、复现状态。

不把一次成功修复升级为所有系统的经验定律——案例必须带适用条件。复现失败同样记录,并指出环境差异或信息缺失。

18.5.2 生命周期三态

状态 含义 能否写成事实
已弃用 仍可用,不建议新用 是(已发生)
计划移除 移除动作尚未发生 可写“官方计划”,不可写“已移除”
已移除 路径不存在 是(已发生)

两个真实案例 SOURCE_REGISTERED S20:

  • Dynamo 1.5.0 把 KVBM 标为弃用,并计划在 1.6 移除——"计划移除"不是已发生,本稿不预判后续发布状态;
  • Ascend 0.26.0rc1 已移除 FlashComm2——这是已发生的移除,旧教程的相关开关在该版本上直接失效。

18.5.3 更新记录链接到受影响部位

每条生命周期变化要链接到:受影响的章节、配置、实验与回滚路径。状态语义不可混用:已移除的功能不能继续作为默认推荐。

依据与核验入口:R16 R25 S20


18.6 公开技术书、公众号与工程资产

本节要回答的问题:如何共享研究而不把书变成营销材料?

18.6.1 分层

载体 内容
技术书 完整论证、实验与反例
单篇笔记 选一个问题形成独立文章
工具与商业交付 可从实践中生长,但不决定书中结论

二者引用同一研究资产,不必逐字相同。

18.6.2 公开前检查

客户信息脱敏、授权、引用、实测状态四项都要检查。读者无需购买服务也能理解并有条件复现公开内容,商业材料不混入技术结论。

依据与核验入口:R18


18.7 毕业项目:提交可审计的工程决策包

本节要回答的问题:怎样证明读者已经能独立接手一套推理系统?

18.7.1 交付清单

系统档案、可信基线、诊断假设、受控优化、质量验收、容量成本、回滚建议——七项齐全。

18.7.2 审查重点

三条:

  1. 数据是否支持结论(而不是结论挑选数据);
  2. 未知项是否明确(缺口表是否诚实);
  3. 决策是否可执行(有采用条件、风险、回滚与后续验证范围)。

18.7.3 两种合格成果

  • 证明了某项优化有效并给出边界;
  • 证明了某项复杂优化不值得采用。

无需追求最大模型或最高提升。

18.7.4 不能做的事

不存在把设计当实测、把引用当自测、把局部结果当通则的情况。可交付完整设计而诚实保留缺口。

依据与核验入口:R14 R25 S17


18.8 深化:让书中的结论变成可持续审查的工程资产

18.8.1 结论、来源、运行和图表不再是孤立文件

一条可维护的结论至少关联四种对象:声明它说了什么的claim、支持它的source或run、重算它的test、展示它的figure。它们的身份不必相同。例如“Qwen2.5-7B的逻辑KV为56 KiB/Token”依赖模型配置、BF16状态假设和计算程序;它不依赖一条GPU吞吐日志,也不能被标成GPU测量。

配套目录将来源登记、新核验来源、公开案例数值、输入、E0测试结果与图表分开放置。原始章节的修改记录保留旧文和新文,便于追踪某个结论为什么改变。来源页面升级,只新增适用记录,不覆盖旧运行的事实。

18.8.2 回归门槛必须区分噪声与业务风险

固定“P99变化超过12%就失败”可以是某个项目的规则,但不是全书通用统计标准。应先定义最小重要效应、可接受风险和重复单位,再估计运行波动。关键功能错误可以直接否决;性能则应结合差值区间、重复方向和实际业务影响。

同时检查很多指标会产生多重比较问题。作为纯统计例子,若20个相互独立、实际无变化的检验都使用5%误报率,则至少一个误报的概率为 \(1-0.95^{20}\approx64.15\%\)。真实指标相关,不能把这个值照搬,但它说明“指标越多就越可靠”不成立。可以预注册少量主指标,把其它指标作为诊断信息,再用独立运行确认报警。

对于模板升级,权重哈希不变只说明权重制品未变。输入分布和激活范围可能改变,量化、长上下文和关键任务仍需按影响范围回归。因此本书不设无条件豁免权重量化验收。

18.8.3 真正执行的端到端链条是 E0,不是假装 GPU 已测

本次可在普通计算机上完成的链条为:读取输入和公开数值→执行定义明确的计算→运行断言→生成数据表→重绘图表→检查文件关联。它验证了指标、公式和出版资产之间的一致性。读取官方报告、复算其数字,不等于重新运行报告中的模型。

读者可从配套程序的测试入口重跑:首Token与终端时间差、单Token请求的TPOT空值、分位算法差异、KV分片容量、关键路径和区间并集、推测推进量、扩容等待、净收益与整数副本数。报告中记录实际执行的测试数量和结果,不再靠正文反复写“已复算”作为证明。

硬件LAB另需服务授权、运行manifest、逐请求输出、质量标签和观测证据。其状态不因E0通过而自动升级。一个没有这些证据的“18章端到端实验全部完成”结论,在本书仍然不允许出现。

18.8.4 发布应有三个独立门槛

第一是内容门槛:公式、单位、术语和跨章引用一致,重要论断有来源或明确推导。第二是资产门槛:图表可重建、链接有效、运行输入和脚本身份可追溯。第三是证据门槛:每个论断都能追溯到来源或明确推导,构造数据与公开数据分别标记。

公开资料、推导与第三方实测可以长期共存,不要求把所有例子替换掉。真正需要避免的是混淆它们,从而让读者误以为所有精确数字都来自同一套设备、同一模型和同一次实验。


18.9 实验图表补充:把多指标回归变成可验证的发布门槛

当每项指标都用5%的显著性门槛,且20项检验的零假设都为真、检验相互独立时,至少一项误报的概率是 \(1-0.95^{20}\approx64.15\%\)。这不是指标越多质量越差,而是未经调整的多重检验会改变整体误报风险。

图的第二条线给每项分配 \(0.05/m\) 的Bonferroni门槛。独立示例可画精确曲线;“整体误报率不超过5%”的并集界本身不要求独立。代价是统计功效下降,因此不能据此机械制定所有发布规则。关键业务门槛、重大质量失败和统计比较应分层,而非把所有量塞入同一个p值体系。

本节新增图表均对应 JSON 数据、公式或来源定位,并给出E0算术/资产检查。本次69项算术与资产检查全部通过(具体清单见随包JSON);它们不测试GPU/NPU运行正确性,也不能把早期版本的 98 项计数当作这次全部重跑。

发布纪律:新实验新增run_id和source_record,不覆盖旧记录;数据与预期故事冲突时修改结论。将图、数值表、原始样本、环境身份、阈值与决策绑定。公开案例缺少逐请求样本时,就保留证据缺口,而不是为了完整章节补出一份不存在的结果。

图18-V1 回归指标越多,误报至少一次的概率越高

图18-V1 回归指标越多,误报至少一次的概率越高

数据身份:TEACHING_CALCULATION。条件:所有零假设为真;独立检验时画精确族错误率;Bonferroni的≤5%保证不要求独立。

指标数 未校正至少一次/% Bonferroni独立例/%
1 5.0 5.0
5 22.622 4.901
10 40.126 4.889
20 64.151 4.883
50 92.306 4.879

复算公式:FWER=1-(1-alpha)^m。

出处与边界:本书18.8;概率复算;回归指标往往相关,红线不是实测误报率;统计显著也不能替代业务阈值。

数据与绘图点 · PNG · 重绘代码

18.10 第三方数据补充:漂移与噪声:回归治理的两个前提

本章前面讲回归测试与版本治理。这一节补两组第三方实测,为"必须先固定制品与运行条件"这条纪律提供依据。

前提一:确实会漂移(Chen、Zaharia、Zou《How Is ChatGPT\'s Behavior Changing over Time?》,arXiv:2307.09009;HDSR 2024)。同一 provider、同一批任务、同一套提示(仅 user prompt、默认 system prompt、temperature=0.1),三个月内:GPT-4 的素数判定准确率从 84.0% 掉到 51.1%(−32.9 个百分点),代码可执行率从 52.0% 掉到 10.0%(−42.0 个百分点)。这不是"模型变笨了"的段子,而是回归治理的现实:升级可能是静默的,且方向不保证。

前提二:不固定条件,连自己和自己都比不了。两个方向相反的噪声同时存在。污染让分数虚高:对泄漏样本做难度保持改写后,GSM8K 准确率下降 22.9%、MMLU 下降 19.0%(Inference-Time Decontamination,arXiv:2406.13990)。非确定性让分数抖动:同一提示、temperature=0、重复 1000 次贪心采样,竟得到 80 个不同结果;换成 batch 不变内核后才收敛为 1 个(Thinking Machines Lab)。前者要求评测集去污染,后者要求固定内核与批处理。

边界:漂移实验仅两版快照、单一 provider、时间窗 3 个月,部分任务样本小(代码 n=50),不能外推到所有升级。ITD 去污染依赖检测器精度(报告检测到 62.7%/79.4% 泄漏),残留污染仍会高估;其证明性实验为人工注入污染,下降幅度不等于真实污染程度。非确定性实验来自单一团队、集中于 vLLM 与少数内核。

图18-T1 同一个模型,三个月后就不一样了(第三方跨版本实测)

图18-T1 同一个模型,三个月后就不一样了(第三方跨版本实测)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:Chen、Zaharia、Zou《How Is ChatGPT's Behavior Changing over Time?》(arXiv:2307.09009;HDSR 2024);两版均为当时在售 API 版本,仅用 user prompt、默认 system prompt、temperature=0.1。

任务 GPT-4 (2023-03) GPT-4 (2023-06) 变化
素数判定准确率/% 84.0 51.1 −32.9 个百分点
代码可执行率/% 52.0 10.0 −42.0 个百分点

出处与边界:Z17;仅两版快照、单一 provider、时间窗 3 个月,不能外推到所有版本升级;部分任务样本较小(代码 n=50);未公开确切内部版本号。

数据与绘图点 · PNG · 重绘代码

图18-T2 评测噪声的两个来源:基准污染与内核非确定性(第三方实测)

图18-T2 评测噪声的两个来源:基准污染与内核非确定性(第三方实测)

数据身份:THIRD_PARTY_MEASURED(第三方公开数据)。条件:ITD(arXiv:2406.13990):对泄漏样本做难度保持改写,GSM8K 8-shot CoT、MMLU 官方 5-shot、greedy;Thinking Machines Lab:vLLM + FlexAttention,temperature=0,重复 1000 次贪心采样。

现象 数值 口径
GSM8K 去污染后准确率下降 22.9 %(Inference-Time Decontamination)
MMLU 去污染后准确率下降 19.0 %(Inference-Time Decontamination)
temperature=0 重复 1000 次得到的不同结果数 80 个(batch 不变性缺失)
替换为 batch 不变内核后的不同结果数 1 个(1000 次完全一致)

出处与边界:Z18;ITD 去污染依赖检测器精度(报告检测到 62.7%/79.4% 泄漏),残留污染仍会高估;其证明性实验为人工注入污染,下降幅度不能等同真实污染程度。非确定性实验来自单一团队、集中于 vLLM 与少数内核;batch 不变内核的端到端开销随实现而变。

数据与绘图点 · PNG · 重绘代码

章末检查问题

1. 实测结果与预期故事冲突时,应该修改什么?

答案方向:修改结论,不修改数据。具体三步:① 先检查是不是测量问题(口径、时间域、分母、工具映射)——第10章假设树的第 ④ 支就是为此而设;② 若测量无误,则按证据改写结论,包括允许"尚无法区分两种解释"这种结论,并写明下一项证据;③ 记录这次冲突本身进案例库(18.5),症状、环境、证据、处理、反例齐全。严禁的是:为了故事完整而删掉不利样本、放宽质量门槛,或把推测写成已验证。第18.2 节的三类支线模板明确要求结果栏标注证据状态。

2. 模型模板变化但权重没变,哪些回归必须触发?

答案方向:模板变化至少触发质量、格式与停止、长度分布、性能与前缀命中回归。权重文件未改变,只能复用制品身份校验,不能无条件豁免量化质量与数值风险验收。模板会改变输入分布、激活范围、推理长度和校准覆盖;若它触及量化敏感场景、长上下文或关键任务,必须重跑相应独立子集。回归范围按影响分析决定,而不是仅检查权重哈希。

3. 如何防止官网更新覆盖历史实验适用的版本事实?

答案方向:三条机制。① 固定身份:来源必须固定 commit 或存档快照,禁止用浮动 latest 作为唯一身份;② 三个日期分开存:事件日、发布日期、查阅日分别记录(18.4.2),后续更新新增一条记录而不是修改旧记录;③ 两个独立布尔:body_verified(是否取到正文)与 runtime_verified(是否本地运行)分开,找到入口不等于核验了正文。这样旧实验结论仍绑定它当时的版本事实,新版网页只产生一条待复核的新记录。三条入口的历史状态不变;本次已补的 N08/N09 使用新记录,不能倒填为旧入口当时已核验 SOURCE_REGISTERED S25。


本章验收清单

节 验收标准
18.1 主案例八环节齐全且不重复造数据;最终建议可追溯到原始运行、负载与质量条件
18.2 三类支线模板齐全;结果栏待验证;至少保留一个无收益/不采用的真实案例
18.3 三层回归与变更→范围矩阵完整;阈值考虑波动与分层;设备证据不被配置解析替代
18.4 六类身份分别保留;source_record 含三日期与两布尔;升级不覆盖旧数据;不用 latest 作唯一身份
18.5 失败案例卡带适用条件;生命周期三态不混用;更新记录链接到受影响部位
18.6 技术书与单篇笔记分层;公开前四项检查完成;商业材料不混入技术结论
18.7 七项交付齐全;审查三重点通过;存在"不采用"类合格成果;不把设计当实测

配套资料

新增图表与数据 · 来源登记 · E0测试清单 · 修订记录

增补图表

共 49 张:第三方公开数据 16 张、深化补充 10 张、基础补充 23 张。图注取自各章原 Markdown,第三方公开数据与公开结果分列,每张图均标注来源。

作者与公众号

Jason Zhang 《大模型推理系统工程:性能、诊断与优化》作者。

这本书的出发点很简单:推理性能的讨论常常停在「换个框架试试」,而真正卡住人的是算不清的账——KV Cache 到底吃多少显存、这条链路的带宽上限在哪、扩容与优化哪个先做。

公众号「AI算力局」用于放这本书放不下的东西:章节勘误、新版本框架的行为变化、以及推理与算力方向的工程笔记。读的过程中发现数字对不上、结论过期,欢迎到那里告诉我。

微信扫码关注公众号 AI算力局
微信扫码 · 或搜一搜「AI算力局」