← 文章 / 云原生与基础设施
HuggingFace博客 1小时前 · 2026-10-10 07:21:13 · 8 阅读

GPU 集群的影响力调度

构建集群调度器,在保持满负荷运行的同时,优先保障高价值研究工作

Impactful Scheduling for GPU Clusters - Google Docs-image-1 (1)

作为 Ai2 的 AI 基础设施团队,我们负责提供研究所的 GPU 算力,专门面向大规模分布式训练负载。我们将这一任务看作由四个层层递进的指标构成的金字塔。

底层是可用率:即硬件处于健康且随时可用状态的比例。上一层是占用率:指分配给特定工作负载的时间占可用总时间的比例。再上一层是影响力:衡量最关键的负载获得资源的频率。金字塔的顶端是利用率:指在整个工作负载生命周期内,GPU 容量被实际使用的比例。

Pyramid of GPU compute metrics, from availability at the base through occupancy and impact to utilization at the top.

本文探讨如何提升调度决策的影响力。我们最近用一套新系统取代了原有的基于优先级的调度器,新系统包含 GPU 时间预算、分层公平份额分配以及时间片机制。由此,关于各项目应分配多少 GPU 时间的争论,从逐案处理的运营事务,转变为透明的行政预算流程。

资源超额分配

在 Ai2,我们管理着数千块 NVIDIA H100、B200 和 B300 GPU。这些 GPU 组成规模从 88 到 1024 块不等的集群,专为 AI 模型的大规模分布式训练而建。集群服务于约 150 位内部研究人员,其工作覆盖广泛的 AI 领域,包括 LLM 和 VLM 训练的完整模型流程、机器人强化学习(RL)仿真,以及针对科学代理场景的后训练。

和许多实验室一样,我们对 GPU 时间的需求远远超过供给。从提交的工作负载来看,任何时刻未满足的 GPU 请求量都是现有资源的 2-3 倍。换个角度看,集群上每一小时的 GPU 时间,都有 2-3 个不同的研究任务在争抢。

过去我们使用基于优先级的调度器,并允许工作负载选择免受抢占。每个团队都有一个并发 GPU 上限,受保护(免抢占)的工作负载不能超过这个上限;可抢占的工作负载则可以在空闲 GPU 上突破限制。这种策略带来了一些可预见的问题。比如,我们观察到 GPU “蹲占”现象:用户会挂着空转的任务,等需要时再接入使用。原因在于研究人员发现,启动调试任务无法做到足够低的延迟,难以实时处理问题。我们还观察到优先级膨胀:最终所有被调度的工作负载都用上了 HIGH 优先级,导致更低优先级的任务完全分不到 GPU 时间。又因为可抢占是可选项,我们的值班工程师不得不把大量工单处理时间花在协商关闭那些运行在有已知维护问题主机上的不可抢占工作负载上。

公地悲剧

这些问题出现后,我们花了很久才找到根源。最初为确保重要工作能拿到 GPU 时间,我们尝试更严格地控制优先级设定,甚至干脆绕过基于优先级的调度器,直接把独占的 GPU 分配给重要项目。虽然当时没有意识到,但我们实际上搭建了一个观察“公地悲剧”的完美实验场:个体在争抢稀缺的共享资源,各自追求自身利益最大化的结果,是全局非最优,同时底层资源也被滥用。

发现这种交互现象的我们,并非先驱。资源分配是一个迷人的研究领域,它融合了算法设计、经济学与系统管理。核心问题在于:用户往往比组织更清楚自己任务的价值,但这反而激励他们隐瞒真实价值,或是在损害整体性能的情况下仍占用资源。例如,Ghodsi 等人 2011 年在其介绍主导资源公平(Dominant Resource Fairness)的论文中,讲述了一个轶事:某搜索引擎公司为确保高利用率,只将专用机器分配给能保证高使用率的作业。结果他们很快发现,“用户会在代码中插入无限循环,以人为虚高利用率。”虽然硬件在变,但使资源分配变得复杂的基本问题始终存在。

配额而非排期

解决“公地悲剧”的经典方案是将共享资源私有化——所有者有动力最大化财产价值。当我们赋予团队对特定 GPU 集群的独占权时,其实已经在践行这一理念,只是粒度太粗。由于科研具有季节性,这导致 GPU 常处于闲置状态。各团队运行实验和训练的时间点不同,独占分配意味着某些时段无任务可跑,而其他团队只能在等待算力中干等。

我们实际上是在手动解决背包问题,试图将动态变化的科研需求硬塞进一个静态排期中。我们既需要所有者的激励机制,又希望保持 GPU 的全满载运行。

我们决定对所有权模型进行迭代改进。我们不再直接分配 GPU 设备,而是分配 GPU 的使用时长。由于未来需求预测需要知道全新科学实验的结果,因此无法精准预估。不过,不同研究任务之间的优先级属于战略问题,可以更容易地通过讨论提前确定。我们没有试图解决调度难题,而是让管理层具备投资者思维。在任务产生之前,基于对各项目潜在影响的判断,预先决定如何通过分配 GPU 时长为各项研究提供“资金”。调度器随后即可利用这些信息,为新到达的任务安排优先级。

基于这一理念,我们设计了一套分层系统,允许管理者向其所负责的项目和研究人员按比例分配 GPU 时长。如下图所示,这将项目战略直接转化为受保障的 GPU 时长份额。项目 A1 清楚自己拥有总产能 35% 的保障份额,无论其他项目在其他地方排了多少队。

研究项目和项目间的 GPU 时长分层分配;项目 A1 占总产能的 35%。

括号中的数值代表分配给叶节点项目的集群总产能份额。

在这个系统里,每一次申请 GPU 时间都必须由预算来支撑,否则就无法免于被抢占。旧系统中,HIGH 优先级没有任何成本,加上不可抢占的机制,团队可以无限制地占满并发 GPU 上限,于是人人都这么做。现在一切都有价,任何获取 GPU 时间的手段都会消耗使用者自己的配额。占着资源不干活的工作负载,等于白白烧掉团队预算。我们的策略是:让钻调度器空子的代价比坦诚参与预算之争更高。这个预算评审流程我们一直在迭代,但有两个关键要求:研究人员要有足够频繁的机会为自己的时间需求发声,而决策要由最了解相关权衡取舍的管理者来做。也就是说,研究项目内的分配由项目负责人决定,研究计划内由首席研究员决定,跨计划则由计划负责人或 CEO 决定。

公平份额

与 GPU 时间预算工具配套,我们构建了一个层级式公平份额(fair-share)调度器,用来管理整个项目树中各分配的实际占用。这里的算法并不新鲜——基于时间窗口的层级公平份额可以追溯到 2009 年的 Hadoop Fair Scheduler,同样的思路如今仍在 SLURM 的 Fair Tree 和 YARN 的 Fair Scheduler 中使用。对我们的创新在于输入:树结构对应研究计划的组织结构,权重则由管理者设定的预算决定,而非静态配额。

调度器会在一个滑动回看窗口内(默认 7 天)跟踪占用情况,把来自未用满配额的工作负载排在已超额占用的之前。这样,只要各组持续提交足够需求的工作负载,就能在一周的时间尺度上拿到自己分得的 GPU 时间。

“新调度器让我们感觉仿佛多了 30% 的算力。在旧调度器下,如果我们有不需要使用全部配额额度的时段,那些算力基本上就浪费了。现在有了新调度器,如果出现这种情况,我们可以在后续时段突破分配上限,作业仍能快速调度且不被抢占,这让我们得以收回那些算力。我们的任务负载往往具有突发特性,因此这一机制帮我们找回了大量算力。”——Chris Clark

调度器区分两种占用类型。分配占用是指任务被计入预算的时间段。这会消耗任务所有者的分配额度,并影响公平份额预算的计算;处于最小运行时间窗口内的这类任务受到保护,不会被抢占。未分配占用不计入任何预算,从初始状态起就缺乏保护,随时可能被任何已分配请求抢占。这种机制确保即使在分配额度与需求不匹配时,GPU 也能保持满负荷运转,同时也防止团队白白浪费空闲的 GPU 周期。

调度契约

分布式训练的一个额外特性使得公平的资源分配变得困难:任务运行时间可能非常长。训练作业通常运行数小时、数天,有时甚至长达数周。一旦调度,任务可能在其分配的 GPU 上停留一周或更久,这剥夺了其他用户获得预算时间的机会。正是这一系统特性导致了 GPU 占位现象。这也迫使值班工程师不得不与长时间运行任务的拥有者协商,以解决持续存在的维护问题。

为解决这些问题,我们引入了“调度契约”。作为使用集群资源的交换条件,任务必须申报其最小运行时间,即取得有意义进展所需的最短占用时长。在此期间,任务受保护不会被抢占。这既向研究者保证了进展,也赋予调度器在该进展达成后重新平衡资源的权利,并自动将可恢复的任务重新入队。或者,用户可以将最小运行时间设为零,表示 GPU 时间属于未分配性质。这类任务始终可能被抢占,但由于不计入任何预算,它们在成本上是免费的。

任务生命周期遵循以下模式:

  1. 工作负载提交时需提供最短运行时间,并指明其是否可恢复。
  2. 工作负载根据公平份额算法进行调度,权重为回溯窗口内实际占用时间与分配时间的比值。
  3. 工作负载运行其最短运行时间,该时段计入其配额。
  4. 只要关联的配额仍优先于其他任务,工作负载可继续运行。这部分时间同样计入配额。
  5. 工作负载可能被抢占并重新入队,此时返回第 2 步。
  6. 工作负载完成,释放其对任何资源的主张。

这些约定共同为调度器引入了时间切片机制。运行中的工作负载可被自动移除并重新入队,从而促进公平份额的收敛,并遏制占坑行为。同时,这些机制允许不健康的主机在达到最短运行时间后排出其工作负载,使修复活动得以完全自动化。规划此工作时,我们低估了最后一点的重要性。它减少了 74% 需要人工介入的修复任务,显著降低了值班负担。

模拟仿真

我们知道,调度策略的调整可能带来意外后果。问题的零和性质意味着给一位研究员增加时间,就会剥夺另一位研究员的时间。在这场交换中受损的用户往往会寻找新的规避方案。在全面推出基于预算的系统之前,我们希望找到一种快速预测较长等待时间可能出现在哪里,并测试如回溯窗口长度或最短运行时间最大值(我们选为 8 小时)等配置参数的方法。

我们构建了一个小型仿真环境,输入为一组工作负载及其提交计划,允许调度器做出抢占和 GPU 分配决策。基于每个工作负载请求的 GPU 数量和总运行时间,模拟器可跳至可调度时刻,并在几秒内提供对队列等待时间、抢占事件以及跨项目 GPU 时间分布的分析,覆盖多个模拟日。我们将模拟器应用于历史提交数据以及我们希望更好理解的各种构造场景。

我们想验证的一个假设与“调试工作负载”有关。这类任务只需要少量 GPU,运行时间不超过 15 分钟就足够——用户只需确认任务是成功启动,还是因为 bug 或配置错误而早期崩溃。我们想知道这类任务的排队等待时间是否比大型训练任务更短,后者往往需要大量 GPU 和数小时才能取得实质性进展。直觉上,小任务应该会排在队列前面,因为小任务能塞进的位置比大任务多。但具体的排队延迟很关键:等一两分钟会催生一种新的开发习惯,等十分钟就不现实了。

由于历史数据中这类调试型工作负载的量不够多,我们的仿真需要手工构造测试数据。结果支持了这一假设:调试工作负载的 p90 等待时间从约 6 小时降到了仅 5 分钟。

Simulator timelines comparing GPU occupancy under the baseline scheduler and the new allocations scheduler.

较小规模的仿真可视化:左边是基线调度器,右边是新的“allocations”调度器。每一行代表一块 GPU,每个色块代表一个任务,按所属工作负载着色,每个团队一种色调;斜线标记任务可被中断的时间段,红边表示抢占。在基线中,长时间的紧急任务从不会被中断,低优先级任务上的抢占也更少。新调度器中每块 GPU 上的颜色混合更丰富,说明团队之间的占用在轮换。

结果

拿到仿真结果后,我们从 7 月底开始逐个集群推进上线。我们关心的结果是:我们选定要保障的工作负载是否拿到了时间、新系统是否保持了满占用率,以及研究人员是否能理解调度器的行为并据此做出明智决策。

系统上线后,我们持续观察到用户和团队都能按时拿到分配给他们的 GPU 时间。我们将团队应得的时长定义为其分配额度与实际需求逐小时取较小值后的结果。在为期 30 天的测试期内,团队实际获取的 GPU 时长达到了应得时长的 98%;15 个团队中,有 13 个获得的时长不低于应得值的 95%,表现最差的一个团队也拿到了 90%。集群利用率在调整前后均稳定在 98%,且两个阶段的需求量都是容量的 2 至 3 倍。已交付 GPU 时长中有 18% 属于未分配部分,这让我们能在资金支持的用例尚未就绪时,依然维持集群的高利用率。

模拟结果的方向性与真实表现一致,且实际效果优于预测。在新调度器下,调试工作负载的 p90 排队等待时间从 2 小时降至 30 秒;而基于手工测试场景的模拟预测值则从 6 小时缩短至 5 分钟。需要注意的是,基线中调试工作负载的样本量较小,导致该指标的波动性更高。时间片化机制作为副作用整体改善了队列延迟:在我们最大的 H100 集群上,中位数排队等待时间从 5 分钟降至 24 秒,p90 等待时间则下降了约三分之一(从 2.8 小时降至 1.8 小时)。

回顾我们最初要解决的三个问题,进展如下:

  1. 资源占位(Squatting):短小的调试工作负载能在 1 分钟内启动,这让占位策略的收益大打折扣。该行为产生的计算成本会直接计入占位者所属团队的预算,从而阻止其在真正需要资源时获得足够时长。

  2. 优先级膨胀(Priority inflation):我们仍允许工作负载声明优先级,但这仅影响团队内部的排序。管理层有动力监控整个小组的优先级使用情况,以优化预算效率。

  3. 值班负担(On-call toil):当工作负载达到最小运行时长时,不健康的宿主机会自动排空(drain)。需要人工介入的修复次数减少了 74%。

挑战

学习曲线比我们预想的更陡峭。我们分阶段推进这项变更,导致早期研究者根据目标集群不同,体验到的行为差异很大。此外,部分接口沿用了旧术语(如工作负载优先级),但其含义已经改变。仅靠文档无法消除这种混乱。真正奏效的是开展实时讲解,搭建一个论坛,让研究者可以提问,让工程团队借助真实案例,更深入地解释调度器为何以及如何做出优先级决策。

这是一个关键时刻,标志着我们从早期的挫败感和民间猜测,转向了当前的模式:研究团队更频繁、更广泛地交流实验的 GPU 需求。研究者在参与预算讨论时,对为新需求所做的权衡有了更清晰的认识。

除现场会议外,上线后我们还引入了新的可视化界面,帮助用户更好把握所分配的 GPU 时间与预期配额的吻合程度,并直接展示用于对工作负载队列排序的指标。当工作负载被抢占时,这里提供了一个简单的入口,帮助理解原因。这些可视化也帮助了预算负责人,让他们能看清其管理下各个项目的 GPU 时间使用情况。

分配使用时间,展示分配的 GPU 时间如何追踪预期配额。

分配使用时间随时间变化的示例可视化。

并非所有场景都得到了改善。除了分布式训练,研究人员还会启动交互式会话,在其中做数据分析、边写训练代码边测试。旧系统里,这样的会话最长可以保持一周。引入时间片机制后,这些会话同样受 8 小时保护时限的约束,一旦超出分配额度就可能被抢占。我们此前没有意识到,研究人员对这些会话中的临时状态有多依赖——会话被抢占意味着要重新排队等新会话,还得手动重建状态。在调研了这一问题的波及范围后,我们新立了两个路线图项目。一是在本地存储旁边建设一个纯 CPU 集群,专门用于数据准备类的开发会话,把训练集群的资源留给真正需要的任务。二是为这些纯 CPU 任务构建可恢复的会话。这样我们依然可以在最小运行时间结束后,为维护或时间片调度而抢占任务,同时还能把会话恢复到其他节点上,研究人员不必手动重建。这样既保留了新系统在运维和调度上的收益,又改善了用户体验。

我们也在持续关注新出现的问题。目前正在排查的一个潜在问题是容量碎片化,它可能导致最大规模任务的排队等待时间变长。我们的直觉是,最小运行时间保护被应用到了过去依赖 preemptible 机制来超出团队 GPU 并发上限的那类任务上。以前这类任务随时可能被打断,虽然会浪费一些已运行的进度,但也让大任务更容易被调度。现在调度器更难一次性打断大量任务来腾出空间给排队中的大任务。我们正在用模拟工具复现这一问题,同时在生产环境中测量真实数据。

未来

在完成本文所述的调度工作之后,我们的目标是金字塔的顶点:利用率。我们需要确保引导、checkpoint 保存以及训练应用本身都以尽可能高的效率运行,让每个任务获得的调度时间发挥出最大价值。

如果你希望与研究人员紧密合作,解决上述挑战,我们鼓励你关注 Ai2 的开放工程职位。

原始来源: HuggingFace博客

评论 (0)