一、单机多卡调度策略:时间片轮转、优先级调度、Gang Scheduling

各位同学,今天我们来聊聊单机多卡场景下的调度策略。说实话,这块内容我当年刚接触时也觉得挺枯燥的,直到我在一个训练任务里踩了坑——8张卡,跑一个模型,结果有的卡忙死,有的卡闲死。嗯,从那以后我才真正重视起调度策略来。

单机多卡,说白了就是一台机器上插了多块GPU。你想想看,这些卡怎么分配任务?谁先跑?谁后跑?跑多久?这就是调度策略要解决的问题。我个人习惯把调度策略分成三类:时间片轮转、优先级调度、还有Gang Scheduling。咱们一个一个来看。

核心观点:调度策略的选择直接影响GPU利用率和训练效率。选错了,再好的硬件也白搭。

1.1 时间片轮转(Round-Robin)

时间片轮转,这个名字听起来挺唬人,其实原理很简单。就像学校食堂打饭,每个人轮流来,每人打一勺,打完一轮再从头开始。GPU调度也是这个道理——每个任务分到一个时间片,时间到了就换下一个任务。

我在项目中遇到过这样一个场景:4张卡,跑8个推理任务。如果用时间片轮转,每张卡上轮流跑2个任务,每个任务跑100ms就切换。这样做的好处是什么?公平!每个任务都能得到差不多的GPU时间,谁也别想独占。

但这里有个坑——上下文切换开销。我曾经在一个推理服务里用时间片轮转,结果发现切换太频繁了,GPU光忙着保存和恢复上下文了,真正干活的时间反而少了。后来我把时间片从50ms调到了200ms,吞吐量直接提升了30%。

经验之谈:时间片大小怎么设?我个人建议:推理任务用100-200ms,训练任务用500ms-1s。当然,具体还得看你的任务特点。

时间片轮转的优缺点很明显:

  • 优点:实现简单,公平性好,每个任务都能得到响应
  • 缺点:没有优先级概念,重要任务也得排队;上下文切换有开销

1.2 优先级调度(Priority Scheduling)

优先级调度就更好理解了——谁重要谁先上。就像医院急诊,危重病人优先处理。在GPU调度里,我们可以给每个任务设置一个优先级,调度器会优先把GPU资源分配给优先级高的任务。

我记得有一次做线上推理服务,用户请求分两种:VIP用户的实时请求和普通用户的批量请求。VIP请求必须秒级响应,普通请求可以等几秒。这时候优先级调度就派上用场了——VIP请求优先级设为10,普通请求设为5。调度器一看,有VIP请求来了,立马把GPU让给它。

但优先级调度有个经典问题——优先级反转。什么意思呢?就是低优先级任务占着资源不放,高优先级任务干等着。我曾经就遇到过:一个低优先级的预处理任务占着GPU显存,高优先级的推理任务死活拿不到资源,结果VIP用户等了5秒才得到响应。后来我加了优先级继承机制,才解决了这个问题。

避坑指南:我曾经在优先级调度里忘了处理优先级反转,结果线上出了事故。记住:高优先级任务等待低优先级任务释放资源时,低优先级任务应该临时继承高优先级。

优先级调度的关键参数:

参数 说明 建议值
优先级范围 任务优先级数值范围 0-100(数值越大优先级越高)
优先级继承 是否启用优先级继承机制 建议开启
抢占粒度 高优先级任务能否抢占正在运行的低优先级任务 建议允许抢占

1.3 Gang Scheduling(组调度)

Gang Scheduling,中文叫组调度,这个稍微复杂一点。它的核心思想是:要么一起跑,要么都不跑。什么意思呢?就是一组相关的任务必须同时被调度到不同的GPU上运行,要么就全部等待。

你想想看,在分布式训练里,一个模型被切分到多张卡上,每张卡跑模型的一部分。如果只有部分卡在跑,其他卡闲着,那跑起来的卡也得等着——因为数据依赖啊!这就是Gang Scheduling要解决的问题。

我在做大规模语言模型训练时,就遇到过这个问题。模型被切分到8张卡上,结果调度器只分配了6张卡,剩下2张卡还在等资源。那6张卡跑了一会儿就得停下来等同步,GPU利用率直接掉到40%。后来改用Gang Scheduling,8张卡要么一起跑,要么一起等,利用率提升到了85%以上。

关键点:Gang Scheduling特别适合数据并行和模型并行的训练场景。说白了,就是那些需要多卡协同工作的任务。

Gang Scheduling的实现要点:

  • 组定义:哪些任务属于同一个组?通常是一个训练任务的所有子任务
  • 资源预留:调度器必须同时为组内所有任务分配资源,不能部分分配
  • 同步机制:组内任务之间需要同步点,确保进度一致

三种调度策略的对比:

策略 适用场景 优点 缺点
时间片轮转 推理服务、多任务共享GPU 公平、实现简单 无优先级、切换开销
优先级调度 有QoS要求的推理服务 重要任务优先 优先级反转问题
Gang Scheduling 分布式训练、多卡协同 避免资源碎片、提高利用率 实现复杂、资源预留可能导致等待

最后,我给大家画了一张图,把三种调度策略的核心逻辑展示出来。这张图我画了好几次才满意,希望能帮你们理解。

单机多卡调度策略核心逻辑 时间片轮转 任务队列: [T1, T2, T3, T4] GPU0: T1(100ms) → T2(100ms) GPU1: T3(100ms) → T4(100ms) → 循环执行 特点: 公平轮转,无优先级 优先级调度 任务队列(按优先级排序): T1(优先级10) → 优先执行 T2(优先级5) → 等待 T3(优先级3) → 等待 特点: 高优先级优先,可抢占 Gang Scheduling 任务组: [T1, T2, T3, T4] GPU0: T1 ←→ GPU1: T2 GPU2: T3 ←→ GPU3: T4 → 同时运行或同时等待 特点: 组内任务协同调度 三种策略对比 策略 适用场景 核心优势 主要缺点 时间片轮转 推理服务 公平性 切换开销 优先级调度 QoS场景 重要任务优先 优先级反转 Gang Scheduling 分布式训练 协同效率高 实现复杂 实际项目中常组合使用多种策略,例如:优先级调度 + 时间片轮转

好了,三种调度策略就讲到这里。说实话,没有哪种策略是万能的。我个人的做法是:根据场景选策略,根据数据调参数。推理服务用时间片轮转加优先级,训练任务用Gang Scheduling。当然,实际项目中往往需要组合使用,这个我们后面章节再细聊。

最后一个小建议:刚开始做多卡调度时,别想着一步到位。先用时间片轮转跑起来,观察GPU利用率,再逐步优化。我曾经就是太着急,一上来就搞Gang Scheduling,结果调试了三天才跑通。

想要更多资料,联系可可