想象一家大型综合医院。每天清晨,挂号台前排起长队:有人深夜急诊,有人慢性病复查,有人只是来开药。护士们并不是按先来后到处理,而是先分诊——危重病人戴红色腕带直接进抢救室,普通病人去各科室排队,开药的小事被安排到低峰时段。这家医院,就是一台算力集群:病人是等待运行的作业,诊室和病床是计算节点,分诊台就是调度器。Slurm 是医院的“总护士长”,负责全院的号牌、科室和绿色通道;K8s 更像急诊科的“腕带分诊系统”,决定谁先上手术台。要想既不让危重病人等死(低延迟),又不让诊室闲置(高利用率),就需要一套精致的队列与优先级管理。本文带你从挂号分诊一路走到手术室插缝,看懂 Slurm 与 K8s 如何管理每一张“病床”。
掌握队列与优先级管理,是 AI 集群管理员在多租户环境下保障服务质量、提升硬件利用率的核心技能,也是调度系统从“能跑”走向“会治理”的关键一跃。
目录
可视化演示
展示网络拓扑中数据包从源节点S到目标节点T的多路径路由过程,数据包沿不同路径传输,路由器转发数据包,支持自动发送和路径切换。
本节配有交互动效(进入视口后自动加载,约 3KB)。
网络路由 青色节点为路由器,S为源端,T为目标端 橙色亮点为传输中的数据包 路径 = Dijkstra(S→T)
调度器总览:挂号的病人与分诊的护士
走进一家大型综合医院,第一步不是找医生,而是到分诊台挂号。护士快速评估病情:心跳骤停的进抢救室,高烧的挂急诊,慢病复查挂普通门诊,开药则安排到低峰时段。算力集群的入口同样是“分诊台”——调度器。用户提交的作业就是病人,调度器决定它进哪个队列、排什么位置、何时轮到它“看病”。
这个流程在 Slurm 中由 slurmctld 主守护进程完成,在 K8s 中由 kube-scheduler 完成。两者的职责高度一致:接收作业(挂号)、评估优先级(分诊)、放入队列(候诊)、分配合适节点(叫号就诊)。把这三个动作连起来,就是一套完整的调度流水线。
集群通常由多个课题组或业务线共享,就像病房分属不同科室。调度器最核心的挑战,是在“公平”和“效率”之间找平衡:既要让每个租户都能分到资源,又不能让任何节点闲置。衡量效率的关键指标是资源利用率,它直接反映“病床”有没有空转。
调度员心里永远有两本账:危重病人要及时抢救,普通诊室不能空转。队列与优先级管理,正是这两本账共同遵守的记账规则。
- 分子含义把每个作业的节点数与运行时长相乘后累加,相当于统计各诊室实际收治病人的“床位小时”。
- 分母含义所有节点在观察窗口内能提供的最大资源量,相当于全院床位总数乘以营业时间。
- 比值含义利用率越接近 1,说明床越不闲;但一味逼近 1 往往意味着紧急病人被耽误,所以需要优先级管理来平衡公平与效率。
队列设计:分诊后的候诊区
分诊之后,病人被引导到不同候诊区:急诊区、普通门诊区、慢病区。这些候诊区就是队列。Slurm 用 partition(分区)来划分队列,每个分区对应一组节点和调度策略;K8s 则用 namespace(命名空间)做租户隔离,不同业务跑在不同“候诊室”里互不干扰。
大医院还有多级结构:院区、住院楼、科室层层嵌套。Slurm 中 partition、account、QOS 可以组合出树状多级队列,让资源配额按层级继承;K8s 生态中的 Kueue 项目也提供 ClusterQueue 层级队列,支持父队列把配额分给子队列。多级队列解决的是“大组织如何分级治理”的问题。
队列设计要回答三个问题:分几类(分区维度)、上限多少(配额)、如何隔离(防互相挤占)。Slurm 用 GrpTRES、MaxJobs 等参数约束队列配额,K8s 用 ResourceQuota 限制 namespace 的资源总量。配额就像候诊区的座位数——座位满了,新来的病人只能等下一波。
候诊区划得好,医院才不混乱;队列设计得好,集群才不会出现“急诊区人满为患、慢病区空无一人”的资源冰火两重天。
- 分子含义该队列当前实际占用的资源量,如 GPU 卡数、CPU 核数或内存大小,对应候诊区正在候诊的病人数。
- 分母含义管理员为该队列设定的配额上限,如“最多 64 卡”,对应候诊区的核定座位数。
- 判断规则使用率接近或超过 1 时,队列满载,调度器将新作业挂起或按策略限制提交。
优先级机制:彩色标签与权重天平
分诊护士会给病人贴彩色标签:红色危重、黄色较重、绿色轻症。标签决定排队的先后顺序,这就是优先级。Slurm 中每个作业都有一套优先级分数,K8s 中每个 Pod 则有一个 PriorityClass 数值,两者都是“标签”的具体形态。
优先级不是拍脑袋得出的单一数字,而是多个因子加权求和的结果。Slurm 的常见因子包括:年龄因子(等待时长)、公平份额因子(fairshare)、QOS 因子、分区因子和作业规模因子。其中年龄因子正是“优先级衰减”的原理——排队越久分数越高,防止低优先级作业永远轮不上。
公平份额因子负责多租户间的长期公平:某用户或课题组用量超出公平份额后,他后续作业的优先级会被压低,把机会让给用量不足的租户。权重则由管理员配置,相当于医院规定“心跳骤停权重 100、普通感冒权重 1”。
分诊标签不是一成不变的——病人等久了,护士会重新评估。调度器同样动态调整年龄因子,让久等的“绿标病人”自动升级,这是优先级管理中最温暖的设计。
- 加权求和各因子与管理员配置的权重相乘后累加,得到最终优先级分数,按分数降序调度。
- 年龄因子 A等待越久 A 越大,这就是优先级衰减的数学表达,像等得太久的病人被护士重新评定为更重。
- 公平份额 F用量超出公平份额越多,F 越小甚至为负,给其他租户让路。
- QOS 加成 Q来自绿色通道卡的固定加分,体现“重点病人优先”的管理意图。
Slurm QOS:急诊绿色通道
大医院都有绿色通道:胸痛、卒中患者凭卡免排队直接进抢救室。Slurm 的 QOS(Quality of Service,服务质量)就是算力集群的“绿色通道卡”,它可以绑定到用户、账户或分区,决定作业享受的“待遇等级”。
QOS 能管三件事:资源限额(同时运行作业数、CPU 时长、节点数上限)、优先级加成(Priority 值注入优先级计算)、抢占权限(允许该 QOS 的作业抢占低优先级作业)。它既像通行证又像紧箍咒——给特权的同时必配上限。
管理员用 sacctmgr 命令创建和绑定 QOS:先执行 sacctmgr add qos name=express 定义,再执行 sacctmgr modify user=zhangsan set qos+=express 关联用户。生产环境常见做法是配置 normal、high、express 三级 QOS,分别对应不同优先级与限额,实现多租户精细治理。
绿色通道卡不能乱发——人人都插队,通道就瘫痪了。QOS 的精髓是限额与加成并举:既给特权,又设上界,确保特权作业不会吞噬整座医院。
- 基础分年龄、公平份额、规模等因子加权得到的基础优先级,相当于普通病人的排队分。
- 加成项QOS 配置的 Priority 数值带来的固定加分,类似绿色通道卡直接插队。
- 求和最终分数决定队列内排序,QOS 加持的作业整体排在普通作业前面。
K8s优先级:手术臂环的秩序
急诊室靠“手术臂环”决定上台顺序:红色臂环先做,黄色次之,绿色最后。K8s 的 PriorityClass 就是这种臂环——它是一个集群级对象,通过 value 字段定义 0 到 10 亿的数值,Pod 声明 priorityClassName 来佩戴属于自己的臂环。
调度器按数值降序逐个调度 Pod:数值越大越先被安排。当高优先级 Pod 在集群中找不到足够资源的节点时,会触发抢占(Preemption):从候选节点上驱逐优先级较低的 Pod,腾出“床位”给新来的重症病人,被驱逐的 Pod 会被重新排队。
与 Slurm QOS 对照可以看到两种设计思路:QOS 是用户或作业级的绿色通道,管的是限额、加成与排队秩序;PriorityClass 是 Pod 级的个人臂环,管的是调度顺序与驱逐。一个管“通道”,一个管“臂环”,作用维度不同,却共同服务优先级管理。
臂环规则的设计核心是“克制”:只有新臂环严格更高且资源确实不足时才触发驱逐,就像手术室不会因为来了 VIP 就把缝合到一半的病人赶下台。
- 抢占者新到达却找不到资源的高优先级 Pod,它的 PriorityClass 数值是“红臂环”。
- 被驱逐者节点上已运行的较低优先级 Pod,它的数值是“旧臂环”。
- 驱逐规则只有严格大于时才允许驱逐,等于或小于都不触发,避免同级 Pod 互相倾轧造成调度颠簸。
回填调度:手术室的插缝艺术
排期表上写着一台明天上午开台的 5 小时大手术,今天下午手术室却空着。聪明的护士长会把割阑尾、清创这些小手术插进空隙,只要保证大手术开台前结束。这就是回填调度(backfill)的日常画面。
Slurm 默认加载 sched/backfill 插件:当高优先级作业因资源不足而排队时,调度器为它“预约”未来资源,同时允许低优先级短作业利用当前空闲资源先行运行,前提是小作业能在预约时刻之前完成。预约像大手术的排期,回填像见缝插针的小手术。
回填的前提是“时长估计”:调度器按用户申报或历史数据预估作业运行时间。如果小作业超时,就可能耽误大作业,所以需要配套超时强杀等兜底机制。K8s 原生调度器没有经典回填,需要借助 Volcano、Kueue 等调度组件实现类似能力。
回填的精髓是“用确定性换效率”:大手术的时间表雷打不动,小手术见缝插针。调度系统正是靠预约与插缝的组合,让每一张手术台都不闲着。
- 小作业时长待回填作业的预估运行时间,对应小手术的预计时长。
- 空闲窗口当前时刻到大作业预约启动时刻之间的时间差,对应大手术开台前的空闲空档。
- 比较判断只有小作业能在窗口内结束时才放行,否则宁肯空着,保证大手术准点。
AI 应用场景
高校超算中心的多租户治理(Slurm QOS + 分区)
阶段:算力集群调度与运维
解决的问题:传统“大锅饭”模式下,热门 GPU 分区排长队、冷门分区闲置,重点科研任务被普通作业挤占。通过 partition 按院系划分队列,再用 QOS 设置优先级加成与限额,就像医院给急诊科开绿色通道,保证急难任务优先却不失控。
具体案例:某高校超算中心 64 节点 GPU 集群,设置 gpu-express(QOS:限额 32 卡、Priority=50000,绑定紧急科研账号)与 gpu-normal(Priority=10000,限制每人最多 16 卡)。配合 fairshare 按课题组历史用量动态调权,高峰排队时间从 3 天缩短到 6 小时,GPU 利用率稳定在 85% 以上。
云原生 AI 平台的推理训练混部(K8s PriorityClass + 抢占)
阶段:在线推理与离线训练的混合调度
解决的问题:在线推理服务对延迟极其敏感,离线训练任务却长期占满 GPU;固定资源池划分导致一方拥塞一方闲置。用 PriorityClass 给推理 Pod 戴“红臂环”、训练 Pod 戴“灰臂环”,高峰时推理自动抢占训练释放的 GPU,低谷时训练回填空闲资源。
具体案例:某 AI 中台将 200 个推理 Pod 绑定 high-priority(value=100)、训练任务绑定 low-priority(value=10)。晚高峰推理流量上涨时,kube-scheduler 自动驱逐低优训练 Pod;凌晨流量下降后,训练任务利用空闲资源回填继续跑。推理 P99 延迟从 400ms 降到 120ms,GPU 平均利用率仍保持 78%。
Python 演示
class BackfillScheduler: """回填调度模拟: 高优大作业未来才集齐资源, 低优短作业利用眼前缝隙先跑""" def __init__(self, node_count=4): self.node_count = node_count self.plan = [] # [(名称, 开始, 结束, 节点列表)] self.placed_count = 0 # 已放置作业数 def _free_at(self, node, start, end): for _, s, e, nodes in self.plan: if node in nodes and s < end and e > start: return False return True def earliest_start(self, need, duration): """找出最早能让 need 个节点连续空闲 duration 的时刻(找缝隙)""" t = 0 while True: free = sum(1 for n in range(self.node_count) if self._free_at(n, t, t + duration)) if free >= need: return t nxt = min((e for _, s, e, _ in self.plan if e > t), default=t + 1) t = nxt def schedule(self, jobs): """jobs: [(名称, 时长, 节点数, 优先级)], 按优先级降序依次放置""" result = [] for name, dur, need, prio in sorted(jobs, key=lambda x: -x[3]): start = self.earliest_start(need, dur) nodes = [n for n in range(self.node_count) if self._free_at(n, start, start + dur)][:need] self.plan.append((name, start, start + dur, nodes)) result.append((name, start, start + dur)) self.placed_count += 1 return result def main(): jobs = [ ("h1", 30, 1, 10), # 高优小作业: 先用 1 节点跑 30 小时 ("big", 300, 4, 10), # 高优大作业: 30 小时才能集齐 4 节点 ("small1", 20, 1, 1), # 低优短作业: 回填进 0-20 的缝隙 ("small2", 10, 2, 1), # 低优短作业: 回填进 0-10 的缝隙 ] sch = BackfillScheduler() plan = sch.schedule(jobs) for name, start, end in plan: kind = "回填" if name.startswith("small") else "预约" print("%s: %3d -> %3d [%s]" % (name, start, end, kind)) big_start = [s for n, s, _ in plan if n == "big"][0] assert all(e <= big_start for n, _, e in plan if n.startswith("small")) print("小作业都在 big 启动前完成, 大作业不被推迟 - 这就是回填") if __name__ == "__main__": main()
def earliest_start(self, need, duration):回填的核心方法:为作业找最早能容纳 need 个节点连续空闲 duration 的窗口,相当于护士长寻找手术室的空档。free = sum(1 for n in range(self.node_count) if self._free_at(n, t, t + duration))数出当前时刻空闲的节点数,凑不够 need 说明窗口不够大。nxt = min((e for _, s, e, _ in self.plan if e > t), default=t + 1)窗口不够时快进到下一个作业的结束时刻,避免逐秒扫描浪费时间。for name, dur, need, prio in sorted(jobs, key=lambda x: -x[3]):按优先级降序处理作业,高优大作业先“占坑”,低优小作业随后见缝插针。nodes = [n for n in range(self.node_count) if self._free_at(n, start, start + dur)][:need]选取窗口内真正空闲的节点,而不是默认从 0 号节点开始,避免与已有预约冲突。assert all(e <= big_start for n, _, e in plan if n.startswith("small"))校验小作业都在大作业启动前完成,验证回填“不误事”的核心承诺。
常见误区
误区我以为给作业设了超高优先级,它就像急诊病人一样直接插队开跑,根本不用等资源就绪。
正解优先级只决定“排队顺序”,不保证“立刻运行”。作业还需要满足资源就绪、分区限制等硬约束。真正能“抢跑”的是配置了抢占(Preempt)机制的作业,且抢占本身有代价:被驱逐作业要重新排队,已算的资源时间被浪费。把优先级当成万能插队卡,是新手最常见的误解。
为何常见:把“队列里排最前”和“立即运行”画了等号,忽视了资源可用性这一硬约束。
误区既然 PriorityClass 数值决定调度顺序,我把重要 Pod 全部设成 10 亿,肯定万无一失。
正解优先级只在相对比较时有意义,全设 1e9 等于没有优先级——调度退化为先来先到,抢占时也分不清谁该被驱逐。K8s 社区建议保留极高数值给系统级 Pod,业务 Pod 应分档设计(如在线推理 100、离线训练 10)。数值内卷只会让分诊台彻底瘫痪。
为何常见:直觉上以为“数值=地位”,忽略了优先级的本质是相对排序而非绝对权力。
误区每个业务线建一个队列,再派生子队列,队列越细资源管理越精细。
正解队列过多会造成资源碎片化:热门队列爆满排队、冷门队列空置闲置。多级队列的价值在于分级治理——配额继承、权限分层、优先级差异化,而非单纯追求数量。实践上建议按业务规模收敛队列数量,搭配 QOS 做限流和加成,保持队列少而精。
为何常见:把“精细化”误读成“数量多”,忽略了所有队列最终共享同一个资源池的本质。
误区回填让低优小作业提前跑,万一超时就把大作业耽误了,稳妥起见直接禁用回填。
正解回填的放行条件本身已包含安全窗口——小作业预估完成时间必须早于大作业预约启动时间;超时风险可借助作业时长上限、超时强杀等机制兜底。禁用回填的代价是确定的:集群利用率下降,空闲资源白白浪费,好比手术室明明有空档却拒绝做小手术。
为何常见:过度放大回填的“万一”风险,忽略了资源闲置是每天都在发生的确定性损失。
面试题
Q1. Slurm 中 QOS 的核心作用是什么?
- A.限额/优先级/抢占等策略← 正确答案
- B.定义计算节点硬件规格
- C.管理集群电源与能耗
- D.负责节点存储数据同步
答案:A。QOS 相当于医院的绿色通道卡,把作业数上限、CPU 时长限额、优先级加成和抢占权限打包绑定到用户或账户。B 是节点属性(gres/features)负责的事,C 属于电源管理,D 属于存储层功能,都与 QOS 无关。
Q2. K8s 中 PriorityClass 的数值越大表示什么?
- A.作业执行时长更长
- B.作业占用资源更多
- C.作业调度优先级越高← 正确答案
- D.节点亲和绑定更强
答案:C。PriorityClass 数值表示调度优先级,调度器按数值降序逐个调度 Pod,数值越大越先被安排。它不控制运行时长、资源大小或亲和性,那些由资源请求、LimitRange、nodeSelector 等字段决定。
Q3. 优先级衰减(年龄因子)的直接作用是什么?
- A.加速作业运行速度
- B.防止低优作业饿死← 正确答案
- C.降低集群总体能耗
- D.提高单节点利用率
答案:B。年龄因子让等待时间转化为优先级加分:排得越久分数越高,排队位置越靠前,从而避免低优先级作业永远轮不到资源。它与运行速度、能耗、单节点利用率都没有直接关系。
Q4. Slurm 优先级的公平份额因子主要实现什么?
- A.让作业时长完全相等
- B.保证按提交顺序执行
- C.让节点数量平均分配
- D.平衡不同租户资源使用← 正确答案
答案:D。公平份额(fairshare)根据历史用量动态调整优先级:用量超出公平份额的租户,其后续作业优先级被压低,把机会让给用量不足的租户,实现多租户间的长期公平。它不追求作业时长相等,也不按提交顺序执行。
Q5. 回填调度允许小作业运行的充分前提是什么?
- A.小作业优先级更高
- B.小作业只用单节点
- C.能在预约启动前完成← 正确答案
- D.大作业确认被取消
答案:C。回填的放行条件是 T_job ≤ T_reserve − T_now,即小作业能在高优大作业预约启动前跑完。小作业不需要更高优先级,也不限定必须单节点;若大作业取消则正常排队即可,无需回填。
Q6. K8s 高优 Pod 触发抢占时,优先驱逐的对象是?
- A.节点上低优先级的Pod← 正确答案
- B.同级高优的各个Pod
- C.系统命名空间的Pod
- D.运行时间最长的Pod
答案:A。抢占会从候选节点中挑选优先级低于新 Pod 的 Pod 作为驱逐对象,同时会考虑 PDB、QoS 等因素;同级或更高优先级的 Pod 不在考虑范围,系统命名空间的 Pod 有额外保护,运行时长也不是首要标准。
Q7. Slurm QOS 的 Priority 字段如何影响作业?
- A.乘权重覆盖年龄因子
- B.仅对超额用户生效
- C.作为加成叠加到基础分← 正确答案
- D.只在回填时起作用
答案:C。QOS 的 Priority 通过 PriorityWeightQOS 因子折算后叠加到作业的基础优先级,相当于绿色通道的固定加分。它不会覆盖年龄因子,也不只对超额用户或回填场景生效。
Q8. 关于 K8s PriorityClass 与 Slurm QOS,正确的说法是?
- A.管粒度是Pod或作业← 正确答案
- B.两者都直接操作节点
- C.都能限制CPU使用时长
- D.都只能用于单个用户
答案:A。PriorityClass 是 Pod 级机制,决定调度顺序与驱逐;QOS 是作业或用户级机制,决定限额、加成与排队秩序。二者都不直接操作节点硬件;K8s 优先级不限制 CPU 时长;Slurm QOS 可绑定用户、账户或分区,并非单用户专用。
Q9. Slurm 优先级计算的常见因子有哪些?
- A.年龄因子即等待时长← 正确答案
- B.公平份额因子权重← 正确答案
- C.QOS优先级加成值← 正确答案
- D.节点功耗因子项
答案:ABC。Slurm 优先级由年龄因子(等待时长)、公平份额因子、QOS 因子、分区因子、作业规模因子等加权求和。节点功耗不属于优先级计算因子,那是能耗调度研究的方向。
Q10. 关于回填调度,下列说法正确的有?
- A.回填能提升资源利用率← 正确答案
- B.回填依赖作业时长估计← 正确答案
- C.回填作业优先级最高
- D.回填会推迟高优作业
答案:AB。回填让低优短作业利用高优作业启动前的空闲窗口运行,从而提升利用率;其安全性依赖时长估计,只有能在窗口内完成才放行。回填作业不要求最高优先级,且前提是“不推迟”高优作业,因此 C、D 错误。