
我最早接触高性能计算资源调度是被实验室一台 32 核服务器“折磨”出来的。那时候大家提交作业全靠手气——谁先抢到节点谁算后提交的直接排队排到凌晨。节点利用率低得可怜有人用 1 个核跑大任务占着整台机器有人想开 64 进程的并行计算却发现内存早已被占满。等你真正上手一套集群才明白资源调度不是“排队”那么简单它是整个高性能计算系统的中枢神经直接决定了你的算力到底能发挥出几成。这篇文章我不会给你讲那种教科书式的调度器原理而是站在一个实际搭过集群、调过参数、被排队机制坑过无数回的角度把高性能计算资源调度从“为什么需要”到“怎么选型”再到“怎么调优”一次性讲透。不管你是刚接手学校计算集群的运维还是准备给公司的 GPU 服务器搭一套调度系统这篇文章都值得你花十分钟读完。1. 高性能计算场景下资源调度到底在解决什么问题很多人理解资源调度觉得无非就是把任务排个队、分个节点。但高性能计算里的调度远不是“先来后到”这么简单。它要解决的核心问题可以拆成四个层面。第一个层面是资源分配。一台计算节点有几十甚至上百个 CPU 核心有几百 GB 甚至 TB 级别的内存有本地 NVMe SSD可能还挂着多张 GPU 卡。调度器要做的是把任务精确地映射到这些资源上。比如一个任务申请“32 核、64GB 内存、1 块 A100 GPU”调度器要能找到满足这个组合条件的节点并且在任务运行期间把这些资源“锁住”防止别的任务抢占。第二个层面是任务排队。当申请的资源大于集群空闲资源时任务就要等待。但问题是谁该先跑按提交时间按优先级按用户所在的项目组这正是调度策略发挥作用的地方。一个合理的排队机制要保证紧急任务能插队又不让普通任务永远饿死还要让集群整体吞吐量尽可能高。第三个层面是资源隔离。高性能计算集群通常是多用户共享的。如果没做好隔离一个任务的内存泄漏可能导致整台节点宕机一个任务的疯狂 CPU 占用会让同节点的其他任务卡成幻灯片。调度器需要借助 cgroup、Linux 内核的隔离机制或者容器技术把故障限制在单个任务范围内。第四个层面是弹性伸缩。现代高性能计算集群尤其是云上的集群资源不是固定的。计算任务多的时候要自动扩节点任务跑完了要自动缩容。调度器要能感知负载变化动态调整资源池的规模避免高峰期算力不够用、闲时又白花钱。这四种能力说起来简单但真正落地时会遇到大量细节问题。比如 CPU 密集型和内存密集型任务如何混布怎么处理资源碎片如何平衡公平性和利用率GPU 显存该按整卡分配还是允许分片共享——每一个都是值得深入优化的点。2. 调度器核心机制拆解从排队到反压术语背后的真实含义市面上任何一套调度器不管界面长什么样底层机制都是围绕几个核心概念展开的。弄懂这些概念你就掌握了调度器的“通用语言”。2.1 队列与分区谁说排队一定是“先来先服务”队列Queue或分区Partition是调度最基本的概念。你可以理解成不同的“候车通道”——普通任务走普通通道紧急任务走绿色通道GPU 任务走专门通道。每个通道有独立的资源配额和优先级规则。但通道内部并不一定是先来先服务。调度器支持多种排队算法最常见的是优先级排序——每个任务有一个综合优先级值这个值由多个因素叠加计算而来包括用户优先级、项目组优先级、任务已等待时间、申请资源大小等。如果一个低优先级任务等了太久它的优先级会随时间增长最终可能超过新提交的高优先级任务。这就是防饿死机制。我见过很多初学 HPC 的人提交任务后干等不知道自己的任务为什么排在别人后面。其实用sprioSlurm或qstat -fPBS看一眼任务的优先级构成就能找到原因是等待时间不够还是所在队列优先级本身就低。2.2 公平共享Fairshare防止“大户垄断”的看不见的手在一个多人共享的集群中如果某个课题组提交了大量任务调度器是按“谁提交的多谁优先”还是“按历史使用量做惩罚”答案是后者。公平共享算法的逻辑是统计每个用户或项目组在过去一段时间内的资源使用量使用得越多当前优先级就越低。这样能保证资源在多个用户之间相对均衡地分配而不是被一两个大户长期霸占。Slurm 的PriorityTypepriority/multifactor插件、LSF 的 Fairshare、PBS Pro 的 Fairshare 都是这个思路。不过我实操下来公平共享的默认参数往往不满足实际需求。比如我们课题组内部约定某些临时加入的合作项目需要借一部分算力但默认配置下它们会挤占我们自己的任务。这就需要手动调整 fairshare 的时间窗口和衰减系数。这类调参问题后面我会单独展开。2.3 回填Backfill把碎片时间榨干的最高性价比手段这是让我觉得调度器“高级”起来的一个机制。假设当前集群上排队了一个需要 128 节点的大任务但主队列的任务至少还要等 30 分钟才能释放足够节点。此时如果严格按照“先到先得”排在后面的一堆小任务就得一起等这 30 分钟集群出现大量空闲节点。回填机制的思路是只要小任务的运行不会导致大任务的预期开始时间被推迟就让小任务先用空闲节点跑。这样大任务不受影响地被安排在原定时间启动小任务也能提前完成。集群利用率从 50% 提升到 80% 往往靠的就是这一招。回填的触发条件、限制条件是调优的重点。过于激进可能导致很多任务“滚回”重新排队反而降低吞吐量。比如我们曾经把回填的阈值设得过低导致小任务频繁被杀掉重排失败率飙升。2.4 抢占Preemption优先级冲突时的最终裁决机制当一个新的高优先级任务到来而集群资源已经被低优先级任务占用时调度器可以“抢占”。抢占有两种形式一种是直接杀掉低优先级任务suspend/kill把资源让给高优先级任务另一种是让低优先级任务挂起等资源空闲再恢复运行。抢占机制的启用要十分谨慎。如果频繁抢占低优先级任务的反复被杀会导致大量无效计算用户体验极差甚至引发数据完整性问题。我通常建议至少设定一个“可被抢占任务最长已运行时间”的保护阈值让任务运行超过某个时长后不再被抢占。3. 主流开源工具选型Slurm、LSF、PBS Pro、Kubernetes 的取舍2015 年前后高性能计算集群基本被三大传统调度器统治Slurm、LSF 和 PBS Pro。近几年 Kubernetes 也挤进了高性能计算领域尤其在 GPU 集群上势头很猛。选型没有绝对的好坏只有适不适合你的场景。3.1 主流调度器横向对比我根据自己的实际使用经验把这四类工具从适用场景、学习成本、扩展性、GPU 支持等维度做了个对比调度器适用规模学习成本常见行业GPU 支持动态扩缩容多年维护经验Slurm单机到超算低高校、科研院所较强但需要配置弱需配合外部工具我用的主力文档全LSF中大型中企业研发、金融、制药较强中等稳定但商业化生态封闭PBS Pro中大型中科研超算中心较强中等传统老兵被 Altair 收购后更新放缓Kubernetes中大型高互联网、GPU 云服务原生很强极强容器生态社区最活跃3.2 高层选择的判断依据如果你面对的是一个传统数值计算场景——跑的是 CFD、分子动力学、气象模型这类 MPI 程序用户习惯用sbatch或qsub提交脚本那么Slurm是首选。它是目前开源领域事实上的标准几乎每一位超算运维都熟悉它出了问题在网上能搜到大量经验。如果你们公司高层强调资源利用率、多租户隔离、GPU 共享并且基础设施已经容器化那Kubernetes Volcano/Kueue这条路线更值得考虑。它天然支持容器的调度和弹性伸缩和云环境对接非常顺滑。不过要把 MPI 应用跑在 Kubernete 上需要处理网络方案如 RDMA 直通、特权容器、固定 IP 等一系列问题上手门槛明显更高。LSF 和 PBS Pro在企业级市场仍有大量存量用户。LSF 的排队预测、资源配额功能做得很成熟客户服务也扎实但它是商业软件需要按核数采购 license。PBS Pro 从开源底座出发Altair 接手后主要面向大型超算中心服务普通团队如果不是有历史包袱新项目我建议优先评估 Slurm。3.3 GPU 密集型场景的特殊考量现在的资源调度选型GPU 是绕不开的话题。传统调度器处理 GPU 的方式都比较“粗”常见的是把一张 GPU 卡作为一个整体调度单位。而深度学习训练和推理任务往往只需要半张卡甚至更小的显存整卡分配会浪费大量算力。Kubernetes 生态里对 GPU 的支持做得最细可以结合显存、算力、显存带宽等维度做更细粒度的调度。Volcano 这类批量调度器还能实现 GPU 共享、显存隔离甚至多任务的 GPU 时间片复用。但我不建议你为了 GPU 共享功能就盲目迁移到 Kubernetes。如果你的团队对传统 HPC 更熟悉Slurm 也能通过--gresgpu:2这样的方式管理 GPU而且新版 Slurm 已经支持 MIGMulti-Instance GPU调度可以把 A100 切成多个实例分别分配。关键还是看你们团队的技能栈和长期路线。4. 生产集群调度调优实战从 slurm.conf 到队列级别的大坑复盘选型定了接下来最烧脑的部分是参数调优。下面这段全程用 Slurm 举例但思路可以迁移到其他调度器。4.1 集群级参数cpu、内存与节点权重的正确姿势Slurm 的slurm.conf里一个典型节点配置看起来是NodeNamecompute[01-32] CPUs64 RealMemory245000 Sockets2 CoresPerSocket32 ThreadsPerCore1 Gresgpu:a100:8有两个参数我踩过坑。一个是RealMemory它最好设置成物理内存的 90% 左右而不是满值。因为操作系统本身和系统服务要占用一部分内存如果按满值分配任务一旦申请到节点全部内存很容易触发 Linux OOM Killer。另一个是CPUs如果你开启了超线程这里要按逻辑 CPU 数填还是物理核心数填从高吞吐角度可以填逻辑数让多个任务共享物理核从稳定性角度建议填物理核心数避免一个任务的两个线程被调度到同一个物理核上。4.2 分区Partition设计一次设计失误半年后还在还债分区设计是调度系统的“顶层架构”设计得好能省无数运维精力。我建议按用途和业务特点拆分而不是按用户拆分。一个大致的模板分区名用途节点规模最大运行时间优先级debug短时测试、单节点小任务4 节点1 小时高batch常规批量作业50 节点48 小时中long长时大规模计算20 节点30 天低gpuAI 训练与推理12 节点96 卡7 天中这个设计里debug 分区是给用户调试用的排队快、限制严防止有人拿测试任务占用生产资源。long 分区给那些动辄跑几天几周的任务优先级低是合理的因为长任务占资源的时间长不应该让它压过短平快的任务。4.3 优先级参数调优Multifactor PrioritySlurm 的 multifactor 优先级默认参数是一个比较复杂的公式核心是三类因子的加权归一化。常见的调整手法是加大PriorityWeightAge和PriorityWeightFairshare的权重。但权重太大也有问题——如果一个新提交的任务因为 Fairshare 权重高而频繁插队老任务重排后重新启动的时间成本也是隐性浪费。举个例子我们有个应用需要跑 3 天每次被抢占后断点续算需要额外消耗 30 分钟检查和加载存档。如果集群平均每天发生 3 次抢占3 天下来光重新加载就浪费了几个小时的有效算力。后来我把抢占的PriorityWeightAge拉高同时设置了PreemptExemptTime3600让运行超过一小时的任务免于被抢占整体失败率降了差不多一半。4.4 cgroup 隔离防内存泄漏的保命措施Slurm 如果要开启严格的内存隔离需要在slurm.conf里加上ProctrackTypeproctrack/cgroup并把ConstrainCoresyes、ConstrainRAMSpaceyes打开。同时还需要确认节点上安装了slurm-cgroups相关插件。这个配置的价值我深有体会。以前没有约束时某个用户跑 Python 脚本内存不像数值计算那样规范释放系统出现内存溢出直接拖垮节点。开启 cgroup 后任务超限时只会被 kill 掉不会影响其他任务。但注意cgroup 的内存约束功能在某些情况下会和 MPI 程序冲突官方文档要求给 MPI 设置更大的额外内存空间MemoryAllocTruncate相关参数这一段配置需要仔细测试。4.5 镜像监控和日志亡羊补牢不如天天看调度不是配置完就一劳永逸的。我们日常巡检主要看三个东西节点sinfo状态、队列深度squeue、历史作业统计sacct。特别注意节点频繁进入down或drain状态多半是硬件故障或内存 ECC 错误排队任务数突增的时段背后往往有个别用户写了错误的循环提交脚本大型作业失败率过高的节点需要及时查/var/log/slurm/slurmd.log如果日志量太大建议部署 Prometheus Slurm Exporter把节点状态、作业排队时间做成曲线图。我自己是 habits 上线一套 Grafana 面板每天扫一眼曲线就能提前发现很多问题。5. 从排队到资源折叠常见调度问题的排查链路复盘实战中高技术含量的活儿是排查问题。这里分享三个我印象特别深的案例排查过程基本代表了几类典型问题。5.1 节点明明有空闲为什么任务却一直排队现象用户抱怨squeue显示任务在 pending但sinfo看节点明明有空闲 CPU。排查我第一反应是看任务卡在什么状态。用scontrol show job jobid -dd发现任务 Reason 是Resources或者Priority说明调度器认为“不是不想跑而是排不上”。Resources常见原因是单节点剩余资源不满足任务申请比如任务申请 “48 核”,但节点只有两个 32 核插槽调度器只会将任务放在单节点内不会跨节点分配一个任务里的 48 核MPI 例外。这时候scontrol show node看一下空闲资源的碎片分布就知道了。5.2 内存超卖节点开启后被 cgroup 秒杀现象用户任务一运行就报错被杀节点反复重启。排查节点down的原因为 OOM看journalctl -u slurmd发现 cgroup 报告任务请求 96GB 但实际分配超出RealMemory的 90%。原因是用户使用--mem-per-cpu4000申请了 32 核恰好 128GB 内存但内存管理发生了换页RSS计算方式和 cgroup 报告不一致。解决有两种要么调整slurm.conf的RealMemory预留更多系统空闲要么给用户培训正确估算内存申请量。我后来把MemoryAllocTruncate做了配置又给管理员写了内存预估的文档人手不够时最稳妥是后者。5.3 GPU 资源显示空闲但新任务迟迟无法调度现象GPU 分区节点gres显示还有 4 张空闲卡但新任务的 Reason 总显示Resources。排查看scontrol show job发现任务请求了--gresgpu:4但四个空闲 GPU 分散在不同的物理节点上——每个节点剩一两张拼不成一个 4 卡的申请。这是一个典型的资源碎片问题。解决法是启用 Slurm 的 GPU 亲和性GresTypes节点级分配或者引导用户将申请降到单节点能容纳的卡数。还有一种方式是开启 Slurm 的SelectTypeParametersCR_Core_GPU_Weight但实测配置复杂最终我们选择调整分区节点规格让 GPU 分区节点都配同型号 GPU碎片问题明显减轻。6. 如果从头搭一套调度系统我的规划建议与避坑清单如果你现在准备从零开始搭一套高性能计算资源调度系统有几个规划层面的建议比具体参数更值得先想清楚。6.1 先定业务模型再选调度器不要先急着装软件。先梳理清楚集群主要跑什么类型的工作负载用户规模和他们的技术水平如何是否涉及 GPU 训练和推理是否需要和云上资源联动这些答案直接决定调度器选型和分区设计。比如纯建模计算场景用 Slurm 足够但如果是混合负载——既有机密数值计算又有在线训练推理——你可能要考虑两套系统并存而非强行用一个工具包打天下。6.2 用户教育是成功的一半再好的调度策略遇到不会提交任务的用户也白搭。务必要给用户准备一份清晰的提交手册至少包括常用sbatch参数模板、合理的资源申请估算方法、查看任务状态和日志的命令、常见报错含义。把典型错误直接做成 FAQ能省下你大量重复答疑的时间。text 用户最常犯的错排行榜第一位——内存申请过小导致任务被杀第二位——CPU 申请过大导致排队时间无限拉长。这两类问题都是资源预估帖子没吃透造成的。入门的资源预估讨论数据### 6.3 可观测性建设要趁早 我见过太多集群,调度配置做得不错但没有任何监控告警。等用户抱怨“跑得慢”才发现节点早就不健康了。强烈建议从第一天就部署监控至少包含 - 节点基础指标:CPU、内存、磁盘、网络、温度 - Slurm 专属指标:节点状态、排队作业数、作业等待时间、失败率 - 告警规则:节点 down、作业失败率突增、队列深处堆积、GPU 卡 ECC 错误 ### 6.4 高可用设计容易忽视的点 调度器的 HA 通常是主备结构。Slurm 的 slurmctld 主备切换我踩过一个坑备节点接管后很多新提交的作业报 Invalid job id。问题出在主备节点的 StateSaveLocation 必须是共享存储否则备份节点恢复不了作业记账数据。所以高可用不是起两个进程那么简单共享存储、网络、节点上的 slurmd 都要同步考虑。 ## 7. 总结一句过来话 资源调度系统建设没有“一次调完永远不调”的捷径。它就像一个持续演化的活系统随着用户水平提升、业务类型变化、硬件升级调度策略也要跟着迭代。最关键的一点是——一定要建立完整的监控和数据指标体系用数字驱动调度策略的调整而不是拍脑袋改参数。 最后分享一个小习惯每次调度策略变更后我都建议跑一次为期两周的 A/B 对比用 sacct 统计平均排队时间、平均周转时间、节点利用率、作业失败率四个核心指标。没有数据支撑的调优都是在裸奔。