
提到高性能计算资源调度很多人第一反应是“不就是排队吗”但真正跑过千卡集群、排过上万核作业的人都知道调度是整个HPC系统的中枢神经。它决定你的GPU利用率是30%还是90%决定你提交的作业是在两分钟内跑起来还是在满队列里排两天晚上甚至决定你花几千万搭建的集群到底是在干活还是在空转。我用调度器的时间不算短经历过手写脚本分发任务的时代也用过后来的各类资源管理系统踩过的坑凑一桌麻将还有富余。这篇就围绕高性能计算资源调度这件事把我在实际项目中总结的调度策略、工具选型、配置调优、排错经验一次讲清楚内容偏实操适合刚接触集群的开发者也适合已经在用调度器但想进一步榨干集群性能的朋友。1. 高性能计算资源调度到底在解决什么问题1.1 一句话说清资源调度的本质资源调度处理的是一个非常朴素的问题多个人都想用计算资源但资源就那么多先给谁用、给多少、用完了怎么回收、有人中途不跑了怎么把资源再分配出去这些规则的总和就是调度。我们做仿真、AI训练或者大规模数据处理时任务通常不是一个进程能跑完的往往需要几十个节点、几百张卡协同工作。用户提交一个作业需要指定要多少节点、多少CPU、多少内存、多少GPU还要指定跑什么命令。调度器负责审核这些请求是否合法把它排到某个时间点然后在这个时间点去真正的计算节点上拉起任务。核心目标是尽量不浪费资源尽量让所有用户的等待时间都能被接受。可以把调度器类比成一家餐厅的领位员。客人来了餐厅有四人桌、六人桌、包间你没法让一个人占十人包间也没法让十六个人的团队硬塞进六人桌。领位员要不断翻台、拼桌、预订还必须预计哪些桌什么时候会空出来。调度器做的事情和这个完全一样只不过它管理的是计算节点、CPU核心、内存和加速卡客户是各种各样的计算作业。1.2 调度器在整个HPC系统中的位置搞清楚调度器的位置很重要。一个典型的高性能计算集群大概分四层登录节点Login Node用户登录、提交作业、编辑代码、查看结果。管理节点Management Node运行调度服务、账号权限、日志和服务节点监控。计算节点Compute Node真正跑任务的机器通常无盘启动用户不能直接登录必须通过调度器申请。存储系统Storage并行文件系统或对象存储提供共享数据访问让所有计算节点看到同一个数据视图。调度器运行在管理节点上它连接了用户和计算节点。用户不直接在计算节点上跑命令而是通过 sbatch、qsub 或 bsub 提交作业调度器根据当前资源的使用情况做决策决定一个作业何时何地运行。这里有个常见的认知误区调度器只负责分配不负责帮你优化作业本身。你的程序如果是单线程却申请了128个核调度器也只会把128个核分给你然后看着利用率表上出现一排“单核100%、其余0%”的尴尬数据。所以调度器的设定和用户的行为需要相互配合才能让集群整体效率达到理想状态。2. 调度策略与算法从FIFO到抢占式调度的演进2.1 经典调度策略盘点调度器背后最核心的是调度策略策略决定了哪些作业先跑哪些作业往后排。最早的调度策略就是先来先服务谁先提交谁先跑。实现简单对用户也公平但问题非常明显一个需要128个节点的作业排在最前面时如果当前集群只剩64个节点这个作业只能等而后面的多个小作业虽然资源足够也无法插队。这就造成了大量资源空闲。于是在FIFO基础上发展出了一些更聪明的策略我挑几个实际用得最多的讲。公平调度目标是让每个用户或每个项目组大致得到与配额相符的资源份额。作业多的时候每个用户都能分到一点避免某个用户起一批大作业把整个集群占满影响其他人的使用体验。不过在抢大规模资源时公平调度可能让所有作业都“跑得不够快”因为资源被切得太碎了。面向吞吐量的调度优先让那些申请少量资源、运行时间短的作业先跑因为这类作业完成得快单位时间能完成的作业数自然就高。对于吞吐量敏感的生产集群这个策略很有效但代价是大型作业等待时间可能被拉长到让人崩溃。抢占式调度高优先级作业来了把低优先级作业正在占用的资源直接收回去分配给高优先级作业。被强杀的任务可能丢掉之前所有计算结果所以一般会配合检查点机制让作业定期保存进度被抢后可以恢复。不是每个场景都适合开启抢占如果线上没有做检查点保护强杀带来的损失可能比等更难受。回填调度Backfilling这是我自己最喜欢也最推荐优先考虑的策略。它保留了FIFO的优先级排序同时允许排在前面的作业如果没有足够资源运行就把剩余的空闲资源分配给排在后面的小作业前提是这些小作业不会让前面的大作业延迟启动。回填策略几乎是现代调度器的标配因为它能同时照顾作业优先级和集群利用率。实际收益很明显在一个混合负载集群中开启回填通常能把资源利用率从60%以下提升到85%以上。2.2 优先级计算与公平性调度策略最终都要落到优先级的计算上。优先级不是一个简单的整数它通常由一组因素加权得出用户或项目组的配额使用情况用得越少优先级越高。作业的提交时间越早越高。作业申请的节点数或资源量申请越多部分策略里权重越高。QoS级别即服务质量等级。当前队列的负载情况某些时间段可以调整权重以平衡负载。调度器每隔一段时间通常几秒重新计算一次所有排队作业的优先级再决定下一批调度哪些作业。这个间隔也叫调度周期周期太短调度器本身CPU开销大周期太长作业启动不够及时。一般生产环境设成5到15秒需要根据提交频率调整。公平调度里常出现一个现象叫“饥饿”极少数用户的作业因为优先级低一直被新来的高优先级作业挤到后面。调度器一般会设置一个“最大等待时间”或者“防饥饿阈值”作业排队超过一定时间自动提升优先级。所以如果发现作业在队列里卡了很久先别急着骂调度器查一下这个机制的开关和阈值是否合理。2.3 异构资源的调度GPU、FPGA传统CPU调度只要考虑核数和内存现在很多HPC集群的核心计算力其实来自加速卡GPU、FPGA、NPU等等。异构资源调度跟传统CPU调度完全不同它必须考虑资源之间的映射关系。首先是数量GPU 显存在不同型号间差异很大80GB、40GB、24GB 都有作业申请时最好显式声明需要的显存大小而不仅仅是卡的数量。否则调度器可能把一个需要 40GB 显存的作业分到只能提供 24GB 的节点上任务一启动就报 out of memory。其次是拓扑亲和性。多卡训练时数据通信的路径对训练性能影响极大。同一块主板上 CPU 和 GPU 之间的 PCIe 通道连接方式、GPU 之间是否通过 NVLink 或 InfiniBand 连接会造成显著性能差异。调度器在分配多卡作业时应该尽量把作业绑定到物理上互联最优的一组卡上而不是随机挑出几张卡拼凑。实际操作中我习惯在提交作业时通过调度参数显式声明资源约束比如要求某些节点标签、要求同等显卡型号、要求显存下限宁可排队等一等也不愿意因为异构导致程序运行失败或性能折半。3. 实操落地主流调度器的选型与配置3.1 选型对比调度器选型是整个资源调度体系最基础也最难回头的一步换调度器或者从没有调度器换成有调度器要迁移用户作业脚本、重写管理脚本、同步账户数据痛苦指数极高。主流的几款我列个对比表方便按场景选调度器典型使用场景优势需要留意的点Slurm绝大多数高校、企业自建HPC集群开源免费社区活跃文档全支持复杂拓扑调度配置项非常多初次上手有学习曲线PBS/Torque老牌科研集群很多商业软件的默认对接对象部署简单历史包袱轻旧脚本兼容性好开发迭代慢复杂策略支持较弱LSF金融、芯片设计、部分大企业稳定性好商业支持调度策略成熟商业化收费授权成本高KubernetesAI训练、云原生环境、微服务弹性好容器化标准GPU虚拟化支持丰富对MPI、高并发跨节点通信支持偏弱调度延迟偏高自研调度业务极其特殊、需求完全无法被通用调度器满足完全贴合业务维护成本高初期开发量大建议先用商用方案再评估我个人对没有特殊合规限制的团队建议选 Slurm。它开源、免费、社区巨大、API 齐全几乎任何你遇到过的调度问题在社区里都能找到答案。Slurm 的术语体系有点反直觉比如它把队列叫 Partition把节点数量约束叫 Constraint但上手半个月后就习惯了。3.2 队列配置和分区设计队列或分区是调度策略的载体。同一个集群物理资源相同但通过分成不同的队列可以让不同类型作业获得不同策略。比如给关键业务单独分一个队列配置较高优先级AI训练作业进 GPU 队列短小测试作业进 debug 队列。我建议至少划分四类队列debug 队列限制每用户节点数和运行时间比如最多2节点、运行2小时保证快速响应测试需求。生产队列限制较宽松但启用回填和调度优先级队列运行时长可到几天。GPU 队列额外管理显卡资源设置 GPU 数量和显存约束。大作业队列需要特别多节点或大量内存的作业超长运行时间单独限制配额。配置队列时注意资源隔离。Slurm 的 Partition 之间没有硬隔离如果多个队列共用一套节点高优先级队列抢资源时会把低优先级队列的作业挤掉。所以如果业务上有硬隔离需求要在物理隔离不同节点组和逻辑隔离同一批节点但不同的调度策略之间做出选择。队列设计有一个很容易忽略的点运行时间上限。作业可以申请运行时间调度器基于这个时间做回填决策。如果你把上限设得太宽松比如所有队列都不限时长那么排在前面的作业占用的时间窗口就非常不确定回填算法就没法判断“后面小作业是否会影响大作业启动”结果就是回填效果大打折扣。务必给每类作业设置合理的时间上限。3.3 GPU资源管理与设置GPU 集群的调度配置比纯 CPU 集群要复杂得多。首先是 GPU 资源的建模方式调度器需要知道每个节点有几张卡、每张卡的型号和显存、这些卡之间的通信拓扑才能真正合理分配。Slurm 的 GPU 管理有两种模式一种是每节点固定配置一种是更细粒度的 GPU 索引绑定。我在项目里实际验证下来的配置要点在节点配置里准确声明 GPU 型号和数量不要用模糊的 gres/gpu1至少带上 type 标签否则调度器无法区分 A100 和 RTX 4090。提交作业时要求用户显式指定 --gpus4 而不是默认拿满全部卡避免资源浪费。对不需要 GPU 的作业严格限制 GPU 队列的进出尽量做到“人人都能用 GPU但只有真正需要的任务能用 GPU”。排队时间压缩之后整个集群的吞吐量反而会提升。如果需要显存隔离建议在 GPU 节点启用 MPS它可以把一张卡分成多个计算实例多任务共享显存但卡间通信交叉会引入性能损失需实测评估。还有一个很容易踩的坑很多人认为 GPU 调度只是分配“卡”但实际应用中显存才是第一约束。深度学习作业申请4张卡可能是需要每卡40GB显存也可能是每卡要80GB。调度器层面可以做显存约束具体做法是在 slurm.conf 里配置 gres/gpu 的 File 属性。但很多旧集群不做细分于是用户只能通过“申请整卡数”来控制显存占用显然会造成浪费。这种场景建议引入显存感知的调度策略或让用户通过虚拟化组件申请显存片段。4. 调度性能优化瓶颈分析与参数调优4.1 调度延迟的构成调度延迟指的是作业从提交到真正起跑之间的时间这个指标对用户体验至关重要。调度延迟不完全等于排队时间它包含几个部分调度探测器收集集群状态的时间间隔。优先级计算和决策时间与排队作业数量正相关。作业启动的物理耗时分配节点、启动远程进程、加载环境、挂载文件系统。网络和存储的额外开销比如并行文件系统目录元数据竞争。如果用户报“提交作业后迟迟不动”不一定是排队也可能卡在调度器状态同步粒度上。比如调度线程默认每15秒同步一次节点状态一个作业启动后资源释放的展示延迟就有15秒的滞后在大量短作业场景下这个延迟非常致命。我常做的优化是把调度消息和状态收集的间隔调低比如官方推荐是每秒收集资源状态、每2秒做调度决策但也要评估调度器自身的CPU开销。一个经验值是如果集群节点数小于500调度间隔 3~5 秒足够不用调到 1 秒。4.2 亲源性与拓扑感知调度现代HPC调度的关键词是拓扑感知。物理上一个集群可以划分为机架Rack、交换机Switch和节点Node不同层级之间的通信延迟和带宽差异是客观存在的。作业调度时如果无视物理位置把需要频繁通信的进程分配到相隔很远的节点上网络通信就成为瓶颈表现为运行时间大幅增加、MPI 通信一直卡在等待上。拓扑感知调度的思路是调度器知道哪些节点在网络里属于同一个共享叶子即Leaf switch尽量把同一个作业的进程部署在同一叶子下。如果作业规模超过一个叶子能提供的节点数再沿树往上分配。这个特性的实际效果我在一个8机架集群上测过一个纯 MPI 作业随机分配节点时的通信耗时比拓扑感知分配高30%~50%总运行时长拉长了近三分之一。所以如果集群已经有多个交换机、多条上联链路强烈建议打开拓扑感知。另外CPU 亲和性设置同样不容忽视进程绑定到固定CPU核心上能减少上下文切换和跨NUMA访问。调度器一般在节点内部做绑定可以用 cgroup 或者 taskset 辅助完成。特别是做内存访问密集计算时NUMA 绑定做不做好性能差距经常是一倍。4.3 吞吐优化与回填策略回填的实现细节直接影响最终效果。简单的回填策略是“只填小作业”现代调度器已经支持预测回填调度器模拟前面大作业的未来资源释放时间基于预计释放时间再决定是否能插入更多小作业。在调整回填参数时第一个关键值是“预留时间窗口”。窗口越大回填安全性越高但也意味着资源空置时间变长利用率下降。窗口太小则可能触发回填后的作业抢占导致被回收。我在实际中会把可回填时间窗口设为2小时并根据作业运行时长分布动态调整。如果大部分作业在1小时内完成窗口可以缩小到30分钟。第二个关键是作业优先级和队列的“紧急程度”映射。不要让所有作业都享受相同的回填待遇比如关键业务队列的回填窗口可以更长其他队列的回填窗口做收紧避免关键业务作业因为回填失败而排队。回填的坑也必须提前知道回填作业一旦启动就不能被杀掉或迁移除非开启抢占否则数据一致性会出大问题。所以每个回填作业启动前都要做资源生命周期检查保证它在预期时间段内不会与大作业冲突。5. 常见问题排查实录5.1 资源利用率很低但作业一直在排队这个现象特别常见表现是一堆作业在队列里等但集群监控显示 CPU/GPU 利用率只有40%。排查思路按顺序来查看调度器队列状态确认作业是否因为时间限制或节点约束无法分配。查看资源利用率按节点分布如果某些节点负载极高、某些节点空闲就是调度器选择了把所有作业压到同一批节点通常是因为节点分组策略没生效或预留了过多资源。查看节点是否有大量“预留”资源。调度器出于种种原因会预留部分节点比如高优先级队列占用、维护窗口、系统预留等这些预留不干活会造成全局利用率低下。查看是否所有作业都在等同一个特定节点标签。比如用户脚本里写了只认某一批节点的标签而该批节点刚好被其他作业占满其他可用节点空着也没用。最常见的元凶其实是用户作业申请的资源粒度过大比如一个作业申请了8节点但该队列空闲节点只有6个它排在队首并阻塞了后面所有作业FIFO策略 未开回填时尤其严重。解决方法是开启回填同时引导用户按实际需要申请资源。5.2 作业“卡死”既不运行也不报错这种“僵尸状态”我排查过很多次大致有以下几种原因调度器与节点心跳丢失节点状态显示为 down/drain但实际该节点还活着作业队列里显示正在等待。作业脚本里的无限等待某些脚本在等待文件锁、等待其他作业输出文件导致程序停滞自己以为是在排队。共享文件系统故障或慢挂载作业虽然有节点但启动时挂载存储超时看起来一直没起来。调度器自身数据库锁冲突多个节点同时上报状态导致调度器数据库短暂锁死作业排不进去。排查步骤上先进入登录节点用专用命令查看作业状态看作业的 RUNNING 还是 PENDING如果是 PENDING 看具体等待原因。如果是 RUNNING 但没有任何输出再查看作业所属进程是否真实存在确认它是否卡在启动脚本阶段。我遇到过最典型的一次作业状态显示 RUNNING但计算节点上的 GPU 利用率是0。查下来发现用户在 slurm 脚本里写错了环境变量程序一直在等待某个不存在的路径出现也不退出。这不是调度器问题但很多人会误以为是调度器卡住。5.3 内存溢出与OOM引发的连锁故障资源调度分配了节点但不监听运行时的内存使用一旦作业内存超出节点物理内存Linux 内核的 OOM Killer 机制会杀掉进程用户看到的可能是“主机内存不足”或者“被信号9终止”。这个问题的根子在于调度器是否做了严格的内存上限绑定。如果只配置了内存数量约束但没有启用 cgroups 做硬隔离那么 B 节点上的一个作业占用过剩内存时会直接威胁同一个节点上的其他作业造成随机杀进程。我建议在生产集群里全面启用内存的硬上限。配置后如果作业超内存会立即被调度器杀掉并释放资源而不是等 OOM Killer 随机挑选受害者。对用户来说这种“快速失败”反而容易发现程序问题对集群来说不会出现无厘头的整节点故障。另外需要设置禁用的节点排水机制节点上只要有一个作业发生 OOM就把该节点标记为维护状态不允许新作业进入等待人工检查后再放回调度池。虽然是稍微保守的做法但在故障频发期能大幅降低排查难度。6. 一些小经验和值得记住的心得把我在HPC资源调度这条路上印象最深的几件事放在最后这些不是从书上看来的是实实在在被教训出来的。第一个心得是资源调度的核心目标不是“让所有作业都尽快完成”而是“让整个集群在满足业务约束下的效率最大化”。如果只追求单作业快就会倾向让大作业独占集群如果只追求利用率就会牺牲大量小作业的等待体验。真正好的调度方案一定是个平衡平衡业务优先级、资源占用、等待时间、瓶颈带宽这几个维度。第二个心得是监控是调度方案落地的眼睛。调度器里那些“调度成功率”、“节点平均利用”、“排队时长中位数”指标如果没有监控可视化等于什么也没有。我一般在集群搭建第一周就把三块数据完善节点级资源利用率、作业级排队/运行状态、用户配额使用趋势。后续所有调优动作的正确性都靠这些数据来验证。第三个心得是文档和规范比参数配置更影响集群健康。我发现集群利用率长期上不去的团队很大原因是用户提交作业的时候根本不知道应该申请多少资源随手申请64核实际单核跑。这种问题只能靠新人指引、作业脚本模板、资源申请规范从源头解决。在调度器层面加上配额限制可以治标但科普培训才能治本。第四个心得是检查点要早做。对着生产集群做调度优化试错是可以的但每个优化都可能让一批正在运行的作业被杀掉。因此在做资源配置调整、启停调度器、改分区策略前一定确认业务作业做好检查点最好是调度器层面的周期快照都开启别抱着“应该没问题”的心态去动生产环境。第五个心得任何新调度方案上线之前先在一个独立的测试集群上模拟专门造一批“脏作业”去压测例如短作业、成百上千个小作业、内存超标作业、多G作业混合场景。只有在这种模拟环境里验证过我才敢上生产。这个习惯帮我避开了不下十次大规模翻车。如果你正在打算给自己团队引入全新的调度体系或者正在头疼集群利用率提不上去我的建议是先把当前集群的作业分布情况和真实资源需求摸底清楚再选调度器和设计队列配置。不要一上来就追求复杂功能把一套标准化流程先跑通再逐步增加高级调度策略这个节奏才是最稳的。希望这篇能帮你少走一些我走过的弯路。