哈佛-CS249r-机器学习系统第二卷-四-
哈佛 CS249r:机器学习系统第二卷(四)
原文:Machine-Learning-Systems-Vol2
译者:飞龙
Ensure: 一种通信成本最低的“全有或全无”放置,或作业保持在队列中
-
枚举可行槽位:满足每个资源维度的N个空闲加速器的集合 — gang: 全有或全无
-
如果 不存在可行槽位 则
-
将作业保持在队列中,且不占用任何加速器 — 防止持有-等待死锁
-
结束如果
-
对于 每个可行槽位 执行
-
将张量并行排名映射到 NVLink 域内,流水线阶段映射到机架内,数据并行副本映射到机架之间
-
根据局部性成本对槽位进行评分:Cost[locality] = ∑[i < j]w(d(g[i], g[j]))
-
结束循环
-
返回 具有最低 Cost[locality]的可行槽位 — 拓扑最优,原子性
分散放置导致 AllReduce 的速度比拓扑感知的槽位慢 4.8×,图 8.4 从随机放置到拓扑最优放置的四种放置策略中详细说明了这一差距。

图 8.4:拓扑放置影响:相比于 64 个 GPU 上 1 GB 梯度同步的随机放置,通过拓扑感知调度(机架/轨优化)将 AllReduce 延迟降低 4.8×。
分层放置与拓扑感知调度
分层放置算法
分层放置算法按照匹配该并行层次结构的层次分配资源。
-
张量并行组:将张量并行组分配到各个节点,确保张量并行组内的所有 GPU 共享
NVLink连接。 -
流水线阶段:将这些节点分组为机架内的流水线阶段,将相邻的流水线阶段放置在通过同一叶子交换机连接的节点上。
-
数据并行副本:将数据并行副本分布在各个机架上,确保每个数据并行组都能访问集群的全分节带宽以进行
AllReduce操作。
这种三层放置镜像了三层拓扑和三层并行策略,在逻辑结构和物理结构之间创造了自然的对齐。
物理 GPU 放置对集合通信性能的影响
GPU 在网络交换结构上的物理位置极大地影响集合通信性能。以跨 64 个 GPU 的 AllReduce 操作为例:
-
随机放置:分散在许多机架中 → 流量遍历核心交换机 → 120 ms 延迟。
-
机架感知:限制在单个机架内(顶部交换机) → 85 ms。
-
Rail 优化:对齐到专用“Rail”(专用标准 IB 交换机) → 40 ms。
-
拓扑最优:所有 GPU 在同一叶子交换机上 → 25 ms。
仅通过尊重网络拓扑,合理的调度即可在模型代码零变更的情况下实现 4.8× 的加速。
拓扑感知放置的计算成本
拓扑感知放置的计算成本并非微不足道。在一个拥有 10,000 个 GPU 的集群中,评估一个 256-GPU 作业的所有可能放置在组合上是不可行的。生产环境调度器使用启发式方法:
-
贪心算法:自底向上构建放置(先填满
NVLink域,再填满机架,最后跨机架分散)。 -
带剪枝的树搜索算法:优先探索最有希望的放置。
-
基于评分的方法:评估一部分可行的放置并选择最佳者。
与数小时到数周的作业运行时间相比,拓扑感知算法引入的调度延迟(毫秒到秒级,取决于集群规模和算法复杂度)微不足道,这使得吞吐量的提升值得调度开销。
场景:采用 3D 并行的 256-GPU 训练作业
配置:8 路张量并行,4 路流水线并行,8 路数据并行。
优秀的放置(拓扑感知)
-
张量并行组:32 组,每组 8 个 GPU,每组限制在一个 8-GPU 节点内(
NVLink:900 GB/s)。 -
流水线阶段:同一机架内的 4 个连续节点(1 个交换机跳数,
InfiniBand:50 GB/s)。 -
数据并行副本:8 个机架(跨机架
AllReduce为 2 个交换机跳数)。
差劲的放置(拓扑无感)
-
张量并行组:分散在 2 个节点上(使用
InfiniBand而非NVLink)。 -
流水线阶段:分散在各机架上(2 个交换机跳数而非 1 个)。
-
数据并行副本:随机分布。
数学影响:张量并行通信
仅张量并行通信就足以说明影响。NVLink 提供每向 450 GB/s 的带宽,传输 1 GB 激活值约需 2.2 ms。而在 50 GB/s 的 InfiniBand 上,相同传输约需 20 ms,产生 9× 的减速。
系统洞察
图 8.4 中的 4.8× AllReduce 延迟差距仅是通信层面的测量;它不会转化为 4.8× 的端到端惩罚,因为同步训练将大部分通信与下一迭代的计算重叠了。由于张量并行在每个 Transformer 层都进行通信(大模型通常为 80+ 层),剩余暴露的通信累积导致总训练吞吐量下降 15% 至 30%,这是 3D 并行作业需重点关注的数字。
Rail 优化调度
大型 GPU 集群越来越多地采用 Rail 优化拓扑,即节点内的每个 GPU 连接到不同的网络交换机,在网络结构中创建并行的“Rail”。这种设计在第 3.5.3 节中分析过,通过将流量分散到独立的交换机层级,为所有 GPU 提供均衡的带宽。然而,它也带来了调度约束:当通信的 GPU 位于同一 Rail(连接到同一交换机链)时,AllReduce 操作效率最高,因为同 Rail 通信避免了可能在共享交换机端口产生拥塞的跨 Rail 流量。
Rail 感知调度分配数据并行副本,使得跨节点的对应 GPU 共享同一 Rail。对于每节点 8 GPU、共 8 条 Rail 的集群,节点上 GPU 0 连接 Rail 0,GPU 1 连接 Rail 1,以此类推。调度器将数据并行秩 r 放置在所选节点上的 GPU r mod 8 处,确保每个数据并行组的 AllReduce 仅遍历单一的 Rail 交换机层级,而非跨 Rail。这种对齐意味着 AllReduce 流量被限制在独立的交换机树中,消除了数据并行组之间的竞争,并为每个组提供其专用 Rail 的全部带宽。
多维优化问题
Rail 感知调度与拓扑感知放置的相互作用构成了一个多维优化问题。调度器必须同时针对以下目标进行优化:
-
节点内 NVLink 局部性:张量并行要求 GPU 在同一节点上。
-
机架内交换机局部性:流水线并行受益于阶段间最少的交换机跳数。
-
Rail 对齐:数据并行受益于同 Rail 通信。
这三个目标部分冲突:为 Rail 对齐而将数据并行副本限制在每个节点内的特定 GPU 位置,降低了调度器基于机架局部性选择节点的灵活性;而为 NVLink 局部性要求整节点分配,阻止了通过节点共享提高打包效率的可能。
生产调度器的解决方案
生产调度器使用加权评分函数解决这些冲突,根据工作负载特征确定约束优先级:
-
大规模训练作业(3D 并行):张量并行局部性获得最高权重(因为
NVLink到InfiniBand的性能下降最大),其次是 Rail 对齐(因为它消除了最频繁通信模式的拥塞),最后是机架局部性(因为性能收益较小,且可部分通过计算通信重叠补偿)。 -
纯数据并行作业:Rail 对齐获得最高权重。
-
单节点作业:拓扑约束无关紧要,调度器纯粹针对打包效率优化。
拓扑发现与动态适应
像 Slurm 的 topology.conf 或 Kubernetes 节点标签这样的静态配置文件提供了集群的基线地图,但它们代表的是预期状态,而非有效状态。真正的拓扑发现需要运行时自省,以验证物理硬件是否与架构图匹配。像 NCCL 这样的库完全绕过静态配置,解析 /sys/class/infiniband 下的 Linux 虚拟文件系统,以映射 PCIe 总线层级、枚举 NVLink 连接并验证 NIC 邻近性。这创建了一个基准真理图,其中边代表经过验证的、可操作的链路,而非理论上的线缆。
集群拓扑是一种运行状态,而非固定的图纸。硬件故障、维护窗口和热节流不断重塑调度器可用的有效拓扑。在 Clos 网络中,单个脊叶交换机故障不会完全切断连通性,但会大幅降低跨越受影响机架的作业可用的分节带宽。如果为我们的 1750 亿参数模型的数据并行 AllReduce 组服务的脊叶交换机发生故障,跨机架带宽可能下降 50%。调度器面临关键优化决策:以降低的吞吐量继续训练,实际上浪费了 25% 的昂贵 GPU 周期等待滞后梯度;或者发起拓扑感知迁移。
迁移权衡
迁移并非零成本;它需要协调检查点、终止作业、在健康节点上进行重新调度事件以及预热阶段。此过程通常会消耗 5 到 15 分钟的空闲时间。然而,对于长达数周的训练运行,迁移在数学上往往更有利。如果网络降级导致 20% 的吞吐量损失,10 分钟迁移的盈亏平衡点仅为不到一小时的训练时间。复杂的编排器持续监控集合通信库上报的有效带宽指标。当分配的拓扑评分与实际吞吐量之间的差值超过阈值时,调度器会触发驱逐并迁移工作流,将网络降级视作与 GPU 硬件故障同等严重的事件处理。
场景
一个基础模型训练作业显示出巨大的吞吐量回归。GPU 运行正常,代码未变更,静态拓扑图显示该作业拥有全剖面带宽。
故障模式
单个交换机或收发器可能技术上保持“启动”状态,却产生高 CRC 错误率、丢包或重传。二进制健康检查报告绿色;集合通信性能报告红色。
后果
如果调度器依赖静态拓扑图,它会继续将通信密集型作业调度到降级的机架上,假设存在已不复存在的全带宽。
系统洞察
二进制健康检查(启动/关闭)是不够的。调度器需要性能感知的拓扑监控,该监控需结合交换机计数器、重传率和集合带宽测试。连接到“跛足”交换机的节点应动态标记为不适合通信密集型作业,直到硬件修复完成。
隔离调度
通用的调度理念是隔离:当运行时证据表明某节点、机架或链路已降级时,调度器应停止将敏感工作放置在那里,即使名义资源数量看起来仍然可用。Kubernetes 通过 taints and tolerations[¹³¹] 实现这一模式,而 Slurm 集群通常使用排空状态、节点特性或分区规则。互补问题是作业如何在可用资源动态变化时进行适应。与其将资源分配视为作业生命周期内固定不变的,弹性训练允许作业在无需完全重启的情况下增长、收缩和恢复。
弹性调度
当集群需求波动时,刚性分配会造成两类浪费。在非高峰时段,运行在 256 个 GPU 上的作业无法吸收空闲资源以加速训练。在高峰需求期间,同一作业若不被完全抢占,就无法释放多余资源以容纳更高优先级的工作。调度器的选项只有“让它保留所有资源”或“彻底终止它”,两者之间没有中间地带。
弹性调度消除了这种二元选择。第 7.8 节 中开发的弹性训练恢复机制展示了分布式作业如何调整工作进程数量、重新校准批次大小和学习率,并在故障后重建其通信组。从调度器的角度来看,相同的能力解决了在需求波动下的资源分配问题。一个训练作业可能从 128 个 GPU(实现合理吞吐量所需的最小值)启动,在资源可用时扩展到 256 个,当更高优先级工作到达时收缩回 192 个,同时保持持续的训练进度。这种灵活性将调度器的动作空间从二元的 {继续, 抢占} 转变为资源分配级别的连续谱系。
调度器-框架契约
弹性调度要求调度器与训练框架之间有明确定义的契约。调度器保证通过定义的信号传达资源变更,并提供足够的提前量以便优雅适应。框架保证它能在不损坏模型状态的情况下处理资源变更,使用批次大小调整、学习率重新校准和通信组重建机制(详见第 7.8.1 节)。
从调度器的角度来看,该契约有三个参数:
-
弹性范围:
[N_min, N_max]指定作业可运行的工作进程数量区间。 -
过渡成本:汇聚周期,通常为 10 到 60 秒,决定了调度器在不使开销抵消吞吐量增益的前提下,可以多久调整一次规模。
-
扩展效率曲线:每增加一个工作进程获得的吞吐量增益,决定了将空闲 GPU 分配给该作业是否比为待处理的帮派调度作业保留它们更有成效。
该契约为调度器提供了足够的信息来调整作业规模,而无需将弹性视为免费产能。
调度器还必须考虑到这样一个约束:弹性扩缩容会改变有效批次大小(B_effective = B_per-worker × N)。处理此问题的两种策略(恒定全局批次大小 vs. 自适应学习率)对调度器可激进调整规模的程度施加了不同的限制:恒定全局批次大小保持收敛性,但在低工作进程数时可能违反单 GPU 内存限制;而自适应学习率简化了工作进程管理,但在快速扩缩容期间引入了收敛风险。
调度器接口(如 TorchElastic)直接暴露这些参数,如清单 8.2 所示。
清单 8.2:TorchElastic 启动配置:--nnodes=4:16 范围告诉调度器该作业可使用 32 到 128 个 GPU 运行。调度器根据当前集群负载将作业置于该范围内的任意点,并随需求变化调整其规模。
--nnodes=4:16 规范声明该作业可使用 4 到 16 个节点(每节点 8 个 GPU,即 32 到 128 个 GPU)运行。此范围即为调度器的动作空间:它可根据当前集群负载将作业置于该范围内的任意点,并随需求变化调整其规模。
调度对比示例
设想一个拥塞的集群,一个 128-GPU 的作业等待 8 小时以获得完整的帮派分配。
刚性调度
等待 8 小时,然后以全吞吐量训练 24 小时。总墙钟时间:32 小时。
弹性调度(最小 32,最大 128)
-
立即以 32 个 GPU 启动(吞吐量 = 全速的 25%)
-
2 小时后,扩展到 64 个(吞吐量 = 50%)
-
4 小时后,扩展到 128 个(吞吐量 = 100%)
-
在整个等待期间训练持续进展
8 小时“排队等待”期间完成的近似工作量:
-
第 0 到 2 小时:25% 吞吐量 × 2 小时 = 0.5 小时等效全速工作量
-
第 2 到 4 小时:50% 吞吐量 × 2 小时 = 1.0 小时等效全速工作量
-
第 4 到 8 小时:100% 吞吐量 × 4 小时 = 4.0 小时等效全速工作量
-
总计:5.5 小时等效全速工作量
弹性作业约在 26.5 小时内完成(8 小时扩容训练加 18.5 小时全速训练),相比刚性调度节省了 5.5 小时。这 17% 的提升完全源于利用原本会空闲的排队时间进行生产性工作。
调度器并不能把每个作业都视为弹性作业。最显著的限制是与模型并行性的不兼容。弹性扩缩本质上是一种数据并行操作:调度器增加或移除模型的副本,改变全局批大小,但保持模型架构不变。对于依赖每个节点内 8 路张量并行的 175 B 参数作业,弹性只能严格在 node(节点)层面操作。该作业可以在 128 节点(1,024 GPU)和 32 节点(256 GPU)之间以整节点为步长进行扩缩,从而改变数据并行度。它不能弹性地从张量并行组中移除单个 GPU;这样做需要重新分片模型权重,相当于一次完整的 checkpoint‑and‑restart 循环。流水线并行在原理上允许有限的弹性,但添加或移除阶段会迫使重新计算流水线调度,往往抵消收益。
迁移成本也限制了调度器的调整频率。每一次扩缩都会导致框架在重建通信组期间吞吐量下降 10 到 60 秒。调度器必须在后续运行时长上摊销这部分成本,因此只有当作业在新规模下运行的时间显著长于迁移窗口时,才值得进行小幅度的调整;第 8.4.3 节 正是通过这种规则排除了抖动情形。
调度器集成
弹性训练的价值关键在于 调度器集成。若调度器不感知弹性,弹性训练仅是一个容错机制(替换失效的工作节点)。而有了调度器集成,它就成为一种调度优化,从根本上改变集群利用率的经济性。调度器必须了解弹性作业可以在 [N_min, N_max] 工作节点范围内运行,从而实现三大关键调度优化。
最直接的收益是 更快的作业启动。一个请求 32 到 128 GPU 的弹性作业可以在只要有 32 GPU 可用时立即启动,而无需在群组调度队列中等待 128 个连续的 GPU。在大型作业的排队时间可能长达数小时甚至数天的拥堵集群中,以降低规模立即启动往往能比等待完整规模分配更快完成作业,这一权衡在下面的弹性扩缩决策中会定量体现。调度器可以动态计算,选择使 期望总时长(等待时间+执行时间) 最小的启动时间和初始规模。
机会型扩缩使调度器能够将弹性作业用作灵活的容量吸收器。当资源释放(其他作业完成、Spot 实例被配置、被抢占作业释放资源)时,调度器可以扩大正在运行的弹性作业以提升其吞吐量;当需要资源(更高优先级作业到达、保留容量被收回)时,调度器可以在不完全抢占的前提下收缩弹性作业。这种双向弹性将弹性作业转变为一个缓冲区,吸收利用率波动,平滑分配容量与实际生产能力之间的差距。
平滑抢占改变了调度器面对资源压力的响应方式。调度器不再直接杀掉训练作业以回收资源,而是请求 scale‑down,降低作业的资源配额,以释放容量给更高优先级的工作。作业继续以较少的工作节点运行,保持进度,只是吞吐量下降,而不是自上次 checkpoint 起全部丢失进度并承担重启开销。这为调度器提供了一个介于“全资源”和“完全抢占”之间的连续谱,而不是传统调度的二元选择。平滑抢占还能与基于 checkpoint 的抢占良好结合:如果调度器需要回收全部资源,它先把作业缩到最小规模,然后启动 checkpoint‑and‑preempt 流程,最小化丢失的工作量。
上述所有收益的权衡是系统复杂性。弹性训练需要框架支持(并非所有训练框架都支持),并会因批大小变化引入潜在的收敛问题(需验证弹性扩缩不会降低模型质量),同时增加性能建模难度(作业的吞吐量不再是常数,容量规划更困难)。并非所有工作负载都能同等受益于弹性扩缩:通信占比高的作业在增加工作节点时收益递减(因为通信开销随节点数增长),因此对 scale‑up 的收益较小;而计算占比高、通信开销低的作业则更线性地扩展,能更好地利用机会型扩缩。调度器必须对每个作业的扩缩特性建模,才能做出最优的弹性扩缩决策。
弹性扩缩策略
向运行中的作业添加资源并不总能加速收敛时间。把吞吐量视为随工作节点线性增长的朴素假设在通信开销的重压下会崩溃,调度器必须依据 经验效率曲线 而非单纯的资源可用性来强制弹性扩缩策略。作业的扩缩效率 η_scaling(k)(在 k 工作节点上观测到的吞吐量与理想线性加速的比值)通常呈现三段明显的行为:线性阶段(计算占主导,通信可忽略),次线性阶段(梯度同步延迟开始掩盖计算),以及饱和阶段(AllReduce 环路成为瓶颈)。如果调度器盲目在饱和阶段分配可用 GPU,便会浪费集群容量;相反,在作业低于其最小可行规模时运行,则会让它占用内存和网络资源却只产生极低的进展。
弹性扩缩的收益在进入饱和后趋于平缓。
因此,扩缩决策应基于 边际效用 而非纯粹的空闲资源。以我们的 175 B 参数模型为例,效率曲线来源于实测:在 64‑512 GPU 区间呈线性扩展,512‑1,024 GPU 区间次线性,超过 1,024 GPU 后收益递减。于是调度器规定弹性范围为 [256, 1024]。若集群有空闲资源但作业已达 1,024 GPU,调度器会保留额外节点,判断 5% 的边际吞吐提升不足以抵消重新分片的开销或导致待处理作业被饿死的机会成本。类似地,若仅有 128 GPU 可用,调度器可能选择将作业排队而非运行,因为每 GPU 的训练强度过低,无法掩盖通信延迟。
这种逻辑同样适用于 时间维度:调度器必须在较小配额的即时进展与等待更大配额的延迟收益之间权衡。若一个 512‑GPU 作业可以立即使用 128 GPU 启动,或等待两小时获得全部请求,最佳选择取决于 吞吐量随时间的积分。缩放的抢占开销(停止、checkpoint、重启的成本)必须在后续运行时长上摊销。一个“机会型扩缩”策略——每当单个节点空闲即对作业进行一次 resize——往往因分布式进程组的不断抖动而导致净负进展。
考虑一个目标批大小需要 512 块 A100 才能达到峰值效率的大语言模型训练作业。三种调度策略在 24 小时窗口内进行评估。
给定条件
在 128 张 GPU 上的吞吐率为 1 个 epoch/小时(基线)。在 256 张 GPU 上的吞吐率为 1.8 个 epoch/小时(90% 扩展效率)。在 512 张 GPU 上的吞吐率为 3.2 个 epoch/小时(80% 扩展效率)。重新扩缩容成本为 10 分钟(0.17 小时),用于执行检查点、重新分片和重启。
策略 1 – 立即启动(无扩缩容)
作业立即使用 128 张 GPU 启动,并运行完整的 24 小时。总工作量:24 × 1.0 = 24 epochs。
策略 2 – 等待容量(Gang Scheduling / 群组调度)
作业在队列中等待 4 小时,直到 512 张 GPU 可用,然后运行 20 小时。总工作量:20 × 3.2 = 64 epochs。
策略 3 – 弹性扩缩容(阶梯式扩容)
作业立即使用 128 张 GPU 启动。4 小时后,256 张 GPU 变为可用。作业暂停、扩容并恢复。
-
阶段 1:
4 × 1.0 = 4 epochs -
阶段 2:
(24 − 4 − 0.17) × 1.8 ≈ 35.7 epochs
总工作量:4 + 35.7 = 39.7 epochs。
弹性策略完成的 epoch 等效工作量几乎是立即启动的小规模运行的 1.7 倍,但仍比群组调度落后约 38%。当队列等待时间增长或峰值容量持续稀缺时,这一差距会缩小;重新扩缩容的开销仅为 10 分钟,如果该开销超过 2 小时(由于检查点传输缓慢或编译耗时),策略 3 将进一步落后于策略 2。
迄今讨论的放置和弹性机制针对的是单个作业的效率,优化每个作业如何使用其分配的资源。车队经济学将目标转移到集体层面:分配策略必须在每次放置、抢占和预留都会产生机会成本的情况下,最大化所有作业的有用工作总量。
成本优化
第 8.1 节中建立的利用率与美元关系,现在成为编排器的目标函数。一个拥有 10,000 张 GPU 的集群代表约 3 亿美元的资本投入,并每月消耗数百万美元的电力和制冷费用。如果糟糕的调度导致 20% 的 GPU 因碎片化空隙或等待数据而空闲,约 6 亿美元的部署资本将闲置不用,按云等效费率计算,同样的闲置每年将消耗约 3500 万美元的运营成本。成本优化迫使车队编排器将集群视为一个巨大的金融资产,其投资回报率(ROI)必须最大化。
图 8.5 通过绘制年度浪费成本作为集群规模和利用率水平的函数,使这种浪费具体化。在 10,000 张 GPU、利用率 50% 的情况下,按本章 2 美元/GPU-小时的核算场景,年度浪费超过 8700 万美元。生产集群的公开测量数据表明,利用率不足是真实存在的运营问题:微软 Philly 集群研究(Jeon 等人 2019)测得的平均 GPU 利用率约为 52%,Li 等人(2023)发现 NERSC Perlmutter 超级计算机上 50% 的作业使用了不到 25% 的分配 GPU 显存。图中 30% 到 70% 的阴影带是用于成本模型的说明性运行范围,而非关于所有生产集群的普遍实证声明。在本场景下,在 10,000 张 GPU 的集群上将利用率从 50% 提高到 70%,无需购买任何额外 GPU,每年可节省超过 3500 万美元。

图 8.5:闲置 GPU 的成本:年度浪费成本作为集群规模的函数,针对不同 GPU 利用率水平,假设 2 美元/GPU-小时(云等效价格)。30% 到 70% 之间的阴影区域是场景分析的说明性运行范围。在 10,000 张 GPU、50% 利用率下,该模型得出的年度浪费产能为 8760 万美元。微软 Philly 数据点标记了公开发表的 52% 平均 GPU 利用率。数据来源:(Jeon 等人 2019; Li 等人 2023)。
闲置产能数字确立了经济目标;其余成本控制手段解释了调度器如何向该目标迈进。可中断容量降低了容错工作负载的价格,预留和配额策略决定哪些作业值得稀缺的保证容量,而问责机制则让造成需求的团队能够看到闲置的 GPU。
抢占式实例与可抢占虚拟机
云服务商提供 抢占式实例¹³²(AWS)或 可抢占虚拟机(GCP),价格比按需定价低 60% 到 90%,但有一个条件:当提供商需要为按需客户回收容量时,实例可能在短短 30 秒到 2 分钟内被收回。按每 GPU-小时 0.70 美元而非 2 美元计算,抢占式实例改变了大规模训练的经济核算,有可能将多周预训练运行的成本从数十万美元降低到十万美元以下。
抢占式实例用于 ML 训练的可行性取决于两个因素,它们决定折扣能否转化为实际节省:每次中断的成本(检查点开销 + 重启时间 + 自上次检查点以来丢失的工作量)和中断频率(因实例类型、区域、时间段及整体云需求而异)。公式 8.3 给出了抢占式训练的 有效成本:
C_effective = C_spot × (T_total / T_productive) (8.3)
其中 T_total 包含有效训练时间 + 检查点开销 + 中断后的重启开销,T_productive 是推进训练所花费的时间。如果中断罕见(每天少于一次)且检查点高效(开销小于 5%),有效成本将接近抢占价格,节省幅度几乎等于全部折扣。如果中断频繁(每几小时一次)且重启昂贵(大模型、检查点加载慢、GPU 缓存冷启动),有效成本可能接近甚至超过按需定价,将表面上的折扣变成隐性溢价。
抢占式节省与容错基础设施之间的关系在一个重要意义上是循环的。第 7 章中的容错机制(频繁的异步检查点、快速检查点加载、弹性恢复)通过最小化每次中断的成本,直接支持了抢占式实例的使用。弹性训练允许当部分抢占式实例被回收时,作业以减少的工作进程数量继续运行,完全避免了全量重启。那些为可靠性原因投资容错基础设施的组织会发现,同一基础设施通过抢占式实例利用率解锁了显著的成本节省,带来的投资回报远远超出了避免硬件故障导致的工作丢失。
战略洞见在于:容错基础设施具有双重回报:它既防止非自愿损失(硬件故障),又支持自愿成本节省(抢占式实例)。这种双重回报往往能为那些仅凭可靠性理由难以证明合理性的容错投资提供依据,因为抢占式利用带来的成本节省是具体且可衡量的,而硬件故障防护带来的成本规避是统计性的且更难量化。
面向 ML 训练的抢占式实例策略
抢占式实例优化策略
为了在最小化干扰的同时最大化抢占式实例的效用,复杂的调度器采用多元化和预测性策略,而不仅仅是简单的“失败重试”逻辑。
可用区多元化
云服务商通常按可用区 (AZ) 独立管理抢占式资源池。如果一个训练作业需要 1,024 块 GPU,将它们作为一个整体块分配在单个区域中,会使该作业面临完全停止的风险,一旦该特定区域的抢占式资源池被回收,作业就会中断。将作业分散到多个可用区,可确保某个区域发生回收事件时,仅影响一小部分机群。调度器仍需仔细建模相关性:独立的实例级中断不太可能导致大规模同时损失,但资源池级或可用区级的回收可一次性移除数百块 GPU,必须将其视为弹性收缩或故障转移事件,而非普通的孤立故障。
实例类型多元化
实例类型多元化利用了不同 GPU SKU 间独立的抢占式市场。严格请求单一实例类型的作业,只能在单一拥挤的市场中竞争。接受多种等效实例类型的作业,能显著提高其调度概率。调度器应维护一个按优先级排序的可接受实例类型列表,当主要类型不可用时,回退到更大内存的实例。虽然这种混合部署需要仔细管理节点间的点对点带宽,但它有效地利用了“升级”策略,以远低于按需价格的混合成本维持训练进度。
抢占中断预测
抢占中断预测利用云服务商公开的关于回收可能性的数据,例如 AWS Spot Placement Score(抢占式实例放置评分)或 GCP 抢占数据。高级调度器摄取这些数据源,以估算给定实例类型和区域的每日预期中断次数。如果预期中断率上升到重启开销超过成本节省的阈值(对于大模型而言,通常为每天超过 4 次中断),调度器应自动将作业迁移至按需容量或其他区域,防止作业花费在恢复上的时间多于训练时间(即“抖动”现象)。
检查点频率优化
针对抢占式实例的检查点频率优化不同于标准的可靠性逻辑。第 7.1.2 节 中开发的 Young-Daly 公式针对不可预测的硬件故障进行优化。然而,抢占式中断带有预警:AWS 为 2 分钟,GCP 为 30 秒。最优策略是采用两级检查点方法:按 Young-Daly 间隔进行周期性检查点以防止硬崩溃,并结合由终止通知触发的即时检查点。这种“恐慌检查点”在节点消失前几秒捕获训练的确切状态,将丢失的工作量降至接近零,将抢占从回滚事件转变为暂停并恢复事件。
当抢占式中断是相关的而非孤立时,同样的应急规划需求变得更加迫切。
故障模式分析
背景:抢占式容量之所以便宜,是因为它可被回收。这种风险并非在实例间独立存在:云区域、实例类型、产品发布和截止日期周期都可能产生相关需求。
故障模式:将所有可中断训练集中在一个区域和一种实例类型的机群,可能瞬间丢失大量工作节点,即使每个单独的工作节点看起来都是可重启的。
后果:检查点处理孤立中断;但当每次重试都针对同一个耗尽的资源池时,它无法解决全机群抢占问题。
系统洞察:稳健的调度器必须跨区域和实例类型进行多元化,监控中断风险,并在抢占式市场变得“恶劣”时,维护一条通往按需容量的“应急”路径。
容量预留策略
依赖云基础设施的组织(或从经济角度考虑本地基础设施的组织)在三个层级的计算容量之间进行平衡,每个层级在成本、可用性和承诺方面提供不同的权衡。表 8.4 从成本、可用性、中断风险和工作负载适配性等维度对比了这些层级。
| 容量层级 | 成本与承诺 | 可用性与中断风险 | 最适用工作负载 |
| --- | --- | --- | --- |
| 预留容量 | 一至三年承诺,较按需价格享 30–60% 折扣。 | 保证承诺基线的可用性。 | 可预测的持续工作负载:7×24 小时推理、计划内训练、CI 与回归测试、受 SLA 约束的作业。 |
| 按需容量 | 全价,无长期承诺。 | 当排队或中断不可接受时,即时供给。 | 紧急重训练、交互式调试、以及不值得进行抢占式管理的短生命周期作业。 |
| 抢占式容量 | 折扣的可中断容量。 | 可随云需求波动,工作节点可被回收。 | 可重启容忍的作业:超参数搜索、消融实验、带检查点的预训练、探索性研究。 |
表 8.4:云容量预留层级:容量层级在成本和承诺与可用性保证及中断风险之间权衡,为调度器提供了一个价格阶梯,以便根据紧急程度和重启容忍度路由工作负载。
最佳组合取决于工作负载特征和组织优先级。一个稳态利用率为 60%、突发需求为 40% 的组织,可能会为峰值需求的 60% 预留容量,将按需容量用于需要即时供给的延迟敏感型突发,并将抢占式容量用于其余部分。调度系统必须理解这些容量层级并相应地路由工作负载:推理工作负载路由至预留容量以保证可用性;大规模训练作业路由至预留容量(用于基础分配)和抢占式容量(用于通过弹性扩展增加额外工作节点)的组合;探索性工作完全路由至抢占式容量,以在中断容忍度最高的地方最大化成本节省。
成本分析示例
考虑训练一个 70B 参数模型,需要 512 块 GPU 运行 14 天。
按需:512 GPUs × 336 hours × $2/GPU-hour = $344,064
抢占式 (65% 折扣,5% 检查点开销,14 次中断/天,每次重启 30 分钟):
-
基础成本:
512 GPUs × 336 hours × $0.70/GPU-hour = $120,422 -
检查点开销:
5% × 336 hours = 16.8 hours -
重启开销:
196 interruptions × 0.5 hours = 98 hours -
总时间:
336 hours + 16.8 hours + 98 hours = 450.8 hours -
总成本:
512 GPUs × 450.8 hours × $0.70/GPU-hour = $161,567
节省:$182,497 (53.0%),代价是墙钟时间约增加 34.2%。
当容错基础设施已就绪时,经济效益极具吸引力。没有检查点,一次中断就会强制从头重启,可能浪费数天的算力,抵消所有节省。
面向成本效率的调度
成本感知调度将调度器的优化目标从利用率和公平性扩展至财务效率。传统调度器最小化单一目标(例如平均作业完成时间或最大等待时间),但成本感知调度器必须在时间、金钱和可靠性均为决策变量的多目标权衡中导航。调度器必须在三类决策中反复权衡:
-
等待 vs. 立即运行:立即在预留的 A100 上运行作业,还是等待 2 小时使用抢占式 H100,后者能以一半的成本将作业完成速度提高 3 倍。
-
抢占 vs. 支付更多:抢占低优先级的超参数搜索以腾出预留容量给高优先级训练运行,还是将训练运行放在更昂贵的按需实例上。
回退与弹性缩容
当 Spot 容量被回收时,将作业迁移到按需实例以更高成本维持吞吐量,或弹性缩容以降低成本但减少吞吐量。
每个决策对时间、金钱和可靠性的定价各异,因此成本感知调度属于策略设计,而非简单的装箱问题。
这些决策要求调度器同时建模计算的时间价值(研究人员生产力延迟 2 小时、产品发布进度或竞争地位带来的组织成本)和不同资源分配的财务成本。时间价值因工作负载类型而异:将生产模型重新训练延迟 2 小时可能违反服务等级协议(SLA)并导致数千美元的罚款,而将探索性实验延迟 2 小时仅消耗研究人员的耐心,无其他成本。
生产系统通常为每类工作负载定义成本策略,将这些权衡编码为配置,而非要求对每个决策进行人工判断。推理工作负载固定在预留容量上,以保证可用性和可预测的延迟,因为推理中断的成本(收入损失、用户体验下降)远超预留定价的溢价。大规模训练运行混合使用预留容量(用于最小可行分配)和 Spot 容量(用于弹性扩展),并在 Spot 被回收且作业处于可配置截止时间内时自动回退到按需实例。探索性工作负载仅限于 Spot 以最小化成本,前提是理解它们可能被中断且必须容忍可变的完成时间。
技术优化必须与财务问责制相结合,即机器学习 FinOps。在许多组织中,GPU 成本被归入通用的“基础设施”类别,造成“公地悲剧”:团队缺乏对其资源消耗的可见性,且无优化激励。有效的治理需要细粒度标记来实施摊回(向团队报告成本)或分摊(向团队预算计费)。指标必须从“GPU 小时”演进为业务对齐单位:“每模型版本成本”、“每实验成本”和“每 100 万次预测成本”。这一转变将成本从不透明的基础设施支出转变为产品决策的可衡量输入,使团队能在模型质量、训练速度和基础设施成本之间做出明智的权衡。第 12.7.8 节开发了使这些成本可见且可归因的平台机制;此处关注的范围较窄,即价格信号如何重塑争夺集群的团队的调度和配额行为。
总拥有成本
针对 Spot 实例可用性或调度密度进行优化,仅触及基础设施冰山的显性顶端。机器学习集群的总成本远超 GPU 小时:在高性能集群中,计算芯片通常仅占总系统成本的 40% 到 60%。其余部分由保障芯片“吃饱喝足”所需的支撑基础设施消耗:高带宽网络(InfiniBand 交换机、光模块和布线)、用于检查点的并行文件系统,以及专用的供电和冷却系统。单个 H100 节点机柜的功耗可达 40 kW 以上,是标准 Web 服务器机柜密度的十倍,迫使设施部署液冷或后门热交换器,从而重塑设施的资本需求。仅优化 GPU 小时的调度器因此对集群的大部分实际成本视而不见。
该固定容量是否值得拥有,取决于利用率。第 12.3 节展开了完整的自建与购买分析,将三年的资本和运营支出摊销后与云租赁对比,以找到决定两者选择的盈亏平衡利用率。对调度器的影响是直接的:自有硬件仅在保持忙碌时才能摊销成本,因此调度器回收的每一个利用率百分点都会让集群向盈亏平衡点迈进,从而证明其资本支出的合理性;而因打包不当或碎片化而闲置的集群,会使自有基础设施比原本旨在削减成本的云更昂贵。
在转向机器学习专用调度器之前,回顾成本优化机制如何相互作用:
Spot 定价和预留策略压缩了运行作业的成本,但留下了第二个未被触及的效率来源:训练循环内部的学习动态。前述成本模型优化的是完成时间,而研究组织真正关心的指标是达到目标精度的时间,即运行直到达到目标验证指标所需的墙钟时间。对收敛过程“视而不见”的调度器无法判断运行处于陡峭的早期阶段还是边际收益递减的尾部阶段,因此无法将资源重定向到能购买最大进展的地方。弥合这一鸿沟是下文将探讨的机器学习感知调度器的任务。
自定义 ML 调度器
通用调度器将训练作业视为不透明的黑盒,因此无法判断模型处于学习的陡峭早期阶段,还是最终的边际收益递减阶段。自定义 ML 调度器以实现复杂度为代价换取这种可见性:它们深入训练循环内部,优化达到目标精度的时间,而非仅优化完成时间。选择的关键在于哪个内部信号能解决集群的约束瓶颈,这四个系统的区别不在于调度器的复杂程度,而在于它们要求平台信任哪种信号:消耗的服务量、迭代边界、完成价值,或测量的有效吞吐率。
Tiresias:与时长无关的调度
Tiresias (Gu 等人, 2019) 解决了 第 8.1.5 节 所指出的根本问题:ML 作业时长不可预测,依赖精确时长估计的调度策略在估计不可用或不可靠时会做出糟糕的决策。Tiresias 不与这种信息不对称对抗,而是彻底消除了对时长估计的需求。它采用二维获得服务 ¹³³ 调度器,基于作业已消耗的资源而非声称将消耗的资源做出优先级决策。作业根据消耗的 GPU 时间累积“服务量”,服务量越大,优先级越低。两个维度分别是时间(作业运行了多久)和资源(作业使用多少 GPU),同时捕获了已用时长和资源强度。
离散化版本将作业分组到服务存储桶中(例如:小于 1 GPU 小时、1 到 10 GPU 小时、10 到 100 GPU 小时、大于 100 GPU 小时),将处于较低存储桶的作业提升到队列前端。这种存储桶结构意味着所有短作业(提交工作的大多数)获得高优先级并快速运行,而消耗大部分集群资源的少数长作业逐渐失去优先级,相对于新到达的作业被降级。关键洞见在于:这种行为近似于理论最优的最短剩余处理时间(SRPT)策略,却不需要 SRPT 所需的未来知识:消耗服务少的作业在统计上更可能是短作业,因此优先调度它们是最小化平均完成时间的良好启发式方法。
在微软和阿里巴巴的生产集群追踪实验中,与 FIFO 调度相比,平均作业完成时间缩减了 40% 到 60%,此前因排在长时间训练任务后而等待的短作业改善幅度最大。这种改进并非零成本:长时间运行的训练作业完成时间会增加,因为它们被系统性地降低了优先级。然而,整体收益为正,因为短作业数量远多于长作业,且短作业的改善总量超过了长作业受到的惩罚总量。
Gandiva:感知迭代的调度
Gandiva(Xiao et al. 2018)利用了通用调度器完全忽略的一个特性:深度学习训练的迭代本质。每个训练迭代都遵循可预测、重复的模式:一个 GPU 密集型的前向传播、一个 GPU 密集型的反向传播、一个通信密集型的梯度同步,以及在下一次迭代之前的一个 CPU 密集型数据加载和预处理阶段。在数据加载阶段,GPU 处于部分闲置状态,在等待下一批数据准备就绪时,计算利用率低于满载。
Gandiva 采用迭代边界时间切片,通过受控的过度订阅实现更高的利用率。如果一个拥有 100 块 GPU 的集群中,每个作业在迭代时间里有 20% 用于等待数据,那么该集群或许能支持 120 个并发作业,因为调度器可以在同一设备上交织安排:一个作业进行数据加载,另一个作业进行 GPU 计算。使这一做法切实可行的关键洞见在于:迭代边界提供了 GPU 状态极小的自然抢占点。在两次迭代之间,GPU 上仅驻留模型权重和优化器状态;前向和反向传播产生的中间激活值已被释放。这种最小状态使上下文切换变得廉价(秒级而非分钟级),这与在迭代中途抢占时完整的激活内存正在使用的情况截然不同。
Gandiva 还实现了比第 8.4 节中讨论的框架级弹性训练粒度更细的增减弹性。Gandiva 根据实时资源可用性自动调整数据并行度,利用剖析得出的迭代时间来预测增加或减少工作进程对吞吐量的影响。当高优先级作业到达时,Gandiva 通过减少低优先级作业的工作进程数量来收缩它们,而不是彻底终止,从而在释放资源的同时保留其进度。当资源再次可用时,它将作业扩展回其首选的并行度。这种由实际迭代级剖析数据指导的细粒度弹性,实现了通用系统无法做出的调度决策,因为它们缺乏对作业内部的可见性。
Themis:完成时间公平性
传统的公平份额调度将所有 GPU-秒视为等同:无论这 100 GPU-小时代表长时间训练运行的前 10%,还是即将完成的运行的最后 10%,消耗 100 GPU-小时的作业受到同等对待。从资源核算角度看,100 GPU-小时就是 100 GPU-小时,与上下文无关。Themis(Mahajan et al. 2020)认为,这种以资源为中心的观点从根本上是不公平的,因为它忽略了已执行工作的沉没成本。
Themis 定义了一种完成时间公平性指标,分配资源以最小化任何作业相对于独占访问(假设作业独占整个集群的假设场景)所经历的最大减速比。根据该指标,一个已完成 90% 且仅需再投入 10 GPU-小时即可完成的作业,优先级高于一个仅完成 10% 且还需 90 GPU-小时的作业。其推理基于经济学:将即将完成的作业延迟一小时,会浪费已投入的 900 GPU-小时(因为只有作业完成时,这些小时才产生价值),而将早期阶段的作业延迟一小时,对总投资回报的影响按比例较小。
这种方法使较短作业和接近完成的作业受益,且不对较长作业施加过度惩罚,它将调度决策与完成工作的经济价值对齐,而非简单地核算资源消耗。Themis 通过拍卖机制实现该指标,作业根据其当前边际价值(每分配一 GPU-小时,完成时间改善多少)竞标资源,创造出一种类市场动态,自然地将资源导向其最高价值用途。
Pollux:自适应资源分配
Pollux(Qiao et al. 2021)在四个研究调度器中采取了最激进的方法,联合优化资源分配和训练超参数。Tiresias、Gandiva 和 Themis 是针对作业做出调度决策(何时运行、如何共享),而 Pollux 是在作业内部做出决策(使用多少 GPU、什么批大小)。关键观察在于:训练作业的最优 GPU 数量取决于当前的批大小、学习率和梯度噪声水平,而随着模型经历收敛的不同阶段,这些因素在训练过程中都会发生变化。
Pollux 动态调整每个作业的 GPU 分配和批大小,以最大化集群级有效吞吐,其定义为所有作业同时进行的有用训练进展速率。有效吞吐指标将权衡的双方折叠为一个数字:统计效率(每个梯度步骤推进收敛的程度,取决于批大小)和系统吞吐(每秒梯度步骤数,取决于 GPU 数量和通信开销)。一个因通信开销超线性增长而从当前 GPU 数量获得边际收益递减的作业,其 GPU 可能被重新分配给另一个作业,因为后者处于更大批大小能提升统计效率的训练阶段。
这种调度与超参数的联合优化,与固定资源分配的调度相比,使平均作业完成时间提升了 37% 到 50%。改进来源于两方面:更好的资源分配(GPU 流向能产生最大进展的作业)和更好的批大小调优(每个作业在其当前训练阶段,以平衡统计效率和计算效率的批大小运行)。联合效应大于单一优化的效果。
调度器对比框架
自定义 ML 调度器代表了实现复杂度与调度精度之间的渐进式权衡,因此表 8.5 中的对比作为部署过滤器而非排名而存在。通用调度器看到的是声明的资源和粗粒度的运行时元数据;这些研究系统增加了 ML 特有的信号,如迭代结构、弹性、收敛阶段和测量的有效吞吐。
| 调度器 | 关键洞见 | 调度信号 | 优化目标 | 开销 | 生产就绪度 |
| --- | --- | --- | --- | --- | --- |
| Tiresias | 已获服务可预测剩余时间 | 已消耗 GPU-小时 | 最小化平均 JCT | 低(无需剖析) | 中 |
| Gandiva | 迭代边界是抢占点 | 迭代时序 | 最大化利用率 | 中(需剖析) | 中 |
| Themis | 沉没成本对公平性至关重要 | 完成百分比 | 最小化最大减速 | 低(作业元数据) | 低(拍卖复杂) |
| Pollux | 批大小与分配耦合 | 有效吞吐(统计+系统) | 最大化集群有效吞吐 | 高(持续剖析) | 低(需框架集成) |
表 8.5:自定义 ML 调度器对比
自定义 ML 调度器对比:基于其利用的特定工作负载特征,对研究型调度器的分类学。Tiresias 和 Themis 利用外部信号优化排队指标(JCT、公平性),而 Gandiva 和 Pollux 利用内部剖析数据优化效率。
正如表 8.5 所总结的,这一设计空间揭示了一个根本张力:调度器复杂度与调度质量的对比。Tiresias 在掌握最少信息的情况下运行,本质上是在猜测“年轻”作业很快就会完成,但仅通过防止大作业阻塞小作业就取得了显著收益。它很稳健,因为它不需要用户或训练框架的任何配合。另一极端是 Pollux,它需要深度的双向集成:调度器必须了解作业的扩展曲线和梯度噪声尺度,而作业必须接受命令即时改变其批量大小和学习率。当这种集成奏效时,它能产生最高的集群级吞吐量;当假设被违反时(例如,一个收敛动态不稳定、无法容忍批量大小变化的模型),优化就会崩溃。
故障模式通常遵循这一复杂度梯度。Tiresias 优雅地失效:如果其假设(过去的使用情况预测未来的使用情况)是错的,它只会退化为标准的公平共享策略。Gandiva 的故障模式是性能抖动:如果剖析阶段错误地估计了迭代方差,它可能会将不兼容的作业打包到同一节点上,导致干扰。Pollux 拥有最脆弱的故障模式,因为它修改了训练语义本身;一个不正确的有效吞吐模型理论上可能损害模型收敛,这是大多数生产团队不愿为 15% 的效率增益所承担的风险。
对于我们的 1750 亿参数模型,这些权衡决定了一个混合生命周期。Pollux 在训练早期、不稳定的阶段(此时批量大小较小且作业具有弹性)提供了最高的潜在改进;它可以动态调整作业大小以适应集群中的可用空隙。然而,有效使用 Pollux 需要修改训练循环使其具备弹性感知。Tiresias 提供了最简单的部署路径:它会将 1750 亿参数模型视为“重”作业并相对于小实验降低其优先级,确保集群对研究人员保持响应,而无需对 1750 亿参数模型的代码进行任何更改。实际上,大多数基础设施团队从类 Tiresias 的策略开始,以解决“队列阻塞”问题,然后再尝试 Pollux 所需的深度集成。
所有四个系统的一致主题是:利用关于 ML 工作负载的领域特定知识(可预测的迭代时间、并行扩展的边际效益递减、固有的检查点重启能力)能实现比单纯的静态资源请求好得多的调度结果。通用调度器看到的是一个请求 64 块 GPU 用 3 天的作业;ML 感知调度器看到的是一个随机梯度下降过程,它非线性收敛,在性能停滞时释放资源,且可以以最小损失被抢占。在这些调度器中选择的决策框架取决于主要瓶颈:对于高流失率的探索性集群,Gandiva 的时间切片能最小化排队延迟;对于作业完成时间是主要 SLA 的生产训练,Tiresias 基于年龄的优先级防止饥饿;对于资源效率至关重要的大规模训练,Pollux 的自适应扩展回收了静态分配留在桌面上的“弹性缺口”。
生产系统面临的挑战是在保持生产环境所需的运维可靠性、策略灵活性和组织治理的同时融入这些见解。生产平台通常选择性地采用调度器思路,因为可靠性、治理、异构硬件和用户策略约束了纯研究设计。
从研究到生产
只有当研究调度器的核心信号经受住异构数据中心的考验时,它们才在生产中变得有用。在研究中,集群通常被假设为一个具有统一互连拓扑的同构加速器池,作业是准确声明其资源需求的规范实体。在生产中,“集群”是一代代硬件的地质构造:V100 与 A100 和 H100 并存,通过 InfiniBand 和以太网的拼凑网络相连,运行着频繁崩溃、挂起或错误报告内存需求的作业。因此,没有任何大型超大规模厂商只是简单地“安装”一个研究调度器;相反,他们通过 Kubernetes 算子或 Slurm 插件将特定算法有选择地移植到标准编排器中。
考虑我们的 1750 亿参数模型训练运行。纯粹的 Pollux 实现可能会基于全局集群负载激进地调整作业大小,上下调整 GPU 数量以最大化有效吞吐。然而,对于同步训练作业,改变工作进程数量需要一个检查点-重启周期,对于这种规模的模型,这涉及将 TB 级的优化器状态写入持久存储。如果重新缩放间隔太短,检查点的 I/O 开销就会抵消吞吐量收益。实际上,生产团队采用混合方法:他们主要在训练动荡的早期阶段或超参数搜索中使用类 Pollux 的自适应分配,但一旦学习率预热完成、作业进入长达数月的稳态,就将 1750 亿参数模型锁定在静态的、拓扑感知的分配中。
构建与购买自定义调度器的决策是一项巨大的资本投资。开发一个健壮、容错且性能优于 Kubernetes 默认装箱行为的调度器,需要一个由 3 到 5 名系统工程师组成的专业团队工作 6 到 12 个月。ROI 计算证明了这项工作的合理性:如果团队能在 10,000 块 H100 GPU 的集群上将整体利用率提高 10%(按资本折旧和电力约每小时 25,000 美元计算),节省的金额每年约为 2,200 万美元。这种惊人的投资回报率驱动了每个主要 AI 实验室内部调度平台的发展,将调度器从单纯的工具转变为运营效率的主要杠杆。
风险不仅在于实现成本;调度器行为也可能无法匹配分布式训练的执行语义。
背景:OpenAI 将 Kubernetes 集群扩展至 7,500 个节点,用于包括 GPT-3、CLIP 和 DALL-E 在内的工作负载。它们的大规模训练作业使用 MPI 风格执行,一个组中的所有工作进程必须同时被调度,训练才能取得进展(Sigler 2021)。
故障模式:默认的 Kubernetes 调度不能保证在开始调度填充另一个作业之前,一个作业的所有工作进程都已被放置。如果两个实验各自请求整个集群,Kubernetes 可能会调度每个作业的一半,而不是全部调度其中一个作业,导致两个作业都无法运行。
后果:集群可能看起来很忙碌,但没有实验取得有用的训练进展:这是由部分分配导致的经典帮派调度死锁。
系统教训:分布式 ML 调度必须编码作业的执行语义。对于 MPI 风格训练,“部分 Pod 已调度”不是部分进展;它是浪费的分配。生产调度器需要帮派调度、配额控制和工作负载感知策略,而不仅仅是通用的 Pod 放置。
调度器权衡与服务资源管理
调度器设计权衡
专为通用 Pod 放置设计的调度器会错误处理需要集团(gang)语义的工作负载,而仅增加集团感知无法解决竞争训练作业之间的优先级冲突。上述研究的四种调度器各自解决了问题的一个侧面(运行时估计、迭代边界抢占、完成时间维度的公平性,或联合资源-超参数调优),但没有一种能同时解决所有侧面。因此,设计空间是一组显式的权衡,而非单一的最优策略。
请考虑这四种研究调度器之间的权衡:
定制调度器可最大化长时间运行训练作业的吞吐量,但当模型部署用于推理时,运维现实会发生剧烈变化。目标函数从最大化集群利用率翻转为保证毫秒级响应能力。服务资源管理必须在遵守严格的服务等级目标(SLO)的前提下,处理突发且不可预测的流量模式。
服务资源管理
训练作业是可预测的、面向批处理的工作负载,运行数周;服务工作负载则是易变的、面向用户的服务,必须在毫秒级内响应不可预测的流量峰值。当电商平台发起大促时,服务舰队必须即刻自动扩容以应对每秒数千次的请求。服务资源管理迫使编排器在激进的延迟约束与原始硬件利用率之间取得平衡。
对编排而言,关键的服务事实在于:推理容量是围绕用户流量的实时包络,而非围绕训练作业的批量分配。舰队编排器通过随需求缩放副本数量、将服务工作负载与干扰隔离、以及平衡推理资源需求与训练对同一共享基础设施的占用来管理该包络。
推理自动扩容
Kubernetes Horizontal Pod Autoscaling (HPA)(横向 Pod 自动扩缩)根据观测指标调整副本数量,负载增加时添加实例,负载减少时移除实例。通用云工作负载的默认自动扩缩指标为 CPU 利用率,目标通常在 50% 至 70% 之间。该默认值无法很好地反映 GPU 推理工作负载,主要有两个原因。首先,GPU 利用率是一个有噪声的指标,即使系统未处理用户请求(后台维护、模型预热),该指标也可能很高。其次,GPU 利用率与推理延迟的关系高度非线性:当利用率跨过阈值(GPU 推理通常为 70% 至 80%)时,延迟可能急剧飙升,导致基于利用率的扩缩容具有滞后性而非预测性。因此,指标选择是服务调度的首个决策:表 8.6 列出了可在用户感知性能下降发生前预测其情况的自定义推理指标。
| 指标 | 目标范围 | 考量因素 |
| --- | --- | --- |
| GPU 利用率 | 60% 至 80% | 随模型批处理效率而异 |
| 请求队列深度 | 10 至 50 个请求 | 在延迟峰值体现在 P99 指标前加以预防 |
| P99 延迟 | 低于 SLO 目标 | 滞后于需求变化数秒至数分钟的滞后指标 |
| 待处理 Token 数 | 取决于模型 | 活跃请求中排队或正在解码的 Token 总数 |
表 8.6:推理自动扩缩指标:GPU 推理工作负载的自定义指标比默认 CPU 利用率更准确地捕捉负载与用户感知性能之间的关系。队列深度提供延迟劣化的领先指标,而待处理 Token 捕捉未完成的自回归解码工作;KV 缓存驻留情况和上下文长度分布进而决定该工作是否也构成内存压力风险。
除表 8.6 中面向 HPA 的指标外,Vertical Pod Autoscaling (VPA)(纵向 Pod 自动扩缩)在另一维度运作,调整单个 Pod 的资源请求与限制。HPA 改变运行实例的数量,VPA 改变每个实例获得的资源量。对推理而言,VPA 可根据观测到的使用模式调整内存分配,防止 CPU 内存和主机资源过度预留,从而增加每个节点可容纳的推理 Pod 数量。GPU 资源无法在不重启 Pod 的情况下纵向扩缩(必须重新初始化 CUDA 上下文),这限制了 VPA 在模型加载耗时数分钟的加速工作负载上的实用性。然而,VPA 对推理管线的 CPU 组件(如预处理、后处理和分词)极具价值,这些组件的资源需求可能与初始估计显著不同,且可通过短暂的 Pod 重启进行调整。
大语言模型(LLM)推理因 键值(KV)缓存 的内存增长模式,需要 HPA 和 VPA 均无法完全覆盖的专门扩缩容考量。服务长上下文请求的 700 亿参数模型,仅 KV 缓存可能就需要超过 80 GB 的 GPU 显存,即使服务引擎以分页、非连续块的形式存储该缓存。因此,LLM 服务的 GPU 显存瓶颈不在于模型权重(固定不变),而在于 KV 缓存(随请求数和上下文长度增长)。扩缩容决策必须同时考量请求速率和上下文长度分布,而非仅看计算负载。突然涌入的长上下文请求(例如从简短聊天机器人查询转向文档摘要工作负载)即使在请求速率适中时也能耗尽 GPU 显存,迫使在 GPU 计算利用率不高的情况下扩容。反之,大量短上下文请求的突发可能给计算带来压力,却不威胁内存上限。
这种双资源维度(计算与内存)意味着有效的 LLM 自动扩缩应同时监控 GPU 计算利用率和 GPU 内存压力,当任一指标逼近临界阈值时触发扩容。某些生产系统定义了融合这两个维度的复合扩缩指标,当归一化计算利用率与归一化内存压力的最大值超过阈值时触发扩容。这种复合方法可防止系统因任一类资源耗尽而措手不及。
推理自动扩缩还必须处理 冷启动延迟,这在成本效率与响应能力之间制造了张力,无简单解法。由于模型权重体量巨大,服务场景的冷启动问题比标准 Web 服务严重得多。FP16 精度下的 700 亿参数模型约占 140 GB,首个请求得到服务前,所有权重必须传入 GPU 显存,传输受限于 PCIe 或 NVLink 带宽。即便按 PCIe Gen4 x16 单向 32 GB/s 的速率计算,裸传输也需 4 秒以上,实际加上模型初始化、图编译和安全检查,耗时延长至 60–120 秒。从 NVMe SSD(读带宽 7 GB/s)加载约需 20 秒;从网络存储加载则可能超过 2 分钟。
在短暂流量低谷期激进地缩容终止副本,会导致几分钟后流量回升时出现痛苦的冷启动,产生违反 SLO 并降低用户体验的延迟峰值。根本问题在于推理流量在多个时间尺度(秒、分、时)上均表现出突发性,短期低谷无法可靠预测持续的低需求。五分钟的平静期后可能跟随流量峰值,此时冷启动的代价(模型加载期间所有请求的延迟劣化)可能超过释放 GPU 容量节省的几分钟成本。
生产系统通过三种互补策略解决冷启动问题
-
最小副本数量:保持至少一个温热副本(数量高于零)可为首批突发流量消除冷启动,但即便在真正空闲时期也会产生基准成本。
-
预测性扩缩容:依据历史流量模式(如昼夜周期、每周模式及已知事件)进行扩缩容,在预期高峰到来前增加容量,而非在负载到达后被动响应。
-
预热:将模型副本加载到待机 GPU 的显存中,可将激活时间从数分钟的模型加载缩短至数秒的进程初始化,但待机副本会占用无法服务于其他工作负载的 GPU 显存。
多个模型竞争同一 GPU 池的组织,必须在预热开销与冷启动惩罚之间取得平衡,根据模型的流量模式和延迟要求决定保持哪些模型处于温热状态。
预测性自动扩缩容与 SLO 管理
反应式自动扩缩容(如 Kubernetes HPA)本质上是向后看的:它观察指标违规(例如 GPU 利用率超过 80%),等待稳定窗口,随后触发扩容事件。对于毫秒级启动的容器,这种滞后可以忽略不计。但对于需要 3 分钟将权重从磁盘加载到 GPU 显存的 1750 亿参数模型,这种滞后会对 SLO 造成致命打击。如果流量在 30 秒内翻倍(产品发布或病毒式传播事件中的现实场景),反应式扩缩容器只会在激增流量已使现有机群饱和之后才供给新副本,导致 3 分钟的延迟降级和请求丢弃窗口。
在此冷启动间隙中,连续批处理(也称在飞批处理)充当了缓冲器。实现连续批处理的推理引擎(如 vLLM 或 TensorRT-LLM)不会等待固定批次填满才开始生成;相反,它们会在序列槽位空闲时,将新到达的请求插入正在进行的解码步骤中。当负载激增且新副本仍在加载时,引擎会将实时批次大小向 KV 缓存内存上限方向增加,每次迭代接受更多并发序列。这能在不拒绝请求的情况下吸收额外请求,以更高的单请求内存压力为代价维持可接受的延迟。缓解效果有界:一旦 KV 缓存完全饱和——即活跃序列占用了每一个可用内存页——引擎必须开始排队或削峰,无论批处理弹性如何。因此,调度器拥有一个狭窄的时间窗口——通常从自动扩缩容器触发那一刻起,直到 KV 缓存饱和——连续批处理在此窗口内争取时间。调整最小副本数量和扩容阈值以保持该窗口开启,是防止流量爬坡超出冷启动持续时间时发生 SLO 违规的主要手段。
反应式自动扩缩容在冷启动期间造成 SLO 缺口。
预测性自动扩缩容将扩缩容动作与当前负载解耦。通过分析历史流量模式(昼夜周期、每周季节性)并结合实时领先指标(例如登录请求激增常预示推理查询激增),调度器可在需求到达前预供给容量。为满足 500 ms P99 SLO 服务我们的 1750 亿参数模型,需要这种前瞻性。如果模型需 3 分钟就绪,预测性扩缩容器必须在预期流量爬坡前至少 4 分钟发出扩容指令。这将扩缩容问题从控制理论问题(响应误差)转变为预测问题(预测未来)。
有效的扩缩容策略必须以 SLO 为驱动,而非以利用率为驱动。以固定利用率为目标(例如“将 GPU 利用率维持在 70%”)是一种常失效的代理指标:模型可能因内存带宽争用在 60% 利用率时触及延迟 SLO,也可能在请求为计算密集型且均匀时在 90% 利用率下安全运行。SLO 驱动策略显式以关键指标为目标:“当 P99 延迟超过 400 ms(500 ms 目标的 80%)时扩容。”此法能自动适应工作负载特性变化。若新模型版本效率降低,延迟指标上升更快,扩缩容器将供给更多副本以维持 SLO,无需人工调整利用率阈值。
为防止震荡——即扩缩容器在嘈杂流量中快速增减副本——生产系统实施滞后机制和冷却期。典型策略为激进扩容(无稳定窗口)以保护 SLO,保守缩容(15 分钟冷却)以避免抖动。这种不对称性承认:不必要扩容的成本(15 分钟的算力浪费)远低于错失扩容的成本(SLO 违规与用户流失)。
假设一个服务机群有 10 个副本,每个副本在满足 500 ms SLO 下可支撑 5 QPS。总容量:50 QPS。
场景:流量在 60 秒内从 40 QPS 线性爬坡至 80 QPS。模型加载时间:3 分钟(180 秒)。
反应式扩缩容:HPA 在 t = 15 秒(流量达 50 QPS 时)检测到过载。请求 10 个新副本。这些副本在 t = 195 秒(15 + 180 秒)就绪。从 t = 15 秒到 t = 195 秒,需求超过了容量。爬坡期间,超额需求从 0 线性增长至 30 QPS;爬坡达 80 QPS 后,满额 30 QPS 过载持续至新副本就绪。超容量区域约为 4,725 个请求,全部被排队或丢弃并违反 SLO。
预测性扩缩容:扩缩容器预测到爬坡,在 t = -180 秒触发扩容。10 个新副本在 t = 0 秒上线,将容量推至 100 QPS,恰逢爬坡开始。零 SLO 违规。
资源隔离
当多个推理工作负载共享同一物理硬件时,若某工作负载的资源消耗降低了另一负载的性能,便会出现“吵闹邻居”问题。在 GPU 上,干扰通过四个共享资源通道表现:
-
内存带宽:某负载的数据移动可能饱和 HBM 接口。
-
L2 缓存争用:某负载的工作集可能驱逐另一负载的缓存数据。
-
PCIe 瓶颈:并发主机-设备传输可能竞争总线带宽。
-
热效应:某负载的发热可能导致热节流,影响同一 GPU 上的所有负载。
对延迟敏感的推理而言,即使微小干扰也可能推高 P99 延迟至 SLO 阈值之上,将看似资源充足的系统变为违反 SLO 的系统。
MIG 处于此设计空间的强隔离端。表 8.2 已展示固定配置文件;其运维含义是硬件隔离消除了大多数跨租户干扰,但将配置文件选择转化为放置约束。MIG 仅在 A100 及更新 GPU 上可用,配置文件在未清空 GPU 所有负载前无法更改,且固定分区大小可能不匹配工作负载需求。需 15 GB GPU 显存的负载,在 20 GB MIG 实例上浪费 5 GB,或无法放入 10 GB 实例。
软件层面的隔离方案
软件层面的隔离方案以较弱的保证为代价提供了更大的灵活性。要高效地共享 GPU 进行推理,需要驾驭一套隔离机制的层级体系,每种机制都在性能与安全之间进行权衡。在最简单的层面上,CUDA time-slicing(CUDA 时间片轮转)允许多个进程通过上下文切换计算资源来共享一块 GPU。虽然灵活,但这会带来高延迟惩罚(通常为 10–20 ms),且不提供内存隔离。NVIDIA 的 Multi-Process Service (MPS)(多进程服务)对此进行了改进,它允许来自不同进程的内核在同一 GPU 上并发运行,提高了小批量推理的吞吐量,但其隔离性比 MIG 弱(Volta 及更新架构为每个客户端提供独立的 GPU 地址空间,但计算资源和内存带宽仍然共享)。
GPU 内存隔离通过由容器运行时强制执行的显式内存限制,防止一个模型占用另一个模型所需的内存。如果没有此类限制,内存泄漏、意外的大批量(由上下文异常长的请求触发)或行为异常的自定义内核可能会耗尽所有可用 GPU 内存,导致同位部署的工作负载崩溃。MPS 通过消除时间片轮转的上下文切换开销,提高了小工作负载的利用率,但它会增加每次内核启动约 5 到 10 微秒的延迟开销,且对内存带宽干扰的保护有限。
此外,长期运行的服务实例还面临动态内存碎片化问题。关键罪魁祸首是 Transformer 推理中的 KV 缓存,它会随请求序列长度的增长和缩减而变化。标准分配器难以应对这些高度可变的生命周期,在 GPU 内存中留下空洞——这些空洞太小,无法容纳新请求,但合计起来却浪费了数 GB 内存。服务引擎越来越多地采用分页内存管理,以非连续块的方式分配 KV 缓存,从而减少由碎片化导致的内存不足(OOM)故障;第 10 章将全面阐述分页分配机制。
在 CPU 层面,核心绑定将特定的 CPU 核心分配给推理 Pod,防止 Linux 调度器以使处理器缓存失效的方式在核心间迁移进程。对于延迟敏感型工作负载,使用 isolcpus 内核参数和 taskset 亲和性隔离核心,可以消除表现为随机延迟尖峰的操作系统调度抖动。结合 NUMA 感知的放置策略——确保推理 Pod 通过最近的内存控制器访问内存(避免每次访问增加 50 到 100 ns 的远程 NUMA 访问开销),这能使亚毫秒级推理任务的 P99 延迟降低 10% 到 30%。原理很简单:推理延迟主要由内存访问模式主导,任何内存访问不可预测性的来源——无论是核心迁移导致的缓存驱逐、远程 NUMA 访问,还是地址空间切换导致的 TLB 未命中——都会直接降低尾部延迟。
此处讨论的隔离技术构成了一个光谱,从强隔离(MIG、硬件隔离)到灵活共享(MPS、软件共享),选择取决于同位部署工作负载之间的信任边界。为不同外部客户提供服务的多租户推理平台通常需要 MIG 来实现安全隔离。而在同一 GPU 上为不同内部模型提供服务的单租户平台,则可以在隔离要求较弱的情况下使用 MPS 或时间片轮转。
GPU 共享经济学
在单个 GPU 上共置多个模型的决定,定义了推理集群的效率前沿。这一选择在一个光谱上运作:一端是独占访问,它保证隔离但导致产能闲置;另一端是激进共享(通过 MPS 或时间片轮转),它最大化利用率但带来延迟干扰风险。这一权衡的物理本质由内存碎片化和计算资源争用决定。
共享部署两个租户,将浪费内存从 67% 降至 35%。
以一块服务 70 亿参数模型的 80 GB A100 GPU 为例。FP16 精度下的模型权重约消耗 14 GB。加上典型的 KV 缓存和激活开销,总运行时占用约为 26 GB。在独占访问策略下,该单一模型留出 54 GB(67%)的 GPU 高带宽内存闲置,造成了硬件资本的浪费。使用 MIG 对 GPU 进行分区(例如 3g.40gb 配置)可以收紧容器边界,将分区内的浪费降至约 35%,但仍将 GPU 的其余部分严格分割。启用 MPS 将两个此类模型打包到同一 80 GB 设备上,共消耗 52 GB,使总内存浪费降至仅 35%。对于运行数百个小模型的集群,从独占转向共享托管可将所需 GPU 数量减少 2–3 倍。
这种密度的代价是干扰。当两个工作负载通过 MPS 共享 GPU 时,它们竞争流式多处理器(SM)和内存带宽。降级程度取决于工作负载:两个计算密集型模型(例如大批量处理)争夺 ALU 时,吞吐量通常各自下降 15% 到 20%。然而,计算密集型模型与内存密集型模型(例如解码密集型生成)共置时,往往能相安无事,降级仅 5% 左右,因为它们在不同的硬件资源上成为瓶颈。因此,调度器的工作变成了一个多维装箱问题:寻找互补的工作负载,在遵守延迟 SLO 的前提下最大化密度。
对于异构集群,这一逻辑决定了双轨策略。我们的 1750 亿参数模型仅 FP16 权重就需要约 350 GB;在 80 GB A100 上,这意味着在加上 KV 缓存、激活缓冲区、运行时工作空间和张量并行粒度之前,至少需要五块 GPU。实践中,8-GPU 张量并行副本是更清晰的调度单元,适合独占访问。反之,数十个 10 亿至 70 亿参数的辅助模型——如毒性分类器、查询嵌入编码器和辅助生成模型——则是共享的最佳候选。通过将这些较小模型打包到共享的“效用节点”上,编排器为基础模型推理的繁重任务释放了高性能集群。
假设一个集群服务于 100 个不同的 70 亿参数模型(每个占用 26 GB)和 10 个不同的 1750 亿参数模型(每个占用 350 GB)。
策略 A – 独占访问(每 GPU 一个模型):70 亿参数模型需要 100 块 A100 GPU(每块 80 GB),内存利用率仅达 32.5%(100 × 26 GB 已用,共 8000 GB 总量)。1750 亿参数模型需要 80 块 GPU(10 个模型 × 每个 8 块 GPU 进行张量并行)。总集群规模:180 块 GPU。
策略 B – 混合共享(小模型用 MPS,大模型独占):将 70 亿参数模型以每块 A100 两个模型打包(2 × 26 GB = 52 GB,远低于 80 GB),仅需 50 块 GPU,内存利用率达 65%。1750 亿参数模型保持不变,仍需 80 块 GPU。总集群规模:130 块 GPU。
共享使集群规模缩减 27.8%(减少 50 块 GPU)。按每 GPU 每小时 2 美元计算,仅通过调度策略每年即可节省约 876,000 美元的算力成本。
模型路由与流量管理
资源隔离完成后,编排器面临的挑战是将推理查询路由到正确的模型副本。对于单体 Web 服务,简单的轮询负载均衡即可满足需求。但对于跨数千块 GPU 部署的 1750 亿参数语言模型,路由问题变成了一个复杂的分布式系统难题,其中的“服务单元”不再是单个容器,而是 tensor parallel group(张量并行组)——一组必须作为单一逻辑实体运行的 8 块 GPU。负载均衡器不能简单地将请求路由到“GPU 12”;它必须路由到“TP Group 3”,并确保请求恰好在该组准备好处理时到达。
请求路由策略决定了集群级别的吞吐量。朴素的 round-robin(轮询)分发策略不适用于生成式工作负载,因为请求处理时间会因输出 Token 长度不同而相差数个数量级。round-robin 调度器必然会将新请求发送给正在处理长生成任务、积压严重的副本,导致尾部延迟飙升。Least-outstanding-requests(LOR,最少未完成请求)路由通过将流量导向最空闲的副本来改善这一问题。然而,对于大语言模型,memory-aware routing(内存感知路由)效果更佳。通过追踪每个副本的 KV 缓存占用情况,路由器可将长上下文请求发送给空闲内存充足的副本,将短请求发送给接近满载的副本,从而防止因碎片化导致的内存溢出(OOM)错误,并使有效批次大小比盲目路由提升 30% 到 40%。
金丝雀发布和 A/B 测试等流量切换机制对于安全地更新模型至关重要。部署模型 v2 时,编排器不会直接替换模型 v1。相反,它会在 v1 旁边启动 v2 副本,组成一个影子舰队。流量管理器将 1% 的实时请求路由到 v2(通常采用“影子模式”,即计算响应但丢弃),以验证其延迟和错误率是否达标。只有通过统计验证后,编排器才会逐步调整权重(5%、25%、100%),并在 v2 证明稳定后才逐步下线 v1 副本。
多模型服务进一步增加了复杂性。单个集群往往托管多种模型:产品推荐模型(P99 SLO 10 ms,高吞吐)与代码助手模型(P99 SLO 2000 ms,突发式使用)并存。将它们放在同一节点会引发干扰;分置在不同集群又会造成产能闲置。最优策略采用 latency-aware bin packing(延迟感知装箱):编排器将延迟敏感型模型与可瞬时降速的批处理任务(如嵌入生成)共置,在不违反严格 SLO 的前提下确保高利用率。
训练-服务资源边界
ML 集群中最深层的利用率鸿沟,是训练与服务之间的墙。大多数组织将二者作为独立的领地管理:“生产服务”集群必须应对峰值流量(因此深夜 50% 闲置),而“科研训练”集群则长期积压。这种 static partitioning(静态分区)虽安全,却经济低效。Dynamic sharing(动态共享)统一了这些资源池,但引入了根本性的摩擦:模式切换成本。
将 GPU 从训练转为服务并非瞬时完成。编排器需为训练作业做检查点(1 到 5 分钟)、终止容器、启动推理服务器、从存储加载模型权重(2 到 10 分钟),并运行预热查询(1 到 5 分钟)以填充编译缓存。这 5 到 20 分钟的切换延迟意味着 GPU 无法对秒级流量突发做出反应,而必须遵循昼夜规律。
推理流量通常遵循“人类曲线”:凌晨 3 点低谷,早 8 点回升,下午 2 点达峰,晚 10 点回落。智能编排器会提前 30 分钟预测该曲线。早 7:30,它会抢占低优先级训练作业(如超参数搜索)以预热推理副本;晚 10:30,随着流量下降,它会排空推理副本并将 GPU 归还给训练调度器。
以由 1,024 块 GPU 组成的集群为例,对比两种管理机制:
场景 A - 静态分区:服务池预留 400 块 GPU 应对峰值需求,但平均使用量仅 150 块(利用率 37.5%)。训练池预留 618 块 GPU,积压任务保证 100% 利用率。结果:150 + 618 = 768 块活跃 GPU。集群利用率:75%。
场景 B - 动态共享:服务按需占用 GPU(平均 150 块)。训练回收未使用的服务 GPU(平均 250 块),减去 117 块安全缓冲。编排器根据昼夜预测,每天两次在两种角色间转移约 133 块 GPU。结果:服务(150 块)+ 训练(618 + 133 块)= 901 块活跃 GPU(训练项的第二部分为回收的服务产能)。集群利用率:88%。
动态共享回收了 133 块 GPU 的有效产能。按 $2/GPU-hour 计算,这在不购买任何额外加速器的情况下,每年创造约 230 万美元的价值。
针对单个部署优化隔离很直观,但当成千上万的训练和服务工作负载共存于同一基础设施上时,维持严格保障就会变得动荡不安。高优先级推理 SLA 与资源饥渴型训练作业的碰撞,制造了单纯容器化无法解决的“吵闹邻居”问题。多租户和配额成为仲裁公平访问、防止共享集群陷入“公地悲剧”的治理层。
多租户与配额
当计算机视觉团队囤积 500 块 GPU 长达一周,而自然语言处理(NLP)团队因集群常年被占满、无法推送关键修复到生产环境而陷入阻塞时,共享集群便退化为公地悲剧。提交作业最激进、作业规模最大、重试脚本最执着的团队会不成比例地占用资源,而需求较小或间歇性的团队发现集群永远满载。这引发组织摩擦、升级为管理层博弈,并导致那些进展较慢但潜在价值可能更高的项目投入不足——因为其团队缺乏工程精力去争夺资源。
多租户和配额提供了防止这种退化所需的组织与技术防火墙。本节讨论的配额系统建立了资源分配、借用和回收的正式策略,使公平访问和高整体利用率成为可由调度器强制执行的属性,而非管理层协商的产物。
分层公平共享
GPU 配额分配通常在命名空间或项目层面运作,定义每个团队有权使用的集群份额。最简单的方法是给每个团队分配固定 GPU 数量:团队 A 得 500 块,团队 B 得 300 块,团队 C 得 200 块。此法易懂易行,但当团队负载波动时会导致系统性低利用。若团队 A 在空闲期 500 块 GPU 分配有 50% 闲置,而团队 B 队列因紧急工作溢出,集群便因刚性配额白白浪费 250 块 GPU 产能。另一种做法——给每个团队分配更少配额——则造成另一问题:即便集群整体有闲置产能,团队在高峰期也无法运行合法工作。
分层配额通过设立部门级上限、允许子团队灵活分配及跨团队借用,化解了这一张力。公式 8.4 通过某团队自身的分配额度,以及同部门其他团队尚未认领的父部门产能,共同约束该团队的有效配额,其中 U[allocated] 为部门内已承诺给其他各团队的资源量:
有效配额计算公式
\(Q_{effective} = min(Q_{team}, Q_{department} - \sum_{other teams} U_{allocated})\) (8.4)
配额借用机制
当聚合需求超过容量时,每个团队按其份额分配比例获取资源,确保没有团队系统性地处于劣势。未使用的容量可向下借用:若团队 A 仅使用其 500 GPU 分配量的 60%,剩余 200 GPU 将可供同部门其他团队使用。这些借用资源的抢占优先级低于自有资源:当团队 A 后续提交需要其全额分配的作业时,调度器会通过抢占在这些资源上运行的作业来回收借用容量,将容量归还给合法所有者。
这种借用机制对于在多租户集群中实现高利用率至关重要。没有借用,组织面临两难选择:要么分配足够配额以应对每个团队的峰值需求(当需求不均时保证低利用率,因为所有团队的峰值很少同时出现);要么分配较少配额(保证某个团队在其峰值期间始终被配额阻塞)。借用通过允许峰值级分配作为保证,同时允许空闲容量流向需求所在之处,化解了这一困境。一个被保证拥有 500 GPU 的团队,需要时始终能获得 500 GPU(通过抢占借用作业),但当其实际需求较低时,不会浪费集群容量。
借用的抢占动态需要与 第 7 章 的检查点基础设施仔细集成。在借用容量上运行的作业必须足够频繁地进行检查点,以便当所有者团队回收资源时发生抢占,不会浪费大量工作。共享集群通常区分“保证型”和“机会型”调度层级:保证型作业运行在团队自有配额上,仅面临来自更高优先级工作的抢占;而机会型作业运行在借用容量上,接受当所有者团队需要资源回收时可能被抢占。支持弹性伸缩的训练框架可通过缩减而非终止来响应借用容量回收,进一步降低借用机制的成本。
突发容量与超额订阅
在共享 GPU 集群中,静态配额不可避免地导致“突发容量”悖论:团队 A 配额为 64 GPU,但周末长时间训练运行需要 256 GPU,而团队 B 分配的 64 GPU 闲置。解决方案要求将“保证配额”与“限制配额”解耦。团队被保证 64 GPU,可即时访问,但若集群有闲置容量,可突发至 512 GPU。这种“超额订阅”模型依赖抢占:若团队 B 唤醒并回收其保证份额,调度器必须立即终止团队 A 的突发作业。
突发容量处理使团队在集群资源可用时临时超出配额,提供比配额借用更激进的共享机制。借用将一个团队的空闲容量重新分配给另一个团队,而突发容量允许团队通过利用请求资源与实际利用率之间的差距,超出总分配容量。当准入控制器跟踪实际与请求资源并在争用出现时干预时,1.2–1.5× 的超额承诺比率是合理的。发生争用时,使用突发容量的作业优先面临抢占,保护每个团队自有配额内的保证分配。
为安全管理此机制,集群必须实施严格的优先级类别:
-
生产服务:优先级 0 工作负载不可抢占。
-
生产训练:优先级 1 工作负载处理核心模型更新,仅可被服务中断抢占。
-
交互/调试:优先级 2 工作负载获得高调度优先级但有短时间限制。
-
批处理/研究:优先级 3 工作负载完全可抢占且具备容错性。
此层级确保大规模超参数扫描填补集群缝隙,但在关键重训练作业到达时瞬间消失。
合适的超额承诺比率取决于工作负载特征,需基于实际资源利用模式的仔细观察进行校准。若大多数作业请求 100% GPU 利用率但实际仅达 70%(因数据加载阶段、通信开销、内存分配间隙或次优内核调度),1.3× 超额承诺比率可在无显著争用下提升集群整体利用率,因总实际需求(70% × 1.3 = 91%)低于物理容量。若作业真正受计算束缚且持续维持近 100% GPU 利用率,超额承诺会导致资源争用,使所有共置工作负载性能下降。因此超额承诺比率应基于集群级利用率遥测数据实证校准,而非基于工作负载行为的理论假设设定。
资源核算与可观测性
研究员忘记取消预留导致 64 GPU 周末闲置,这在多租户云舰队中属于不可见浪费。在专用集群中,此类浪费至少可见:灯亮着,但风扇静音。在共享环境中,成本隐蔽且跨数千作业累积。有效治理要求超越简单的“分配”指标,采用分层核算模型,区分请求了什么、使用了什么、有什么用处。
三层测量揭示这一差距:
-
分配容量:调度器为作业预留的资源,对其他作业不可用。
-
计算利用率:GPU 内核活跃时间的百分比,衡量硬件忙碌程度。
-
生产性利用率:GPU 推进模型状态的时间比例,排除数据加载暂停、通信开销和检查点。
此区分关乎财务而非仅技术。团队可能“分配” 500 GPU,但仅使用 350 个(70% 计算利用率),仅“生产性使用” 280 个(分配量的 56%)。若组织按分配付费但以训练进度衡量成功,这 44% 差距即纯烧钱。
分配、活跃与生产性利用率彼此相距甚远。
共享舰队中的归因是个取证挑战。单个“训练”作业可能是三个团队间的共享实验,或 SRE 运行的平台测试。无作业级粒度标签,成本默认归入“基础设施”桶,形成无人认领账单的公地悲剧。Kubernetes 标签和 Slurm 账户提供归因机制,但持续应用它们的组织纪律才是更难的问题。成熟组织实施“无标签,不调度”策略,在准入控制器层级拒绝未打标签的作业。
这些数据馈送到利用率仪表板——集群操作员监控集群健康的主要工具。该视图必须综合展示各团队利用率与配额的对比、按优先级类别划分的队列深度,以及碎片化指数——即因拓扑约束而无法分配的空闲 GPU 数量的度量。关键在于,它必须追踪“混乱成本”:抢占率、Spot 实例中断频率,以及由此产生的恢复开销。当这些指标可见时,它们就会驱动行为。针对我们 175B 模型的一次 64 GPU 微调运行持续 816 小时,消耗约 130,560 美元的算力成本(64 GPU × 816 小时 × 2.50 美元/GPU-小时)。若无适当核算,该成本对申请团队不可见。实施成本分摊后,团队必须根据模型预期业务价值为这笔支出辩护。
自动化异常检测将这些指标转化为可执行的信号。某团队消耗量突然飙升至其历史常态的 3 倍,可能表明有失控脚本无限生成作业。队列增长速度快于消耗速度,预示着容量短缺,需由自动扩缩容解决。反之,集群整体功耗突然下降,通常预示着大规模作业失败事件,常因共享依赖项(如存储中断)导致。这些告警使操作员能在预算耗尽或队列失控前介入调试。
要调试集群效率,需在三个不同层面测量利用率。分配利用率 是调度器预留的物理 GPU 百分比;数值偏低表明需求不足或配额过于严格。计算利用率 是分配的 GPU 执行内核的时间百分比(由 nvidia-smi 报告);数值偏低表明代码低效、I/O 瓶颈或通信停滞。生产性利用率 是有效用于训练(不含开销)的分配时间百分比;数值偏低表明分布式扩展不佳、检查点过于频繁或频繁重启。
安全与命名空间隔离
多租户不仅要求公平的资源分配,还要求安全隔离,防止团队干扰或窥探彼此的工作负载。风险真实存在:ML 模型代表重要知识产权,训练数据可能包含受隐私法规约束的敏感信息,模型本身可能编码专有业务逻辑。若无适当隔离,某团队命名空间中被攻陷或配置错误的工作负载,可能访问另一团队的模型权重、训练数据或推理流量。
在 Kubernetes 环境中,命名空间分离提供基础隔离边界。各团队在专用命名空间中运行,基于角色的访问控制(RBAC)将可见性限制在自身资源。RBAC 策略控制谁可提交作业、查看日志、访问模型工件、修改各命名空间内的调度策略,为集群使用提供组织治理。
网络策略将隔离延伸至网络层,除显式允许的服务外,禁止跨命名空间通信。ML 工作负载的网络策略必须在隔离与分布式训练通信需求间取得平衡。一种实用策略可能允许命名空间内无限制的全互通(分布式训练所需,如第 6 章分析环形 AllReduce 时所述,每个工作进程必须与其他所有工作进程通信),同时阻止来自其他命名空间的所有入站流量(防止外部工作负载拦截梯度流量或模型更新)。出站策略可防止训练作业访问外部网络,降低受损训练代码或被毒依赖导致的数据渗漏风险。
表 8.7 在隔离强度、资源效率和工作负载适配性三个维度对比 GPU 虚拟化选项。
| 选项 | 隔离强度 | 效率与灵活性 | 适用工作负载或信任边界 |
| --- | --- | --- | --- |
| 时间切片 | 低;工作负载通过软件调度共享 GPU。 | 高灵活性与堆叠效率。 | 同一团队为成本效率共享 GPU 的可信工作负载。 |
| MIG | 具有固定 GPU 切片的强硬件分区。 | 可预测的隔离但放置灵活性较低。 | 不同客户共享同一物理 GPU 的多租户推理。 |
| 全设备直通 | 通过独占一个或多个 GPU 实现完全隔离。 | 最低堆叠效率。 | 饱和资源或无法容忍共置工作干扰的训练作业。 |
表 8.7:GPU 虚拟化隔离谱系:GPU 共享机制在隔离与堆叠效率间权衡,因此正确机制取决于信任边界与工作负载对干扰的容忍度。
选择取决于共置工作负载间的信任边界及各工作负载类型的性能敏感度。多租户环境的安全性不止于简单的资源公平。共享 GPU 上的侧信道攻击是已记录的漏洞;通过监控共享缓存或内存控制器上的争用,恶意租户可推断同驻模型的架构甚至数据特性。在高敏感环境中,硬件隔离机制(如 MIG)或严格将整个 GPU 节点专供单租户成为强制要求。
优先级抢占级联
一次驱逐波及多方:抢占级联。
当高优先级作业进入饱和集群,调度器必须决定终止哪些运行中工作负载以释放资源。该决策极少孤立存在。在紧凑集群中,为容纳高优先级请求而驱逐中优先级作业,常触发抢占级联——被驱逐作业立即尝试通过置换更低优先级工作负载来重新调度。若无阻尼控制,单个紧急推理服务部署即可波及整个队列,使数十个训练作业失稳,引发检查点重载风暴,饱和存储带宽。
抢占的工程成本远超调度延迟。抢占税 是自上次检查点以来的算力损失、持久化状态开销、及恢复时的冷启动惩罚之和。对大规模分布式训练,此税是非线性的。以我们 175B 参数模型的 64 GPU 微调运行为例。若被抢占,作业将丢失上次快照以来的工作(平均 15 分钟)。重启时,需 20 分钟从分布式存储重载海量优化器状态,另需 10 分钟预热——数据管道重填预取缓冲、即时(JIT)编译器重新优化内核、GPU 缓存重新填充。
一个进一步的隐性成本加剧了这一代价:重构分布式数据管道的状态。抢占大规模训练作业不仅会中断梯度计算,还会丢弃数据加载器的确定性状态,包括用于打乱顺序的随机种子、应用于该轮次的打乱顺序,以及记录确切已消费哪些样本的数据集分片字节偏移量。如果无法精确恢复此状态,重启的作业要么会重复读取已处理的样本(通过放大特定样本毒害梯度统计量),要么会使用不同的打乱顺序(破坏轮次间的统计独立性)。将 PB 级分布式数据管道恢复到抢占前的状态,通常需要在所有数据加载器工作进程间重放元数据日志,这一过程可能会使有效预热窗口远远超出仅重新加载优化器状态所消耗的时间。存储紧凑型数据加载器检查点(记录种子、采样的排列索引和当前分片偏移量)的框架能显著降低此成本,但大多数生产训练管道仍需一段人工预热期,以在梯度质量恢复基线前近似原始数据顺序。
在这 45 分钟的恢复窗口期内,GPU 处于活跃状态但实际无产出。按 $2/GPU-hour 计算,单次抢占事件造成 $96 的算力资本浪费。若调度器允许无限制抢占,每天 12 次抢占将浪费该作业每天 9 小时的分配时长,即该 64-GPU 运行每天浪费 576 GPU-hours。为缓解此问题,调度器实施抢占预算。这些速率限制约束了干扰频率,例如防止作业每小时被抢占超过一次,或将任何 10 分钟窗口内的集群总抢占变动上限设为容量的 5%。这迫使调度器等待作业自然完成,而非触发级联抢占,以略微增加的排队时间换取显著更高的聚合吞吐量。
配额治理与组织动态
调度器管理秒级的硅资源分配,但配额治理管理月度的组织意图分配。配额是工程约束与业务优先级之间的接口。在成熟的 ML 组织中,配额很少是静态的;它们通过定期的配额审查周期进行调整,根据实际利用率而非预测需求重新校准分配。一个始终只使用其 128-GPU 配额 40% 的团队,实际上是在阻挠其他团队启动实验,制造出一种虚假稀缺,尽管物理容量充足,却推高了排队时间。
配额管理的核心矛盾在于囤积问题。工程团队出于合理愿望以最小化等待时间,往往会申请最大容量“以防万一”项目加速。这导致平均利用率低而爆发需求高。两种 ML 特有的模式放大了这种行为,超出了通用的资源焦虑范畴。第一种是基于人类反馈的强化学习(RLHF)评估阶段,此时分配给策略模型的 GPU 节点在团队等待人工标注员打分时大多处于空闲。团队保留预留权以避免标注批次到达后再次排队,但在标注窗口期内实际 GPU 占用率可能降至 10% 以下。第二种模式发生在团队为训练运行预留 GPU 块,但其预处理管道尚未完成时。大规模语料库的 CPU 密集型数据分词和词汇归一化常超出团队预估;GPU 被预留且空闲,因为释放预留权有失去它的风险。这两种场景从团队角度看是理性的,但在集群层面代表系统性浪费。为应对这一点,平台团队实施基于利用率的回收:若配额池在设定周期内保持低利用率,系统自动削减分配,将资源返还通用池。这种技术强制将举证责任推回给团队,要求其为预留做出合理解释。
财务问责进一步对齐了激励。组织通常从 showback(成本回显) 起步,这是一种报告机制,向工程管理者展示 GPU 使用的美元成本,但不直接影响其预算。这能培养成本意识,却缺乏约束力。随着支出规模扩大,组织过渡到 chargeback(成本分摊),即从团队预算中扣除计算成本。Chargeback 产生即时压力以优化代码并释放未用配额,但若内部定价模型过于激进,可能会抑制探索性研究。
一个具体的配额场景展示了空闲预留如何迅速变得昂贵。
场景:因数据延迟导致预留块空闲
故障模式:上游数据延迟推迟了训练作业,但团队保留预留权以确保“数据到达时”有资源可用。
后果:GPU 空闲三周,造成约 $504,000 的资本浪费,而其他团队的队列日益增长。
系统洞察:基础设施团队实施了“用进废退”策略。任何连续 72 小时利用率低于 20% 的预留配额组将被自动回收并转换为通用池的弹性容量。回收配额需要 VP 级批准,有效消除了“占位”闲置硬件的行为。
“用进废退”回收策略解决了配额囤积最显性的症状,但引出了一个二阶问题:当闲置容量借给另一团队时,系统必须区分借用容量(短期内可抢占)与保障预留(受 SLO 保护)。将借用、抢占和保障层级定义为一流策略原语,是核心多租户设计决策。
设想一个 2,000-GPU 集群,在研究团队(60% 分配)和生产团队(40% 分配)间共享:
-
保障层:有严格 SLO 支持的预留。生产团队的推理服务池(800 GPU)受保障;绝不被抢占。
-
借用层:借给尽力型工作负载的空闲保障容量。若生产团队仅用 600 GPU,剩余 200 GPU 作为可抢占容量借给研究团队。
-
机会层:未预留的集群容量(弹性实例)。对任何团队可用,当保障或借用工作负载需要资源时可即时抢占。
即使部署了复杂的多租户和配额策略,集群仍可能陷入复杂的系统性瓶颈,这些瓶颈无法通过简单监控发现。当利用率不明原因下降尽管队列已满时,运维人员必须从仪表盘转向系统性利用率调试。
调试集群利用率
集群仪表盘显示平均 GPU 利用率卡在 60%,但开发者抱怨小型调试作业排队三天。数百 GPU 空闲,却无作业启动。弥合理论容量与生产现实间的鸿沟,需要有条理的取证分析,以理清硬件拓扑、调度器策略和用户行为间的相互作用。如图 8.6 所示,利用率与等待时间呈高度非线性关系:运行在 80% 以上会导致等待时间爆炸式增长。

图 8.6:利用率悖论:随着集群利用率逼近饱和(100%),作业等待时间双曲线式增长并在饱和点附近发散,而非线性增长。在 80% 利用率以上运行(危险区)会导致不可预测的调度延迟,尤其是对大型作业,这说明为何 100% 利用率是交互式研究集群的反模式。虚线显示目标利用率为 75%。
调试方法是将调度器状态与物理能力进行对比,将作业请求与实际硬件池进行核对,然后测试是否存在策略-基础设施不匹配。当调度器遵守仪表盘已聚合掉的约束时,满载的队列和闲置的加速器可以共存。下文工作示例的核心集群运行着表 8.8 中列出的异构硬件,这些硬件累积自三个采购周期。
| GPU 类型 | 数量 | 显存 | 互联 | 节点 |
| --- | --- | --- | --- | --- |
| A100-80 GB | 400 GPUs | 80 GB HBM2e | NVLink (600 GB/s) | 50 节点 × 8 GPUs |
| A100-40 GB | 424 GPUs | 40 GB HBM2e | NVLink (600 GB/s) | 53 节点 × 8 GPUs |
| V100-32 GB | 200 GPUs | 32 GB HBM2 | PCIe Gen3 (15.8 GB/s) | 50 节点 × 4 GPUs |
表 8.8:集群硬件清单 – 三代采购硬件创造了具有不同能力的独特资源池。A100 节点通过 NVLink 支持张量并行,而 V100 节点仅限于数据并行。
问题:一个 1,024-GPU 集群报告平均利用率为 60%,低于 80% 的运营目标,尽管队列已满,有超过 50 个作业排队等待。仪表盘显示有空闲 GPU,但用户仍在等待。识别绑定约束和诊断路径。
图 1.13 中的 fleet-stack 框架构建了诊断结构:首先检查硬件能力,然后是调度策略,最后是两者之间的不匹配。
诊断步骤
-
基础设施层分析 – 表 8.8 中的硬件清单在检查调度器策略前先划分了物理能力:
A100节点可通过 NVLink 运行张量并行作业,而V100池仅限于通过 PCIe 进行数据并行。 -
分发层分析 – Slurm 调度器实施带有严格资源类型匹配的帮派调度。检查作业规格揭示了需求模式:
-
15 个大型训练作业请求至少 64 个
A100-80 GB容量的 GPU(总需求 1,200 GPUs)。 -
8 个中型作业请求 32 个
A100-40 GB容量的 GPU(总需求 256 GPUs)。 -
0 个作业显式请求
V100资源。
-
调度器的分配日志显示 A100-80 GB GPU 几乎被少数运行中的作业饱和,而其余大型作业因无法形成完整的 64-GPU A100-80 GB 分配而保持挂起。结果不是标准的 Slurm 部分分配死锁;这是一个策略导致的利用率差距,严格的资源请求导致 A100-40 GB 和 V100 池闲置,而 A100-80 GB 作业在等待。
分析
多种病理现象叠加导致了低利用率:
-
过度指定 – 模型显存分析显示,大多数大型作业实际上每 GPU 仅需 35 GB 峰值显存,完全在
A100-40 GB容量范围内。用户复制作业模板时指定了A100-80 GB,却未重新计算需求。 -
池碎片化 – 严格的同质性要求意味着请求“
A100-80 GB”的 64-GPU 作业无法使用任何A100-40 GBGPU,即使有 153 个 GPU 处于闲置。 -
资源搁浅 – 没有作业针对
V100硬件,因为研究人员认为它是“遗留”设备。这 200 个 GPU 在消耗电力和制冷的同时,贡献了零生产性工作。 -
原子分配饥饿 – 大型作业一直挂起,直到出现完整的兼容分配,而其他资源池因策略阻止兼容替换或回填而闲置。
诊断结论
作业模板和组织实践是为同质 A100-80 GB 集群演进而来。当基础设施通过异构硬件扩展时,分发层策略从未更新。调度器正确执行了其配置的策略;是策略本身造成了利用率差距。
解决方案
实施具有拓扑感知的分级资源匹配。这些策略变更持久有效:
-
表达能力需求而非精确的 GPU SKU。
-
为需要张量并行的作业预留 NVLink 互联节点。
-
将兼容的较小作业回填到原本闲置的池中。
-
以较低成本核算将合适的工作负载播种到旧式加速器上。
-
让正确的资源请求比过度指定的模板更容易。
影响
实施分级匹配和回填调度后,为期两周的验证运行在每个池中都恢复了闲置容量,各池修复前后的分配情况见表 8.9。
| 资源池 | 修复前 | 修复后 | 变化 |
| --- | --- | --- | --- |
| A100-80 GB | 95 % 已分配,72 % 有效 | 89 % 已分配,94 % 有效 | +22 pp |
| A100-40 GB | 64 % 已分配 | 88 % 已分配 | +24 pp |
| V100 | 0 % 已分配 | 71 % 已分配 | +71 pp |
| 集群整体 | 60 % | 84 % | +24 pp |
表 8.9:利用率恢复结果 – “已分配”与“有效”利用率的区别捕捉了策略导致的利用率差距:修复前,A100-80 GB GPU 在 Slurm 中显示高分配率但实际计算利用率较低,而严格的作业模板搁浅了其他有能力的硬件池。A100-80 GB 的“有效”值为已分配 GPU 的测量利用率;集群整体利用率按完整异构机队计算。
正如表 8.9 所示,这一利用率提升增加了约 246 个完全利用的 GPU 的生产性工作。若这些 GPU 以旧有的 60% 利用率运行,这相当于购买 410 个额外的原始 GPU。按 $2 / GPU‑hour 计算,等效的原始容量代表每年约 700 万美元的回收价值,且无需任何额外硬件投资,仅通过策略变更即可实现。
常见利用率反模式
高层指标往往掩盖了深层的低效。集群仪表盘可能报告 85% 的分配率,但生产性利用率——实际推进模型状态的周期份额——可能显著更低。调试这些差距需要在 GPU 遥测中识别特定特征,以揭示潜在瓶颈。
僵尸作业 代表了最严重的浪费:一个已崩溃或死锁但继续占用 GPU 资源的训练进程。在一个训练 175B 参数模型的分布式运行中,单个秩的故障若进程清理失败,可导致 128 个 GPU 被分配却处于空闲。其特征是高显存分配(大于 300 GB)配合近零 SM 利用率(小于 5 %),并持续超过心跳超时(通常为 10 分钟)。这种状态通常源于 CUDA 上下文损坏或驱动级死锁,阻止容器运行时成功回收进程,需要强制节点级补救来回收设备。
数据饥饿模式表现为 GPU 利用率的高频锯齿波,设备在 100% 负载(前向/反向传播)和 0% 负载(等待下一个批次)之间振荡。虽然平均利用率看起来可以接受(60% 到 70%),但 GPU 实际上有三分之一的时间处于空闲状态。当 CPU 端的数据管线无法维持 GPU 所需的吞吐量时就会发生这种情况,这在计算机视觉领域很常见,因为 JPEG 解码和数据增强会使主机 CPU 饱和。利用率方差超过 30% 标准差是主要的诊断指标,表明需要主动预取、增加加载器并行度或本地 NVMe 缓存来填满加速器。
通信瓶颈表现为分布式组中所有秩的 SM 利用率周期性、同步下降。对于 175B 模型,同步 350 GB 参数的梯度需要巨大的带宽;如果网络规模不足或拥塞,GPU 花在等待 AllReduce 完成上的时间将超过计算时间。诊断指标是步时间比率:如果总迭代时间超过纯计算时间(隔离的前向加反向传播)的 1.5 倍,网络就已成为主要约束。修复需要拓扑感知的放置策略,将流量保持在高带宽交换机层级,或采用梯度累积等算法变更以提高计算与通信的比率。
内存碎片陷阱是长时间运行的推理服务器中一种隐蔽的故障模式。一个服务不同请求长度的 175B 模型,可能会有 80% 的 HBM 分配给 KV 缓存,却无法接受新请求,因为空闲内存分散在微小的、不连续的块中。GPU 对调度器显示为已满,但就吞吐量而言却是未充分利用的。这类似于经典的堆碎片问题,但风险更高:一次重启来整理碎片会丢弃数百 GB 的状态。监控请求的 token 槽位与已分配内存字节的比率往往能暴露这一差距,分页缓存分配器通过将逻辑序列连续性与物理内存布局解耦来解决此问题,在不增加硬件的情况下恢复可用内存并将服务吞吐量提升 2–4 倍。
调试示例阐述了一个适用于本章讨论的每个系统的更广泛原则:调度系统的有效性取决于其实现的策略,而策略必须随基础设施协同演进。当基础设施发生变化(添加新硬件世代、升级网络拓扑、工作负载组合从训练主导转向推理主导)时,编码在调度器配置、作业模板和组织实践中的策略必须重新评估并更新。最昂贵的调度错误往往不是软件缺陷,而是适用于旧基础设施的策略从未为新基础设施更新过。
这一原则将调度机制与车队治理联系起来。技术策略(GPU 型号匹配、帮派调度超时、抢占宽限期)与组织策略(团队配额、优先级层级、成本分摊)以及人类行为(作业模板复用、资源请求习惯、队列提交模式)相互作用。有效的车队编排需要同时关注这三个层面。
谬误与陷阱
这些策略-基础设施不匹配以一小组误解的形式反复出现,会使一个看似健康的调度器浪费昂贵的车队容量。看着集群以 98% 的利用率运行并宣布胜利很诱人,结果却发现研究人员完全停止提交作业,因为等待时间已延长到数周。集群编排充满了这种反直觉的陷阱,优化单一指标会破坏整体系统价值。
谬误:更复杂的调度算法总是能提高利用率。
面对低利用率,工程师往往寻求更高级的调度器,假设瓶颈在于算法。正如 第 8.9 节 的调试示例所示,根本原因通常是策略配置错误,而非算法局限。一个具有正确策略(基于能力的匹配、带超时的回填、适当的帮派调度约束)的简单 FIFO 调度器,往往优于一个策略错误的复杂调度器。在升级调度器前,先审计策略:确认用户没有请求不需要的资源、异构资源已正确暴露、帮派调度超时已配置。
陷阱:衡量利用率时将所有 GPU 小时视为等价。
集群仪表板通常将“GPU 利用率”作为单一百分比报告,对所有 GPU 和所有工作负载取平均。该指标掩盖了关键信息。集群可能报告 80% 利用率,其中 60% 是高效训练,15% 是等待帮派完成而分配给作业的空闲 GPU,5% 是运行数据加载但无活跃计算的 GPU。有效的监控要区分分配利用率(分配给作业的 GPU)、计算利用率(执行内核的 GPU)和生产性利用率(推进有用训练或服务请求的 GPU)。只有最后一个指标与实际交付价值相关。
谬误:帮派调度对分布式训练总是必要的。
帮派调度防止同步训练的死锁,但并非所有分布式训练都是同步的。异步训练方法容忍工作节点的到达和离开,弹性训练框架处理可变的工作节点数量。对于可异步或弹性运行的工作负载,放宽帮派调度要求能显著提高调度灵活性并减少队列等待时间。代价是陈旧梯度或批次大小变化可能导致收敛性下降,但对许多实际工作负载(超参数搜索、微调、自适应批次大小的预训练),这种权衡是有利的。
陷阱:基于峰值需求设置静态配额。
组织通常设置团队配额以应对最坏情况需求,理由是团队在冲刺期间需要保证访问权。如果团队 A 的峰值需求是 500 GPU,团队 B 是 300 GPU,静态配额下集群需要 800 GPU。实际上,峰值很少同时出现。带借用的分层公平共享可用 600 GPU 集群同时满足两个团队的峰值需求,因为当团队 A 峰值时,团队 B 通常处于中等需求并可释放借用容量。峰值水平的静态配额在有保证但未使用的分配上浪费了 25% 到 40% 的集群容量。
谬误:抢占式实例对训练总是更便宜。
60% 到 90% 的折扣给人一种自动省钱的印象。有效成本取决于中断频率、检查点开销和重启时间。对于检查点耗时 15 分钟、重启耗时 30 分钟的大模型,一次抢占式中断会损失 45 分钟的生产时间。如果中断每 2 小时发生一次,有效成本上升到名义 65% 折扣仅剩约 52% 节省。对于检查点缓慢且抢占式中断频繁的 100B+ 参数模型,核算所有开销后,按需实例实际上可能更便宜。该决策需要对特定工作负载和抢占式市场状况进行定量分析,而非笼统假设成本节省。
陷阱:调度分布式训练时忽略拓扑。
集群编排:谬误与陷阱
将 GPU 视为可互换单元的调度器会产生这样的分配:张量并行组跨节点(强制 NVLink 速度的操作跑在 InfiniBand 上),或 AllReduce 组跨脊叶交换机(增加延迟和拥塞)。由糟糕的拓扑放置导致的 15% 到 30% 吞吐量损失会在为期数周的训练运行中累积,浪费的资源可能比等待拓扑感知分配所损失的还要多。拓扑感知调度增加了调度复杂度,并可能降低装箱效率,但对于大规模分布式训练作业,吞吐量的提升往往能证明这种权衡是合理的。
谬误:仅凭 GPU 利用率就足以作为自动扩缩容信号
GPU 利用率是推理健康状况的不良代理指标。GPU 可能达到 90% 利用率,却仍违反延迟 SLO,因为该利用率来自于处理积压的排队请求。反之,40% 利用率的 GPU 可能非常健康,如果它正以低延迟服务请求并为突发流量预留了余量。正确的指标是队列深度、相对于 SLO 的 P99 延迟,以及针对 LLM 工作负载的 KV 缓存内存压力。依赖利用率会产生滞后指标,仅在性能已经下降后才触发扩容,而队列深度和 SLO 余量提供了领先指标,使集群能在用户体验受损前进行扩容。此外,对于 LLM,KV 缓存增长导致的内存耗尽可能在低计算利用率下发生,导致请求停滞或失败。仅监控计算利用率的自动扩缩容器将完全错过这种故障模式,尽管看似有剩余容量,服务却已降级。
陷阱:使用弹性训练以规避帮派调度决策
弹性训练是帮派调度的补充,而非替代。虽然它允许作业调整规模,但调整规模的步骤本身往往具有原子性要求。弹性训练适用于数据并行,但不适用于张量并行(后者要求每组 GPU 数量固定)。使用 8 路张量并行的 175B 模型,每个张量并行组需要恰好 8 张 GPU 才能运行;这是弹性训练无法放宽的帮派约束。弹性训练调整的是数据并行副本的数量,而非副本内部的并行度配置,这意味着调度器仍必须为模型的基本单元强制执行帮派约束。如果调度器将这 8 张 GPU 盲目放置在不同机架,或未能原子性地分配它们,张量并行组要么无法初始化,要么遭遇灾难性的性能下降。
谬误:调度器无需针对特定工作负载进行验证
随着工作负载演进,调度器配置需要持续调优。Slurm 或 Kubernetes 中的默认配置针对通用工作负载进行了优化,通常优先考虑简单的公平性而非吞吐量。针对 ML 的专项调优(帮派调度超时、拓扑感知放置权重、抢占宽限期、公平份额衰减率)可将利用率提升 15% 到 25%。部署调度器后从未重审其配置的组织,将大量价值拱手让人。默认的抢占宽限期可能过短,导致大模型无法完成检查点从而造成工作浪费;未调优的拓扑权重可能不必要地碎片化集群。随着集群规模扩大和工作负载组合变化(例如从训练密集转向推理密集),有效的调度参数也随之变化。将调度器视为“设置即不管”的设备,会让集群逐渐陷入低效。
陷阱:将高利用率视为集群健康的证明
集群以 95% 利用率运行听起来很高效,但可能预示着问题:容量不足正在扼杀实验速度。当利用率持续超过 85% 到 90% 时,排队时间会根据随机过程理论中著名的 M/M/1 排队模型结果迅速增长。在该模型中,平均等待时间与 ρ/(1 − ρ) 成正比,其中 ρ 为利用率;在 90% 利用率下,平均等待时间是服务时间的 9 倍,在 95% 时则是 19 倍。等待数小时甚至数天获取资源的研究人员,实验次数减少、假设验证减少、模型设计迭代变慢。这种实验速度的降低具有真实成本,虽难以像 GPU 小时数那样量化,但总量可能更大。有效的利用率目标在资源效率与研究人员生产力之间取得平衡,对于训练集群而言,由于队列响应性直接影响研究产出,该目标通常落在 70% 到 85% 之间。推理集群的请求要么立即服务要么完全不服务,往往以更低的利用率(50% 到 70%)为目标,以维持应对流量突发的延迟余量。
识别这些谬误可防止平台团队追逐那些在仪表盘上看起来很美、实则损害开发者速度的虚假优化。成功驾驭这些权衡的核心编排原则有一条共同主线:它们将调度视为一门工程学科,而非行政职能。
总结
集群编排将原始数据中心容量转化为高产的 ML 基础设施。本章探讨的调度算法、放置策略和资源管理策略,决定了成千上万个孤立的 GPU 是作为一个连贯的计算平台运作,还是作为一堆昂贵服务器的碎片化集合存在。这两种结果的区别不仅仅在于硬件能力,而在于调度器在部分故障、网络分区、陈旧资源视图,以及相互竞争的一致性和可用性要求下,维护有用状态的能力。Slurm 和 Kubernetes 体现了该光谱上不同的权衡,大型集群可能两者兼用,因为批量训练和自愈服务部署以不同方式对调度器施压。
本章的核心技术教训是:放置即性能。拓扑感知调度利用 GPU 集群的通信层级,当张量并行、流水线并行和数据并行组被放置在与其流量匹配的网络层级上时,可将训练吞吐量提升 15% 到 30%。弹性训练、抢占感知检查点和抢占式实例策略增加了经济灵活性,但前提是作业能在变动的工作节点数或中断中存活。Tiresias、Gandiva、Themis 和 Pollux 等研究调度器从算法层面佐证了同一观点:ML 作业具有可观测的结构,包括迭代进展、重尾持续时间、沉没成本经济学和收敛依赖的扩缩容,利用这些结构的调度器可将平均完成时间较通用策略缩短 40% 到 60%。
目标函数在服务阶段再次变化。训练集群可在排队等待与利用率之间权衡,但推理集群必须在突发需求下保护尾部延迟。因此自动扩缩容需要队列深度、P99 延迟和 KV 缓存压力,而非仅靠 GPU 利用率;多租户需要 MIG、MPS、时间片、配额、借用和记账策略,在隔离与全局效率间取得平衡。同一个在利用率仪表盘上看似高效的调度器,如果研究人员等待数天才能实验、推理 SLO 在突发负载下下降、或碎片化的 GPU 无法形成训练作业所需的帮派分配,它依然可能是在糟糕地管理集群。
车队编排的精通因此既关乎诊断与经济,也关乎算法。僵尸作业、数据饥饿、拓扑错配和资源碎片化,均为调度器策略未能匹配基础设施现实的症状;增添节点往往仅能掩盖症状而无法修正根因。随着计算节点已定义、网络已互联、存储已配置、容错已就位、调度器已运行,车队栈的基础设施层现已形成一个连贯的平台,而非一排独立的机器。
-
分布式调度本质上是困难的:集群调度面临着单机调度器从未遇到的挑战(部分故障、网络分区、状态不一致)。CAP 定理迫使在一致性和可用性之间做出权衡,这塑造了每个调度系统的设计。
-
帮派调度防止死锁但降低了灵活性:帮派调度为分布式训练提供原子资源分配,以防止请求并保持式死锁,但僵化的帮派调度在与回填超时和弹性训练替代方案结合时会浪费资源。
-
拓扑决定性能:GPU 在集群层级中的放置位置,与作业获得多少 GPU 一样重要。NVLink 与 InfiniBand 的放置决策,可使同一作业在相同硬件上产生 15% 到 30% 的吞吐量差异。
-
面向 ML 的调度优于通用方法:利用工作负载特征(可预测的资源需求、迭代计算、边际收益递减),与通用的 FIFO 或公平共享策略相比,可使作业完成时间提升 40% 到 60%。
-
利用率是第一性的经济驱动力:在大型集群上将利用率从 50% 提升至 80%,实际上增加了价值数千张 GPU 的产能。通过策略和算法工作来弥补这一差距,其投资回报快于购买额外的加速器。
-
策略必须随基础设施演进:调度算法的有效性取决于其所实施的策略。当基础设施发生变化(异构硬件新增、拓扑升级、工作负载组合变化)时,必须重新评估并更新策略。
车队现拥有所需的每一个物理部件:计算、通信、存储,以及维持其运行的弹性。在某物决定“何物运行于何处”之前,这一切皆不产出任何价值,而该决策纯粹是协调。追求峰值利用率的调度器,当两个大型作业需要相同节点时,首次便会陷入死锁;因此,保证进展的调度器必须刻意预留部分产能闲置。车队理论可达吞吐量与其可安全维持吞吐量之间的差距,即是协调的代价,而编排则是将这笔代价花在能买到最多完成工作之处的学科。在车队规模下,约束瓶颈再次上移栈层,从硬件速度转移至分配硬件的策略质量。
我们已组织起机器学习车队的运营层,即资源管理的“谁获得什么、何时获得、在何处获得”。然而车队的目的并非运行训练作业;而是生产服务于用户的模型。下一个挑战是从已编排的硬件中榨取最大吞吐量。第 9 章 探讨实现此目标的技术:算子融合、精度工程、图编译和执行调度。这些优化决定了车队昂贵的硅片是发挥其理论潜能,还是在低效执行中浪费周期。编排层确保分配到正确的资源;性能工程确保这些资源被有效利用。
-
调度器应如何为分布式训练作业在利用率、公平性、拓扑局部性和死锁预防之间进行权衡?
-
布局策略应如何纳入集合通信开销和故障域边界?
-
抢占、抢占式容量(Spot Capacity)和弹性训练何时能降低总成本,而非增加损失工作量和方差?
-
除加速器利用率外,哪些信号应驱动自动扩缩容和集群健康决策?
大规模部署原则
第一部分和第二部分构建了车队并训练了模型。生产部署现成为运维问责制的试金石。第三部分将焦点从单次大规模训练运行转移至全球推理车队:成千上万地理分布的服务器和数十亿边缘设备,实时向用户提供模型输出。这种从训练到生产的转变从根本上改变了工程约束:从在静态数据集上最大化吞吐量,变为在动态请求流上最小化延迟,且均处于全球服务的物理限制之内。
在此机制下,工程成为与用户体验的博弈。系统可在数据中心针对高批量吞吐进行优化,但这冒着违反用户延迟预算的风险。将模型部署至边缘消除了网络延迟,却引入了移动设备和物联网(IoT)硬件严苛的内存和功耗包络。这些原则管辖大规模部署智能的性能工程与运维经济。
首要约束源于生成机制本身。
不变量:在低批量稠密 Transformer 解码中,吞吐量受限于内存带宽,因为模型权重必须为每个生成的 Token在设备间流式传输。更高的批量规模可摊销权重流式传输,而键值(KV)缓存流量在长上下文时会转移绑定约束。
推论:吞吐量最初随批量大小提升而改善,通过跨请求摊销权重读取,直到单设备延迟预算、KV 缓存容量或持续计算成为绑定上限。因此服务系统需要批处理、缓存管理和内存布局策略,在不违反延迟 SLO 的前提下保持带宽有效利用。
同一核算从设备带宽延伸至车队经济。
不变量:对于高流量生产模型,其生命周期内的累积推理成本可能超过一次性训练成本的 10–100 倍。
推论:推理效率往往成为主导生命周期的优化目标。当流量规模、产品寿命和重训节奏使服务成本占主导时,优化工作(量化、蒸馏、稀疏化)应聚焦于推理路径。
规模化下,路由策略成为保护延迟的另一杠杆。
不变量:在具有 R 个副本的理想化“球入箱”模型下,仅查询两个随机副本并选择负载最低者,可将任一副本上的最大负载从 Θ(log R/log log R) 降至 Θ(log log R)。 Θ(log R/log log R) → Θ(log log R)
推论:简单的随机负载均衡算法以 𝒪(1) 的协调开销产生显著更低的最大负载界。在服务排队假设下,较低的最大负载转化为规模化下的尾延迟降低。
第三部分:从集群到世界
第三部分将训练好的模型从集群推向世界。路径始于性能工程,其中精度选择、内核行为和编译决定了系统实际能利用硬件理论容量的多少。随后转向大规模推理,其中请求批处理、缓存管理、路由和分片将单设备效率提升为全队列级的延迟控制。边缘智能将同样的纪律应用于内存、功率和连接预算远低于中心化设备的场景,而大规模运维通过监控已部署的机队、协调平台所有权以及在生产行为偏离设计假设时作出响应来闭合回路。这些章节共同培养了将训练好的模型转化为耐用生产系统的运营纪律。
性能工程

目的
我们如何让数十亿参数的模型在毫秒级时间尺度上运行?
模型压缩减少了模型计算的规模。性能工程则重塑该计算,使其匹配硬件的物理特性。这种区别至关重要:如果将一个量化模型天真地加载到一个加速器内核中,该内核会从片外内存读取每个权重,那么它就会浪费掉量化本来所设计要节省的带宽。真正的性能来自于理解张量从寄存器经过片内存储器到高带宽存储器再返回的完整路径,然后对每一步进行工程化以消除浪费的移动。本章节发展了系统级优化技术,以弥合理论高效模型与能够饱和硬件的生产工件之间的差距。杠杆点包括操作符融合和平铺策略(使数据停留在快速本地内存)、精度格式(可使有效带宽翻倍)、编译框架(可自动化内核选择)以及算法创新(如投机解码和稀疏专家路由,它们改变了性能方程)。它们共同将本应快速的模型转化为真正快速的模型,往往提升一个数量级。这一数量级完全来源于 C³分类学中的计算项:性能工程从机队已有的周期中提取更多有用的工作。
-
应用屋顶线分析,将目标加速器上的 ML 内核分类为计算受限、内存受限或启动受限
-
分析预填充、解码和批量大小 regimes,以预测 LLM 服务中的延迟-吞吐行为
-
设计融合、平铺和
CUDA图策略,以降低 HBM 流量和启动开销 -
根据带宽、质量和部署约束选择精度、编译和运行时优化
-
使用接受率、批处理限制和 AllToAll 成本来评估投机解码和 MoE 路由
-
使用性能分析器、屋顶线图和机队效率指标来诊断服务瓶颈
-
在计算、通信、协调和质量的权衡中合成 70B 服务优化方案
内存墙与屋顶线诊断
一块能够达到 989 TFLOP/s FP16 计算的H100 GPU,在小批量语言模型解码时仍可能表现出个位数的计算利用率。处理器并非缺乏算力;它是在挨饿数据。性能工程在这种限制下运作:即内存墙,其中将字节从内存移动到计算单元的开销可能在 Tensor Cores 达到其公布峰值之前就限制了吞吐量。
放置、同步、恢复和调度可以将工作负载放在合适的硬件上并保持其活跃。剩下的问题是本地执行:昂贵的硅片在工作到达后仍可能空闲。如图 1.13 所示的机队栈中,性能工程是服务层内的优化学科,在内核、内存层次、互连或框架开销决定实现吞吐量时,会深入到分发和基础设施层。答案通常是数据移动,因此性能工程从内存层次结构开始,然后通过融合、精度、编译和算法变化来跟踪其后果——这些变化能够减少字节移动、降低启动开销,或彻底改变工作内容。
ML 性能的铁律
内存墙是更大预算中的一个术语。公式 9.1 阐述了 ML 系统性能的铁律,将执行时间分解为三种竞争成本:
在重叠执行中,屋顶线风格的简化将计算和数据移动的和替换为较慢的暴露项:
继承的铁律将执行时间分解为三项。计算项代表总浮点运算除以实现的硬件吞吐量。数据项代表总传输字节除以内存带宽。屋顶线近似则询问在给定运行点下哪个暴露项占主导:例如,对于内存受限的工作负载,仅提高计算吞吐量不会显著改善性能,直到内存项被降低。开销项捕获其他所有内容:内核启动延迟、同步、通信和软件栈低效。PyTorch训练循环会暴露这一项中的特别大的一部分:每次内核启动都必须穿越 Python 全局解释器锁(GIL)和框架的 CPU 调度器,才能到达 GPU 命令队列,在任何 GPU 工作开始之前,每次操作就会在 Python 分派上花费数十微秒。这正是为什么 ML 框架开发者优先考虑torch.compile(mode="reduce-overhead")和CUDA Graphs 的原因:它们完全追溯并规避了 Python 调度路径,将重复的 GPU 提交转换为单次原生重播,在每一步都绕过GIL和调度器。
自回归 Transformer 解码通常是内存受限的:在铁律中,数据移动项占主导。
标准模型压缩(剪枝、量化、蒸馏)通过在更小的数据上执行更少的操作来减小模型的固有工作量。系统优化则针对互补问题:通过实现而非模型变更来缩小该工作与硬件峰值之间的差距——即将相同的项通过实现方式移动,使数据停留在快速内存中、使每次传输更密集,并消除软件开销。
若干杠杆直接映射到这些项上。当内存流量占主导时,操作符融合和平铺通过消除中间 HBM 往返来降低暴露的数据体积项。一个将其中间结果保留在SRAM中的融合序列能够显著降低暴露的内存访问项,注意力计算方面常见 10–30×的降幅。精度工程则从另一个角度攻击相同的分子:FP8、INT4和 KV 缓存压缩(以更少字节存储每个 token 的注意力键和值)使每个值使用更少字节,从而让相同的物理带宽承载更多有用的模型状态。
内存墙与效率前沿
优化分类学与诊断流程
其他优化手段则针对开销或计算项。图编译,包括 torch.compile、加速线性代数 (XLA) 和 TensorRT,通过消除内核启动间隙、融合算子以及跨图优化内存分配来降低开销。通信-计算重叠使分布式通信与有效工作并发进行,当满足 T_comm(N) - T_overlap <= T_compute/N 时(即通信在下一层计算完成前结束),将其从关键路径中移除。这个不等式是暴露时间测试,而非新定律:重叠仅在有效计算量足以覆盖通信时才有帮助。推测解码和专家混合 (MoE) 等算法创新通过让模型执行不同的计算,在保持输出契约不变的前提下以更低的暴露成本改变了计算项本身。
每种技术针对不同的项,这种分类学指导优化策略:诊断哪个项占主导地位(使用第 9.1.5 节中的屋顶线模型),然后应用针对该项的技术。应用针对非主导项的技术会浪费工程精力。
在使用此诊断流程之前,请核对铁律中的每一项如何映射到实际的优化杠杆。
验证您对系统级性能诊断的理解:
同一诊断流程可编码为决策流程图,将每个瓶颈映射到其对应的优化技术。图 9.1 中的流程图明确了排序:在将剩余工作负载归类为计算受限或内存受限之前,先排除 I/O、CPU 和通信开销。
图 9.1:铁律诊断流程图:一个训练变慢故障排查流程,从观察到的症状(训练慢、GPU 利用率低)开始,分支检查 I/O、CPU 和通信开销,最后在叶子节点将工作负载归类为计算受限或内存受限。计算受限路径指向精度工程和算法变更;内存受限路径指向算子融合和分块。开销受限症状(图调度、通信)在流程早期浮现,并在最终的计算/内存分类前得到解决。
图 9.1 的核心教训是:剖析必须先于优化。对计算受限工作负载应用算子融合,或对开销受限工作负载应用精度工程,无论实现质量如何,都无法带来任何提升。误诊不仅浪费精力;盲目发布的性能变更可能将系统推向其运行边界上的错误位置。
效率前沿
该边界即 效率前沿,即模型质量与系统吞吐量的帕累托最优曲线。处于前沿上的模型无法在不牺牲质量的情况下提高吞吐量,反之亦然。Thompson 等人 (2021) 论证了为何此前沿对深度学习至关重要:质量提升需要算力呈指数级增长,使得效率增益成为持续进步的核心。性能工程通过使每个质量级别在更高吞吐量下可达成,或等价地使每个吞吐量级别在更高质量下可达成,将前沿向外推进。组织的目标不仅是达到前沿,更是找到最符合其延迟、吞吐量、成本和质量要求的前沿上的点。
该前沿的多维特性使得优化充满挑战。表 9.1 列出了性能工程师在选择优化目标前必须权衡的五个维度。
| 维度 | 衡量方式 | 约束对象 | 典型张力 |
| --- | --- | --- | --- |
| 吞吐量 | Tokens/秒 或 Requests/秒 | 系统单位时间内完成的工作量 | 更大的批次提高吞吐量,但往往降低延迟性能 |
| 延迟 | 首 token 时间 和 逐 token 间延迟 | 系统响应单个请求的速度 | 降低延迟可能需要更小的批次和更高的单 token 成本 |
| 成本 | 美元/百万 tokens | 系统的经济效率 | 最便宜的配置可能无法满足延迟或质量目标 |
| 质量 | 困惑度、基准准确率 或 人类偏好 | 模型输出的准确性和实用性 | 降低精度和推测执行需要质量护栏 |
| 内存 | 峰值 GPU 显存 | 可行的批次大小和序列长度 | 更大的上下文和批次消耗模型状态所需的容量 |
表 9.1:效率前沿维度:性能优化平衡可衡量的吞吐量、延迟、成本、质量和内存约束,而非单一的硬件利用率数字。
表 9.1 中的维度以非显而易见的方式相互影响。增加批次大小可提高吞吐量和成本效率,但会降低延迟性能。降低精度可提高吞吐量和内存利用率,但可能损害质量。推测解码可改善延迟,但可能增加单 token 成本。性能工程师的任务是在应用特定需求的指导下驾驭这些权衡。
实时聊天机器人优先考虑延迟(首 token 时间 200 毫秒以下,逐 token 间延迟 50 毫秒以下),并可容忍较高的单 token 成本。文档摘要的批处理流水线优先考虑吞吐量和成本,可容忍数秒的延迟。医疗诊断系统将质量置于首位,接受较低的吞吐量和较高的成本。每个应用映射到效率前沿上的不同最优点,性能工程工具箱提供了到达该点的方法。为使其具体化,考虑同一 70B 大语言模型 (LLM) 的两种部署配置。
配置 A 为延迟优化:FP16 权重,批次大小 1,启用推测解码。模型副本每秒产出约 50 tokens,逐 token 间延迟 20 ms,占用 8 张 H100 GPU 服务单用户流。成本:约每 1,000 个输出 tokens 0.12 美元。
配置 B 为吞吐量优化:INT4 权重,批次大小 64,无推测。每张 H100 为所有批处理请求提供约 4,000 tokens/秒的聚合吞吐量,每请求逐 token 间延迟 120 ms。成本:4 张 GPU 服务 64 个并发用户,约每 1,000 个输出 tokens 0.002 美元。
配置 B 的单 token 成本比配置 A 低 60 倍,但延迟高 6 倍。没有哪种配置客观上“更好”;它们代表效率前沿上针对不同应用优化的不同点。性能工程就是在该点间导航的学科。
内存墙
效率前沿确立了我们优化的目标。内存带宽的物理特性决定了我们的起点。许多基于加速器的 ML 性能问题始于同一个观察:瓶颈是内存带宽,而非计算。考虑大语言模型中的单步自回归解码。模型从高带宽内存 (HBM)¹³⁷ 读取其完整权重矩阵以生成单个 token,每加载一个权重仅执行一到两次乘累加运算。
NVIDIA H100 每秒提供 1979 TFLOP/s 的 FP8 计算能力,但仅有 3.35 TB/s 的内存带宽。如果每从内存加载的字节所执行的算术运算少于 590.7 FLOP/byte,则计算单元将因数据不足而闲置。计算能力与内存传输速率之间的这种差距即为内存墙,它定义了所有性能工程所作用的格局。
内存墙代表的是一种根本的物理限制,而非暂时的工程限制。数据移动的能耗与距离成正比。从片上 SRAM(L1 缓存)访问一个值大约消耗 0.5 pJ,而从片外 HBM 获取相同值大约消耗 640 pJ,二者之比约为 1280×。制造工艺限制了可靠近计算单元的 SRAM 数量。HBM 提供容量(H100 提供 80 GB),但距离更远,因而数据需穿越更长的导线。其根本张力在于:模型需要千兆字节的参数和状态,但物理定律规定在任意时刻,仅能有千字节级别的数据靠近计算单元。
访问能耗从寄存器上升到 HBM。
容量-带宽张力塑造了优化空间。算子融合通过合并操作使中间结果停留在 SRAM 中,从而减少前往 HBM 的次数。精度工程通过使用 FP8 或 INT4 而非 FP16 表示数值,从而降低每次传输的字节数。分块策略重构算法以最大化 SRAM 内的数据复用。图编译器自动化这些变换。每种技术都针对同一基本方程中的不同项:最小化移动字节数与执行操作数的比率。
GPU 内存层次结构
要理解为何存在内存墙,需考虑 GPU 内存系统的物理结构。表 9.2 按规模、访问成本以及每一级所施加的优化约束总结了四个层次。
| 级别 | H100 上的规模 | 访问成本 | 优化约束 |
| --- | --- | --- | --- |
| 寄存器 | 每个 SM 包含 256 KB,跨 132 个 SM,总计约 33 MB | 每次访问一个时钟周期,约 0.01 pJ | 私有于每个线程;FP32 累加器可能导致寄存器溢出至 L1/共享内存,每次访问需 20–30 个时钟周期 |
| 共享内存(SRAM) | 每个 SM 可配置高达 228 KB 共享内存 | 每次访问 20–30 个时钟周期(约 20 ns),约 0.5 pJ | 在线程块内共享;当中间结果适合存放在此而非返回 HBM 时,算子融合才具盈利性 |
| L2 缓存 | 片上 50 MB 缓冲区 | 大约 200 个时钟周期(约 130 ns) | 自动跨 SM 捕获重用,但核作者无法显式管理 |
| 高带宽内存(HBM) | 80 GB,带宽 3.35 TB/s | 每次访问约 300 ns,640 pJ | 提供模型和激活容量,但读取整个设备大约需 24 ms,远超实时推理延迟目标 |
表 9.2:GPU 内存层次结构约束:更快的 GPU 存储容量更小、更靠近计算单元且更易显式管理;而 HBM 在更高延迟和能耗下提供容量。
该表将寄存器压力置为自定义 Triton 和 CUDA 内核的一级设计约束:分块大小同时控制算术强度和寄存器需求。它还解释了为何共享内存和 L2 复用对注意力机制如此重要。如果 KV 缓存条目或融合中间结果保留在片上,则内核可避免缓慢且耗能高的 HBM 往返——这正是低算术强度操作的主导因素。
数据移动的能耗在数据中心规模下具有直接的经济后果。假设有一个由 1000 块 H100 GPU 组成的训练集群,在内存受限工作负载下,每个 GPU 每秒大约执行 10¹² 次内存访问。如果每次访问从 HBM 读取数据耗能为 640 pJ,则单独的内存子系统每 GPU 消耗大约 640 W,占 H100 700 W TDP 的显著比例。如果算子融合将其中一半的访问从 HBM 转移到 SRAM(每次访问耗能 0.5 pJ),则每 GPU 的内存功耗可降低约 320 W。按 1000 个 GPU 计算,这可节省 320 kW,相当于为大约 250 户家庭供电。这绝非次要考量;在云电价下,年度成本差异十分可观,且随集群规模线性增长。数据移动的物理不仅是性能约束,也是经济约束。
性能工程挑战归结为一个数据定位问题:将计算单元所需的数据保留在能容纳它的最快内存中。当内核从 HBM 读取一个张量、对其进行处理,并将结果写回 HBM 时,对于任何算术强度低的操作,HBM 往返都将主导执行时间。此处的每一种技术共享同一目标:让数据尽可能长时间地靠近计算单元。
GPU 内存层次结构类比一位学者在图书馆中研究:
-
寄存器(33 MB)是 工作记忆:访问即时,但容量仅够一次保存少数几个值。
-
共享内存(SRAM) 是 书桌:访问极快,但容量仅够放置少数几本开放的参考书。
-
L2 缓存(50 MB)是 书架旁的推车:访问成本小,可容纳中等规模的工作集。
-
HBM(80 GB)是 图书馆地下室:存放所有可能需要的数据,但每次往返需花费数百纳秒。
性能工程是将前往地下室的次数降至最低的艺术。
差距的扩大
内存墙并非静态;在此处比较的加速器世代中,计算吞吐量的增长速度快于片外内存带宽。内存带宽提升较慢,因为离 chip 信号传输的物理限制和 HBM 制造的经济性共同限制了数据离开芯片的速度。
表 9.3 量化了硬件平衡在这些加速器世代中的转移。关键列是“脊点”:随着计算增长快于带宽,更多算子需要更高的算术强度才能保持计算受限状态。
| GPU | 年份 | 峰值 FP16 (TFLOP/s) | HBM 带宽 (TB/s) | 脊点 (FLOP/byte) |
| --- | --- | --- | --- | --- |
| V100 | 2017 | 125 | 0.9 | 139 |
| A100 | 2020 | 312 | 2.04 | 153 |
| H100 | 2022 | 989 | 3.35 | 295 |
| B200 | 2024 | 2,250 | 8 | 281 |
表 9.3:扩大的内存墙:从 V100 到 B200 在七年内,计算吞吐量增长了 18×,而内存带宽仅增长了 8.9×。脊点总体上约翻了一倍(V100 到 B200),使得更多工作负载随时间推移陷入内存受限状态,尽管每代变化并非单调递增(B200 略低于 H100)。
就 FP16/BF16 表格而言,脊点从 V100 的 139 FLOP/byte 增至 B200 的 281 FLOP/byte,约增加了 2×。算术强度为 200 FLOP/byte 的操作在 V100 和 A100 上是计算受限的,但在 H100 和 B200 上变为内存受限的。因此,随着脊点的上升,针对内存效率、融合、精度和分块的性能工程技术变得愈发重要,而非降低重要性。系统层面的启示不是某个特定内核能永远适用,而是当计算增长快于带宽时,降低暴露的内存流量变得更为有价值。
楔线模型
屋顶线模型与算术强度
第 2.3.2 节 介绍了屋顶线模型[¹³⁸](Williams et al. 2009)和算术强度,作为诊断给定加速器上工作负载是计算受限还是内存受限的框架,并计算了 H100 的屋脊点。我们在此回顾它,只是为了将其推广到舰队规模:屋脊点如何跨硬件代际变迁,FP8 如何移动它,生产级 ML 工作负载相对于它处于何处,以及批大小如何使工作负载跨越它。作为提醒,该模型将可达性能绘制为算术强度的函数,后者是浮点运算次数与从内存传输的字节数之比,两个区域的交点即为屋脊点[¹³⁹]。
算术强度
算术强度(I)是 ML 工作负载执行的浮点运算次数与从内存传输的字节数之比(FLOP/字节)。
-
意义:它表征工作负载的计算密度。它是屋顶线模型中的自变量,决定系统处于带宽受限(BW)还是计算受限(
R[peak])区域。 -
区别:与峰值吞吐量(硬件属性)不同,算术强度是算法属性,衡量工作负载在数据加载到处理器后复用数据的效率。
-
常见误区:一个常见的误解是算术强度对某个模型是固定的。实际上,它随实现而变化:算子融合等技术通过将数据保留在本地寄存器中来提高算术强度,而增加批大小则会提高高参数复用层的算术强度。
对于具有峰值算力 R[peak](单位:FLOP/s)和峰值内存带宽 BW(单位:字节/s)的给定加速器,公式 9.2 给出了算术强度为 I(单位:FLOP/字节)的工作负载的可达性能:
可达 FLOP/s = min(R[peak], BW × I) (9.2)
公式 9.3 给出了这两个限制相交的屋脊点位置:
I_ridge = R_peak / BW (9.3)
I < I[ridge] 的工作负载是内存受限的:其性能受限于数据加载速度,而非处理速度。I > I[ridge] 的工作负载是计算受限的:算术单元是瓶颈。图 9.2 以图形方式展示了这一关系。
图 9.2:屋顶线模型:对数-对数图上可达性能(y 轴)作为算术强度(x 轴)的函数。斜线代表内存带宽上限;水平线代表标注精度下的算力上限(H100 上 FP8 张量核吞吐量,约 1979 TFLOP/s);二者交点即为屋脊点,FP8 下约为 590.7 FLOP/字节。大多数 Transformer 推理运算落在内存受限区域(屋脊点左侧),而大批量 GEMM 落在计算受限区域(屋脊点右侧)。对应的 FP16 屋脊点约为 295 FLOP/字节(989 TFLOP/s 除以 3.35 TB/s)。
NVIDIA H100 在 FP16 精度下的屋脊点直接源自 表 9.3 中使用的相同峰值算力和带宽值:
I_ridge^H100, FP16 = 989 TFLOP/s / 3.35 TB/s ≈ 295 FLOP/byte
在 H100 上以 FP16 运行,每加载一字节数据执行的浮点运算少于 295.2 次的操作均为内存受限。在 FP8 精度下,算力翻倍至 1979 TFLOP/s 而带宽维持 3.35 TB/s,屋脊点上升至约 590.7 FLOP/字节。A100 具有 312 TFLOP/s 和 2.04 TB/s,其 FP16 下的屋脊点较低,约为 153 FLOP/字节。跨越 表 9.3 中的各代产品,算力增长超过了带宽,屋脊点大致翻倍(V100 到 B200),因此随着时间的推移,更多工作负载落入内存受限区域;然而,单代际的变化并非单调,因为每款芯片都搭配自己的算力和带宽(B200 的屋脊点略低于 H100)。重要的是持久的趋势,而非某一步变化,这使得随着硬件进步,内存效率技术变得更有价值。
图 9.3 在单个对数-对数图上叠加了四代 GPU 的屋顶线模型,使先前列表中提到的代际屋脊点变化一目了然。像朴素自注意力这样算术强度约为 10 FLOP/字节的操作,在每一代上都是内存受限的,并且随着每一代新芯片的推出,其相对于屋脊点的位置逐渐下降。更关键的是,算术强度接近 200 FLOP/字节的操作(如某些矩阵乘法和融合算子),随着硬件变化可能改变受限区域。同一个内核可能跨硬件代际改变性能受限区域,这一事实要求在硬件升级时必须重新进行性能分析。

图 9.3:跨 GPU 代际的屋顶线变迁:V100 到 B200 的 FP16/BF16 屋顶线模型叠加图显示屋脊点从 139 增长到 281 FLOP/字节。接近 200 FLOP/字节的操作可能随着屋顶线的平移而改变受限区域。同一个内核可能跨硬件代际改变性能受限区域。
ML 工作负载的分布位置
ML 运算的算术强度跨越三个数量级,每个运算在屋顶线上的位置决定了适用的优化策略。
大型通用矩阵乘法(GEMM)运算是 ML 中计算最密集的运算。FP16 下 4096 × 4096 维的方阵乘法执行约 1374 亿 FLOP,同时加载约 100.7 MB 数据,算术强度约为 1365.3 FLOP/字节。这远高于 H100 的屋脊点,使大型 GEMM 牢固地处于计算受限区域。
逐元素运算则完全相反。应用于 4096 × 4096 张量的高斯误差线性单元(GELU)激活函数每元素约执行 5 次运算,但必须加载并存储每个元素,算术强度约为 1.2 FLOP/字节。GPU 几乎将所有时间花在等待数据传输而非计算上,使这些运算深度内存受限。
批大小为 1 的自回归 LLM 解码代表了极端情况。每个解码步骤读取整个权重矩阵(GB 级数据)以产生单个输出 token。隐藏维度为 4096、批大小为 1 时,算术强度约为 1 FLOP/字节,深陷内存受限区域。算术强度解释了为何 LLM token 生成仅达到峰值 FLOP/s 的极小部分:GPU 几乎将所有时间花在读取权重,而非乘法运算上。
表 9.4 揭示了这些示例背后的核心模式:批处理 GEMM 可达计算受限区域,但注意力、逐元素操作和批大小为 1 的解码位于屋脊点下方,受制于移动的字节数而非标称的 FLOP 数。
| 运算 | 算术强度 | H100 FP16 区域 | 主要瓶颈 |
| --- | --- | --- | --- |
| GEMM (4096 × 4096) | ~1,365 FLOP/字节 | 计算受限 | 张量核吞吐量 |
| 自注意力 (seq=2048) | ~50–200 FLOP/字节 | 内存受限 | HBM 带宽 |
| 逐元素 (GELU, LayerNorm) | ~1–3 FLOP/字节 | 内存受限 | HBM 带宽 |
| LLM 解码 (batch=1) | ~1–2 FLOP/字节 | 内存受限 | HBM 带宽 |
表 9.4: 常见机器学习操作的算术强度
许多转换器推理操作(除大批量 GEMM 外)低于 H100 的锐点,因此受内存限制。性能工程的重点是减少这些操作的内存流量。
转换器推理流水线中的大多数操作都是内存受限的。具有大批量大小的训练工作负载会因 GEMM 维度随批量大小增加而将更多操作转移到计算受限区。然而,推理(尤其是自回归生成)主要由内存受限操作主导。融合、分块、降低精度和算法快捷方式都针对同一根本问题:减少每次操作移动的字节数。
推理的内存受限特性还解释了一个常见的混淆来源:GPU 基准测试报告的峰值 TFLOP/s 往往无法预测真实的推理性能。两个 TFLOP/s 不同但内存带宽相同的 GPU 在批量大小为 1 时,将获得几乎相同的 LLM 解码吞吐量,因为解码完全受内存限制。用于比较 GPU 在 LLM 推理中的正确指标不是 FLOP/s,而是内存带宽和内存容量的组合。带宽决定了令牌生成速率,容量决定了最大批量大小(因此决定了吞吐量)。只有在批量大小较大、解码接近计算受限区时,GPU 之间的 FLOP/s 差异才会转化为吞吐量差异。
提服务模式:批量大小和 Prefill-Decode
诊断定位工作负载在屋顶线上的位置;服务模式决定哪些旋钮会移动它。LLM 推理的两个结构特征——批量维度以及提示处理和令牌生成之间的划分——决定了解码是仍停滞在内存受限斜坡上,还是向计算屋顶攀升。这两个因素是本章余下技术作用的杠杆。
批量大小作为通用控制旋钮
对于内存受限的 LLM 服务,批量大小通常是首先测试的性能杠杆,也是最受限制的杠杆之一。增加批量大小会改变每个操作的算术强度。对于 LLM 解码步骤,算术强度与批量大小成线性关系:
I_decode(B) = (2PB) / (P * s_param + B * d_model * s_elem)
这里,P 是参数数量,B 是批量大小,d_model 是隐藏宽度,s_param 是每个存储参数的字节数,s_elem 是每个激活元素的字节数。在批量大小为 1 时,分母由权重项(P * s_param)主导,因此 I ≈ 2 / s_param ≈ 1 FLOP/byte(对于 FP16)。在批量大小为 256 时,权重项仍然主导,但相同的权重字节被摊薄到更多请求上,因此 I ≈ 2 × 256 / s_param ≈ 256 FLOP/byte,正接近计算受限区。
较大的批量大小会提升算术强度,使其向锐点靠近。
在大批量大小时,GPU 从内存受限转向计算受限,利用率显著提升。单个 H100 在批量大小为 1 时可能仅达到 5% 的利用率,而在大批量大小时可达 40% 的利用率。经济影响显著:随着批量处理将工作负载从内存受限区转移到计算受限区,每个令牌的成本大约降低 8 倍。
限制在于内存:批量中的每个额外请求都需要自己的 KV 缓存(即存储截至目前每个生成令牌的注意力键和值的每请求存储),所有请求的 KV 缓存总和必须与模型权重一起容纳在 GPU 内存中。这里介绍的 70B 模型在 8 块 H100 上的部署是本章的反复示例,将在精度红利(第 9.4.3 节)和案例研究(第 9.10.3 节)中以完整的量化细节重新审视。一个在 FP16 下权重为 140 GB 的 70B 模型必须跨多个 GPU 分片;在一个 8-GPU 节点上,这会为 KV 缓存留出约 62.5 GB/GPU(扣除开销后)。本章的精度工程技术正是针对这一限制:INT4 权重量化将每个 GPU 的权重占用降至约 4.4 GB,从而每个 GPU 可释放约 13 GB 用于 KV 缓存,使得批量大小得以提升,从而改变服务的经济性。
服务调度器可以通过在旧请求完成时添加新请求来保持有效批量充满,但此策略仅在有足够内存供活跃请求使用时才有效。性能工程的作用是通过主要通过 KV 缓存压缩和权重量化来最小化每个请求的内存占用,使调度器的工作变得可行。推理服务(第 10 章)将开发完整的调度器;这里的要点是,内存优化扩大了调度器可以安全接纳的批量大小。
实现大批量大小的关键推动因素是 PagedAttention,即 vLLM 的分页 KV 缓存管理技术(Kwon et al. 2023)。传统的 KV 缓存实现会为每个请求的最大可能序列长度预分配连续内存。
如果最大长度是 4,096 个标记但平均只有 500,则大约 88% 的分配内存被浪费。PagedAttention 将 KV 缓存划分为固定大小的块(页面),按需在序列增长时分配。这消除了内存碎片,并使得 KV 缓存内存预算的利用率接近 100%。性能影响是间接但显著的:通过减少内存浪费,PagedAttention 能够实现 2–4 倍更大的有效批量大小,这进一步通过前文所述的批量大小机制提升吞吐量和 GPU 利用率。
PagedAttention 与 KV 缓存量化相互乘增作用。PagedAttention 减少内存 浪费(源于碎片),而量化减少内存 使用(源于精度)。两者结合相比于预分配 FP16 KV 缓存的基线系统,能够将有效批量大小提升 8–16 倍,从根本上改变了 LLM 服务的经济性。
Prefill-Decode 分解
现代 LLM 服务系统将每个请求分解为两个具有根本不同性能特征的不同阶段。这一区别驱动了系统架构和优化策略。
Prefill 阶段 并行处理整个输入提示。如果提示包含 S 个标记,则预填充阶段对所有 S 个标记同时执行一次前向传递。GEMM 操作的形状为 [S, d_model] × [d_model, d_model],因此批量维度等于 S。对于 1024 个标记的提示,这在算术上是密集的:算术强度大约为 2 × 1024 / 2 = 1024 FLOP/byte(针对 FP16 权重),远在计算受限区。因此,预填充阶段受限于 Tensor Core 吞吐量,而非内存带宽。
Decode 阶段 自回归地逐个生成输出标记。每一步的批量维度为 1(针对单个请求)或并发请求的数量(针对批量服务)。在批量大小为 1 时,如第 9.1.6 节所分析的,解码阶段深度受内存限制。
Prefill-Decode 分解对系统设计有直接影响。一个针对 prefill 进行优化(最大化 FLOP/s 利用率)的系统将使用大矩阵规模和高计算吞吐量。一个针对 decode 进行优化(最大化带宽利用率)的系统将采用激进的量化和内存优化。一个实际的服务系统必须同时处理这两个阶段,通常是在不同的活跃请求之间并行进行。
解耦服务
解耦服务通过在独立的硬件池上运行预填充和解码来解决这种错配。这里的解耦表明预填充和解码存在不同的瓶颈;第 10 章 将开发路由、准入控制和服务策略机制。预填充服务器针对算力进行了优化(较少但更高 FLOP/s 的 GPU),而解码服务器则针对内存带宽和容量进行了优化(每 GPU 更多内存、激进量化)。预填充期间计算的 KV 缓存会被传输到解码服务器,后者处理后续的自回归生成。这种解耦使得每个阶段都能使用针对其特定瓶颈调优的硬件和软件配置。
每个阶段的性能特征决定了适用的优化技术。FlashAttention 在预填充阶段提供最大增益,因为此时二次注意力计算占主导地位。KV 缓存量化和推测性解码仅适用于解码阶段。精度工程(FP8/INT4 权重)对两个阶段都有益,但通过不同的机制:预填充受益于算力吞吐翻倍(FP8 Tensor Cores),而解码受益于有效带宽翻倍(每次权重读取的字节数减半)。
一个快速的解码计算表明,即使加速器拥有大量未使用的 FLOP/s,内存墙依然占主导地位。
问题:一个 70B 参数的 LLM 部署在 8× H100 GPU 上并采用张量并行。在批大小为 1 时,每个 GPU 以 FP16 格式持有约 87.5 亿参数(17.5 GB 权重)。每个解码步骤读取其权重分片以生成一个 token。请问实现的算术强度是多少?理论最大 token 生成速率是多少?
数学计算:
步骤 1:算术强度。每 GPU 每解码步骤:FLOPs = 2 × 8.75 × 10⁹ = 175 亿 FLOPs。加载字节数 = 8.75 × 10⁹ × 2 字节 = 17.5 GB。
\(I = \frac{1.75 \times 10^{10} \text{ FLOP}}{1.75 \times 10^{10} \text{ bytes}} = 1 \text{ FLOP/byte}\)
在 1 FLOP/byte 时,该运算远低于 H100 的屋顶点 ~295 FLOP/byte:属于深度内存受限。
步骤 2:Token 速率。由于运算受内存限制,性能受限于带宽而非算力:
\(t_{\text{decode}} = \frac{17.5\,\text{GB}}{3.35\,\text{TB/s}} \approx 5.2\,\text{ms per token}\)
这产生约 191.4 tokens/s 每 GPU,或约 191.4 tokens/s 整个模型(因为张量并行不会为内存受限的解码乘以吞吐量)。实际上,来自 KV 缓存读取和 NVLink 同步的开销会使该数值大幅低于理想的仅带宽限制。
系统洞察:在批大小为 1 时,仅使用了 H100 FP16 FLOP/s 的约 0.3%。所有改进路径都针对同一个权重流式传输成本:更大的批次将每次权重读取分摊到更多请求上,量化减少了每个权重读取的字节数,而推测性解码试图从一次目标模型权重传递中获得多个被接受的 token。
屋顶模型确立了制约所有后续优化的物理规律。突破内存墙的首要且最具影响力的策略是将数据保留在 SRAM 中,而不是在 HBM 中往返。
算子融合与内核工程
考虑简单的操作序列 Y = LayerNorm(GELU(XW + b))。在朴素实现中,GPU 将矩阵乘法的输出写回主内存,再读回进行 GELU,再次写出,最后再读一次进行 LayerNorm。这种冗余的数据移动严重破坏了性能。算子融合 通过将结果保留在超高速寄存器中,在一次内存访问中执行整个序列,从而消除了这些中间往返。
想象一个厨房,一名厨师切好蔬菜放进冰箱(HBM),然后另一名厨师取出煮沸,再放回冰箱,第三名厨师再取出摆盘。这是非融合执行:瓶颈不在于烹饪,而在于不断往返冰箱。
算子融合将食谱分配给单个厨师,让其将食材放在案板上(SRAM/寄存器)并连续完成三个步骤,直到最终菜品完成前绝不返回冰箱。
内核启动问题
每次 GPU 内核启动都涉及开销:CPU 必须准备启动参数,分派到 GPU 命令队列,GPU 必须在其流式多处理器(SM)上调度线程块。对于 H100 等加速器上的小型逐元素操作,此开销可达 5–20 μs,而内存受限内核可能在此期间已完成其有效工作。当一个 Transformer 层包含数十个小操作(加、乘、归一化、激活)时,累积的启动开销变得显著。
每个非融合内核还必须在 HBM 中实现其输出。考虑三个操作的序列:Y = LayerNorm(GELU(XW + b))。没有融合时,这需要三次通过 HBM:
-
GEMM 内核:从 HBM 读取 X 和 W,计算 XW + b,将结果 Z[1] 写入 HBM。
-
GELU 内核:从 HBM 读取 Z[1],计算 GELU(Z[1]),将 Z[2] 写入 HBM。
-
LayerNorm 内核:从 HBM 读取 Z[2],计算 LayerNorm(Z[2]),将 Y 写入 HBM。
中间张量 Z[1] 和 Z[2] 各占用与输出 Y 相同的内存。对于隐藏维度 4096、批大小 2048 的 FP16 场景,每个中间张量为 16.8 MB。非融合执行将实例化 33.6 MB 的中间张量并产生 67.1 MB 的中间 HBM 流量,先写入再重读每个张量。融合内核通过在 SM 内的寄存器或共享内存(SRAM)中保持 Z[1] 和 Z[2],完全避免了该流量。图 9.4 对比了这两条执行路径,直观展示了 HBM 流量节省。

图 9.4:算子融合:融合前与融合后:顶部:三个独立内核各自从 HBM 读取和写入,实例化中间量 Z[1] 和 Z[2],非融合路径共六次 HBM 往返。底部:单个融合内核读取输入一次,在 SRAM 中保持中间量,仅写入最终输出,仅留两次 HBM 往返。融合路径每层实现 HBM 流量约 3× 减少。
如 图 9.4 所具体展示,融合将每层 HBM 往返从六次减少到两次,该层块的片外内存流量大约减少 3×。在没有算子融合的朴素实现中,执行一个 Transformer 层大约需要 50 次独立的内核启动。如果每次启动产生 10 微秒开销,系统将纯粹在调度延迟上花费 500 微秒。如果该层实际算术执行仅需 2 毫秒,启动开销将消耗 20% 的总墙钟时间,导致 GPU 计算单元在推理周期的五分之一时间内处于空闲。这种“启动受限”机制限制了更快硬件的收益;将 GPU 的 FLOP/s 翻倍对减少 500 微秒的固定成本毫无作用。算子融合通过将这 50 个离散操作编译为少量融合内核(通常减少到 5–10 次启动)来解决此问题,从而夺回损失的周期,将工作负载从调度开销转移回屋顶模型捕获的硬件极限。
融合类别
当融合移除的 HBM 流量值得其引入的内核复杂度时,融合才是有利的。三种常见类别因该权衡而异:它们消除多少数据移动、需要多少同步,以及编译器能多频繁地自动应用它们。
逐元素融合处于低复杂度端:连续的逐元素操作(加法、乘法、激活函数)合并为单个内核。由于每个输出元素仅依赖于恰好一个输入元素,因此这种融合始终是合法且易于实现的。深度学习框架通常会针对支持的模式自动执行逐元素融合。
当序列包含归约操作时,融合决策变得更受限。归约融合将逐元素操作与后续的归约操作(如为损失函数求和元素,或为层归一化计算均值和方差)结合起来。归约操作要求内核内进行线程间通信,使用 Warp 级乱序指令或共享内存来跨线程聚合部分结果。尽管存在这种复杂性,但内存节省可观:归约前的中间张量永远不会在 HBM 中实例化。具体就层归一化而言,归约融合避免了将大型归一化前张量写入 HBM 并为均值/方差计算再次读回。
收益最高的情况是算子专用融合。这些是为特定操作序列设计的自定义内核,例如融合注意力或融合 GEMM-偏置-激活。内核架构师必须同时推理数据流、共享内存分配和线程调度。收益巨大:我们接下来将探讨的 FlashAttention,消除了二次注意力工作空间,并使在线 Softmax 状态随序列长度线性增长。
为了量化影响,考虑每一类应用于隐藏维度为 4096、批大小为 2048、FP16 精度的单个 Transformer 层。此形状下,偏置-GELU-Dropout 链的逐元素融合消除了两个各 16.8 MB 的中间张量,每层节省 67.1 MB 的 HBM 流量。跨越 80 层,每次前向传播可回收约 5.4 GB 的 HBM 流量。LayerNorm 的归约融合避免实例化大型归一化前张量和中间统计量。算子专用的注意力融合通过移除主导长上下文注意力的二次评分和概率矩阵,提供了最大的单一收益。所有三类融合的累积效应可以移除内存受限 Transformer 前向传播中很大一部分的 HBM 流量,但端到端加速仍需通过性能分析验证,因为瓶颈可能会转移。
CUDA Graphs:消除启动开销
降低铁律中开销项的一种正交技术是 CUDA Graphs¹⁴¹。算子融合将多个操作合并为更少的内核,而 CUDA Graphs 则消除了启动这些内核的 CPU 开销。
在标准 PyTorch 执行中,每次内核启动都需要 CPU 向 GPU 的命令队列推送一条指令。对于包含 30 多个内核的 Transformer 解码器层,这种 CPU 到 GPU 的往返(通常每次启动 5–10 μs)每层累计达 150–300 μs。对于 70 层模型,仅内核启动开销每次前向传播就贡献 10–20 ms,占内存受限推理总时间的显著比例。
CUDA Graphs 通过将一系列 GPU 操作(内核启动、内存拷贝)记录到可重放的图中来解决此问题。记录发生在预热阶段的一次性过程中。后续迭代中,重放图仅需一条 CPU 到 GPU 的指令即可分发整个记录序列,无论内核数量多少,将启动开销降低至约 5–10 μs 总计。
收益巨大:对于每层 30 多个内核、70 多层的模型,基线内核启动开销每次前向传播可超过 15 ms。CUDA Graphs 将其降至 0.1 ms 以下,回收的 15 ms 直接转化为更高的 Token 生成率。
约束在于 CUDA Graphs 要求确定性执行:操作序列、张量形状和内存地址在重放时必须完全相同。这与动态推理模式冲突,如变长序列、变化的批大小和条件计算(早退、MoE 路由)。实践中,CUDA Graphs 对 LLM 服务的解码阶段最有效,因为计算模式重复(每个 Token 相同操作),而对输入长度各异的预填充阶段用处较小。
算子融合(减少内核数量)与 CUDA Graphs(减少每内核开销)的结合,可以共同消除前向传播中几乎所有的非计算开销。当性能分析显示内核启动间隙占执行时间超过 10% 时,CUDA Graphs 应作为首选干预手段。
FlashAttention:分块注意力作为系统原语
标准自注意力计算 \(\text{Softmax}(QK^T / \sqrt{d_k})V\),其中 Q、K 和 V 是形状为 [序列长度 × 头维度] 的矩阵。朴素实现将完整的 S × S 注意力矩阵 A = QK^(T) 实例化在 HBM 中,其中 S 为序列长度。对于 S = 8192 和 FP16 精度,仅此矩阵每个注意力头就消耗 8192 × 8192 × 2 ≈ 134 MB,因此每层的二次评分张量高达数 GB。下面的标准核算针对 64 头 Llama 风格层精确计算了评分和概率流量,以及持久的 Q、K、V 和输出张量。
FlashAttention (Dao et al. 2022) 利用分块重新定义了注意力。它不实例化完整的 S × S 注意力矩阵,而是处理能放入片上 SRAM 的 Q、K、V 小块。算法加载 Q、K、V 的分块,计算部分注意力评分,并维护运行统计量(在线 Softmax)以产生精确结果,且从未将完整注意力矩阵存入 HBM。
FlashAttention 将注意力内存压缩约 65 倍。
实例化注意力状态的减少极为显著。对于序列长度 8192、64 头、头维度 128、FP16 精度的配置,朴素实现为整层实例化并重访约 34.9 GB 的 HBM 驻留张量,或每头 545.3 MB。FlashAttention 避免了二次评分和概率张量;其持久的 HBM 可见张量由 Q、K、V 和输出 Y 主导,整层总计约 536.9 MB,每头约 8.4 MB。此简化核算排除了特定内核内与调度相关的分块重载,但捕捉到了重要的缩放结果:该配置下 HBM 可见注意力状态减少 65 倍。
FlashAttention 背后的关键洞见是 在线 Softmax¹⁴² 技巧,它使分块成为可能,用于一个看似需要全局信息的操作。标准 Softmax 计算 softmax(s[i]) = e(*s*[*i*])/∑[*j*]*e*(s[j]),但为数值稳定性会先减去全局最大值:softmax(s[i]) = e^(s[i] − m)/∑[j]e^(s[j] − m),其中 m = max[j]s[j]。寻找这个全局最大值似乎需要先看到所有评分,这将强制实例化完整的 S × S 矩阵。
在线算法通过维护随每个分块处理增量更新的运行统计量来避免此问题。处理分块 t 时,算法执行四个步骤:
-
计算局部评分块 A[t] = Q[分块]K[t]^(T)。
-
更新运行最大值:m[新] = max(m[旧], max(A[t]))。
-
重缩放之前的运行求和和输出:乘以 e^(m[旧] − m[新]) 以修正更新后的最大值。
使用 m[new] 计算局部 Softmax 贡献并累加到正在运行的输出中。
处理完所有分块后,正在运行的输出包含的结果在数学上等同于标准算法的结果,仅存在所选精度的浮点舍入误差。重缩放步骤(步骤 3)是关键创新:它允许算法在新分块揭示出更大的最大值时“修正”之前的部分结果。该算法在数学意义上是精确的,但浮点执行顺序仍可能导致最后几位与未融合的实现相比发生变化。
这种分块的代价是额外的算术运算:步骤 3 中的重缩放操作增加了标准算法不执行的 FLOPs。由于该操作受内存带宽严重限制(标准注意力的算术强度一旦将其物化的分数和概率张量计入它们产生的 HBM 流量,就会降至约 1–10 FLOP/byte,低于表 9.4 中更高的与机制相关的数值),因此额外的计算是“免费”的,因为 GPU 的算术单元原本会在等待 HBM 数据传输时处于空闲状态。只要操作受内存带宽限制,用额外的计算换取更少的内存访问就是划算的,这是本章的核心原则。
实现上述物化状态减少量化的机制是分块。FlashAttention 以分块方式处理计算(通常在 H100 上为 128 × 128)。对于一个分块,算法加载一块 Q (128 × 128 × 2 ≈ 32.8 KB)、一块 K (128 × 128 × 2 ≈ 32.8 KB) 和一块 V (32.8 KB),总计约 98.3 KB。这完全适配 H100 每个 SM 228 KB 的共享内存。分块分数 A[tile] = Q[tile] K[tile]^(T) 完全在 SRAM 内计算和消耗;它永远不会被写入 HBM。算法对 64 个行分块中的每一个迭代 8192/128 = 64 个列分块,根据内核调度重新加载分块。关键在于,二次方 S × S 的分数和概率矩阵永远不会被物化:持久的每头张量是 Q、K、V、Y 和 𝒪(S) 的 softmax 统计量,而不是上述标准核算中计算的数百兆字节的二次中间结果。
FlashAttention-2 (Dao 2023) 通过重构并行模式,进一步针对拥有许多流式多处理器的 GPU 架构优化了算法。原始 FlashAttention 在批次和头维度上并行,意味着每个线程块处理一对 (batch, head) 并迭代完整序列。FlashAttention-2 还在查询矩阵的序列维度上并行,更高效地将工作分配给线程块并实现更好的占用率。它还通过重构重缩放操作并利用 Q 循环(外层)与 K/V 循环(内层)之间的不对称性,减少了非 GEMM FLOPs 的数量。
较新的 FlashAttention 系列内核针对 Hopper 时代的硬件特性,如 FP8 Tensor Core 和张量内存加速器 (TMA),后者是 HBM 与共享内存之间异步批量张量移动的硬件路径。系统原则与原始算法相同:随着硬件暴露出更快的移动路径和更低精度的执行路径,注意力调度必须重写以利用这些路径,同时不物化二次工作区。
最初的突破在于工程师理解瓶颈的方式发生了变化:注意力的约束在于内存层级,而非矩阵乘法。
背景
2022 年,Tri Dao 及其合著者提出了 FlashAttention (Dao et al. 2022),表明注意力机制的性能瓶颈可以被视为一个 IO 感知的数据移动问题,而不仅仅是一个矩阵乘法调优问题。
机制
瓶颈是内存层级,而非计算。标准注意力将巨大的 S × S 注意力分数矩阵物化在高延迟的 HBM 中。FlashAttention 利用分块重构算法,将运行时统计量保留在片上 SRAM 中,在从未将完整矩阵写入全局内存的情况下计算 softmax。这将内存复杂度降低至线性 𝒪(S),并在相关注意力工作负载上将墙钟时间缩短了 2–4 倍。
系统启示
FlashAttention 之所以重要,是因为它改变了注意力状态的存放位置。持久的优化不是“更快的内核”,而是一种数据移动减少,它将二次中间结果挡在 HBM 之外。
在转向缩放图之前,请暂停思考该机制:FlashAttention 以少量的额外算术运算为代价,消除了二次方的 HBM 驻留状态。
验证你对内存感知注意力的理解
上述 65× 的数字是规范的、完整层的物化状态减少量;它是要保留下来的数字。图 9.5 仅增加了缩放形状:因为标准注意力的工作区呈二次方增长,而 FlashAttention 的运行状态呈线性增长,所以优势随序列长度增加而扩大。该图的较大比率反映了更窄的核算边界,仅将二次方分数工作区与线性运行统计量进行比较,而非文本中完整的 HBM 可见张量集,因此它们不能直接与 65× 的数字相比较;要点是差距在扩大,而非其在特定长度下的确切值。

图 9.5:FlashAttention 内存节省:𝒪(S²) → 𝒪(S)。标准注意力在 HBM 中分配完整的 S × S 分数矩阵,而 FlashAttention 仅维护 𝒪(S) 的运行统计量。阴影区域代表为 KV 缓存、激活值或更大批次大小释放的内存。此仅工作区比较使用的边界比文本中完整层物化状态核算更窄;其重点是随着上下文增长,差距扩大的形状,而非某个标题式的比率。
FlashAttention 通过跨越 SRAM-HBM 边界进行分块,减少了单个 GPU 内的内存墙。对于超过单 GPU 内存容量的序列长度,同一分块原则通过 Ring Attention (Liu et al. 2023) 扩展至多 GPU。通过将序列块分布在加速器环上并重叠通信与计算,Ring Attention 实现了在单 GPU 配置上不切实际的上下文窗口。第 5.5.2.3 节 在更广泛的张量并行讨论中考察了 Ring Attention 的分布式机制。
精度工程
融合减少了穿越 HBM 的次数;精度工程减少了每次传输的负载。在 HBM 中移动 FP16 权重消耗的字节数是 FP8 的两倍。对于受带宽限制的内核,将权重从 2 字节缩减到 1 字节大致可将权重读取流量减半并提高有效内存带宽。工程决策在于:在何处可以容忍数值噪声以降低带宽压力,而在何处质量要求更高精度。虽然第 5.6 节 将 8 位浮点数 (FP8) 作为训练时基元进行探讨,但此处我们专注于实现大规模高效推理的量化技术。
分块量化
训练后量化 vs. 量化感知训练
第一个推理端的精度决策是在保护承载稀有但关键信号的通道的同时,如何压缩权重。训练后量化到 INT8 或 INT4 为推理带来了更大的带宽节省,但 LLMs 带来了一个独特的挑战:异常特征¹⁴³。Dettmers 等人(2022)发现,大型语言模型会发展出少量的隐藏维度(约占特征的 0.1%),其激活幅度比其余部分大大约 3–20×。应用均匀的逐张量 INT8 量化会裁剪这些异常值,破坏它们携带的信息,或者扩大量化范围以容纳它们,从而在大多数接近零的值上浪费精度。
分块量化
分块量化 是一种机器学习量化方案,它将权重张量划分为 G[block] 个元素的不重叠组,并计算每组的尺度 s[i] = (x[max, i] − x[min, i]) / (2^(b) − 1),从而独立地限制每组内的最坏情况量化误差。
-
意义:使用分块大小
G[block] = 64和b = 4位,权重从 16 位压缩到 4 位(内存减少 4×),而每个 FP16 尺度每个权重增加16/64 = 0.25位。这产生了每个权重 4.25 位的有效位宽:相对于 INT4 载荷 6.25% 的开销,或原始 FP16 权重大小的约 1.6%。 -
区别:与逐张量量化(在整个权重矩阵上应用单一尺度,并强制该尺度容纳异常值,代价是在大多数接近零的权重上浪费精度)不同,分块量化将异常值的损害限制在各个块内,防止单个极端值降低整个张量的量化保真度。
-
常见陷阱:一个常见的误解是分块大小是一个自由的超参数。较小的块(
G[block] = 32)减少量化误差,但与G[block] = 64相比,元数据开销翻倍。在G[block] = 16时,尺度每个权重增加 1 位:相对于 INT4 载荷 25% 的开销,或原始 FP16 权重大小的 6.25%。
部署的选择在于在哪里为异常值保护买单。每种广泛使用的方法都保护相同的敏感信息,但将成本转移到了服务管道的不同位置。
LLM.int8()
LLM.int8() 通过将每次矩阵乘法分解为两部分,在运行时承担异常值成本:一小部分异常维度在 FP16 中处理,其余维度在 INT8 中处理。系统在运行时识别异常维度(幅度超过阈值的维度,通常为 6.0),将它们路由到 FP16 GEMM,将剩余维度路由到 INT8 GEMM。结果被合并以产生最终输出。这为那些在均匀量化下会大幅退化的模型实现了近乎无损的 INT8 推理。
GPTQ
GPTQ(Frantar 等人 2023)通过使用二阶信息的权重量化,将成本转移到校准阶段。GPTQ 不再独立地量化每个权重,而是执行逐层重构传递,利用来自校准激活的近似海森矩阵逆来估计哪些量化误差最重要,然后在剩余的未量化权重中补偿这些误差。这为许多 Transformer 模型生成了精度损失很低的 INT4 权重表示。关键洞察是:一个权重中的量化误差有时可以通过同一层中相关权重来抵消。
AWQ
AWQ激活感知权重量化;Lin 等人([2023)] 通过观察到并非所有权重都同等重要,从而减轻了校准负担:连接到高激活通道的权重对模型输出贡献不成比例地大。AWQ 通过分析校准数据集上的激活幅度来识别这些关键权重,然后在均匀分组量化之前应用逐通道缩放来保护它们。这在避免 GPTQ 风格的海森重构的同时,实现了与基于重构的 PTQ 方法具有竞争力的 INT4 权重量化质量。
SmoothQuant
SmoothQuant(Xiao 等人 2023)在推理前将异常值负担从激活转移到权重上。SmoothQuant 不像 LLM.int8() 那样在运行时处理异常值,也不像 GPTQ、AWQ 那样通过权重优化处理,而是通过将量化难度从激活迁移到权重,在量化前平滑激活分布。关键观察是激活异常值是通道特定的:某些隐藏维度在所有 token 上持续产生大值。SmoothQuant 应用逐通道缩放变换,将激活除以平滑因子,并将相应的权重乘以相同因子。这种数学等价的变换以降低激活异常值幅度为代价,略微增加了权重幅度,使两个张量都更适合均匀 INT8 量化。结果是高效的 W8A8(权重 8 位,激活 8 位)量化,同时利用 INT8 Tensor Core 获得带宽和计算双重收益。
实际考量
实际选择取决于瓶颈资源和质量预算。LLM.int8() 通过混合精度分解在运行时处理异常值,但将压缩限制在 INT8。GPTQ 利用二阶信息实现激进的 INT4 权重压缩,但每个模型需要数小时的校准。AWQ 通过专注于激活感知缩放,在几分钟的校准内达到类似的 INT4 质量。SmoothQuant 通过预处理权重-激活对实现 W8A8 量化。对于仅权重量化的 LLM 服务,当校准时间很重要时,AWQ 通常很有吸引力;对于需要激活量化和 INT8 Tensor Core 的工作负载,SmoothQuant 是更相关的选择。
这些技术的选择还取决于部署目标。对于支持 Tensor Core 的 GPU 推理,GPTQ 和 AWQ 生成的 INT4 权重表示在 GEMM 计算期间被反量化为 FP16,使用 GPU 的 FP16 Tensor Core。对于 CPU 推理或边缘部署,INT8 表示(LLM.int8() 或静态逐通道 INT8 量化)可以直接利用整数算术单元,无需反量化开销。
分块量化的存储成本很小。每 128 个 INT8 权重(1024 位)存储一个 FP32 尺度(32 位),总模型尺寸仅增加 3%。这一小额开销使得分块量化能够隔离异常值的破坏性影响,为 99% 的正常权重保留有效动态范围,而无需承担高精度格式的带宽惩罚。在部署光谱的极端端,量化从一种优化变成了物理必要性。移动或联邦部署提供了这种限制情况:当设备只有千字节或兆字节的内存时,精度不再是在模型选定后的调节旋钮;它是可行性测试的一部分。
联邦 MobileNet 原型
对于原型 C(联邦 MobileNet),量化是生存的前提,而非优化。在只有 512 KB SRAM 的微控制器上,FP16 模型在物理上无法加载。二值神经网络(BNN)和 1-bit 量化将这一点推向极致,用单个比特(+1/-1)表示权重。虽然这牺牲了显著的准确性,但它相对于 FP16 将权重内存占用减少了 16×(相对于 FP32 减少 32×),并将每次操作的能耗降低了高达 100×,从而在靠采集能量(微瓦级)运行的设备上实现了智能。
训练后量化 vs. 量化感知训练
当训练后配方未达到质量预算时,精度决策将从校准转向训练成本。训练后量化 (PTQ) 与 量化感知训练 (QAT) 之间的权衡聚焦于工程灵活性与模型保真度之间的平衡。对于类似 Llama-2-70B 的模型,PTQ 是即时部署的常见首选方案。诸如 GPTQ 或 AWQ 之类的技术使用小规模校准数据集(通常为 128–1024 个样本)逐层处理模型,以最小化重构误差。此过程计算成本较低,通常只需数小时,而非 70B 模型的分布式训练运行。虽然 PTQ 在 INT8 下通常表现稳健,但激进的量化到 INT4 或 INT3 可能导致可见的性能损失:困惑度可能下降,而诸如大规模多任务语言理解 (MMLU) 之类的推理基准测试可能下降若干个百分点,如果量化配方未能保护敏感层和异常通道。
当 PTQ 未能满足质量阈值时,QAT 通过将量化噪声直接融入训练循环提供补救方案。通过在前向传播过程中模拟低精度舍入,并在反向传播过程中通过 直通估计器¹⁴⁴ (STE) 近似梯度,网络学会调整其权重以对量化具有鲁棒性。
成本巨大:QAT 实际上是一次完整的微调运行,通常需要数百个 GPU 小时和一个分布式训练集群。对于 70B 模型而言,这可能意味着需要进行多天多 GPU 的作业,而不是数小时规模的 PTQ 校准运行。基于适配器的量化微调通过冻结低精度基础模型仅更新小型低秩适配器矩阵(而非所有模型权重)来缩小差距。这种混合方法提供了部分 QAT 的质量恢复能力,且内存占用小到足以在更小的硬件上运行,但它是任务适应性补救方案,而非全量化感知重训练的通用替代方案。
在许多部署环境中,实用的工作流程遵循两阶段方法:在 PTQ 满足质量要求时优先部署;若 PTQ 模型在目标精度下失败,则应用 QAT 或基于适配器的量化微调。此序列在保留在需要时获得更高质量的选项的同时,最小化工程工作量。
仅权重 vs. 权重-激活量化
最终的精度选择在于优化应停止于权重层面,还是也包括激活。仅权重量化 (GPTQ, AWQ) 将权重精度降低至 INT4 或 INT3,同时保持激活在 FP16。在 GEMM 过程中,INT4 权重会实时反量化为 FP16,随后计算使用 FP16 Tensor Cores 进行。其好处在于减少权重存储的内存占用并降低权重读取的 HBM 带宽,但 GEMM 本身仍以 FP16 精度运行。此方法非常适合内存受限的推理(批大小为 1 的解码),此时瓶颈在于从 HBM 读取权重。
权重-激活量化 (SmoothQuant, FP8 训练) 将权重和激活均降低至更低精度,使 GEMM 能够使用低精度算术执行(INT8 Tensor Cores、FP8 Tensor Cores)。这同时带来了带宽和计算收益,但在未出现质量下降的情况下实现起来更具挑战性,因为激活分布比权重分布更具动态性且更难量化。
选择取决于运行模式。对于内存受限的推理(小批大小),仅权重 INT4 量化通常能在单位质量损失下提供最大的加速比。对于计算受限的推理(大批大小)或训练,权重-激活 FP8 量化能提供仅权重量化无法匹配的吞吐量提升。高性能服务系统通常根据不同运行点采用不同的量化策略:在小批大小时使用 INT4 仅权重量化(以降低延迟),在大批大小时使用 FP8 权重-激活量化(以提升吞吐量)。
同样的精度问题也适用于键值(KV)缓存,它决定了权重节省是否能转化为更大的批大小。在解码过程中,权重可能被激进压缩,而每个请求的缓存状态仍随序列长度增长,因此一种使缓存保持不变的精度变化可能无法移动服务瓶颈。数值压缩减少了每个缓存键或值所存储的字节数,而分组查询注意力 (GQA) 减少了每层必须缓存的键值头数量。这两种机制都很重要,因为调度器根据权重加激活缓存状态的组合内存占用来接收请求。下面的计算隔离了局部容量效果,在第 10 章 中将回归完整的服务策略讨论。
容量计算使精度选择对服务的影响具体化。
较低精度释放内存,扩大可行批大小。
问题:一个 70B 参数模型在 8× H100 GPU 上进行服务。模型权重在 FP16 下消耗 140 GB(每 GPU 17.5 GB)。FP16 下的 KV 缓存每请求消耗 1.34 GB。将权重量化为 INT4 及 KV 缓存量化为 INT8 如何影响最大批大小?
优化前(全部为 FP16):
-
权重:每 GPU 17.5 GB
-
可用于
KV缓存:80 GB - 17.5 GB = 62.5 GB/GPU -
每请求
KV缓存:总计 1.34 GB,约合 0.17 GB/GPU -
最大批大小:约 372 个请求
优化后(INT4 权重,INT8 KV 缓存):
-
权重:17.5 GB × (4/16) = 4.4 GB/GPU(
INT4) -
可用于
KV缓存:80 GB - 4.4 GB = 75.6 GB/GPU -
KV缓存每请求(INT8):总计约 0.67 GB,约合 0.08 GB/GPU -
最大批大小:约 901 个请求
系统洞察:精度工程通过启用更大批大小改变了服务经济性。更大的批大小摊薄了权重加载的固定成本,使操作从内存受限转向计算受限。此单次优化可使吞吐量提升 2.4× 或更多。
精度工程降低了每次内存事务的字节数。算子融合减少了事务次数。两者共同从互补方向攻击同一根本瓶颈:当数据必须穿越慢速总线时,传输更少的数据(精度)以及更少的传输次数(融合)。这两种技术的乘法交互解释了为什么高性能服务栈常同时部署两者:FlashAttention 消除了二次注意力中间量,而 INT8 KV 缓存压缩进一步减半了剩余的 KV-缓存字节。联合效果超过任一方技术单独所能达到的效果。由于这些融合和精度模式在稳定的层结构中反复出现,下一个问题是:编译器何时能够在整个模型图上掌握这些变换。
图编译
在精度与融合揭示可重复优化模式之后,图编译询问这些模式是否足够稳定,以至于编译器能够掌握它们。为巨型神经网络的每一种可能层组合手动编写融合 CUDA 内核对人类工程师而言是 Sisyphean(注:指艰巨且 seemingly 无尽的任务)工程。图编译器分析模型的计算图,并在形状稳定性和重放体积足以证明编译成本时,将高级 PyTorch 代码转换为硬件感知的专用机器指令,生成优化后的内核。
编译的更深层回报在于空间维度,而非仅仅是算术层面。像 XLA 或 TensorRT 这样的编译器会重塑计算,以适配目标加速器的物理布局:将矩阵乘法分块以匹配脉动阵列的维度,放置中间结果以契合内存层级,将逐元素工作路由至向量 ALU,同时将脉动阵列保留给 GEMM(通用矩阵乘法)。这正是使大规模异构执行变得可行的关键。同一个模型图通过改变映射而非数学本身,即可映射到不同的硅片上。
编译流水线
图编译器通过多阶段流水线,将高层模型定义(Python 代码)转换为优化的硬件指令。为直观展示这一过程,以一个标准的 Transformer FFN(前馈网络)模块为例,它包含一个投影、一个激活函数、第二个投影和一个层归一化:LayerNorm(Linear(GELU(Linear(x))))。在标准的 PyTorch 急切执行模式下,该序列会触发四次独立的内核启动,每次都从 HBM(高带宽内存)读取并写回。
在 图捕获 阶段,编译器通过追踪模型执行来构建计算图,这是一个有向无环图,其中节点代表操作,边代表张量依赖关系。对于 FFN 模块,这会生成一个包含四个主要节点及其关联参数张量的图。动态的 Python 控制流(循环、条件判断)必须通过追踪一条代表性执行路径,或使用编译器特定的注解来标记动态维度来处理。
在 图层面优化 阶段,编译器应用代数简化和操作重写。它识别出第一个 Linear 层中的偏置加法可以融合进矩阵乘法内核。它还识别出 GELU 激活是一个仅依赖于第一个 Linear 输出的逐元素操作。这些标准编译器优化在任何硬件特定工作开始前,就能将图规模缩减 10%-30%。
算子融合通道是性能优化中最关键的一环。它识别可合并为单个内核的操作序列,以减少内存流量。对于 FFN 模块,编译器将 GELU 激活融合进第一个 Linear 内核的尾部(若支持作为尾部操作),或将 GELU 和后续的 LayerNorm 融合为单个内核。融合内核将数据保留在 GPU 的 SRAM 或寄存器中,而非将第一个 Linear 的中间结果写入 HBM 再读回供 GELU 使用。这通常能减少 30%-50% 的 HBM 访问,直接缓解内存带宽瓶颈。
内存规划通道决定张量何时分配与释放。未经优化时,Transformer 可能会为每个中间激活分配单独的缓冲区。编译器分析张量生命周期,识别出第一个 Linear 的输入在第二个 Linear 计算出输出后不再需要,从而复用同一物理内存地址。对于推理及其他纯前向工作负载,这种缓冲区复用可将峰值临时内存从众多层局部缓冲区之和降为最大活跃工作集。必须为反向传播保存的训练激活需要检查点或重物化技术;普通的缓冲区复用无法消除这些已保存的张量。内存规划也与算子融合交互:融合两个操作会消除它们之间的中间张量,这既移除了 HBM 流量,也移除了内存分配。编译器必须联合推理这两种效应以做出有利决策。
内核选择通道将每个融合操作映射到特定的机器码实现。对于计算密集型的线性投影,编译器选择厂商优化的 cuBLAS 或 CUTLASS GEMM 内核。对于融合的 GELU-LayerNorm 序列,它生成一个保持数据在 SRAM 中的自定义 Triton 内核。FFN 模块的最终结果是从 4 个独立内核减少到 2 个高度优化的内核,全局内存流量相应减少。
torch.compile
对于已用 PyTorch 编写的工作负载,torch.compile 是侵入性最小的编译器干预:它尝试捕获足够的静态图以融合内存受限区域,同时保留 PyTorch 的动态 Python 执行模型。它通过三个组件运作:TorchDynamo 用于图捕获,TorchInductor 用于代码生成,以及 AOTAutograd 用于训练工作负载需要编译反向传播时的提前构建反向图。
TorchDynamo 在 Python 字节码层面运作¹⁴⁵,这一设计选择将其与早期的追踪方法区分开来。早期的追踪方法(torch.jit.trace、torch.fx)在 Python 源码或抽象语法树(AST)层面运作,要求用户避免不支持的 Python 结构。TorchDynamo 直接拦截字节码解释器,在不要求用户修改模型代码的情况下捕获计算图。当 TorchDynamo 遇到无法追踪的 Python 结构(数据依赖的控制流、不支持的操作)时,会插入 图中断,将追踪拆分为多个独立编译的子图。目标是在优雅处理动态 Python 行为的同时,捕获尽可能大的子图。
TorchInductor 从捕获的图生成优化的 Triton 内核(用于 GPU)或 C++/OpenMP 代码(用于 CPU)。Triton 是一种用于编写 GPU 内核的领域特定语言,采用类 Python 语法,抽象了线程块管理和内存合并,同时仍暴露分块和融合决策。TorchInductor 自动融合逐元素操作,通过合并共享输入的操作减少内存流量,并通过自动调优选择分块大小。
一个最小示例说明用法:
import torch
def model(x):
return torch.nn.functional.layer_norm(
torch.nn.functional.gelu(torch.nn.functional.linear(x, weight1, bias1)),
normalized_shape, weight2, bias2
)
compiled_model = torch.compile(model)
在该示例中,torch.compile 会将 GELU 激活与周围操作融合,将层归一化的均值/方差/归一化步骤合并为单个内核,并可能将偏置加法与前序 GEMM 融合。模型代码保持标准 PyTorch 风格;编译器负责优化。
XLA 与 TPU 优化
XLA 以静态结构换取性能。作为 JAX 和 TensorFlow 的后端,它生成面向多个后端(包括 Google 张量处理单元 TPU、NVIDIA GPU 和 CPU)的高层优化器(HLO)中间表示。与 TorchInductor 生成面向 NVIDIA GPU 的 Triton 代码且保留更多 Python 灵活性不同,XLA 强制 全程序编译,将整个计算追踪为单个静态图,从而实现跨层甚至跨前向和反向传播的全局优化。
全局视角赋予了 XLA 最独特的能力:面向 ML 计算图的通用且可扩展的并行化,即 GSPMD,其中 SPMD 代表单程序多数据执行模型,每个设备在不同数据分片上运行相同程序。在分布式训练中,GSPMD 基于少量高层用户注解,自动将计算图跨数千个 TPU 核心进行分区。PyTorch 用户需手动用 DistributedDataParallel 或 FullyShardedDataParallel 封装模型,而 XLA 用户只需定义单设备计算,编译器便推断必要的通信原语并将其插入图中。这使得难以手动实现的复杂混合分片策略成为可能。
TPU 硬件优化
针对 TPU 硬件,XLA 会执行其他平台无法实现的布局优化。它将矩阵乘法映射到 TPU 的脉动阵列架构上,对维度进行填充以与 128 × 128 硬件单元对齐,并对指令进行调度以隐藏 HBM 读取的延迟。这些优化的影响体现在 MFU 指标中。在大规模 LLM 训练负载上,高度调优的 JAX/XLA TPU 运行报告的 MFU 在 55–65% 范围内,而调优较少的 PyTorch/GPU 设置可能更低。
XLA 性能的代价是编译延迟和刚性。因为 XLA 必须分析完整的静态图,大模型的初始编译可能需要数分钟,而 torch.compile 仅需数秒。输入形状的任何变更都会触发完全重新编译。这使得 XLA 非常适合图静态且模型运行数天或数周的稳态生产负载,但对于涉及动态形状或快速实验迭代的研究环境则具有挑战性。在 torch.compile 和 XLA 之间的选择通常遵循硬件和软件栈:NVIDIA GPU 工作流通常起步于 PyTorch,而 TPU 工作流通常使用搭配 XLA 的 JAX 或 TensorFlow。
TensorRT:推理优化
NVIDIA TensorRT 是一种专用的推理编译器,它不将模型视为 flexible program(灵活的程序),而是视为 rigid global optimization problem(刚性的全局优化问题)。因为推理不需要反向传播和梯度存储,TensorRT 应用了在训练中在数学上无效或实现起来不切实际的激进变换。它执行一个校准过程,在代表性数据上运行模型以确定每个激活张量的数值范围。
此校准在粒度层面启用了混合精度量化。盲目地将 700 亿参数 LLM 的所有层量化为 INT8 通常会损害困惑度。TensorRT 可以从代表性数据校准 INT8 范围,并在启用或约束精度时构建混合精度推理引擎;对于 LLMs,逐层回退策略通常需要显式的量化分析或用户指定的精度约束。TensorRT 还消除了仅用于训练的操作(Dropout、批量归一化运行统计更新),通过生成针对精确维度调优的内核来针对静态形状进行优化,并由于不需要梯度张量而精确规划内存。
TensorRT 执行的内核自动调优远超简单的启发式方法。对于图中的每个操作,它在实际目标硬件上对数十个候选内核进行基准测试,变化瓦片大小、线程块配置和展开因子。它为该特定 GPU 和输入形状选择最快的实现。TensorRT 与通用编译器之间的性能差距可能非常巨大,特别是对于稳定的推理负载,引擎可以针对固定形状范围和 GPU 架构进行激进专用化。
TensorRT 激进优化的代价是灵活性降低和编译成本高昂。编译时间以分钟到小时计(700 亿参数模型需 30–60 分钟),且生成的引擎严格绑定到特定的 GPU 架构和输入形状范围。更改其中任何一项都需要重新编译。这使得 TensorRT 适用于稳定、大吞吐量的生产部署,而 torch.compile 通常更适合开发和低吞吐量服务,在这些场景中快速迭代比榨取最后百分比的吞吐量更重要。
编译开销与权衡
图编译并非免费。编译过程本身耗时不等,从使用 torch.compile 编译小模型的几秒钟,到使用 TensorRT 完整优化管线编译大模型的几分钟甚至几小时。这笔开销必须分摊到编译模型的执行次数上。
对于运行数小时或数天的训练负载,编译开销可忽略不计。对于服务数百万请求的推理负载,一次性编译成本同样可分摊。棘手的情况是动态或低频负载:一个模型编译一次却仅服务几百个请求就被新版本替换,可能无法收回编译成本。
图中断是 torch.compile 特有的相关挑战。当 TorchDynamo 遇到无法追踪的 Python 代码(数据依赖的控制流、对未编译库的调用、迭代间变化的动态张量形状)时,会插入图中断。每次中断都会生成一个单独的编译子图,带来各自的编译开销和潜在的优化边界。一个有 50 个图中断的模型会产生 50 多个微小的编译区域,每个区域可能都太小而无法进行有意义的融合。减少图中断需要重构模型代码使其更“编译友好”,用张量操作替代 Python 控制流,并在可能的情况下确保静态形状。
动态形状在编译与灵活性之间产生了根本张力。为输入形状 [batch=32, seq=512] 编译的模型在遇到 [batch=16, seq=1024] 时会重新编译。TorchInductor 通过生成带有符号维度的内核支持“动态形状”,但这种通用性以降低优化程度为代价,不如针对精确形状专用化的内核。TensorRT 通过要求用户在编译时指定输入形状范围来规避此问题,生成仅处理指定范围的内核。
尽管存在这些限制,图编译仍然是最易上手的优化技术之一:它不需要修改模型、编写自定义内核,且仅需极少的代码更改。对于许多具有启动开销或可融合逐元素区域的稳定形状 PyTorch 工作负载,单次 torch.compile 调用即可提供 10–40% 的加速,使其成为在考虑更专业技术之前理想的早期优化尝试。
图编译重塑了性能工程工作流。对于符合编译器假设的负载,单行代码即可捕获曾经需要手动内核优化才能获得的显著性能提升部分,释放工程师专注于编译器无法自动化的算法和架构优化,如投机解码、MoE 和精度工程。编译器处理常规图变换;工程师处理特定负载的设计选择。随着编译器覆盖率的提高,本章中高层系统设计技能的价值相对于常规的低层内核调优将变得更高。
编译模式与后端
torch.compile 模式的选择是一项分摊决策:仅当负载会重放稳定形状足够多次以收回成本时,才花费更多编译时间。default(默认)模式是开发基准,应用标准优化,如逐元素融合、内存规划和预调优内核选择,编译时间适合迭代。reduce-overhead(减少开销)模式是针对启动受限场景的选择,增加 CUDA Graphs(详见第 9.3.3 节)以消除内核启动开销,适用于小模型在调度上花费有意义时间比例的情况。max-autotune(最大自动调优)模式是生产专用化选择,对每个操作对多种瓦片大小、线程块配置和内存访问模式进行基准测试以选择最快内核;10–30 分钟的编译成本仅在稳定部署将重复使用优化图许多次时才合理。
后端选择遵循相同约束:工作负载越稳定且针对部署越具体,运行时就能越专用。TorchInductor(默认)为 GPU 生成 Triton 内核,为 CPU 生成 C++ 代码。对于部署专用的优化,模型可通过 torch.export 导出为中间表达形式,供 TensorRT、开放神经网络交换(ONNX)Runtime 或其他推理专用运行时使用。每个后端都在通用图级变换之上应用自己的优化遍。
Triton 语言
在手写 CUDA 与全自动图编译器之间,处于中间地带的是 Triton¹⁴⁶,一种基于 Python 的 GPU 内核编写语言。Triton 占据了一个中间立场:程序员指定算法(分块策略、融合模式),而 Triton 处理底层细节(线程块调度、内存合并、共享内存管理)。
一个用于融合 GELU 激活的 Triton 内核展示了编程模型:
程序员以块(BLOCK_SIZE 个元素)而非单个线程来思考。Triton 将其编译为并行线程执行(PTX)/SASS 指令,自动处理线程到数据的映射、内存合并和寄存器分配。这使得 ML 工程师(而非 GPU 专家)在自动编译器错过融合机会时,也能编写自定义融合内核。
Triton 的真正威力在于融合多个操作时显现。一个实现 y = LayerNorm(GELU(x)) 的 Triton 内核从 HBM 中加载 x 一次,在寄存器/共享内存中同时计算 GELU 和 LayerNorm,并将 y 写入 HBM 一次。若无融合,该序列需要三次 HBM 往返:读取 x,写入 GELU(x),读取 GELU(x),写入 norm_input,读取 norm_input,写入 y。融合内核将 HBM 流量减少 3 倍,对于内存受限操作,这直接转化为 3 倍加速。
性能工程的后果是,Triton 将许多自定义融合机会转化为编译器输出。当 torch.compile 识别出需要自定义内核的融合机会时,TorchInductor 会自动为融合操作生成 Triton 代码。本节所述的许多融合收益因此只需一次 torch.compile 调用即可实现,无需手写 Triton 代码。对于编译器启发式策略遗漏瓶颈的高级情况,手写 Triton 内核在 PyTorch 的易用性与手写调优 CUDA 的性能之间提供了一个中间地带。
一个简单的性能分析分解展示了编译器优化如何将开销转化为有效吞吐量。
问题:一个 13B 参数模型被部署用于推理。未经编译时,PyTorch 急切模式在单张 H100 上以 120 tokens/s 的速度处理。Nsight Systems 跟踪显示,35% 的步耗时花在逐元素内核(LayerNorm、GELU、残差加法)上,15% 花在内核启动开销上。应用带有 max-autotune 后端的 torch.compile,估算新的吞吐量。
数学计算:
步骤 1:确定可优化开销。逐元素内核(35%)和启动开销(15%)合计占执行时间的 50%。torch.compile 融合逐元素操作(因消除 HBM 往返,其耗时减少约 70%)并减少内核启动(消除大部分启动开销)。
步骤 2:估算编译后耗时。原始每 token 耗时:1/120 = 8.33 ms。
-
GEMM 耗时(不变):8.33 × 0.50 = 4.17 ms
-
逐元素耗时(减少 70%):8.33 × 0.35 × 0.30 = 0.87 ms
-
启动开销(减少 80%):8.33 × 0.15 × 0.20 = 0.25 ms
新每 token 耗时:4.17 + 0.87 + 0.25 = 5.29 ms,产生约 189 tokens/s。
系统洞察:torch.compile 通过融合逐元素操作和减少启动开销,在不修改 GEMM 内核的情况下实现了 1.58 倍加速。剩余瓶颈现为 GEMM 本身(占步耗时的 79%),表明进一步改进需要降低精度或增加批处理。
图编译将手动内核工程针对单个操作所实现的优化自动化,并系统地应用于整个模型图。它也揭示了一个边界:编译器能重组已知操作,却无法发明不同的计算。下一节将转向跨越该边界的优化。
算法性能变换
前几节通过移动更少字节、使用更窄格式或让编译器重组内核,优化了同一密集计算。算法变换则改变工作本身。投机解码试图让目标模型每次前向传递接受多个 token,从而攻克内存受限的解码循环;MoE 则通过仅激活 token 所需的专家来攻克密集模型成本。这两种技术均可改善铁律预算,但若其额外的控制逻辑、通信或不平衡超过了所移除的工作量,也会制造新瓶颈。
投机解码
投机解码将解码推向计算受限脊线。
投机解码¹⁴⁷ 是一种延迟优化,它通过使用一个较小的草稿模型来预测多个 token,随后由目标模型并行验证,从而打破自回归生成的顺序瓶颈(Leviathan et al. 2023;Chen et al. 2023)。其性能工程相关性在于算术强度:单次目标模型前向传递可处理 K 个候选 token,权重流式加载成本大致相当于处理一个 token,将解码工作点从内存受限斜坡移向屋顶线脊线。
图 9.6 对比了两条解码路径:标准解码每次顺序目标模型传递仅推进一个 token,而投机解码让草稿模型提出一块 K 个 token,目标模型在一次并行传递中验证它们,将接受的前缀一起输出,仅在首次拒绝处重新采样。
该变换仅当草稿模型足够准确且足够廉价时才有用。若草稿 token 被大量拒绝,目标模型仍加载权重却接受极少有效工作。若草稿模型过于昂贵,验证将变为计算受限,抵消带宽收益。批处理也会改变计算:在大批量下解码阶段已更接近计算受限,投机的边际收益随之缩减。本章用投机解码展示算法如何改变性能方程;第 10.4.6 节将其视为一种具有准入控制和 SLA 后果的服务策略。
图 9.6:投机解码过程:不再用大目标模型顺序生成 token,而是由小草稿模型提出 K 个 token 的序列。目标模型在一次并行前向传递中验证所有 K 个 token。通过接受测试的草稿 token 被一起输出;首个被拒绝的 token 触发修正采样并丢弃剩余草稿。
投机解码
算法 4 使接受测试变得精确:在单次目标模型前向传播后,每个草稿 token 以概率 min(1, p[θ]/q[ϕ]) 被接受,拒绝则从归一化的正残差 (p[θ] − q[ϕ])[+] 中重采样,因此输出序列的分布完全等同于目标模型单独生成时的分布。这种正确性保证使得投机解码能够以草稿模型的算力换取延迟降低,而不改变输出分布。
算法 4:投机解码
输入: 目标分布 pθ;草稿分布 qϕ;前缀 y[1 : t];草稿长度 K
输出: 一个或多个按 p[θ] 分布的新 token
-
for
i = 1toKdo -
从
qϕ中采样草稿ŷ[t + i] -
end for
-
在前缀加所有
K个草稿上运行一次p[θ],获得草稿位置及最终下一个 token 位置的目标分布 -
for
i = 1toKdo -
a_i ← min(1, p_θ(ŷ_{t+i} ∣ y_{1:t}, ŷ_{t+1:t+i-1}) / q_ϕ(ŷ_{t+i} ∣ y_{1:t}, ŷ_{t+1:t+i-1})) -
以概率
a[i]接受ŷ[t + i] -
if
ŷ[t + i]被拒绝 then -
从归一化的正残差
(p[θ] − q[ϕ])[+]中采样一个修正 token -
输出已接受的草稿 token 和修正 token;丢弃剩余草稿;开始新一轮
-
end if
-
end for
-
若所有
K个草稿均被接受,则输出它们并从目标分布中额外采样一个 token
当草稿模型开销较小,且其提出的 token 足够频繁地与目标模型一致,使得一次目标模型前向传播能验证多个 token 时,投机解码便能获益。限制性成本在于拒绝率:被拒绝的草稿浪费了草稿模型的工作量并缩短了接受序列,因此服务系统必须联合调优草稿长度和草稿模型质量,而非将投机视为免费的并行度。
专家混合
万亿参数的稠密模型提供卓越的推理能力,但其延迟和算力成本超出了大多数服务预算的承受范围。专家混合(MoE)架构通过训练一个巨大模型,却仅针对任何给定 token 激活其中一小部分相关参数,从而化解了这一矛盾。通过将输入路由到专门的子网络,MoE 打破了模型规模与推理成本之间的刚性联系。
对性能工程而言,局部问题在于活跃参数量的节省是否超过了路由和通信的开销。只有当非活跃专家不在关键路径上,且路由器避免制造新的 AllToAll 或负载不均瓶颈时,稀疏激活才能改善“铁律”预算。
标准的 MoE Transformer 层用多个并行的“专家”前馈网络(FFN)和一个轻量级路由器(也称门控网络)取代了前馈网络,由路由器选择哪些专家处理每个 token。该架构将性能问题从稠密矩阵吞吐转移到了路由、内存驻留和负载均衡上。当专家分布在多 GPU 上时,专家并行将不同专家放置在不同设备上,AllToAll 交换在返回结果前将每个 token 的隐藏状态移动到其指定专家。仅当该路由流量及任何专家不均衡小于其所替代的稠密计算量时,活跃参数节省才具有价值。
通信-计算重叠
在通过融合、精度、编译、投机或稀疏专家路由改变局部工作后,“铁律”中的下一个暴露项往往是通信。第 6.8 节 确立了重叠作为一种技术,当有足够的有用计算来掩盖通信时,它能将通信从关键路径中移除;本节提供了那些章节所假设的单节点机制,即实际在单个加速器上并发运行计算和通信的 CUDA 流、SM 分区和桶钩子。
考虑一个具体例子:在 8 张 H100 GPU 上采用张量并行的 70B 模型。每个 Transformer 层需要两次 AllReduce 操作(注意力后一次,FFN 后一次)。每次 AllReduce 在 FP16 下传输约 2 × d[model] × B × 2 字节(70B 模型中 d[model] = 8192,B 为批大小)。批大小为 1 时,数据量为 2 × 8192 × 1 × 2 = 32.8 KB。在 900 GB/s NVLink 带宽下,传输耗时约 36.4 ns。然而,AllReduce 启动开销(约 5 μs)以 100 倍之多主导了实际数据传输时间。批大小为 1 时,AllReduce 开销主要由软件启动延迟而非带宽决定,重叠收益有限,因为缺乏足够计算来掩盖其后。
批大小为 64 时,每次 AllReduce 数据量增长至 2 × 8192 × 64 × 2 = 2.1 MB,在 NVLink 带宽下耗时约 2.3 μs。此时分片上的计算路径是一组内核而非单个 GEMM:该配置下注意力和 FFN 工作总耗时约 20 μs,而通信仍约 2.3 μs。计算时间超过通信时间约 9 倍,重叠变得高度有效。这说明为何批大小是通用控制旋钮:它同时提升算术强度、GPU 利用率和通信重叠效果。
这种逐层张量并行示例是重叠问题的小载荷端。训练梯度同步使用相同的暴露时间测试,但载荷大得多,因此梯度重叠计算从激活交换转变为梯度张量上的环形 AllReduce。
量化重叠机会
通信-计算重叠的潜在收益取决于通信与计算时间的相对大小,这在不同系统配置间差异巨大。
考虑在单节点内由 NVLink(双向 450 GB/s)连接的 8 张 H100 GPU 上训练的 7B 参数模型。梯度张量包含 16.1 GB FP16 数值。根据 第 6.4.3 节 的环形 AllReduce 分析,同步传输 2 × (N − 1)/N = 1.75× 倍梯度体量,在 NVLink 带宽下耗时约 62.5 ms。在 40% 模型 FLOPs 利用率(MFU,即用于有效模型 FLOPs 的峰值吞吐率分数,定义见 第 5.4 节)下,反向传播计算耗时约 166.3 ms。
无重叠时,训练步骤需 311.9 ms(前向 + 反向 + AllReduce)。有重叠时,AllReduce 完全隐藏在反向传播之后,步骤时间降至 249.4 ms,提升 1.25×。此例阐释了一个关键特性:当反向计算时间超过 AllReduce 时间时,重叠最有效。正如 图 9.7 所示,随着集群规模从 8 增至 1024 GPU,重叠效率从约 90% 降至约 18%,因为通信开销最终超过了计算预算。图中每个柱归一化为固定的 100 单位计算预算,柱高在 x 轴上约增长五倍,因为堆叠在预算之上的暴露通信片段正是重叠无法再隐藏的通信部分。对于更小的模型或更慢的互联(例如 PCIe 64 GB/s 而非 NVLink 900 GB/s),AllReduce 将超过反向传播,无论重叠多少都无法完全隐藏通信。

图 9.7:重叠预算:随着集群规模增加(从 8 到 1024 个 GPU),通信开销增长,最终超过计算预算。重叠效率(黑线)表示被计算隐藏的通信百分比,由于 70B 参数模型在节点间延迟和带宽限制下,该效率从大约 90% 下降到大约 18%。
CUDA 流和异步执行
在 NVIDIA GPU 上实现重叠的机制是 CUDA 流。CUDA 流是一个有序的 GPU 操作序列(内核启动、内存拷贝、NCCL 集合操作),这些操作在流内部按顺序执行,但可以与其他流中的操作并发执行。应用程序维护两个不同的流:一个用于矩阵乘法和逐元素内核的计算流,以及一个用于 NCCL 操作的通信流。工作流程是在第一个流上启动一个计算内核,并在第二个流上立即触发一个异步通信调用:
GPU 硬件调度器交错两个流的执行单元,在 SMs 上运行 GEMM,同时 NVLink 引擎处理 AllReduce 数据传输。然而,虽然流提供了逻辑并发性,但它们会争夺物理资源。SM 必须管理通信内核的数据移动指令。在拥有 132 个 SM 的 H100 上,一个繁重的 NCCL 操作可能仅用于协议处理和内存拷贝而占用 4–8 个 SM,导致 SM 分区:在通信期间,可用的计算吞吐量降低 3–6%。如果计算内核足够密集以饱和 100% 的 SM,启用重叠可能由于此资源争用而悖论地减慢执行速度,这种现象称为干扰。实际上,3–6% 的计算吞吐量降低远小于否则会暴露的通信时间,使得这种权衡极其有利。
实际上,实现有效的重叠需要关注以下几个细节。通信操作必须足够早启动,以便与后续的计算重叠;在反向传播过程中,DDP 会在每个 bucket 准备就绪时启动其 AllReduce,这样 NCCL 启动延迟就会被后续的梯度内核所隐藏。同步点(一个流等待另一个流的位置)必须最小化,因为每个同步点都会序列化执行。
PyTorch 的分布式数据并行(DDP)模块通过在每个参数上注册反向钩子来实现梯度重叠。当在反向传播过程中计算出一个参数的梯度时,该钩子会在单独的 NCCL 流上触发一个异步的 AllReduce。DDP 在反向传播结束时将此通信流与主计算流同步,以确保每个梯度缩减在优化器步骤读取梯度之前完成。此设计会自动将梯度通信与梯度计算重叠,而无需用户干预。
当工程师为自定义重叠模式创建自己的侧流时,一套 PyTorch 特有的“坑”经常导致无声的正确性或性能失败。PyTorch 的默认行为是所有操作都排队到默认流上。在默认流上生成的张量并在侧流上消费的张量不是自动安全的:如果没有它们之间的显式 event.record() / event.wait() 障碍,GPU 可能会在张量完全写入之前就开始消费它。类似地,未使用 non_blocking=True 启动的内存拷贝会插入隐式的同步点,序列化工程师试图重叠的流,通常会完全消除重叠的好处。DDP 通过在其 bucket 钩子中管理 record_event() 调用来避免这些隐患,确保每个 AllReduce 仅在对应的梯度计算事件完成后才开始。手动编写的重叠代码必须复制这种纪律,否则可能会在框架级别没有错误信号的情况下产生错误的梯度,从而导致数据竞争。
到目前为止所介绍的技术——融合、精度、编译、推测解码、MoE 和通信重叠——针对性能方程的不同方面。在给定情况下识别应用哪种技术需要系统测量,而使这种诊断成为可能的性能分析工具是优化工具包的最后一块拼图。
系统性能分析
一位工程师花了两周时间将一个 PyTorch 模块重写为自定义 CUDA 内核,以使其速度提升 5 倍,但后来发现整个模型延迟没有变化,因为系统完全受 I/O 限制。没有测量的性能工程是昂贵的猜测。系统性能分析提供了所需的外科手段诊断,以精确识别 GPU 在何处等待,使我们能够精确地应用优化。
观察分布式机器学习系统从根本上改变了其行为。收集高分辨率遥测数据(例如跟踪每个 CUDA 内核启动或内存分配)会消耗 PCIe 带宽、CPU 周期和内存容量。在经过高度优化的服务流水线中,性能分析的开销可能会人为地引入工程师试图诊断的非常延迟峰值。这种“海森堡效应”需要一个有策略的方法:在持续生产监控中部署低开销、基于采样的遥测,仅在隔离的诊断环境中启用高保真度、同步追踪。
这种效应在 PyTorch 工作负载中尤为明显。启用 torch.autograd.profiler 或 torch.cuda.memory._record_memory_history() 会指示框架保留中间激活张量的引用,超过其正常生命周期,以便记录分配元数据。这会阻止内存分配器像平常那样重用张量缓冲区,导致 HBM 峰值消耗增加,在边界情况下可能会触发未在无观测执行中发生的 Out-Of-Memory 错误。此外,torch.compile 中的图优化通过检测张量观察并保守地禁用某些缓冲区重用融合,导致被分析的执行遵循比生产路径更慢的代码路径。实际后果是,性能分析运行必须被视为一个独立的实验:它能够准确表征计算的结构,但其绝对延迟和内存数值会高估生产测量结果。
性能分析层次结构
只有当性能分析层次能够引导从症状到干预时,它们才是有用的。机器学习系统性能分析在四个层次上运行,每个层次提供不同的粒度并针对不同的瓶颈类别。图 9.8 明确地说明了这种下钻过程,从应用级别症状开始,逐步深入到框架和内核性能分析,以在硬件计数器层次(第 1 级)找到根本原因。
图 9.8:性能分析层次结构:四个性能分析粒度级别。从第 4 级(应用性能分析)开始,以处理全局症状,然后通过框架和内核性能分析进行下钻,以在第 1 级(硬件计数器)找到根本原因。
钻取分析从应用层面开始,其中诸如每秒 token 数、首 token 延迟时间(TTFT)、P99 延迟以及 GPU 随时间的利用率等端到端指标揭示了用户面临的系统未能达到目标。随后深入到跨 GPU 和节点的分布式分析,通信模式显示集合通信(collectives)是否与计算重叠、哪个操作主导了步长时间以及不同 rank 之间是否存在负载不平衡。细粒度的分析(trace-level profiling)将诊断范围缩小到训练步骤或推理请求期间 GPU 内核、CPU 操作和数据传输的时间线,暴露出启动延迟、空闲泡沫、顺序瓶颈以及重叠机会。操作级分析(operation-level profiling)通过诸如 NVIDIA Nsight Compute 等工具完成整个链条:实现的内存带宽、计算利用率、占用率和指令混合决定了特定内核是受内存限制、计算限制,还是受实现细节限制——包括它是否在发出 Tensor Core 指令(FP16/BF16 使用 HMMA,INT8 使用 IMMA),而不是回退到标量 CUDA Core 指令(FMA/ALU)。如果矩阵乘法的隐藏维度或序列长度分块不能被 Tensor Core 对齐要求整除(FP16 为 8 的倍数,INT8 为 16 的倍数),则会完全跳过 Tensor Core 并在 CUDA Core 上以远低于峰值吞吐量的性能执行。这种形状不匹配是自定义 ML 内核中最常见的隐藏性能错误之一,若不检查指令混合计数器则难以察觉。
性能诊断需要自上而下的钻取方法,从全局症状转向局部原因。在分析 70B 模型服务管线时,应用层可能会显示“端到端延迟为 150 ms/token,超出服务等级目标(SLO)3 倍。”深入到分布式层面,追踪可能显示某个 GPU 在 AllReduce 操作中持续落后,指向拖累者(straggler)或网络拥塞。进一步聚焦于该特定 GPU 的追踪层,揭示内核执行的时间线,暴露出由于调度开销导致 SM 空闲的间隙。最后,内核级分析(使用 Nsight Compute)检查单个矩阵乘法的具体指令混合,揭示缓存未命中或寄存器压力。跳过层级往往会导致优化错误的瓶颈:如果 GPU 有 40% 的时间花在等待网络上,那么优化内核是毫无用处的。
关键性能指标
各项指标不可互换;每个指标回答不同的瓶颈问题。模型 FLOP 利用率(MFU)和硬件带宽利用率(MBU)诊断有用的硬件工作,首 token 延迟时间(TTFT)和 token 间延迟(ITL)诊断服务延迟,吞吐量则暴露在批处理下的容量。理解它们之间的关系和权衡对于性能工程至关重要。
Table 9.5 列出了常见指标及其各自回答的瓶颈问题。
| 指标 | 定义 | 最佳诊断用途 | 注意事项或目标 |
| --- | --- | --- | --- |
| 模型 FLOP 利用率(MFU) | 模型计算实际使用的硬件峰值 FLOP/s 的比例 | 在内存限制、启动开销、通信等待和软件开销方面衡量训练效率 | 调校良好的 LLM 训练中常见 40–60%;超过 60% 表现优秀 |
| 硬件 FLOP 利用率(HFU) | 执行的总算术运算(包括重新计算)除以峰值 FLOP/s | 将有用的模型工作与诸如激活重新计算之类的开销区分开来 | 通常超过 MFU,且 HFU–MFU 差值估计了浪费的计算 |
| 首 token 延迟时间(TTFT) | 从请求到达到首个生成 token 的延迟 | 交互式服务响应能力,包括排队、预填充和初始化 | 低于 500 ms 通常可接受;低于 200 ms 感觉响应迅速 |
| token 间延迟(ITL) | 在解码过程中连续生成 token 之间的时间 | 首个 token 之后的感知生成速度 | 低于 50 ms 支持舒适阅读;实时语音可能需要低于 25 ms |
| 吞吐量 | 系统每秒处理的 token 数,或每秒每个 GPU 的 token 数(用于按设备效率评估) | 在批处理下的综合服务或训练容量 | 大批次可提升吞吐量但会增加单请求延迟;第 F.1.1 节 讨论了排队权衡 |
| 模型带宽利用率(MBU) | 实际达到的内存带宽除以硬件峰值带宽 | 带宽受限的推理,尤其是解码工作负载 | 在 H100 上解码步骤可能显示 2% MFU 但 85% MBU,表明这是一条内存饱和且优化良好的路径 |
表 9.5:性能指标及其诊断用途:每个指标测量系统的不同层次,因此选择错误的指标可能掩盖真实的瓶颈。
使用屋顶线模型进行诊断
Table 9.5 中的指标选择反映了训练与推理之间根本瓶颈的转变:MFU 通常是训练效率的正确视角,而在解码受 HBM 流量限制时,MBU 常是推理效率的正确视角。第 9.1.5 节 中的屋顶线模型在结合分析数据时成为诊断工具。诊断遵循以下三个步骤:
-
测量 使用
Nsight Compute核心的已实现 FLOP/s 和内存带宽。 -
计算 根据算法(FLOP/byte)得到的运算算术强度。
-
绘图 将测得的性能与屋顶线上限进行比较。
如果一个核心远低于计算上限和带宽上限,则说明其实现存在问题:启动开销、内存访问模式不佳或占用率低。如果一个核心达到带宽上限但低于计算上限,则它是受内存限制的,并且需要通过减少内存传输(融合、精度降低)而不是提高计算效率来进一步优化。如果一个核心位于计算上限,则它是受计算限制的,只能通过算法改变(减少 FLOP)或使用更快的硬件来提升性能。
考虑一个具体例子:在 H100 上分析的 LayerNorm 内核报告已实现的计算为 15 TFLOP/s,已实现的内存带宽为 2.8 TB/s。其算术强度为 15 TFLOP/s / 2.8 TB/s ≈ 5.4 FLOP/byte。H100 在 FP16 下的屋顶点大约为 295.2 FLOP/byte。由于 5.4 FLOP/byte 远低于 295.2 FLOP/byte,因此该内核严格受内存限制。其已实现的 2.8 TB/s(相当于 H100 峰值带宽 3.35 TB/s 的 83.6%)证实它正在内存子系统的物理极限附近运行。诊断明确:进一步的 FOP 级优化将收益甚微;只有通过减少数据移动(例如通过融合消除 HBM 往返或降低精度使每元素字节数减半)才能实质性提升性能。
PyTorch 分析器工作流程
当应用指标显示减速但导致减速的层仍然未知时,PyTorch 分析器最为有用。其作用是在使用更重型工具之前,将全局症状缩小到某一层、内核家族或同步点。它通过最小的代码修改与训练循环集成,以捕获详细的追踪:
with torch.profiler.profile(
schedule=torch.profiler.schedule(wait=1, warmup=1, active=3, repeat=2),
on_trace_ready=torch.profiler.tensorboard_trace_handler("./log"),
record_shapes=True,
profile_memory=True,
with_stack=True,
) as prof:
for step, batch in enumerate(dataloader):
train_step(batch)
prof.step()
schedule 参数定义了一个预热期,在此期间分析器运行但不记录数据,使得 CUDA 缓存和即时(JIT)编译在测量开始前得以稳定。如果没有预热,前几次迭代将包含一次性开销(内核编译、内存分配、CUDA 上下文初始化),这些会膨胀测量时间并误代表稳态性能。
生成的 Trace 可在 TensorBoard 或 Chrome 的 Trace Viewer 中查看,其价值仅在于每个视图都映射到一次干预。表 9.6 将这些视图转化为诊断清单。
| Trace 视图 | 诊断信号 | 可能的干预措施 |
| --- | --- | --- |
| 内核时间线 | CUDA 内核、其持续时间、GPU 空闲间隙,以及异常漫长的内核 | 算子融合、使用 CUDA Graphs,或使用 Nsight Compute 调查长内核 |
| 内存时间线 | 分配与释放的峰值,或内存的逐渐增长 | 复用缓冲区、规划分配,或调查泄漏 |
| CPU-GPU 同步 | CPU 等待 GPU 的点位,或 GPU 等待 CPU 启动工作 | 移除阻塞式同步并减少启动开销 |
| 通信事件 | NCCL 集合操作持续时间相对于计算内核的比例 | 调整重叠、桶大小,或并行化策略 |
表 9.6:Profiler 视图与干预措施:当每个可视化信号都指向一个具体的后续实验时,PyTorch Profiler 视图才显得有用。
Nsight Systems:解读时间线
因此,Profiler 是一个分诊工具,而非最终的性能证明:它识别下一个实验应针对融合、内存规划、CUDA Graphs 还是重叠。NVIDIA Nsight Systems 回答了高层 Profiling 引出的 Trace 层面问题:GPU 时间线是否连续、CPU 是否馈送得足够快、通信是否与计算重叠。该工具将每一次 GPU 内核启动、CUDA 内存操作、NCCL 通信和 CPU 线程活动捕获到一个统一的时间线上。
当问题从“哪一层慢”转变为“为什么完整时间线上会有空隙”时,Nsight Systems 就变得有用了。典型工作流始于捕获几次训练或推理迭代的 Trace:
--trace 标志控制记录哪些活动。cuda 标志捕获内核启动和内存操作。nvtx 标志捕获用户标注的区域(PyTorch 自动使用 NVTX 标记标注模块边界)。cublas 和 cudnn 标志捕获库级操作,有助于识别 GEMM 是使用 cuBLAS 还是自定义内核。该命令很有用,因为这些行能暴露瓶颈是启动开销、通信串行化,还是内核实现问题。
生成的 .nsys-rep 文件在 Nsight Systems GUI 中打开,呈现多行时间线。四行承载了最核心的诊断权重。
CUDA HW 行显示 GPU 上实际的内核执行情况。每个彩色条代表一个内核,宽度与执行时间成正比。条之间的间隙表示 GPU 空闲时间,代表浪费的潜力。对于一个优化良好的推理管线,CUDA HW 行应显示近乎连续的内核执行,且间隙极小。
CUDA API 行显示 CPU 侧的 CUDA API 调用(内核启动、内存分配、同步)。如果 CUDA API 条明显比对应的 CUDA HW 条宽,则 CPU 是瓶颈:它无法以足够的速度启动内核以保持 GPU 忙碌。CUDA Graphs 和 torch.compile 正是解决这一内核启动开销问题的。
NCCL 行(针对分布式工作负载)显示集合通信操作。将 NCCL 行与 CUDA HW 行对比,可揭示通信是否与计算重叠。如果 NCCL 条出现在 CUDA HW 行的空隙中,则通信是串行的。如果 NCCL 条与 CUDA HW 条重叠,则重叠工作正常。
NVTX 行显示用户标注的区域,PyTorch 将其映射为模块名称(Linear、LayerNorm、Attention)。这将底层内核名称(通常是晦涩的字符串,如 volta_fp16_s1688gemm_fp16_256x128_ldg8_f2f_nn)与产生它们的模型级操作关联起来。
五种模式揭示了从 Nsight Systems Trace 中获取的最具诊断价值的信息。表 9.7 将每种可视化模式映射到它所回答的瓶颈问题。
| 时间线模式 | 诊断问题 | 优化方向 |
| --- | --- | --- |
| 内核执行 vs. 空闲间隙 | Trace 中有多少是有效的 GPU 执行而非等待? | 调查启动开销、CPU 停顿或输入饥饿 |
| 内核持续时间的分布 | 是否许多短内核碎片化了工作负载? | 尝试融合、CUDA Graphs 或编译器捕获 |
| NCCL 与 CUDA HW 行的对齐 | 通信是否与计算重叠? | 调整桶大小、调度或并行化布局 |
| 内存分配峰值 | 张量是否被反复实例化或分配? | 增加内存规划、缓冲区复用或融合内核 |
| GEMM vs. 非 GEMM 时间 | 有多少执行是有效的矩阵运算而非开销? | 将非 GEMM 工作移入融合内核或减少内存流量 |
表 9.7:Nsight 时间线诊断模式:时间线模式将可视化结构与利用率、融合、重叠、分配和算术效率问题联系起来。
经验丰富的性能工程师会针对这些 Trace 开发模式识别能力,从时间线的可视化结构中快速识别出主要瓶颈。由细密排列、间隙极小的内核条主导的 Trace 表明管线已良好优化。内核条间有大间隙的 Trace,或 NCCL 条未与 CUDA HW 条重叠的 Trace,会立即揭示主要优化目标。
常见瓶颈模式
Profiling 之所以有价值,是因为反复出现的 Trace 模式直接映射到优化选择。表 9.8 将这种模式识别转化为诊断图谱:首先识别可视化特征,然后将其与可能的成因及能改变系统的干预措施关联起来。
| Trace 模式 | 可能成因 | 主要干预措施 | 需权衡的代价 |
| --- | --- | --- | --- |
| 众多内核间的细小间隙 | 内核启动开销占主导 | 使用 torch.compile 进行图编译、CUDA Graphs 或融合 | 编译预热和调试复杂度 |
| 大的内存峰值 | 未融合算子实例化中间张量 | FlashAttention 或自定义 Triton 融合 | 数值验证和内核维护 |
| 低 FLOP/s 伴随高带宽 | 内存受限操作占主导 | 降低精度、增加批次或算法变更 | 精度、缓存压力和质量护栏 |
| GPU 空闲间隙期间出现 NCCL 条 | 通信串行化而非重叠 | DDP 梯度重叠或管线重构 | 桶大小、调度复杂度和同步 |
| 部分 GPU 等待落后者 | 工作分配不均或 MoE 负载不均 | MoE 容量调整、辅助损失调整,甚至张量并行切分 | 路由质量和利用率平衡 |
| 重复的大块分配 | 缓冲区被重复创建而非复用 | 内存规划、预分配缓冲池或检查点 | 重计算成本和分配器行为 |
| 周期性 GPU 利用率下降 | CPU 预处理或分词未流水线化 | 更多数据 Worker、预处理服务或预分词数据集 | 存储成本和数据新鲜度 |
表 9.8:常见瓶颈 Trace 模式:从 Trace 特征到可能成因、干预措施及权衡代价的诊断图谱。该表在 Profiler 已将瓶颈定位到启动开销、内存移动、通信、负载均衡、分配行为或输入准备后最为有用。
一次完整的解码 Trace 展示了这些较小的瓶颈如何组合成主要损耗。
**content]
问题:一个 7B 参数的 LLM 在单张 H100 上运行,在批大小为 1 的自回归生成过程中表现为 45 tokens/s。纯 FP16 权重读取的屋顶线更高,但若为权重、KV 缓存读取、采样、同步和启动开销预算约 35 GB 的总每 token 流量,带宽限制的全解码上限约为 96 tokens/s。剩余 53% 可实现的解码步性能藏在哪里?
调查:
步骤 1:内核级分析。在主要 GEMM 内核上运行 Nsight Compute。结果:在 H100 3.35 TB/s 的峰值带宽中实现了 2.8 TB/s 的有效带宽。效率:84%。该内核表现良好。
步骤 2:追踪级分析。在完整解码步上运行 Nsight Systems。结果:42% 的步耗时花费在 GEMM 内核上。剩余 58% 分配在:
-
Attention 内核(含 KV 缓存读取):28%
-
LayerNorm 与激活内核:12%
-
Softmax 与 top-k 采样:8%
-
内核启动间隙:10%
步骤 3:确定优化目标。
-
KV 缓存 attention 内核因不规则内存访问模式,仅达到 1.9 TB/s 带宽。修复:实现带有更好内存布局的 KV 缓存量化 (INT8)。
-
内核启动间隙(占 10% 时间)源于每层 120+ 个独立内核启动。修复:应用
torch.compile融合逐元素操作,将每层内核数降至 ~30 个。 -
LayerNorm 与激活内核未融合。修复:通过 Triton 实现融合 LayerNorm-GELU 内核。
系统洞察:主要 GEMM 接近最优,但次要操作与启动开销消耗了超过一半的执行时间。系统化的剖析揭示:瓶颈不在直觉指向之处(最大的内核),而在于许多微小操作累积的开销。
剖析反馈回路
有效的性能工程遵循迭代周期:剖析、诊断、优化、验证。验证步骤至关重要且常被忽略。应用优化后,重新剖析以确认目标瓶颈已解决,且无新瓶颈出现。性能优化是“水床问题”:修复一个瓶颈往往会暴露下一个。
常见陷阱是基于微基准而非端到端追踪进行优化。隔离来看快 2× 的内核,若非瓶颈,或因数据依赖导致周围代码无法利用其加速,端到端吞吐可能仅提升 5%。除内核级指标外,务必在应用层面(tokens/秒、步耗时、P99 延迟)衡量影响。同等测量纪律管控发布风险:看似局部的变更,一旦进入生产流量可能消耗全局资源。
剖析本身也会扰动被测系统。启用内存剖析记录完整追踪时,PyTorch Profiler 增加约 10–20% 开销。Nsight Systems 开销较小但仍影响调度。在主动测量前剖析预热步,并忽略前几次 JIT 编译或 CUDA 上下文初始化可能占主导的迭代。
大规模剖析
剖析单 GPU 很直观;剖析拥有数百至数千 GPU 的分布式系统则引入独特挑战。追踪数据量随 GPU 数线性增长:单 GPU 5 秒的 Nsight Systems 追踪约 500 MB;1,000 GPU 同等追踪达 500 GB,存储分析均不切实际。
生产系统通过分层剖析应对。顶层持续从每个 GPU 采集应用级指标(MFU、吞吐、步耗时),开销可忽略。这些聚合指标检测性能退化。检测到退化时,在代表性 GPU 子集(通常每流水线阶段、每数据并行组一张 GPU)上触发短窗口(几个训练步)定向剖析。分析所得追踪以定位具体瓶颈。
另一方法是统计剖析,各 GPU 随机采样少量内核进行详细计时。经多训练步聚合,采样提供统计准确的内核耗时分布,免去完整追踪开销。此法类比传统系统工程中用 Linux perf 等采样剖析器,移植至 GPU 场景。
最棘手场景是间歇性拖后腿:GPU 因热节流、内存错误或网络拥塞偶尔变慢,但大多时间正常。短窗口剖析可能捕捉不到,却能在数小时内使训练吞吐降 10–20%。检测它们需持续的逐 GPU 步耗时监控与统计异常检测,属于监控层而非内核层的剖析基础设施。
本节所述剖析工具与技术,为所有优化工作提供测量基础。无测量,性能工程沦为猜测;有测量,它成为循量化证据引导的系统性学科。
大规模测量
优化单节点是前提,但性能工程的终极考验是车队规模下的效率。从 8 GPU 扩至 1,024 GPU 时,本地追踪不可见的新开销源会涌现。剖析器能解释为何单个内核停滞;却无法仅凭自身判断成千上万加速器是否将算力、内存带宽和网络时间转化为有效的模型进展。大规模测量要求从内核级微基准转向全局效率指标,以捕捉计算、通信与硬件变异的交互。
车队效率指标
硬件利用率报告 GPU 忙碌频率,却无法区分有效工作与浪费周期(如激活重计算或通信气泡)。车队测量因此需要有效工作利用率指标。聚焦模型架构所需的“有效”FLOPs,该指标提供不随软件栈与并行化策略变化的不变系统效率度量。
模型 FLOPs 利用率(MFU)
模型 FLOPs 利用率(MFU),首次在第 5.4 节中引入,是指硬件理论峰值吞吐量(R_peak)中,由直接推动模型训练或推理的 FLOPs 所占用的比例,其中排除了重计算、填充和同步带来的开销。此处的新增内容在于其舰队规模下的行为表现:单节点指标如何在数千个加速器上聚合,以及扩展开销如何将其拉低。
重要性
在舰队规模下,MFU 会跨所有节点进行聚合:通信开销、负载不均衡和流水线气泡会各自加剧利用率损失,因此舰队 MFU 始终低于单节点 MFU。它是诊断硬件投资是否转化为模型进展的主要指标,在一个 10,000 GPU 的集群中,MFU 提升 1% 所降低的成本相当于 100 个 GPU。
区别
与硬件利用率(报告加速器“忙碌”的频率)不同,MFU 报告的是有多少活动有助于模型收敛或推理,其中排除了来自重计算、填充和梯度检查点开销的无效 FLOPs。
常见误区
一个常见的误解是高 GPU 利用率意味着高效率。如果系统在通信气泡或低效的内核实现上浪费周期,它可能显示 90% 的硬件利用率,但实际只达到 30% 的 MFU,这使得 MFU 成为优化决策的正确指标,而非原始利用率。
既然 MFU 定义为有效工作而非单纯的忙碌,下面的计算将展示分布式开销如何将强劲的单节点数字转化为较弱的全舰队结果。
问题:节点间通信开销
一个 70B 参数模型在单个 8 GPU 节点上实现了 65% 的 MFU。当训练扩展到 128 GPU 时,节点间通信开销消耗了多少 MFU?
通信税将局部 MFU 蚕蚀为舰队 MFU。
-
局部节点基线:单个 8 GPU 节点实现 65% MFU。
-
舰队性能:在 128 GPU 下,步长时间增加到 245 ms,MFU 下降至 48%。
数学推导
两个 MFU 数值均源自同一比率:一个步骤执行的有效 FLOPs 除以集群在该步骤墙钟时间内以峰值速度可执行的 FLOPs:
MFU = FLOPs_ 有效 / (N × R_peak × t_ 步骤)
就舰队情况而言,一个步骤执行 14.9 PFLOP 的有效工作,而 128 个 GPU × 每 GPU 989 TFLOP/s × 245 ms 墙钟时间本可在峰值下执行 31 PFLOP;两者相除 14.9 PFLOP ÷ 31 PFLOP ≈ 48%。在单节点上代入同一公式得 65%,扩展税为 1 − 舰队 MFU / 局部 MFU,得出 26.2%。
系统洞察
26.2% 的扩展税代表了节点间通信(InfiniBand 延迟)和同步屏障的成本。在健康的舰队中,该税率应保持稳定;扩展税的突然上升预示着扩展性能倒退,通常由错位的并行化策略或网络结构中的“灰度故障”引起。
检测扩展性能倒退
在大规模下,系统呈非线性特征。单 GPU 上引入微小内存开销的代码变更,可能因垃圾回收暂停增加或 InfiniBand 信用缓冲区耗尽,在 1,000 GPU 上引发灾难性的性能崩溃。表 9.9 展示了分级测试如何在崩溃演变为舰队事故前将其捕获。
表 9.9:扩展性能倒退测试分级
| 测试分级 | 测量内容 | 捕获的倒退类型 |
| :--- | :--- | :--- |
| 小规模金丝雀测试 | 在 8 和 64 GPU 上的模型行为,以建立扩展效率曲线 | 单 GPU 上看似无害,但在全舰队发布前使曲线弯曲的代码变更 |
| 舰队基线对比 | 每次生产运行的 MFU 与该模型架构参考基线的对比 | 减少有效舰队工作的架构、编译器或配置变更 |
| 灰度故障检测 | 舰队中步长时间的分布 | 直拖节点,例如一个慢 10% 的节点可使同步数据并行 MFU 降低 10% |
表 9.9:扩展性能倒退测试分级:舰队性能测试从小规模金丝雀测试推进到生产基线和灰度故障检测,以便在分布式行为下验证局部变更。
这些测量也同样解释了为何基准测试数字不能直接照搬进产能规划。
行业基准(如 MLPerf)往往是“英雄运行”——高度调优的配置,其中日志记录被禁用、安全检查被绕过、硬件刚完成重启。
在生产环境中,实际达到的 MFU 通常比这些英雄数字低 10–20%。必要的运营开销消耗了这部分差额:
-
可观测性:指标收集和日志记录。
-
可靠性:检查点保存和健康心跳。
-
熵增:热节流、内存碎片和多租户网络噪声。
规划产能时,工程师必须为“现实税”做预算。如果基准预测训练需 30 天,生产计划应预留 35–40 天。
无行动的测量只是开销。优化实战手册将节点级追踪和全舰队 MFU 转化为一系列外科手术式的干预,每一项都针对测量所识别的特定瓶颈。
优化实战手册:70B LLM 案例研究
考虑一个原始的、未优化的 700 亿参数 PyTorch 模型,必须在下周前实现生产环境每秒 1,000 token 的吞吐率。优化序列始于基线测量,随后进行屋顶线分类以识别主导瓶颈。盲目应用优化技术是无效的。优化实战手册要求系统性的、按优先级的攻坚:首先突破内存墙,接着融合算子,最后按特定的复合序列应用投机解码等算法技术。
诊断序列
优化始于在动手处理单个内核前测量整体负载。端到端吞吐率、Nsight Systems 追踪,以及来自第 9.9 节的模型 FLOPs 利用率(MFU),确立系统是否拥有巨大的提升空间。下一诊断步骤是屋顶线分类:计算主导内核的算术强度,并将其置于机器平衡点上。该分类决定了主要瓶颈,而主要瓶颈决定了应优先尝试哪种补救措施。对于内存受限、计算受限和通信受限的工作负载,表 9.10 将该决策转化为紧凑的路线图:识别绑定资源,用典型场景作合理性检查,并按顺序尝试干预措施,直到重新分析的追踪显示瓶颈已转移。
表 9.10:瓶颈驱动的优化路径
| 主要瓶颈 | 典型场景 | 优化路径 |
| :--- | :--- | :--- |
| 内存受限 | 推理,尤其是 token 解码阶段 | 降低精度以提高有效带宽;融合算子以消除中间 HBM 流量;编译图以捕捉剩余融合机会;若瓶颈依存,再考虑投机解码或 MoE 等算法变更。 |
| 计算受限 | 大批量训练 | 确保 Tensor Core 处于使用中;应用图编译进行内核选择和内存规划;考虑用 FP8 获得 2× 计算吞吐;重叠通信使计算不空闲。 |
| 通信受限 | 大规模分布式训练 | 将梯度通信与反向传播重叠;压缩梯度或使用降低精度的通信;重构流水线调度,使阶段间通信隐藏在有用的计算之下;使用拓扑感知放置以最小化跨节点流量。 |
表 9.10:按主要瓶颈分类的优化路径:从瓶颈类别到在下一次分析前应尝试的有序干预措施的诊断图谱。
第四步是应用并验证。实施影响最大的优化,重新分析,并验证改进。然后使用新的分析结果从屋顶线分类步骤重新迭代,因为瓶颈可能已经转移。
组合技术
生产系统会组合使用各种技术,因为没有单一干预措施能覆盖铁律中的每一项。一个高度优化的 LLM 服务系统可能会使用 FlashAttention-2 或更新的 FlashAttention 系列内核来减少注意力内存流量,使用 GPTQ 或 AWQ 进行 INT4 权重量化以减少 HBM 读取,并使用带逐通道缩放的 INT8 KV 缓存压缩来增加可行的批次大小。编译器和运行时工具(如 torch.compile 或 TensorRT)随后处理逐元素融合和内核选择。
服务层增加了另一组控制手段。推测解码可在小批次大小下降低延迟,连续批处理在请求完成时重新填充批次(在第 9.2.1 节中引入),动态序列分组提高吞吐量,张量并行将模型分布在多个 GPU 上并重叠 AllReduce。只有当分析显示每一种技术都能移动当前的约束项时,这些技术才应组合在一起。
这些技术带来的加速并非简单相加;它们以需要仔细排序的方式相互作用。例如,INT4 权重量化将每 token 的 HBM 流量降低 4 倍,这可能会将瓶颈从内存受限转移为计算受限。一旦变为计算受限,进一步的带宽优化(KV 缓存压缩)收益递减,计算优化(FP8 Tensor Core)便成为优先项。这就是为什么迭代的分析-优化-验证循环至关重要:最佳组合取决于特定的模型、硬件和工作负载特征。
优化之间的相互作用创建了一个性能工程师必须驾驭的依赖图。某些组合具有协同效应:FlashAttention 移除了注意力中间结果,INT8 KV 缓存压缩减少了 KV 缓存内存,二者共同释放出足够内存以支持更大的批次大小,从而改变服务的经济模型。其他组合是冗余的:同时应用 CUDA Graphs 和 torch.compile 的 reduce-overhead 模式达到相同效果,因为 reduce-overhead 内部使用了 CUDA Graphs。还有一些组合相互冲突:推测解码在小批次大小(解码为内存受限)时获益最大,而许多吞吐量优化则通过增加批次大小起作用。在大批次大小下,推测增加开销而无相应收益。
一个用于优化排序的实用启发式方法是:在添加新的算法机制之前,先应用最廉价的、能移动瓶颈的干预措施。第一步取决于测量到的瓶颈。对于形状稳定且内存显然不是约束约束的工作负载,编译器优先是常见的起点。对于批次为 1 的 70B 解码,精度优先,因为在图开销成为问题之前,权重带宽和 KV 容量占主导地位。
-
主要瓶颈修复:当追踪显示启动开销且形状稳定时应用
torch.compile,或当分析显示受内存容量或带宽约束时首选应用权重/KV 精度优化。 -
FlashAttention 系列内核(库替换):当分析中仍可见注意力实体化或注意力 HBM 流量时应用。
-
权重量化(INT4/FP8,需校准):对于内存受限的服务尽早应用,但在将加速视为可用前必须验证质量。
-
KV 缓存压缩(INT8,库支持,缓存减半):第四步应用。可实现更大批次。
-
推测解码(需草稿模型,工程投入大):最后应用,仅在未达延迟目标时。部署最复杂。
此顺序反映了这样一个原则:被动优化(编译器、库替换)应先于主动优化(算法变更、新模型组件)。每一步都通过重新分析验证后再进入下一步。
测试您设计优化计划的能力:
案例研究:优化 70B LLM 服务管线
为了说明诊断序列和组合原则在实践中如何运作,请考虑为生产服务优化 70B 参数 LLM 的任务。目标是一个实时聊天机器人应用,要求首 token 时间(TTFT)低于 500 ms,token 间延迟(ITL)低于 50 ms,且集群吞吐量至少 1,000 tokens/秒。模型部署在由 NVLink 连接的 8 块 H100 GPU 节点上。
基准测量
初始部署使用 FP16 权重、标准 PyTorch 急切执行,并在 8 个 GPU 上采用张量并行。FP16 下的 70B 模型需要 140 GB 权重存储,分布后每 GPU 约 17.5 GB。四项基准测量揭示了优化差距:
-
TTFT:1,200 ms(远高于 500 ms 目标)
-
ITL:85 ms(高于 50 ms 目标)
-
吞吐量:280 tokens/秒(低于 1,000 tokens/秒目标)
-
最大批次大小:4(受 KV 缓存内存限制)
Nsight Systems 追踪将批次大小为 1 的单次解码步骤分解为五个时间类别:
-
GEMM 内核:占步骤时间的 38%
-
注意力(含 KV 缓存读取):占步骤时间的 24%
-
逐元素操作(LayerNorm、GELU、残差):占步骤时间的 14%
-
AllReduce 通信(张量并行):占步骤时间的 12%
-
内核启动间隙与开销:占步骤时间的 12%
屋顶线分析确认解码深度受内存限制,批次大小为 1 时算术强度约为 1 FLOP/byte。GPU 实现 2.7 TB/s 有效带宽(峰值的 80%),表明内核级效率合理,但存在根本性的算法限制。
优化轮次 1:精度工程
首轮优化瞄准最大机会:减少从 HBM 读取的每权重字节数。应用 AWQ INT4 权重量化将 8 GPU 节点上的单 GPU 权重占用从 17.5 GB 降至约 4.4 GB。原始权重读取流量下降 4 倍,加上即时反量化至 FP16,权重读取的有效带宽大约翻倍。
同时,对 KV 缓存应用 INT8 量化将每请求缓存大小减半。对内存预算的综合影响显著:每 GPU 现可用于 KV 缓存的内存约为 75.6 GB,高于之前的 62.5 GB。根据第 9.4.3 节的 4096-token GQA 缓存计算,这将允许大得多的批次;在该部署中,服务策略为长上下文、碎片预留和尾延迟保护保留内存。在此策略上限下,最大准入批次大小从 4 增至约 32。
优化后指标显示:批次为 1 时 ITL 达到 48 ms,满足 50 ms 目标;批次大小 32 时吞吐量达到 720 tokens/秒,仍低于目标。
Nsight Systems 追踪显示:因权重读取减少,GEMM 时间下降约 45%,但注意力和逐元素操作保持不变。瓶颈已部分转移。
优化轮次 2:算子融合
第二轮优化针对逐元素操作消耗的 14% 步进时间和注意力机制消耗的 24% 步进时间。应用带有 max-autotune 后端的 torch.compile 可融合逐元素操作(GELU、LayerNorm、残差加法),将其占比从 14% 降至步进时间的约 4%。同时,启用 FlashAttention-2 替代标准注意力实现,使预填充阶段的注意力 HBM 流量降低约 16 倍。
对于解码阶段,FlashAttention 的影响较为有限,因为解码注意力主要受限于 KV 缓存读取,而非 S × S 得分矩阵。然而,INT8 KV 缓存压缩与 FlashAttention 高效的 PagedAttention 内核相结合,将注意力解码时间降低了约 30%。torch.compile 中的 reduce-overhead 模式用 CUDA Graph 包装解码步骤,几乎完全消除了 12% 的内核启动开销。
优化后指标显示:TTFT 为 380 ms,达标 500 ms 目标;批大小为 1 时 ITL 为 32 ms,远优于 50 ms 目标;批大小为 32 时吞吐量为 1,050 tokens/second,达标吞吐目标。
优化第三轮:推测解码
在达标吞吐目标后,团队致力于进一步降低 ITL 以提供最佳用户体验。在同一组 GPU 上部署了带有 1.5B 草稿模型(AWQ INT4 量化至 0.75 GB)的推测解码。草稿模型在 4 ms 内生成 5 个候选 token(受益于第一轮应用的 INT4 量化)。目标模型在约 32 ms 内验证候选块,耗时与一次优化后的自回归解码步骤相当。
在标准几何接受模型下,其中每个草稿 token 以概率 p[acc] 独立被接受,且最后一个被接受的 token 后总会跟随一个额外 token,对于 k 个草稿 token,每轮预期接受 token 数为 (1 − p[acc]^(k + 1))/(1 − p[acc])。当 p[acc] = 0.78 且 k = 5 时,结果为 (1 − 0.78⁶)/(1 − 0.78) ≈ 3.5 tokens/轮。有效 ITL 变为:
与第二轮 32 ms 的 ITL 相比,提升了 3.1 倍。然而,推测解码会与批处理产生交互。在批大小 32 时,验证步骤不再“免费”,因为 GPU 接近计算饱和。因此,系统仅在当前批大小低于 16 时应用推测,高负载下回退到标准自回归解码。这种自适应策略同时保持了低负载下的延迟优势和高负载下的吞吐优势。
案例研究的启示
该优化历程阐释了优化顺序至关重要的原则,因为每一步都会移动瓶颈。精度工程(第一轮)优先应用,因为它带来最大的单一改进,并通过释放内存以支持更大批次,为后续优化创造条件。融合优化(第二轮)解决了精度工程暴露出的新瓶颈。推测解码(第三轮)在达标吞吐目标后提供了延迟改进。
每项优化都改变了瓶颈。第一轮前,系统纯粹受限于内存带宽。INT4 量化和批处理后,系统在大批次下部分变为计算受限。融合优化后,内核启动开销可忽略,剩余瓶颈成为解码阶段的基本内存带宽极限。每项优化均通过重新剖析验证,以确认瓶颈转移。
最终系统结合了五种不同技术:INT4 权重量化、INT8 KV 缓存压缩、FlashAttention-2、带 CUDA Graphs 的 torch.compile 以及自适应推测解码。这些技术并非独立;它们相互作用。INT4 量化支持更大批次,改变了推测解码是否有利。FlashAttention 的收益取决于序列长度,而序列长度在生成过程中增长。性能工程师必须在各阶段剖析数据指导下,整体性地推理这些交互。
案例研究展示了分散的优化如何按序复合,将一个不可用的原型转化为生产级部署。然而,通往这些加速的道路铺满了在规模化时往往被证明是灾难性的传统认知。
谬误与陷阱
某团队将推理集群从 A100 升级至 H100,根据规格表的 teraFLOP/s 额定值预期延迟降低 3 倍,结果发现其生成式模型仅快了 15%。陷阱在于:假设原始算力决定推理速度,而该工作负载完全受限于内存带宽。
谬误:更高 FLOP/s 意味着更快推理。
屋顶模型表明,大多数推理操作受限于内存而非计算。对于批大小为 1 的 LLM 解码,峰值 FLOP/s 高 2 倍但内存带宽相同的 GPU,生成 token 速度不会更快。内存受限工作负载的正确指标是带宽,而非 FLOP/s。此谬误导致组织采购最昂贵的计算硬件,而具有同等 HBM 带宽的中端 GPU 能提供完全相同的推理吞吐。
陷阱:将 FP8 采用规划为自动减半训练时间。
FP8 使峰值 TFLOP/s 和有效内存带宽均翻倍,但这些收益仅在 FP16 下受计算或带宽瓶颈限制的操作中实现。激活函数等逐元素操作早已受限于内核启动开销,而非精度。如果通信量(梯度大小)不减少,通信受限的分布式训练步骤从降低算术精度中获益为零。实际加速比取决于精度敏感操作所占执行时间比例。
谬误:最大内核就是全部性能问题。
9.10.3 节 的剖析案例研究说明了这一陷阱。工程师天然关注单个最大内核,这通常是 Transformer 层中的 GEMM。然而当 GEMM 已近乎最优时,剩余性能预算分散在数十个较小操作中:归一化、激活、注意力打分、KV 缓存管理和内核启动开销。合起来,这些“小”操作可消耗超过一半的总执行时间。图编译和系统性融合比进一步优化 GEMM 更有效地解决这条长尾。
陷阱:应用推测解码时未考虑批处理动态。
推测解码在批大小 1 时表现出色,此时解码深度受限于内存,验证步骤基本“免费”(GPU 有充足闲置算力)。在大批次下,解码趋近计算受限区,验证步骤增加实质计算成本。此外,每请求可变的接受 token 数使连续批处理调度复杂化。在大批次高吞吐服务场景中,推测的开销可能超过其延迟收益。
谬误:MoE 专家数量是免费的扩展旋钮。
增加 MoE 模型专家数量可在不成比例增加逐 token 计算的情况下扩大总参数量(容量),看似免费午餐。但每增加一个专家都会增加:(1) 总内存需求,需要更多 GPU;(2) 专家路由的 AllToAll 通信量;(3) 负载均衡难度,因为路由器必须将 token 分发给更多专家;(4) 训练不稳定性,因为更多专家竞争激活。超过约 64–256 个专家后,系统级成本往往超过容量收益。
陷阱:用图编译器替代 Kernel 分析
图编译器已有巨大改进,但仍受限于其代价模型和融合启发式。FlashAttention 需要人类洞察力来识别注意力机制可以重述为带在线 Softmax 的分块算法——这是一种超出常规编译器重写规则范畴的算法洞察。同样,推测解码和 MoE 路由也需要编译器无法发现的算法创新。编译器自动化已知优化;人类工程师发掘新优化。
谬误:支持的最低精度即最佳精度
激进量化(INT4 权重、INT4 KV 缓存、FP8 激活)会以标准基准难以察觉但用户可见的方式降低模型质量。留出数据集上的困惑度可能变化不足 1%,但模型在边缘情况、低资源语言或复杂推理任务上可能产生微妙的劣质响应。正确做法是定向量化:对最不敏感的组件(KV 缓存、中间激活)应用最激进的精度,为最敏感的组件(首尾层、注意力 logits)保留更高精度。在具有代表性的数据集上校准,随后在多样化的质量基准上评估,是在部署任何量化模型到生产环境前的必要步骤。
陷阱:测量吞吐率而不测量质量
若第一个系统通过使用会降低回答质量 15% 的 INT4 量化来实现吞吐,那么每秒生成 200 token 的模型服务系统并不比每秒生成 100 token 的系统好一倍。性能指标必须始终与质量指标一同报告。正确的优化目标是吞吐率与质量的帕累托前沿,而非单纯的吞吐率。
谬误:单次 Profiling 足以刻画性能
ML 系统性能是非平稳的。持续负载下 GPU 热节流会降低时钟频率(进而降低 FLOP/s),有时幅度达 10–15%。数小时服务后内存碎片累积,逐渐减小有效批大小。网络拥塞随集群级流量模式变化。冷启动时的 profiling 运行可能显示与生产服务运行数小时后截然不同的瓶颈模式。可靠的性能刻画要求在现实的持续条件下 profiling,理想情况下在一次生产运行中多次采样。
陷阱:针对平均情况优化而忽视尾延迟
服务系统可能实现优秀的平均 token 间延迟(30 ms),却因 Python 运行时的垃圾回收暂停、CUDA 内存分配停顿,或网络拥塞导致的偶发 AllReduce 延迟,而呈现 500 ms 的 P99 延迟。对交互式应用而言,用户体验由最坏情况主导,而非平均情况。生产系统的性能工程必须专门针对尾延迟进行 profiling 和优化,常通过与本章吞吐优化正交的技术实现:预分配内存池、CUDA Graph 回放(消除分配方差)、面向延迟敏感请求的优先级调度。
总结
识别这些陷阱能让团队避免花费数月在栈的错误层级上优化。性能工程通过攻克基于加速器的 ML 系统的一个根本瓶颈——内存墙,将一个本该高效的模型变为一个确实高效的模型。Roofline Model(屋顶线模型)提供了诊断框架,根据算术强度相对于硬件屋脊点的大小,将算子分类为计算受限或内存受限。对于 NVIDIA H100,在 FP16 下该屋脊点约为 295.2 FLOP/byte,意味着大多数 Transformer 操作落在内存受限区域。
本章技术构成一个优化栈而非可选菜单。算子融合通过将操作序列合并为单个 kernel,消除了冗余的 HBM 往返。FlashAttention 是典范示例,通过分块和在线 Softmax 避免了主导长序列的二次注意力中间态;在 8K 注意力示例中,这实现了实体化注意力状态 65 倍的减少。精度工程接着减少剩余往返中的字节数:FP8 格式提升 H100 硬件上的有效带宽,分块量化保护击败均匀量化的离群特征,KV 缓存压缩直接增加服务节点能容纳的批大小。
图编译将这些局部变换从手写 kernel 迁移至模型图中。torch.compile/TorchInductor 从标准 PyTorch 代码生成优化的 Triton kernel,XLA 为 JAX/TPU 工作负载提供全程序优化,TensorRT 专门化稳定的推理图。通信-计算重叠在分布式边界应用相同的瓶颈逻辑,在通信能在有用计算下运行时隐藏网络延迟。推测解码和 MoE 更进一层:它们不再优化相同的稠密计算,而是通过每次目标模型前向传递获得更多被接受的 token,或仅激活 token 所需的专家,来重构计算本身。系统 profiling 闭环,展示每次变更后哪个项占主导,使下一次干预遵循新瓶颈而非旧直觉。
第 9.10.3 节的案例研究演示了这些技术在实践中如何复合生效:INT4 量化释放内存以容纳更大批次,这改变了算术强度,进而决定进一步的带宽或计算优化是否获利。每次优化都在持续转移瓶颈,要求重新 profiling 和新的优化决策。这种 profile-optimize-reprofile 循环正是性能工程的纪律:不使硬件更快,而是使软件匹配其运行所在硬件的物理特性。内存墙是随每代硬件愈发宽广的物理约束,因此工程师的应对是:让数据贴近计算,将精度降至保持质量的最低点,并重构算法以避免不必要的工作。Profiling 不仅是第一步;它是每一步。
这种迭代心态也决定了随硬件演进哪些技能长存。随着加速器世代更迭移动 Roofline Model 的屋脊点、架构变更改变主导计算模式,个别技术可能改变。根本纪律长存:测量系统,识别约束瓶颈,施用针对该特定约束的优化,然后再次测量。内化此循环的工程师将性能工程视为持续实践而非核对清单,而这种实践正是区分“仅能运行”的系统与“能在规模上高效运行”的系统的分水岭。
-
字节通常为瓶颈:在 H100 级加速器上,本章屋顶线回顾将大多数 Transformer 工作置于
FP16屋脊点 295.2 FLOP/byte 之下,因此有效加速首要来自减少 HBM 流量、将中间结果保留在 SRAM 中、仅在计算能移动活跃瓶颈时才投入算力。 -
融合使数据局部性落地:算子融合、CUDA Graph 与
FlashAttention不只是 kernel 技巧;它们消除启动开销和实体化注意力状态。在 8K 示例中,分块注意力将存储的中间状态削减 65 倍,将内存压力转化为可用吞吐。
大规模推理
核心原则
-
精度以风险换取带宽:只有在妥善管理异常值、缩放因子和质量检查时,FP8、INT8 和 INT4 才能增加有效带宽和批处理容量。量化是数值格式、内核实现、服务内存和可接受模型行为之间的系统契约。
-
算法可移动屋顶线:投机解码和专家混合模型改变了每次目标模型前向传播所执行的有用工作量,但它们引入了接受率、路由、AllToAll 和负载均衡约束。只有在测量了通信和调度成本后,收益才是真实的。
-
剖析贯穿每一步:70B 案例研究展示了优化即瓶颈转移:INT4 改变批大小,批大小改变算术强度,下一个限制随之移动。车队性能工程意味着测量、优化绑定项,然后在相信加速效果前再次测量。
硬件以其峰值性能售出,却靠其维持性能买单,两者之间的差距几乎完全源于数据移动。此处的每一种技术(FlashAttention 最为显著)都通过让字节远离内存与计算间的慢速路径来缩小这一差距。这又是内存墙,同一个物理限制,只是在软件栈中向上移了一层:在硬件层它设定了天花板,而在这里它是软件竭尽全力试图触及的目标。加速器标称的数字一直是真实的;本章讲的是车队实际能保留多少。
算子融合、精度降低、图编译,以及投机解码和 MoE 等算法创新,从在裸金属上执行的单个模型中榨取了最大的计算效率。然而,优化孤立的前向传播仅仅是部署的前奏。在生产环境中,同一个模型必须处理并发请求突发、与其他工作共享加速器,并在请求长度和到达率变化的同时保持延迟预算。第 10 章 将从局部执行机制过渡到健壮服务系统的架构。此处开发的底层优化成为使高可用服务成为可能的基元:它们减少单请求内存、增加有效批大小,并给调度器更多空间来保护尾延迟。
此处用于确保在部分开始前正确插入测验。
-
屋顶线模型和剖析器证据应如何决定优化内存流量、计算、启动开销还是通信?
-
分布式训练和集合通信成本如何限制局部内核优化的价值?
-
何时降低精度可在不违反质量或延迟要求的前提下提高有效带宽?
-
当融合、分块、FlashAttention 和 CUDA Graphs 各自都会转移下一个瓶颈时,应如何评估它们?
目的
为什么推理成本最终会远超训练成本,这对系统设计意味着什么?
训练模型是一次性支出;服务模型则是持续不断的。一个一次性训练花费数百万美元的模型,在其生命周期内可能服务数十亿次请求,而每次请求都消耗算力、内存、网络和能源预算。推理服务成本主导定律捕捉了这一经济现实:对于成功的生产模型,持续的推理运营支出可能比一次性训练资本支出高出数个数量级。这种成本结构改变了架构:将推理延迟削减几毫秒或将加速器利用率提高几个百分点的优化,跨越数十亿次请求累积起来,就会变成可观的节省或成本。性能工程提供了局部工具箱——融合、精度、编译,以及投机解码等算法变更——而本章将这些工具应用于服务规模,在那里批处理、分片、路由、自动扩缩容和隔离必须在实时流量下保持严格的延迟预算。可靠性要求也随之增强,因为服务期间的停机意味着收入损失和用户体验受损,而不只是实验延迟。大规模推理是机器学习系统在经济层面成败的关键。服务车队是一项持续的 C³ 管理实践:为吞吐量批处理计算,同时将协调开销控制在严格的尾延迟预算内。
-
根据请求量、Token 组合、加速器价格、利用率和延迟目标量化全生命周期服务成本
-
为视觉、LLM、推荐和流式工作负载选择批处理和调度策略
-
分析面向内存受限语言模型服务的注意力缓存容量、碎片化及解码时策略
-
使用通信、内存和质量约束对比分片、解耦和量化服务设计
-
根据尾延迟和嘈杂邻居风险评估路由、负载均衡和隔离控制
-
设计自动扩缩容和多区域故障转移策略,以平衡冷启动、成本和 SLO 合规性
-
综合服务架构,以管理请求、副本、服务和平台层面的 C³ 权衡
推理的经济学与架构
设想一个语言模型,训练耗资 500 万美元,历时两个月。一旦部署到全球用户群,每秒服务数千次查询,同一个模型每周就会在推理算力上烧掉 500 万美元。推理经济学强制进行根本性的架构转变:我们不再优化一个临时的批处理作业;我们在运营一个持续运行、对延迟敏感的工厂。
算子融合、精度工程和图编译可以最大化单次前向传播的吞吐量。下一个挑战出现在单个优化模型必须跨全球分布式车队为数千并发用户服务时。单机推理优化(包括批处理、缓存、模型优化和硬件加速)提供了构建模块。当这些技术触及极限时,分布式方法变得必要。
分布式推理系统必须解决单机规模下不存在的问题。当请求必须分发到数百个 GPU 实例同时保持延迟保证时,负载均衡[¹⁴⁸]变得至关重要。请求路由必须考虑模型特定特征:拥有万亿参数嵌入表的推荐系统所需的放置策略,不同于逐 Token 生成响应的大语言模型。自动扩缩容必须预测需求波动,请求量可能在几分钟内变化数个数量级,同时维持用户期望的延迟界限。
大规模推理经济学与训练经济学有根本不同。训练成本主要由计算时间主导,可摊销到模型的整个生命周期。推理成本直接与用户流量和收入挂钩。电商推荐系统在购物高峰期可能每秒服务数百万次请求,每次请求直接贡献潜在收入。闲暇期过度配置或高峰期配置不足的成本会立即转化为业务影响。推理效率成为一级关注点,这是训练效率很少达到的地位。构建这样的服务需要三个相关决策:何时分发变得必要、架构如何在变化负载下保持延迟界限、以及资源利用率如何保持足够高,使服务成本不侵蚀模型创造的价值。
单机服务何时不再足够
三个不同的信号表明分布式推理何时从“可选”转变为“必要”._ 表 10.1 按约束类型和相应策略对这些触发器进行了分类。
第一个信号是内存耗尽,当模型参数、键值缓存或嵌入表超过单设备容量时发生。单张 NVIDIA H100 GPU 提供 80 GB HBM3¹⁴⁹ 显存;_ 附录 G.1 记录了该容量数值的来源,以及后续分析中使用的其他标准硬件常数。GPT-4 级别的模型远超该容量:公开的 ~1.8T 参数混合专家模型估算值意味着仅 FP16 精度的权重就需要 ~3.5 TB,无论吞吐需求如何,都强制分布到多张 GPU 上。拥有万亿参数嵌入表的推荐系统面临类似约束:Meta 的 DLRM 架构¹⁵⁰存储的嵌入表需要数 TB 内存。
除了内存约束,当请求量即使在最优批处理下仍超过单机容量时,吞吐限制便会出现。考虑一个每秒服务 100,000 次查询(QPS)、延迟预算 10 ms 的推荐系统。若单机吞吐峰值为 10,000 QPS,该机器上再多的优化也无法满足需求。跨多副本的水平扩展成为强制选项。
最后,严格的延迟要求会在模型执行时间即使在批大小为 1 时仍超出延迟预算时驱动分布式部署。大语言模型逐 token 生成响应时对此约束尤为敏感。700 亿参数模型在 FP16 下约需 140 GB 显存,尚未计入 KV 缓存就已超出单张 80 GB H100 级 GPU 容量。即使量化或大显存硬件使单副本配置成为可能,解码吞吐仍常受限于内存带宽。将模型跨多 GPU 分片可实现并行计算,将首 token 时间(TTFT)降至可接受阈值以下。
| 约束类型 | 单机极限 | 示例负载 | 分布策略 |
| --- | --- | --- | --- |
| 内存 | 80 GB (H100) | GPT-4 (~3.5 TB FP16) | 张量/流水线并行 |
| 吞吐 | ~10K QPS (视觉) | 100K QPS RecSys | 水平复制 |
| 延迟 | 模型执行时间 | 500 ms LLM TTFT | 模型分片 |
表 10.1:分布式推理的触发条件:每种约束类型指向不同的分布策略。内存约束要求模型分片;吞吐约束要求复制;延迟约束视瓶颈是计算还是内存带宽而定,可能需要上述任一策略。
一旦交互式端点开始流式传输,内存、吞吐和延迟触发器均会变为用户可见。首 token 时间 ¹⁵¹ 决定用户何时看到响应开始,而每输出 token 时间 决定剩余响应能否以可读速度到达;这正是推理将许多训练时的优化优先级颠倒过来的原因。
验证你对何时从单机迁移到分布式推理的理解:
根本性的反转:训练 vs. 推理
训练与推理优化的对比远不止基本的吞吐与延迟之分。训练优化目标为每小时处理样本数,可容忍延迟波动。推理优化目标为响应时间,必须满足严格延迟界限。大规模下,这种反转体现在系统架构、资源分配和运营优先级上。_ 表 10.2 详述了这两者差异显现的六大关键系统层面。
| 层面 | 分布式训练 | 分布式推理 |
| --- | --- | --- |
| 核心指标 | 吞吐 (样本/小时) | 延迟 (P99 ms) |
| 可接受方差 | 小时级 | 毫秒级 |
| 状态管理 | 检查点 (周期性) | 会话状态 (持续性) |
| 批次构建 | 大规模、可控 | 请求驱动、可变 |
| 容错机制 | 从检查点重启 | 重定向且无用户感知影响 |
| 成本结构 | 固定时长、可变费率 | 可变时长、固定 SLO |
表 10.2:训练与推理的系统需求对比:从吞吐到延迟优化的根本性反转,波及系统设计的方方面面。
训练可容忍大量的延迟方差,因为优化目标是数小时或数天内的累积进展。耗时 2 秒而非惯常 1 秒的训练迭代属可接受变动。而耗时 2 秒而非 100 毫秒的推理请求则意味着灾难性故障,可能导致用户流失或引发下游服务的级联超时。
状态管理本质不同。训练维护模型状态(参数、优化器状态),其缓慢演变且可通过周期性检查点捕获。推理常维护会话状态(对话历史、键值缓存、用户上下文),必须跨请求保留,且无法容忍基于检查点恢复带来的陈旧性。
故障处理随之分歧。训练故障触发检查点恢复并继续,数分钟的进度损失可接受。推理故障必须对用户不可见。请求重定向至健康副本,降级结果替代不可用模型,且尽管基础设施不稳定,SLO 仍须得到维持。
服务税:分布式开销
将推理分布到多台机器上会引入单机服务中不存在的开销。这种“服务税”必须被理解并在延迟约束内预算。
网络通信为每次跨机器交互增加延迟。数据中心内,网络往返时延因拓扑和拥塞而异,范围在 50–500 微秒。对于需在不同机器 GPU 间同步的模型分片,每个同步点都会增加此开销。跨 8 台机器分片且每次推理有 4 个同步点的模型,将增加 200 微秒至 2 毫秒的网络延迟。_ 附录 D.1 给出了 α-β 模型,将每次传输分解为固定启动延迟加带宽相关项,为在延迟 SLO 下预算这些同步成本提供了定量框架。
序列化开销加剧了问题,它将内存张量转为网络可传输格式。传统序列化器(如 Protocol Buffers、JSON、Python 的 pickle)每次传输都执行解析-分配-复制循环,1 GB 的激活张量经此类格式序列化和反序列化约需 100 毫秒。零拷贝替代方案(如 FlatBuffers 和 Cap’n Proto¹⁵²)通过就地访问线缆格式数据规避了大部分成本,但强制了更严格的模式布局要求,并非所有生产技术栈都采用。
负载均衡器延迟增加另一层开销。请求须路由至合适副本,这要求检查请求元数据、查阅路由表并转发至选定后端。优化良好的负载均衡器增加 100–500 微秒;配置不当者可增加数毫秒。
协调开销
当请求需要扇出到多个服务时,会出现协调开销。一个并行查询用户模型、物品模型和排序模型的推荐系统,必须协调这些查询并聚合结果。协调逻辑本身会消耗 CPU 周期并引入延迟抖动。
总服务税通常会消耗分布式系统中 10%–30% 的延迟预算,如公式 10.1 所示:
T_total = T_compute + T_network + T_serialization + T_coordination + T_queuing (10.1)
最小化该税负要求将通信组件共置,使用高带宽互连,并设计通信模式以最小化往返次数。
服务成本可主导训练成本
全生命周期服务成本远超一次性训练成本。
上述量化的服务税按请求消耗延迟预算的一部分。当我们考虑模型整个运行生命周期内的成本时,推理的真实经济影响才会显现。服务成本主导定律(原理)指出,服务成本可以比训练成本高出几个数量级,因为训练是一次性资本支出(CapEx),而服务是持续运营支出(OpEx),会随用户增长而扩展。一个快速的成本计算使该倍数具体化。
问题:一个团队花费 200 万美元训练了一个 70B 参数模型,现在为 100 万日活用户(DAU)提供服务,每位用户每天发起 50 次请求。在 1 年内,是训练还是服务占主导成本?
数学计算:
-
训练成本:200 万美元(一次性)。
-
指标:年推理量为 10⁶ users × 50 reqs × 365 days ≈ 182.5 亿次请求/年。
-
每次请求成本:假设在 H100 上运行 70B 模型,成本约为 0.001 美元/请求(输入 + 输出)。
-
年服务成本:182.5 亿次请求 × 0.001 美元/请求 = 1825 万美元。
系统洞察:仅第一年,服务成本就是训练成本的 9.1 倍。服务成本或延迟相关效率提升 10%,第一年可节省约 180 万美元,几乎覆盖了原始训练运行的成本。
模型运行的总成本包含训练成本(一次性支出)和服务成本(持续支出),如公式 10.2 所示:
C_total = C_training + C_serving × T_deployment × Q_rate (10.2)
其中 C_training 是训练模型的一次性成本,C_serving 是每次查询的服务成本,T_deployment 是部署时长(以适当的时间单位计),Q_rate 是查询速率。
相同的杠杆效应适用于具有不同成本结构的各类应用。一个训练成本为 1.2 万美元(3000 美元硬件成本用于 1000 GPU 小时,每 GPU 小时 3 美元,加上 3 倍的数据和实验工程成本)的推荐模型(DLRM),以 1 万 QPS 服务 730 天,累计 6.31 × 10¹¹ 次生命周期查询,按每百万次查询 10 美元计算,服务成本为 630.72 万美元——服务优化相对于训练优化具有 525.6 倍的杠杆率。成本主导比率因应用而异。表 10.3 量化了这种差异:
| Application | Training Cost | Annual Serving Cost | Ratio |
| --- | --- | --- | --- |
| 推荐 (高 QPS) | $10K–$100K | $1M–$10M | 100–1000× |
| 搜索排序 | $100K–$1M | $10M–$100M | 100–1000× |
| LLM API | $1M–$100M | $10M–$1B | 10–100× |
| 内部分析 | $1K–$10K | $10K–$100K | 10–100× |
表 10.3:训练与服务成本比率:高 QPS 应用(如推荐系统)显示了服务成本相对于训练成本最极端的主导地位。
这种成本结构激励服务优化:每个百分点的效率提升都会在模型的整个运行生命周期内产生持续的成本降低。图 10.1 阐释了优化为何重要,展示了朴素服务与优化服务之间的差距如何决定推理服务是盈利还是亏损。

图 10.1:服务成本曲线:随着规模扩大和优化深入,每 1000 个 Token 的成本下降。单 GPU 服务仅在极高吞吐量下达到盈亏平衡。多 GPU 批处理在中等吞吐量下达到盈亏平衡。完全优化的服务(INT4、连续批处理、推测性解码)在更低的吞吐量下即可实现盈利,从根本上改变了推理的经济性。
表 10.3 中的成本比率讲述的是静态故事,但成本累积的动态揭示了更引人注目的模式。图 10.2 绘制了三个典型场景在 36 个月窗口内的累计总部署成本:低流量小模型、服务 100 万日活用户的 70B LLM,以及服务 1 亿用户的推荐系统。垂直交叉标记显示累计服务支出何时等于原始训练成本;由于绘制的曲线包含训练和服务两部分,总成本曲线在该标记处大约是训练基线的两倍。对于推荐系统,服务在几天内即占主导;对于高流量 LLM,大约在六周内。只有训练昂贵且流量低的小模型,训练成本才会在较长时期内占主导。这一时间视角强化了为何在生产规模下推理优化值得尽早投入工程关注。对于生成式 LLM 服务,面向服务的设计可直接转化为吞吐量、成本和功耗的收益(Patel et al. 2024)。

图 10.2:服务成本交叉点:三个场景的累计总部署成本(初始训练加持续服务)。垂直标记指示服务部分等于一次性训练成本的时刻。对于高流量 LLM,服务成本在几周内匹配训练成本。对于日活 1 亿用户的推荐系统,服务在几天内即占主导。只有低流量、昂贵训练的模型,训练成本才会在较长时期内占主导。
推理全景:超越 LLM
一个常见的误解将大规模推理等同于 LLM 服务。大语言模型提出了独特的挑战并吸引了关注,但它们只是生产推理的一部分。恰当的技术选择需要理解完整的推理全景。在大型消费平台上,推荐和排序工作负载可以主导 AI 推理周期和容量,即使它们不是唯一的用户可见模型类(Gupta et al. 2020;Hazelwood et al. 2018)。视觉和图像处理、NLP 和 LLM 工作负载、欺诈检测、广告及其他分类任务构成了其余部分。表 10.4 从服务压力和优化挑战的角度定性地分解了这些模型类型,对比显示每个类别受限于不同的约束:推荐受限于嵌入查找,视觉受限于批处理效率,LLM 受限于内存带宽,语音受限于顺序解码。没有单一优化能服务于所有四类。
推荐系统通常主导高容量消费者推理,因为它们为许多用户交互提供预测。每次页面加载、滚动或点击都可能触发排序或检索推理。浏览电商网站的用户在单个会话中可能产生许多推荐请求。相比之下,LLM 查询通常需要显式的用户动作,发生频率较低。
生产推理格局
分布情况对技术选择有直接影响。推荐系统推动了重要的生产推理创新:动态批处理、嵌入分片、特征存储架构和低延迟服务,这些最初主要是为推荐工作负载开发的(Naumov et al. 2019;Gupta et al. 2020)。针对 LLM 的特定技术(如连续批处理和 KV 缓存管理)解决了生产推理的另一个切面。文生图系统(如DALL-E)提供了一个多模态模型示例(Ramesh et al. 2021),但多模态服务量和延迟目标仍然因产品而异。
| 模型类型 | 服务压力 | 延迟目标 | 关键挑战 |
| --- | --- | --- | --- |
| 推荐/排序 | 消费者平台中极高 | <10 ms P99 | 嵌入查找 |
| 视觉 (CNN) | 取决于工作负载 | 20–100 ms | 批处理效率 |
| LLM | 每次请求成本高 | 100 ms–10s | 内存带宽 |
| 语音/音频 | 流驱动 | 实时 | 顺序解码 |
| 多模态/文生图 | 新兴且产品特定 | 不固定 | 跨模态协调 |
表 10.4:生产推理格局:不同模型类型产生不同的服务压力、延迟要求和优化挑战。该表是定性工作负载图谱,而非通用流量份额分布。技术选择必须匹配特定工作负载。
服务层级
优化技术组织成一个服务层级,这类似于计算机架构中的存储层级。每一层拥有不同的瓶颈,因此对某一层有帮助的优化可能对另一层无效。请求层级跟踪一个请求经过预处理、批处理、缓存和模型执行,其目标是用户所见的延迟。副本层级审视单个模型实例内部,GPU 利用率、内存管理、内核效率和模型优化决定了该副本在饱和前能提供多少有效吞吐量。服务层级跨越同一模型的多个副本,利用负载均衡、请求路由和自动扩缩容将单个副本转化为聚合产能。平台层级跨越服务和租户,资源分配、多租户、调度和放置决定了共享服务集群能否保持高效,同时不让某个工作负载降级另一个。
层级之所以重要,是因为每一层改变不同的指标,并在不同边界失效。相关部署栈见图 10.3,展示了请求在生产环境中如何通过边缘、路由和模型服务基础设施。
图 10.3:服务部署栈:一个三层服务栈,自上而下累积延迟预算。第 1 层 (CDN/边缘缓存) 处理地理分发和静态响应缓存 (SLA <10 ms)。第 2 层 (网关/路由器) 处理请求路由、限流和认证 (SLA <50 ms)。第 3 层 (模型服务集群) 运行带有自动扩缩容和连续批处理的 GPU 工作节点 (SLA <2 s)。
每一层都有独特的优化杠杆,表 10.5 说明了为何杠杆具有层级特异性:请求层级的变更降低单请求延迟,副本层级的变更提升利用率,服务层级的变更增加聚合产能,平台层级的变更提升跨租户的集群效率。优化错误的层级会移动错误的指标。
| 层级 | 优化目标 | 关键技术 |
| --- | --- | --- |
| 请求 | 单请求延迟 | 动态批处理、缓存、预取 |
| 副本 | 吞吐量、利用率 | 内存优化、内核融合 |
| 服务 | 聚合产能 | 负载均衡、路由、自动扩缩容 |
| 平台 | 资源效率 | 多租户、调度、放置 |
表 10.5:服务层级优化目标:层级的每一层用不同技术解决不同指标。
验证你对特定优化位于服务层级何处的理解:
层级指导后续设计,同时允许少数技术跨越层级。批处理、KV 缓存布局和解码时优化始于请求层级。量化、适配器状态和模型分片改变副本的内存和计算预算。解耦服务、负载均衡和自动扩缩容将副本协调为服务,而多租户和资源隔离治理平台。量化贯穿整个层级,因为它是一种表示层面的杠杆,无论模型状态或 KV 状态驻留何处,都会改变内存、带宽和成本预算。
服务架构维度
一个每秒处理 100,000 次嵌入查找、跨越分片特征存储的推荐系统,其所需的服务架构与一个逐个生成 token 的 700 亿参数语言模型截然不同。这种差异不仅仅是框架选择的问题;它反映了批处理策略、内存管理、调度策略和部署拓扑方面截然不同的约束。本节不罗列具体工具,而是识别区分服务系统的架构维度及驱动各项设计决策的约束。具体框架作为这些原则的示例,而非研究对象。
批处理策略:吞吐量-延迟权衡
服务系统中后果最重大的架构决策是如何从传入请求中形成批次。这一选择决定了基本的吞吐量-延迟工作点。
静态批处理在分发给加速器前收集固定数量的请求。对于处理固定大小输入的视觉模型,此方法能最大化 GPU 利用率,因为批次中所有请求执行相同的计算图,且具有可预测的内存访问模式。ResNet-50 推理服务器可将 32 或 64 张图像批处理,实现近线性吞吐量扩展,因为批处理分摊了固定的启动开销,并在多个输入间复用驻留权重。
自回归语言模型打破了这一假设。每个请求生成不同数量的输出 token,因此静态批处理强制所有请求等待批次中最长的生成,并在较短请求完成后让加速器产能闲置。连续批处理¹⁵³ 通过允许请求在每个解码步骤进入和退出批次(一种由 Orca 引入的迭代级调度方法)解决了此问题(Yu et al. 2022)。当一个请求完成生成,新请求立即填补其空位。vLLM 等系统将这种调度风格与基于 PagedAttention 的 KV 缓存管理相结合,通过减少内存浪费,比先前的 LLM 服务系统实现了 2–4 倍的吞吐量提升(Kwon et al. 2023)。
推荐系统引入了第三种模式:特征并行批处理。由于瓶颈在于分布式嵌入查找而非稠密矩阵乘法,这些系统按特征类型对请求分批,并将批次跨嵌入服务器分片。随后的稠密 MLP 计算可对预取、预批处理的特征向量进行操作。批处理策略一节(第 10.3 节)将全面定量地展开这些方法。
内存管理:从预分配到分页
服务系统在管理加速器内存方面存在根本差异,而这一差异决定了最大并发请求容量。
KV Cache 是一个用于 LLM 推理的、按请求划分的内存缓冲区,用于存储所有先前生成 token 的 Key(键)和 Value(值)注意力张量,以便在无需重新计算前缀的完整注意力的情况下生成下一个 token。
-
重要性:它将每 token 的注意力计算复杂度从序列长度的
O(n²)降低至O(n),但其占用空间随上下文长度和批次大小线性增长,往往消耗的 HBM(高带宽内存)超过模型权重本身,从而成为服务容量的瓶颈约束。F.1.2 节 推导了基础的尺寸计算数学原理。 -
区别:与模型权重(在所有请求间共享且静态分配)不同,
KV缓存是请求私有且动态调整大小的;每个并发用户需支付一笔与序列长度成正比的独立内存开销,调度器必须为其预算。 -
常见误区:一个常见的误解是,成比例地减小模型规模就能成比例地减少服务内存。实际上,在长上下文场景下,
KV缓存占据主导地位,因此量化后的 70B 模型仍可能受限于容量,无法承载仅凭权重大小本可轻松容纳的批次大小。
预分配内存管理在接纳请求时,根据最大可能输出长度为每个请求保留固定的内存预算。对于支持 4,096 token 输出的模型,无论请求生成 50 个还是 4,000 个 token,每个请求都会为 4,096 个 token 预留内存。这种方法简单且可预测,但会浪费与最大输出长度与实际输出长度差距成比例的内存。实践中,60%–80% 的预留 KV 缓存内存处于闲置状态。
分页内存管理受操作系统虚拟内存启发,以固定大小的块(页)分配内存,并通过块表将逻辑序列位置映射到物理内存位置。随着请求生成 token,新的物理块按需分配。生成完成后,块立即返回空闲池。这种方法以 PagedAttention 为例(详见 10.4.3 节),通过消除内部碎片(部分填充的预分配)和外部碎片(分配间不可用的间隙),实现了近 100% 的内存利用率。
内存管理策略直接决定批处理容量:在相同硬件上,分页系统可比预分配系统多服务 2–4 倍的并发请求,因为它们回收了预分配浪费的内存。对于 GPU 预算固定的服务,这直接转化为每请求成本降低 2–4 倍。
调度策略:FCFS、抢占式与优先级感知
调度策略决定哪些请求获得 GPU 时间以及顺序。当请求组合异构时(某些请求需 50 个输出 token,另一些需 4,000 个),这一决策变得至关重要。先来先服务(FCFS)调度按到达顺序处理请求。FCFS 公平且简单,但遭受头部阻塞之苦:单个长生成请求会延迟所有后续请求。对于输出长度方差大的工作负载,FCFS 会产生较差的尾部延迟。
抢占式调度允许系统暂停长时间运行的请求,并将其 KV 缓存换出至 CPU 内存(或丢弃以便稍后重新计算),从而为更短、优先级更高的请求腾出空间。抢占的代价是换出开销(在 GPU 和 CPU 内存间传输 KV 缓存)或重新计算成本(当被抢占的请求恢复时重新运行 prefill 阶段)。生产系统通常在某请求消耗的生成长度超过中位数的 2 倍时进行抢占。
优先级感知调度为请求分配不同的服务等级,并保证高优先级请求在低优先级请求之前获得 GPU 时间片。生产 API 可能将创收的客户请求归类为关键级、内部批处理为标准级、免费流量为尽力而为级。调度策略随后确保关键请求永远不会在负载高峰时排在尽力而为流量之后。
部署拓扑:从单 GPU 到解耦部署
部署拓扑决定了模型计算如何映射到物理硬件,这种映射由模型大小与单设备内存容量之比驱动。单 GPU 部署是最简单的拓扑:整个模型适配于单个设备内存,所有推理计算在本地发生。对于参数量低于 150–200 亿(FP16 下约 30–40 GB)的模型,该拓扑提供最低延迟,因为它消除了所有设备间通信。
多 GPU 张量并行将每层的权重矩阵跨多个通过高带宽互联(H100 上 NVLink 达 900 GB/s)连接的 GPU 进行分片。每个 GPU 为每一层计算部分结果,在进入下一层前通过 all-reduce 操作同步部分结果。当模型权重超出单设备内存时,此拓扑成为必需,并能按并行度成比例降低延迟,代价是每层产生通信开销。模型分片一节(10.5 节)定量分析了通信开销。
解耦服务将 prefill 阶段(处理输入提示词,计算密集型)与 decode 阶段(逐个生成 token,内存带宽密集型)分离到针对各自工作负载特性优化的不同硬件池上。Prefill 节点使用高 FLOP/s 加速器;decode 节点使用高带宽内存配置。这种分离使每个阶段都能在其硬件的最佳工作点运行,而非在共享硬件上在两种模式间妥协。解耦服务一节(10.4.9 节)详细阐述了该架构。
有状态与无状态:扩展性的分水岭
服务系统的有状态性决定了水平扩展是微不足道还是需要精细工程。视觉模型和嵌入查找通常是无状态的:任何副本均可服务任何请求,因为请求间无会话状态持久化。水平扩展直截了当——在负载均衡器后添加副本——故障恢复即时,因为负载均衡器仅需路由至健康副本。
带 KV 缓存的 LLM 服务本质上是有状态的:对话期间累积的缓存创造了副本特定的状态,若不通过 prefill 重新运行整个对话历史,则无法重构。这种状态性对系统设计产生级联影响。负载均衡需粘性路由,将对话中后续请求导向同一副本。缩容需要耗尽活跃会话,长对话可能耗时数分钟。故障恢复代价高昂:有状态副本崩溃时,用户要么经历重新生成上下文的延迟(秒级),要么完全丢失对话状态。
在无状态与有状态服务间的选择不是框架特性,而是模型架构的必然结果。服务自回归模型的系统必须为状态性做工程设计;服务固定计算模型的系统可将扩展视为更简单的容量规划练习。
架构对比
表 10.6 总结了批处理、内存、调度、拓扑和状态维度在主要工作负载类型中的相互作用。该表显示,没有单一的服务架构对所有工作负载都是最优的;每种工作负载类型的约束概况决定了在每个维度上合适的设计点。
| 维度 | 视觉/嵌入 | LLM(自回归) | 推荐 |
| --- | --- | --- | --- |
| 批处理 | 静态/动态(统一输入) | 连续(可变输出) | 特征并行(分片嵌入) |
| 内存 | 预分配(可预测) | 分页(可变 KV 缓存) | 分布式(嵌入表) |
| 调度 | FCFS(统一成本) | 抢占式(高方差) | 优先级感知(SLO 层级) |
| 拓扑 | 单 GPU 副本 | 张量/流水线并行 | 混合 CPU-GPU 分片 |
| 状态 | 无状态 | 有状态(KV 缓存) | 无状态(特征存储) |
表 10.6:按工作负载类型划分的服务架构维度:每种工作负载的约束概况决定了沿五个架构维度的最优设计点。选择服务系统等同于选择与工作负载约束相匹配的组合。
验证您对工作负载约束如何驱动架构选择的理解:
架构维度决定了为给定工作负载选择哪个服务系统。工程师不是逐个特性地比较框架,而是识别工作负载在各个维度上的位置(批处理模式、内存概况、调度需求、部署拓扑和状态性),并选择与之匹配的系统。本章中的优化技术在此架构框架内运行,每种技术解决特定维度的问题。
大规模批处理策略
每秒都有数百个截然不同的聊天请求流向推理服务器。逐个处理它们会让 GPU 大部分时间处于空闲状态,在微小的解码步骤之间得不到工作。等待收集一个大的静态批次会迫使排在第一位的请求忍受不可接受的延迟。大规模批处理策略要求动态算法即时融合请求,且不违反严格的尾部延迟截止时间。
将多个请求放在一起处理,将固定成本(模型加载、内核启动开销和内存传输延迟)分摊到更多有用工作中,以每请求更高的延迟为代价,换取吞吐量的显著提升。单机服务通过 动态批处理 应用这一洞见,它在处理前的时间窗口内收集请求,将它们放在一起处理。
在大规模下,批处理变得更加复杂,因为不同的模型架构有独特的批处理需求。对视觉模型最优的策略可能对 LLM 是灾难性的,而为推荐系统开发的技术可能两者都不适用。
在大型消费者平台中,推荐系统可能构成 AI 推理周期或容量压力的大部分,视觉、语言、排序、欺诈、广告和其他分类工作负载共享其余部分(Gupta 等人 2020;Hazelwood 等人 2018)。尽管存在这种分布,我们按概念复杂度顺序介绍批处理策略:视觉(直接批处理)、LLM(带 KV 缓存的连续批处理)和推荐(分布式嵌入的特征并行批处理)。这种教学顺序循序渐进地建立理解,即使从业者可能最先遇到推荐工作负载。以下分类学将批处理策略与模型特征相匹配,提供何时适用每种方法及预期性能的定量分析。
为何批处理因模型类型而异
批处理效率取决于计算如何随批次大小缩放,相对于内存和通信如何缩放。不同模型架构表现出不同的缩放关系,需要不同的批处理策略,如图 10.4 所示。
图 10.4:批处理策略对比:三条时间线展示了批处理策略如何在等待时间和 GPU 利用率之间权衡。(a) 无批处理:请求按顺序处理,请求之间 GPU 空闲(利用率约 40%)。(b) 静态批处理:请求等待直到组装成完整批次后再调度,对短请求进行填充(利用率约 70%)。(c) 动态批处理:在短时间窗口内用自适应大小组装批次,减少等待并提升利用率(利用率约 85%)。
对于 视觉模型,包括处理固定尺寸图像的卷积神经网络 (CNN) 和视觉变压器,计算随批次大小线性缩放,而内存因权重共享而亚线性缩放。较大的批次以最小开销提高 GPU 利用率,使得具有大批次大小的静态或动态批处理成为最优选择。
对于处于解码阶段的 LLM,每个 token 的计算量相对于加载模型权重的内存带宽需求较小。瓶颈是 内存带宽,而非 计算。较大的批次将权重加载分摊到更多 token 上,显著提高吞吐量,但随着批次大小增长收益递减。
对于推荐系统,瓶颈通常是 嵌入查找 而非稠密计算。批处理策略必须针对并行嵌入访问模式进行优化,而非矩阵乘法吞吐量。
批处理的物理学:效率曲线
批处理不仅仅是一种启发式方法;它是由硬件利用率物理学支配的权衡。我们可以对批次大小 (B)、请求延迟 (T[lat]) 和吞吐量 (X) 之间的关系建模,以确定任何推理系统的最佳运行点。
此处 T[lat] 遵循标准排队论表示法,表示请求在系统中的时间。本书其他地方用 L[lat] 表示固定延迟开销或延迟组件;本节保留 T[lat] 作为小定律和批处理方程中使用的端到端请求时间变量。
延迟方程将每请求延迟分解为固定开销(内核启动、内存加载)和可变成本(每样本计算):
Tlat = T[fixed] + B × T[variable]
-
T[fixed]:每批支付一次的成本(例如,从 HBM 加载权重、内核启动延迟)。 -
T[variable]:增加一个请求的边际成本(例如,该样本的计算时间)。
吞吐量方程描述系统的容量:
X(B) = \\frac{B}{T\_{\\text{lat}}(B)} = \\frac{B}{T\_{\\text{fixed}} + B \\times T\_{\\text{variable}}}
由此产生的 批处理效率曲线 显示三个不同的区间。对于小批次大小 (B),吞吐量由 T[fixed] 主导,使系统受限于延迟(或开销),此时增加 B 产生超线性吞吐量增益。随着 B 变大,T[fixed] 项变得可忽略,吞吐量渐近逼近硬件极限 1/T[variable],使系统受限于计算(或 LLM 的带宽)。最优批次大小位于曲线的拐点,即吞吐量增益递减而延迟继续线性增长的点。
当延迟达到 SLO 时,吞吐量在该批次大小处饱和。
工程目标是找到满足 Tlat ≤ SLO 的最大批次大小 B。这一公式解释了为何视觉模型(高 T[variable])在较小的批次下就会饱和,而 LLM(由于权重加载导致高 T[fixed])则需要更大的批次,因而需要不同的调优策略。
问题:工程师正在优化两个服务:视觉模型(ResNet)和 LLM(70B)。各自在什么批次大小下会触及其效率曲线的“拐点”?
数学原理:拐点出现在变量计算成本(B × T[var])开始超过固定开销(T[fixed])之时。
-
视觉模型 (T[fixed] = 2 ms, T[var] = 1 ms):
-
拐点: B ≈ 2 ms / 1 ms = 2。
-
结果:这种简化的延迟模型在极小的批次下即达到其首个开销摊销拐点。生产环境中的 CNN 通常在硬件饱和前(批次 32–64+)仍能持续获得吞吐提升。
-
-
LLM 解码 (T[fixed] = 40 ms, T[var] = 0.5 ms):
-
拐点: B ≈ 40 ms / 0.5 ms = 80。
-
结果:LLM 需要海量批次来摊销昂贵的权重加载开销。
-
系统洞察:由于 LLM 的“固定开销”(从 HBM 加载 140 GB 权重)极大,系统直到批次大小 80 才达到效率拐点。因此 LLM 服务需要连续批处理和分页内存:系统必须将数百个并发用户打包进单个批次,仅以克服内存带宽瓶颈。
将这些批次级方程转化为系统级容量规划,需要排队论中的一个经典结论:Little's Law¹⁵⁴,它关联了任何稳定系统中的并发数、吞吐率和延迟。跨模型架构来看,表 10.7 显示批次选择遵循绑定瓶颈:视觉模型受限于算力,LLM 预填充和解码受限于显存容量或带宽,推荐系统受限于 Embedding 查找,语音模型受限于实时延迟。
| 模型类型 | 批处理策略 | 典型批次大小 | 关键约束 | 吞吐扩展性 |
| --- | --- | --- | --- | --- |
| 视觉 (CNN) | 静态/动态 | 32–256 | GPU 算力 | 至 64+ 近线性 |
| LLM (预填充) | 动态 | 1–64 | 显存容量 | 次线性 |
| LLM (解码) | 连续 | 100–1000s | 显存带宽 | 对数线性 |
| 推荐系统 | 特征并行 | 1000–10000s | Embedding 查找 | 取决于分片 |
| 语音 | 流式 | 1 | 实时性 | N/A (延迟绑定) |
表 10.7:按模型类型划分的批处理策略:每种模型类型都有由其计算瓶颈决定的特征批处理行为。
概念:在任何稳定的排队系统中,系统中的平均请求数 (Q[req]) 等于到达率 (λ[arr]) 乘以请求在系统中停留的平均时间 (T[lat])。
Q[req] = λ[arr] ⋅ T[lat]
应用:并发规划
-
目标吞吐 (λ[arr]):1,000 请求/秒
-
延迟 SLO (T[lat]):100 ms (0.1 s)
所需并发 (Q[req]):Q[req] = 1000 × 0.1 = 100 并发请求
容量规划:若单个 GPU 副本处理批次 8 时延迟为 80 ms:
-
单副本吞吐 = 8 / 0.08 = 100 req/s
-
仅吞吐下界 = ⌈1000 / 100⌉ = 10 个副本
-
SLO 规模副本数 = ⌈100 / 8⌉ = 13 个副本
验证:系统总并发 = 13 副本 × 8 批次 = 104,比 Little's Law 要求的 100 多出 4 个请求槽位。仅按吞吐下界配置将运行在稳定性边界;按 SLO 规模配置的集群运行在约 76.9% 的服务利用率,为排队方差和尾延迟留出了有限余量。
吞吐容量与延迟容量的区别正是排队论形式化的内容;第 F.1.1 节 推导了 M/D/1 等待时间分布,以确定服务 SLO 需要多少余量。
视觉模型的静态与动态批处理
视觉模型代表了最简单的批处理情况,因为输入经过预处理后尺寸统一,且计算遵循可预测模式。单机批处理原则直接适用,规模扩展引入了跨多副本形成批次的考量。
静态批处理在处理前精确收集 B 个请求。当请求到达可预测时,这能最大化 GPU 利用率,但在低流量期间会导致无界延迟。
动态批处理在最大时间窗口 T[window] 内收集请求,或直到达到最大批次大小 B[max],以先到者为准。泊松到达率 λ[arr] 下的期望延迟遵循 公式 10.3:
E[T[total]] = E[T[queue]] + T[batch] + Tinference (10.3)
其中 E[T[queue]] 为期望排队延迟,T[batch] 为批次形成延迟(最长 T[window]),Tinference 为批次大小 B 的推理时间。到达率通过排队项和形成项介入:泊松到达下平均到达间隔为 1/λ[arr],因此更高的 λ[arr] 能更快填满批次,同时压缩 E[T[queue]] 和 T[batch];而较低的速率则将它们推向 T[window] 上限。以下实例将使这些交互具体化。
考虑一个具有以下需求的视觉分类服务:
-
到达率:5,000 QPS
-
延迟 SLO:50 ms P99
-
批处理服务时间:批次=1 时 5 ms,批次=32 时总计 25 ms
-
副本数量:10 个(每个处理 500 QPS)
对于泊松到达率 λ[arr] = 500 QPS 的单个副本:
表 10.8 中的三种选择仅在于它们将多少延迟预算转化为批次大小:
| 策略 | 批处理窗口 | 期望批次 | 单请求计算 | 利用率 | 延迟结果 |
| --- | --- | --- | --- | --- | --- |
| 无批处理 | 0 ms | 1 请求 | 5 ms | ρ[serv] = λ[arr] × T[svc] = 500 × 0.005 = 2.5 | 过载;无法满足需求 |
| 动态 B | 10 ms | 5 请求 | 1.6 ms | ρ[serv] = 500 × 0.0016 = 0.8 | 约 15 ms 平均,约 30 ms P99 |
| 动态 C | 20 ms | 10 请求 | 1.2 ms | ρ[serv] = 500 × 0.0012 = 0.6 | 约 22 ms 平均,约 42 ms P99 |
表 10.8:动态批处理策略对比:选项 C 以更高的平均延迟为代价,实现了约 33% 更高的单副本容量,或 25% 更低的利用率。两种动态批处理策略均满足 50 ms P99 SLO。
系统洞察:动态批处理仅在延迟预算有富余时有用。服务策略将未使用的延迟余量转化为更高吞吐,但相同的窗口会违反已收紧的 SLO。
在多副本规模下,批次形成可在各副本本地进行,或在集中式批处理层进行。副本本地批处理让每个副本独立从其分配的流量中形成批次。这种方法实现简单,但在负载不均时可能导致副本间批次大小不均。集中式批处理使用批处理服务收集请求并分发已形成的批次给副本。这能实现更均匀的批次大小,但增加了中心化瓶颈和额外网络跳数。生产系统通常采用副本本地批处理,配合确保大致均匀流量分发的负载均衡,在无中心化复杂性的情况下获得集中式批处理的收益。
LLM 推理的连续批处理
自回归瓶颈(原理)支配着这一模式:在生成模型中,解码阶段严格受限于内存带宽,因为生成每一个 token 都必须加载整个模型权重集。吞吐量随批大小扩展,通过跨多个请求共享权重加载来实现,而非依赖算力。
连续批处理 是一种服务策略,它将批成员资格与迭代边界解耦,允许新请求在每个解码步骤进入,已完成的请求退出。
-
意义:它通过消除静态批处理固有的填充浪费和队头阻塞,最大化系统吞吐量(
X)。即使请求的序列长度差异巨大,也能确保 GPU 保持饱和状态。 -
区别:不同于静态或动态批处理(它们在请求层面分组请求),连续批处理在迭代层面运作,在每个时钟周期动态重塑计算张量。
-
常见误区:一个常见的误解是连续批处理“纯粹是调度器的变更”。实际上,它需要动态内存管理器(如
PagedAttention),因为不同请求的 KV 缓存以不同速率增长和收缩,阻止了静态内存预分配。
将批成员资格与迭代边界解耦即插槽复用:当一个请求达到其停止 token 时,调度器在下一个解码步骤将其 KV 缓存插槽移交给等待中的请求,而不是让容量闲置直到最长序列完成。
自回归语言模型带来了独特的批处理挑战,静态和动态方法均难以妥善应对。关键洞察源自 Orca 系统¹⁵⁵ (Yu et al. 2022):传统批处理强制批次中所有序列完成后,新序列才能加入,当序列在不同时间完成时造成算力浪费。
设想一个包含 8 个序列的批次。如果一个序列在 10 个 token 后完成,而其他序列需要 100 个 token,则已完成序列的 GPU 资源将闲置 90 次迭代。采用传统批处理:
对于具有高方差的真实输出长度分布,浪费算力可能超过 50%。连续批处理(也称为迭代级批处理)将批成员资格与迭代边界解耦。算法 5 将调度器描述为一个解码循环:已完成请求释放 KV 缓存页,等待请求进入空闲插槽,下一个批处理内核在重组后的活跃集上运行。这项技术是定义大规模 LLM 服务吞吐量-延迟权衡的核心——没有它,原型 A 工作负载(GPT-4/Llama-3,1.6.1 节)在经济上将不可行,因为其受内存带宽限制的解码阶段让计算核心在等待权重加载时闲置,只有通过交织无关请求才能使带宽饱和。
算法 5 连续批处理解码调度器
要求: 等待队列 Q;活跃集 A;KV 缓存页分配器;活跃 token 容量 C[tok];批处理解码内核
保证: 已完成请求的生成 token;持续刷新的活跃批次
-
当 服务运行中 执行
-
在容量和空闲 KV 缓存页允许的情况下从
Q准入请求;分配状态,加入A -
在
A中所有序列上运行一次批处理解码内核 -
追加各采样 token;扩展其 KV 缓存页
-
从
A移除已完成或长度受限的序列;将其页返回分配器 -
若 HBM 压力超过策略阈值 则
-
通过将其 KV 缓存换出到 CPU DRAM 来抢占低优先级序列
-
结束若
-
从
Q填充释放的容量,或恢复其页现已适配的暂停序列 -
结束当
在每次迭代中准入、完成和回填插槽,使活跃 token 容量不再因批次中最长序列而闲置,而抢占路径则在内存压力下增加 HBM 到 CPU DRAM 的传输延迟。因此吞吐量增益随输出长度方差缩放,且必须与 KV 缓存移动权衡。如 图 10.5 所示,静态批处理在等待最长请求时让 GPU 闲置,而连续批处理使其保持饱和。
图 10.5:静态批处理 vs 连续批处理:在静态批处理 (A) 中,批次内所有请求必须等待最长请求完成,GPU 才能开始下一批次,导致大量计算空闲时间(灰色阴影)。连续批处理 (B) 允许新请求在任一请求完成时立即进入批次,使 GPU 保持饱和,从而大幅提高吞吐量。
连续批处理吞吐量分析
图 10.5 中的对比引出了吞吐量分析:静态批处理让 GPU 在每个批次的最长序列持续时间内闲置,而连续批处理通过在每个迭代边界插入新请求消除了这些空闲间隙。连续批处理的动态批管理无论序列长度方差如何都能维持高 GPU 利用率。吞吐量提升取决于序列长度分布。对于变异系数 CV = σ/μ 的分布,增益约为 公式 10.4:
典型 LLM 输出长度的 CV ≈ 1.0 时,连续批处理可实现约 1.5 倍吞吐量提升。对于高变异性输出(对话 vs 代码生成),增益可达 2–4 倍。这种 1 + CV²/2 形式估计了输出长度分布上的平均增益;10.3.6.3 节 推导了一个互补的 1 + k ⋅ CV 上界,将单个最长序列与均值对比。分布估计预测典型吞吐量;最大值均值界限制了工作负载能达到的最佳情况。
这些分析增益直接转化为生产系统。以下实现研究考察 vLLM 如何通过迭代级调度、分页内存管理、抢占、吞吐量和 GPU 利用率实现连续批处理。
vLLM 通过几个关键机制实现连续批处理。迭代级调度在每个解码步骤评估哪些序列已生成序列结束 token(从批次移除)、哪些等待序列适配可用 KV 缓存插槽(加入批次),以及在内存压力存在时哪些序列应被抢占(换出到 CPU)。内存管理使用 PagedAttention(详见 10.4 节),实现无碎片的动态分配。序列完成时,其 KV 缓存页立即可供新序列使用。批处理解码内核尽管批次组成动态变化,仍在单个批处理操作中处理所有活跃序列。内核内将不同生成长度的序列填充至统一形状。
抢占与换出。连续批处理中的一个关键挑战是内存争用。随着序列在生成过程中增长,它们消耗更多 KV 缓存页。如果 GPU 内存耗尽,系统不能简单崩溃;它必须抢占运行中的请求。
vLLM 实现了一种类似于操作系统交换机制的虚拟内存机制。当内存耗尽时,调度器会识别低优先级请求(例如最近才启动的请求),并将它们的 KV 缓存块从 GPU HBM 交换到 CPU DRAM。这些请求会被暂停,直到内存再次可用,此时它们会被换回并恢复执行。这种机制以增加被抢占请求的延迟为代价,确保了系统在高负载下的稳定性。
典型性能:表 10.9 报告了在 8× A100 上运行 Llama-2 70B 的吞吐量和利用率提升:
| 批处理策略 | 吞吐量 (tokens/s) | GPU 利用率 |
| --- | --- | --- |
| 静态 (batch=8) | 400 | 45% |
| 动态 (timeout=50 ms) | 580 | 65% |
| 连续 | 1,200 | 92% |
表 10.9:Llama-2 70B (8× A100) 上的连续批处理吞吐量:在 8× A100 节点上对 Llama-2 70B 服务负载进行静态、动态和连续批处理时的每秒 Token 数和 GPU 利用率。
系统洞察:正如表 10.9 所示,连续批处理带来的 3 倍吞吐量提升源于消除了序列长度变化期间 GPU 的空闲周期。
吞吐量数据确立了当输出长度不同时连续批处理的优势,但并未显示增益的来源及其增长上限。量化传统批处理遗留的浪费,可将这种直觉转化为调度器可执行的具体数值。
定量分析:传统批处理与连续批处理
第一个基准是传统 LLM 批处理,其中不等的输出长度将请求异质性转化为浪费的解码周期。批处理浪费的数学原理精确揭示了损失了多少吞吐量,以及在什么条件下连续批处理能带来最大的改进。
传统批处理的浪费函数
传统批处理(也称为静态批处理)会让批次中的所有请求经历所有解码迭代,直到最长序列完成。对于批次大小 B 和输出长度 {S[1], S[2], ..., S[B]},执行的总计算量为:
O[traditional] = B × S[max] × c[decode]
其中 S[max] = maxi,c[decode] 是每个序列每次解码迭代的计算成本。然而,有用的计算量仅为:
公式 10.5 定义了量化这种低效的浪费率:
其中 S̄ 是平均输出序列长度。这表明浪费完全取决于批次内平均输出长度与最大输出长度的比率。对于均匀的输出长度 (S̄ = S[max]),浪费为零。对于高度可变的长度,浪费可能超过 50%。
实例分析:具有可变长度输出的 LLM 服务
考虑一个 GPT 类模型服务于四个并发请求,其生成长度如表 10.10 所示(单位:Token):
| 请求 | 提示长度 | 输出长度 | 总 Token 数 |
| --- | --- | --- | --- |
| R1 | 100 | 50 | 150 |
| R2 | 80 | 200 | 280 |
| R3 | 120 | 100 | 220 |
| R4 | 90 | 150 | 240 |
表 10.10:并发请求生成长度:GPT 类模型上的四个并发请求,具有不同的提示和输出长度。输出长度 4 倍的跨度(50 到 200 Token)驱动了本节分析的批处理浪费:一个包含四个请求的静态批次将每个请求都填充到最长长度,导致在最长请求完成生成前,对三个较短请求的计算资源被浪费。
问题:给定四个输出长度不等的 LLM 请求,量化传统批处理浪费了多少平均延迟,以及连续批处理如何回收这些空闲时隙时间。
变量:
-
每次迭代的解码时间(批次大小 4):20 ms
-
批次中的最大输出长度:200 Token (R2)
-
平均输出长度:(50 + 200 + 100 + 150)/4 = 125 Token
传统批处理:所有四个请求必须等待 R2 完成其 200 Token。
-
总解码迭代次数:200
-
总批次时间:200 × 20 ms = 4,000 ms
-
请求完成时间:
-
R1 在第 50 次迭代完成有效工作,但需等待至第 200 次迭代 → 延迟 = 4,000 ms
-
R2 在第 200 次迭代完成 → 延迟 = 4,000 ms
-
R3 在第 100 次迭代完成有效工作,但需等待至第 200 次迭代 → 延迟 = 4,000 ms
-
R4 在第 150 次迭代完成有效工作,但需等待至第 200 次迭代 → 延迟 = 4,000 ms
-
使用公式 10.5 进行浪费计算:
\(W = 1 - \\frac{125}{200} = 1 - 0.625 = 37.5\\%\)
GPU 执行了 4 × 200 = 800 次“序列-迭代”,但只有 500 次是有用的。
连续批处理:序列完成后即离开批次,新请求可以加入。
-
第 50 次迭代:R1 完成 → 时隙释放,新请求 R5 可加入
-
第 100 次迭代:R3 完成 → 时隙释放,新请求 R6 可加入
-
第 150 次迭代:R4 完成 → 时隙释放,新请求 R7 可加入
-
第 200 次迭代:R2 完成
连续批处理下的请求延迟(假设无排队延迟):
-
R1: 50 × 20 ms = 1,000 ms(比传统方式提升 4 倍)
-
R3: 100 × 20 ms = 2,000 ms(提升 2 倍)
-
R4: 150 × 20 ms = 3,000 ms(提升 1.33 倍)
-
R2: 200 × 20 ms = 4,000 ms(最长请求无提升)
平均延迟对比
-
传统:4,000 ms(所有请求)
-
连续:(1,000 ms + 2,000 + 3,000 + 4,000) / 4 = 2,500 ms
结果:连续批处理将平均延迟降低了 37.5%,与浪费率完全吻合。
系统洞察:连续批处理并不会让最长的请求变快;它阻止了较短的请求在等待最长请求时占用已完成的时隙。增益来自于在迭代边界重用时隙,这正是图 10.5 中展示的时隙复用模式。
连续批处理提供最大收益的场景
连续批处理分析表明,收益随输出长度方差的增加而扩大。一个有用的上界直觉来自于对比传统批次中最长序列与平均序列长度。设 CV = σ/μ 为输出长度的变异系数。如果批次中最长请求大约比均值高出 k 个标准差,则:
其中 k 是最大输出超过均值的标准差数量。由于调度器开销、KV 缓存压力和回填间隙会消耗部分理论增益,实际系统实现的增益低于此上界。因此表格报告的是有效浪费和加速比,其中加速比 ≈ 1/(1 − W[effective])。
表 10.11 量化了不同工作负载类型下的这种关系,包括检索增强生成(RAG) (Lewis et al. 2020):
| 工作负载类型 | CV | k | 浪费 (传统) | 加速比 (连续) |
| --- | --- | --- | --- | --- |
| 代码补全 | 0.3 | 2.5 | 16.7% | 1.2× |
| 聊天 (短回复) | 0.6 | 2 | 33.3% | 1.5× |
| 通用文本生成 | 1 | 2.5 | 50% | 2× |
| 创意写作 | 1.5 | 3 | 64.3% | 2.8× |
| 变长文档 RAG | 2 | 2.5 | 71.4% | 3.5× |
连续批处理的工作负载收益与实现权衡
表 10.11:按工作负载类型划分的连续批处理收益
输出长度方差 (CV) 越高,连续批处理带来的改进越大。输出长度可预测的工作负载(如代码补全)收益适中,而高度可变的工作负载(如包含不同长度文档的 RAG)则能获得显著改进。
系统模式
当输出长度不可预测、多种工作负载类型共享同一集群、请求量足以重新填补空出的槽位,且延迟 SLO 使提前完成具有价值时,连续批处理最有价值。在以下情况提供的收益极小:
-
输出长度均匀,如分类或嵌入生成
-
批大小太小,槽位复用无关紧要
-
请求量太低,无法重新填补空出的槽位
实现复杂度权衡
使连续批处理有价值的可变性,也使其更难运维。其性能收益伴随着系统工程师必须权衡的实现复杂度。
内存管理复杂度大幅增加。 传统批处理在批次形成时为每个序列分配固定的 KV 缓存区域,仅在整个批次完成时释放。连续批处理要求随着序列增长动态分配,并在完成时立即释放,这需要类似操作系统虚拟内存的复杂内存管理。
调度器复杂度相应上升。 传统批处理使用简单的 FIFO 调度:收集请求直到批次填满或超时,然后执行。连续批处理要求在每次迭代中决定接纳哪些序列、内存压力下抢占哪些序列,以及如何处理优先级类别。这将调度器开销从每批次 O(1) 增加到每次迭代 O(B)。
内核设计也必须适应。 批处理 GPU 内核传统上假设固定的批次组成。连续批处理需要能高效处理变长序列的内核,通常通过将多个短序列打包进共享注意力掩码,或使用支持动态批次成员身份的专用内存布局等技术实现。
表 10.12 总结了这些权衡:
| 维度 | 传统批处理 | 连续批处理 |
| :--- | :--- | :--- |
| 实现工作量 | 低 (标准框架) | 高 (自定义调度器、内核) |
| 内存开销 | 固定分配 | 动态 + 碎片管理 |
| 调度器延迟 | ~0.1 ms/批次 | ~0.5-1 ms/迭代 |
| 调试复杂度 | 确定性行为 | 状态依赖、难以追踪 |
| 吞吐量 (可变长度) | 基准 | 提升 1.5–3.5× |
| 吞吐量 (均匀长度) | 基准 | ~1.0× (无改进) |
表 10.12:传统批处理与连续批处理的权衡:连续批处理以实现复杂度为代价,为可变长度工作负载提供了显著的吞吐量提升。对于均匀长度的工作负载,复杂度开销可能不值得采用。
对于新的 LLM 服务部署,vLLM、TensorRT-LLM 或 TGI 等成熟的连续批处理框架提供了成熟的实现路径。决策点在于采用这些框架还是构建自定义服务基础设施。对于拥有现有传统批处理系统的组织,必须结合表 10.11 权衡迁移成本与工作负载的输出长度方差。
即使有成熟的服务框架,诊断尾部延迟异常也需要跨系统栈进行系统性调查。一个典型的 P99 延迟回归案例展示了车队栈方法论如何应用于基础设施、执行和服务层。
场景:某 LLM 服务系统表现出意外的高尾部延迟。P50 延迟 100 ms,P95 为 180 ms,均在 SLO 内,但 P99 飙升至 500 ms,违反了 200 ms 的目标。GPU 利用率看似健康,为 85%。
部署环境:系统运行在 4 块通过 PCIe Gen4 (每 GPU 32 GB/s) 而非 NVLink 互连的 A100-80 GB GPU 上。每 GPU 内存带宽 2.04 TB/s (HBM2e),足以应对解码操作,但 PCIe 限制了张量并行通信。对于需要在 GPU 间传输 100 MB 激活值的批次,原始带宽计算显示 PCIe 每个同步点约需 3.1 ms,而 NVLink 仅需 0.17 ms。真正的问题是调度策略:动态请求级批处理使用最大批次大小 32 和 50 ms 超时,最大的 5% 批次因 FIFO 批处理将短请求阻塞在长序列请求后而遭遇头部阻塞。
系统经验教训:根因不在硬件层,而在调度策略。实施带有按请求类型区分的批次大小限制的优先级感知调度:
-
请求分类:基于提示模式或用户提示,按预期输出长度标记请求(短:<50 tokens,中:50-200 tokens,长:>200 tokens)
-
差异化批处理:将短请求批次限制为 8,中等为 16,长为 32
-
优先级抢占:当 P99 接近 SLO 时,允许短请求抢占长时间运行的序列
实施这些变更后,P99 降至 185 ms。高平均 GPU 利用率可能掩盖仅影响少量请求但驱动 P99 的头部阻塞;第 10.7 节将展开完整的隔离框架,防止某一工作负载的尾部延迟拖累另一工作负载。
迄今为止考察的批处理策略沿着根本约束边界划分。视觉工作负载是计算密集型且张量形状固定:批次中每张图像执行相同算术运算,因此批次形成问题简化为尽可能紧凑地填满 GPU 计算流水线。LLM 工作负载是内存密集型且序列长度可变:KV 缓存按请求和按 token 增长,因此批次形成问题转向内存核算、驱逐策略和连续批处理提供的迭代级调度。每种模式产生不同的主导策略,因为稀缺资源不同:视觉受限于计算吞吐量,语言受限于内存容量。
推荐系统呈现第三种约束模式,两者皆不类似。稀疏嵌入查找,而非稠密矩阵乘法,主导了计算时间和内存流量。单个推荐请求可能触及分布在分片上的数百万嵌入表条目,而后续的稠密排序头相对较小。这种访问模式要求围绕特征类型和嵌入局部性而非请求形状或序列长度来组织批处理策略。
推荐系统的特征并行批处理
推荐批处理从按嵌入局部性而非请求形状分组工作开始。图 10.6 展示了请求如何在稠密排序头重新组合检索到的表示之前,被拆分为特定特征的路径。
图 10.6:特征并行批处理流水线:请求按特征类型(用户、物品、上下文)批处理,并行分派到专用嵌入服务器。检索到的嵌入随后被拼接,由稠密排序头处理。该架构通过解耦嵌入存储与稠密计算,实现了万亿参数级的扩展。
推荐系统在不同瓶颈下暴露了相同的调度原则。其计算模式包含四个阶段:
-
稀疏特征查找:检索用户、物品和上下文特征的嵌入
-
稠密特征处理:转换和归一化稠密特征
特征交互与排序
-
特征交互:计算特征间的交互(通常通过注意力机制或分解方法)
-
排序头:生成最终得分
稀疏嵌入查找往往主导延迟并决定批处理策略。特征并行批处理并行处理不同特征类型,而不是批量处理整个请求:
请求 1:[user_id_1, item_ids_1, context_1]
请求 2:[user_id_2, item_ids_2, context_2]
请求 3:[user_id_3, item_ids_3, context_3]
特征并行视角:
用户嵌入: [lookup(user_1), lookup(user_2), lookup(user_3)] → 并行
物品嵌入: [lookup(items_1), lookup(items_2), lookup(items_3)] → 并行
上下文特征:[process(ctx_1), process(ctx_2), process(ctx_3)] → 并行
然后:按请求合并特征用于排序
当嵌入跨服务器分片时,特征并行批处理是自然的:每个嵌入服务器处理其分片在批次中所有请求的查找。在 Meta 级别的请求量下,这将特征分片从存储细节转变为服务策略。
Meta 级推荐示例
考虑 Meta 的推荐基础设施,以每秒 1000 万次查询 (QPS) 的速度为平台提供服务:
请求特征:
-
每个请求查询约 100 个物品(候选排序)
-
每个物品需要 50 次嵌入查找(用户特征、物品特征、交叉特征)
-
每个请求总计:5,000 次嵌入查找
-
嵌入表大小:100 TB,分布在 1,000 个分片上
批处理策略:
以 1000 万 QPS 和 1000 个分片,每个分片接收:
在此场景下,速率为每个分片每秒 5000 万次查找。
单线程处理无法维持该速率。相反,系统使用 1 ms 的批积累窗口,在 1000 万 QPS 下产生 10,000 个请求的批次,每个分片每批次约 5 万次查找。
每个嵌入分片在一个批量操作中处理 5 万次查找,通过顺序内存访问模式实现高内存带宽利用率。
正如 表 10.13 所示,各阶段延迟构成包括请求路由、批积累、嵌入查找、特征处理和排序:
| 阶段 | 时长 | 备注 |
| --- | --- | --- |
| 请求路由 | 0.2 ms | 一致性哈希分片 |
| 批积累 | 0.5 ms (平均) | 1 ms 窗口 |
| 嵌入查找 | 2 ms | 批量处理,SSD 支持 |
| 特征处理 | 1 ms | 稠密计算 |
| 排序模型 | 1.5 ms | 最终评分 |
| 总计 | 5.2 ms | 满足 10 ms SLO |
表 10.13:推荐请求的延迟细分:在批处理示例中的请求量和分片数下,单个推荐请求的端到端延迟各阶段贡献。
系统洞察:推荐批处理围绕稀疏特征访问组织,而非围绕相同的请求张量。系统通过在消耗内存带宽的分片处批量处理嵌入查找而获益。
面向实时应用的流式推理
流式工作负载定义了批处理本身成为错误响应的边界。实时语音识别、视频分析和机器人技术要求输入到达时即刻处理,并以最小延迟完成。
流式推理增量处理输入,无需等待批次形成。在语音识别中,它随麦克风到达处理音频帧(10-20 ms 片段)。在视频分析中,它按捕获速率(30-60 FPS)处理帧,无需缓冲。在机器人技术中,它按控制回路频率(100-1000 Hz)处理传感器读数。
对流式应用而言,相关指标不是吞吐量,而是处理每个输入的时间:
其中所有组件必须在帧间间隔内完成。流式语音转文本流水线展示了这些延迟组件如何在严苛的实时约束下组合。
考虑一个采用 20 ms 音频帧的流式语音转文本系统:
延迟预算:100 ms 端到端(5 帧延迟)
表 10.14 追踪了包括特征提取在内的各阶段延迟,流水线必须满足这些延迟以保持在 100 ms 预算内:
| 阶段 | 时长 | 备注 |
| --- | --- | --- |
| 音频采集 | 0 ms (连续) | 麦克风缓冲区 |
| 网络传输至服务器 | 20 ms | 包括抖动缓冲区 |
| 特征提取 | 5 ms | MFCC 计算 |
| 编码器推理 | 30 ms | 流式 Conformer |
| 解码器步骤 | 15 ms | CTC 或转换器解码 |
| 文本格式化 | 5 ms | 大小写、标点 |
| 网络传输至客户端 | 15 ms | 响应传输 |
| 总计 | 90 ms | 满足 100 ms 预算 |
表 10.14:流式语音识别流水线延迟:在 100 ms 端到端预算下,对 20 ms 音频帧运行的流式语音转文本流水线各阶段延迟。
此处,Streaming Conformer 是低延迟语音编码器,而 CTC 和 transducer decoding 是增量解码器,它们从帧级声学状态发射文本,无需等待完整话语。
关键约束:
-
无批处理:每帧单独处理
-
有状态模型:编码器跨帧维护上下文
-
流水线并行:当帧 N 在解码器中时,帧 N+1 在编码器中
流式工作负载的 GPU 利用率通常为 30-50%,以换取延迟保证。
系统洞察:流式推理刻意牺牲加速器利用率以保持端到端延迟。绑定约束是帧间截止期限,而非平均吞吐量。
自适应批处理策略
一旦批处理针对特定工作负载,固定参数便变得脆弱。生产系统基于当前条件调整批处理行为:
流量自适应批处理根据到达率调整批处理窗口:
当流量高时,窗口收缩,因为目标批大小迅速填满。当流量低时,窗口延长但设有上限以限制最大延迟。
SLO 自适应批处理采取互补方法,监控延迟百分位数并激进调整参数:
if P99_ 延迟 > 0.9 * SLO:
减少 B_max 20%
减少 T_window 20%
elif P99_ 延迟 < 0.5 * SLO:
增加 B_max 10%
增加 T_window 10%
反馈回路在维持延迟余量的同时,在正常运行期间最大化吞吐量。请求感知批处理通过在形成批次时考虑请求特征增加了第三个维度。对 LLMs 而言,这意味着按预期输出长度(从提示类型推断)分组请求,按提示长度分组以最小化填充,并将延迟敏感请求优先放入较小批次。
生产服务基础设施体现了这些自适应原则。NVIDIA Triton Inference Server 实现了一个可配置的自适应批处理系统,展示了 SLO 感知和请求感知策略在实践中如何运作。Triton 暴露三个旋钮:max_batch_size(批大小上限)、batching_timeout_ms(等待批次形成的最长时间)和 preferred_batch_size(与内核效率对齐的目标批大小)。内部,调度器为每个首选批大小维护单独队列,并路由请求以最小化总延迟:
此优化同时考虑当前队列长度和生成批次大小的核心效率。表 10.15 展示了在 V100 上运行 ResNet-50 的结果:调度器在流量增长时自动增加批次大小以维持吞吐量,测得的吞吐量随提供的负载增长,直至在接近 2,000 QPS 时饱和。
| 流量级别 | 平均批次大小 | 平均延迟 | 吞吐量 |
| --- | --- | --- | --- |
| 100 QPS | 2.1 | 8 ms | 100 QPS |
| 500 QPS | 6.3 | 12 ms | 500 QPS |
| 1000 QPS | 12.4 | 18 ms | 1000 QPS |
| 2000 QPS | 24.1 | 28 ms | 1980 QPS |
表 10.15:Triton 自适应批处理:V100 上的 ResNet-50:在 V100 上运行 ResNet-50 时,Triton 推理服务器的自适应批处理器在增加流量下的观察到的批次大小、平均延迟和持续吞吐量。
到此为止的每种批处理策略都假设:一旦请求被接收,每个请求的工作量是固定的;连续批处理和自适应窗口调整的是请求何时加入批处理以及系统等待多长时间,但每个请求所需的解码步数被视为给定条件。推理密集型任务打破了这一假设。当模型在回答前可以花费更多的令牌进行思考时,每个请求的工作量就变成了调度器必须设定的变量,而不仅仅是它可以观察到的属性。
推理导致的延迟比快速模式匹配大约增加 128 倍。
测试时计算扩展:逻辑墙
测试时扩展将推理转化为一种服务资源。请求在生成答案之前可能会发出更多生成的令牌、内部思维链(CoT)令牌或搜索步骤。大模型能力工作描述了出现的行为(Wei 等人,2022),而后续分析警告称,一些明显的出现可能是度量选择的产物(Schaeffer 等人,2023)。
从服务系统的角度来看,相关的转变是每个请求发出的工作量。额外的推理会消耗延迟预算、KV 缓存驻留时间和加速器时间。从“快速思考”(即时模式匹配)转向“慢速思考”(深思熟虑的推理)的模型会将压力从 HBM 带宽推向 测试时计算:调度器必须决定请求可以消耗多少搜索步骤或 CoT 令牌。逻辑墙 是由此产生的服务约束:对于复杂问题,每个请求的计算量会随着任务难度增加;而针对每秒令牌数优化的机群也需要一种策略来分配思考时间。这与前一节的批窗口调整是不同的控制:自适应批处理决定请求何时一起运行,而测试时计算决定每个请求被允许完成多少工作。
问题:计算一个模型使用 128 个“思考令牌”来解决一个复杂的数学证明与标准答案相比的延迟影响。
-
标准响应:1 个令牌答案 = 100 毫秒。
-
推理响应:在答案之前进行 128 次内部搜索/CoT。
-
延迟:128 × 100 毫秒 = 12.8 秒。
系统洞察:测试时扩展将服务架构从吞吐量工厂转变为搜索引擎。虽然标准服务优化的是每秒令牌数,但推理密集型模型受限于每秒步骤数。这创造了一个“推理 SLO”:用户可能愿意花 12 秒来获得一个正确的证明,但不会为一个简单的问候等待那么长时间。在机器学习机队中,这促动了动态计算分配,其中调度器根据任务难度、延迟容忍度和机队负载为每个请求授予更多思考时间,而不是对每个提示应用固定的解码预算。
每个请求的可变工作量完成了批处理的故事:连续批处理和自适应批处理处理系统观察到的变化,而测试时计算是系统选择的变化。动态计算分配 根据任务难度、延迟容忍度和机队负载为每个请求分配计算预算,而不是对每个提示应用固定的解码预算。既然批处理机制和每个请求的计算预算都已摆上台面,剩下的任务就是决定对于给定的工作负载,应该运行哪种组合。
定量摘要:批处理策略选择
选择问题在于将批处理机制与模型形状、流量方差和延迟预算相匹配。这种匹配取决于延迟预算实际上在请求路径的哪个位置被花费。
验证您对不同批处理机制的理解:
在选择批处理策略之前,理解延迟在完整请求生命周期中的累积位置至关重要。图 10.7 从客户端到响应映射了每个阶段,揭示了序列化、路由和协调在 GPU 计算之外施加的“服务税”。
图 10.7:端到端推理流水线:请求生命周期的详细视图:客户端请求到达负载均衡器,然后在 CPU 上经历预处理(分词)。数据移动到 GPU 上进行计算密集型的预填充阶段和受内存限制的解码阶段,该阶段使用 KV 缓存。最后,响应在 CPU 上进行后处理(去分词)并发送回去。此可视化突出了关键的“服务税”组成部分(序列化、路由、协调),这些部分在实际 GPU 计算时间之外消耗了延迟预算。
如 图 10.7 所示,非计算阶段(序列化、排队、路由)可能消耗延迟预算的相当大比例,这意味着批处理策略的选择必须考虑整个流水线,而不仅仅是 GPU 执行时间。一个决策树指导策略选择。
它是自回归文本生成吗?
├─ 是 → 带分块预填充的连续批处理
└─ 否 → 是实时流式语音/视频/机器人处理?
├─ 是 → 最小或无批处理的流式处理管线
└─ 否 → 嵌入查找是否主导延迟?
├─ 是 → 特征并行批处理(推荐系统)
└─ 否 → 带自适应参数的动态批处理
表 10.16 表明每种策略都针对不同的目标进行调优,因此值得调优的参数取决于绑定的目标:静态情况下是吞吐量,动态情况下是延迟-吞吐量平衡,连续批处理情况下是解码方差,特征并行情况下是分片容量,以及流式处理情况下是实时截止时间。
| 策略 | 关键参数 | 调优目标 |
| --- | --- | --- |
| 静态 | 批次大小 | 最大化吞吐量 |
| 动态 | 窗口,最大批次 | 平衡延迟与吞吐量 |
| 连续 | 块大小,最大批次 | 最小化解码延迟方差 |
| 特征并行 | 累积窗口 | 匹配嵌入分片容量 |
| 流式 | 管线深度 | 满足实时截止时间 |
表 10.16:批处理策略参数:每种策略都有不同的参数,需要针对特定部署进行调优。
即使是经过良好调优的连续批处理策略也无法消除大型语言模型特有的更深层架构瓶颈。每个活跃请求的上下文窗口必须存储在 GPU 内存中,使得 KV 缓存管理成为服务吞吐量的下一个约束瓶颈。
内存和解码时管理
作为大模型生成 2,000 字的文章时,必须不断回顾之前写过的每一个词。它通过将中间注意力状态存储在一个快速扩张的内存缓冲区——即 KV Cache 中来实现这一点。如果不加管理,这个缓存会激进地碎片化 GPU 显存,即使技术上还有 40% 的 VRAM 空闲,也会导致显存溢出崩溃。
两类技术在此边界相遇。KV-state capacity 技术(如 PagedAttention 和 prefix caching)决定注意力状态驻留何处以及内存管理器容忍多少碎片化。Decode-time latency 技术(如 speculative decoding)改变每个输出 token 所需的目标模型工作量。speculative decoding 并非 KV-cache 压缩;它以额外的草稿模型状态、目标模型验证和调度器复杂度为代价,在相同显存预算下换取更低的单 token 输出时间。
KV cache 墙:显存受限的容量
增加批次大小以最大化 LLM 服务吞吐量受限于 KV cache wall。如 图 10.8 所示,模型权重代表 GPU 显存上固定的“静态税”,而 KV cache 随批次大小和序列长度线性增长。

图 10.8:KV Cache 墙:4-bit 量化 70B 模型(权重 35 GB)在 80 GB H100 上的总 GPU 显存占用。虽然模型在短上下文时轻松放入,但 KV cache(斜线)最终会消耗所有剩余 HBM。在 128K 上下文时,批次大小为 2 在单张 GPU 上物理上不可能(35 GB + 84 GB > 80 GB),迫使系统要么减小批次大小(扼杀吞吐量),要么对模型进行分片。
该可视化揭示了为何副本层级有时必须对本可放入单 GPU 的模型进行分片。分片提供了维持长上下文请求高批次大小所需的显存余量。无分片时,一个 128K 上下文请求实际上会将所有其他用户从 GPU “驱逐”出去。
同一公式可转化为生产硬件的显式批次大小上限。
问题:部署在 8× H100 节点(总 HBM 640 GB)上服务 Llama-3-70B(FP16 权重 ≈ 141.2 GB)。目标是确定 128K token 上下文长度下的最大批次大小。
公式:
-
M[KV] = 2 × N[L] × H[KV] × d[head] × s[elem]
-
Total Memory = M[weights] + (Batch × Context × M[KV])
参数:
-
N[L] = 80,H[KV] = 8(Llama-3-70B GQA),d[head] = 128。
-
s[elem] = 2 bytes(FP16)。
-
Context = 131,072 tokens。
步骤 1:计算每 token 显存。M[KV] = 2 × 80 × 8 × 128 × 2 = 327,680 bytes ≈ 0.33 MB/token
步骤 2:计算每请求缓存。131,072 tokens × 0.33 MB/token ≈ 42.9 GB/request
步骤 3:确定最大批次大小。可用 KV 显存 = 640 GB(总计)- 141.2 GB(权重)- 20 GB(系统)= 478.8 GB。
系统洞察:即使采用 GQA,9.4.3 节所述的共享-KV-head 注意力变体(上述 8-head 计数已反映),128K 上下文请求仍消耗数十 GB 的 KV cache,因此系统在显存成为约束前只能服务少量并发长上下文请求。解决此问题需要 PagedAttention(减少碎片化)以及 KV-cache 量化或分片。
碎片化问题
KV cache 的传统内存分配基于最大预期长度为每个序列预分配连续内存。这造成两种浪费。
内部碎片化浪费分配内部的显存。短于最大分配长度的序列会留下闲置的未用部分。若最大长度 4,096 但平均输出 100 token,则 97.5% 的分配显存被浪费。
外部碎片化跨分配加剧此问题。随着序列完成和新序列开始,显存碎片化为非连续的空闲块。即使总空闲显存充足,也可能没有单个块足以容纳新的最大长度分配。
考虑一个包含 8 个显存槽、最大序列长度为 4 的简化示例。固定大小预留首先产生内部碎片化:
Time 0: Allocate Seq A (slots 0-3), Seq B (slots 4-7)
[A][A][a][a][B][B][B][b]
lowercase = reserved but unused
序列完成和新序列开始后出现外部碎片化:
Time 1: Active Seq C and Seq D leave two 2-slot gaps
[ ][ ][C][C][ ][ ][D][D]
Time 2: Try to allocate Seq E (needs 4 contiguous slots)
[ ][ ][C][C][ ][ ][D][D] <- Total free slots = 4, largest block = 2
Result: enough total free capacity exists, but no contiguous block is large enough.
生产系统报告在真实负载下因碎片化导致 60–80% 显存浪费,严重限制批次大小和吞吐量。
PagedAttention
碎片化示例暴露了分配器失效:聚合层面可能存在足够的 KV-cache 容量,却无连续块供下一个序列使用。PagedAttention 通过将 KV-cache 存储视为虚拟内存而非单一预分配板块,修复了这一服务问题。
PagedAttention 是一种 LLM 服务内存管理技术,将虚拟内存原理应用于 KV Cache 分配,将注意力状态存储在非连续、固定大小的物理块中。
-
意义:消除内部与外部碎片化,后者在连续预分配下可浪费 60–80% 的
KV cache显存。通过允许序列动态增长,它在相同硬件上实现 2–4 倍更高的并发吞吐量 (X)。 -
区别:不同于连续分配(需预留最大上下文长度),
PagedAttention使用块表将逻辑序列索引映射到物理内存页,仅分配当前使用的部分。 -
常见误区:常见误解认为
PagedAttention加速单 token 运算。实际上它是容量优化:通过允许更大批次大小提升系统级效率,虽为指针查找引入少量间接开销 (L[lat])。
虚拟内存转变改变了分配问题:容量无需在请求开始前作为一个连续块预留。
想象一家酒店,每位客人可能住 1 到 10 天,但提前不知具体天数。
连续分配下,酒店经理为每位客人预订 10 天套房以防万一。一家 100 间房的酒店仅 10 位客人就“订满”,浪费 90% 产能(内部碎片化)。
PagedAttention(虚拟内存)下,经理仅分配客人 1 间房。若客人续住,安排任何可用房间,哪怕在不同楼层。前台维护“块表”追踪哪些房间属于哪位客人。酒店现可同时容纳 100 位客人,回收 90% 浪费产能。
PagedAttention(Kwon et al. 2023),在 vLLM 中引入,将虚拟内存概念应用于 KV cache 管理。不再连续分配,而是将 KV cache 划分为固定大小页(vLLM 默认配置 16-token 块),序列按需分配页。图 10.9 展示了关键概念,包括将逻辑序列位置映射到物理内存页的页表、定义每页 token 数的块大小(通常 16 token)、可分配给任意序列的固定大小物理块。
KV 缓存碎片化与 PagedAttention
图 10.9:KV 缓存碎片化与 PagedAttention 解决方案
左上:内部碎片化通过为最大序列长度预分配而浪费内存。右上:外部碎片化留下不可用的内存空隙。底部:PagedAttention 通过将序列的连续逻辑页映射到不连续的物理内存块(通过块表)来解决此问题,从而允许系统用任何序列的小块填充碎片空隙。
PagedAttention 提供四种容量优势:
-
内部碎片化:它仅为实际标记分配所需的页。
-
外部碎片化:任何空闲页都可以被任何序列使用。
-
动态增长:序列可以在不预分配的情况下增长。
-
前缀共享:公共前缀可以共享物理页。
这些特性共同将 KV 缓存容量转变为可调度的资源,而非固定的按请求预留。
内存布局
物理块(16 个标记 × hidden_dim × 2 × 精度):
块 0: [K₀...K₁₅, V₀...V₁₅]
块 1: [K₀...K₁₅, V₀...V₁₅]
...
块 N: [K₀...K₁₅, V₀...V₁₅]
每序列的页面表
(原始来源为空)
注意力内核修改
标准注意力:output = softmax(Q @ K.T/sqrt(d)) @ V
PagedAttention:
(原始来源为空)
系统洞察:gather 操作的吞吐量影响相比内存节省可以忽略不计;表 10.17 表明,由于在相同内存中容纳更多并发序列,吞吐量提升了 2.5–4×。
| 方法 | KV 缓存池利用率 | 吞吐量(相对) |
| :--- | :--- | :--- |
| 连续分配 | 30-40% | 1.0× (基线) |
| PagedAttention | 95%+ | 2.5–4× |
表 10.17:PagedAttention vs. 连续 KV 缓存:连续分配与 PagedAttention 在 KV 缓存池利用率和相对服务吞吐量方面的对比。
前缀缓存
许多 LLM 工作负载在请求之间共享公共前缀。系统提示如“你是一个有用的助手……”会被追加到每个请求的开头。少样本示例对许多查询使用相同的示例。文档上下文涉及对同一文档的多个问题。重新计算这些共享前缀会浪费计算(预填充)和内存(重复的 KV 缓存条目)。
前缀缓存在请求之间共享具有公共前缀的 KV 缓存条目。图 10.10 演示了共享系统提示如何避免冗余计算。
图 10.10:通过块共享实现的前缀缓存
PagedAttention 通过允许多个序列的块表指向共享内容的相同物理块,实现高效的前缀缓存。在此示例中,系统提示存储在块 0-5。请求 A 和请求 B 将它们的前 6 个逻辑页映射到这些相同的物理块,仅在新块中存储它们的唯一后缀。这极大地减少了具有共享上下文的工作负载的内存使用和预填充计算。
使用 PagedAttention,前缀缓存通过写时复制语义自然集成:
系统提示 → 物理块 [0, 1, 2, 3, 4, 5]
请求 A 的页面表:[0, 1, 2, 3, 4, 5, 10, 11] <- 共享前缀块
请求 B 的页面表:[0, 1, 2, 3, 4, 5, 12, 13, 14] <- 共享前缀块
请求 C 的页面表:[0, 1, 2, 3, 4, 5, 15] <- 共享前缀块
这三个请求为系统提示引用相同的物理块。只有在生成唯一标记时,它们才会分配新块。当许多并发请求共享相同的系统提示时,节省效果可观;以下示例对典型聊天机器人部署进行了量化。
场景:一个聊天机器人服务使用 2,000 个标记的系统提示,并服务 1,000 个并发用户。
无前缀缓存:

浙公网安备 33010602011771号