从闲置GPU到共享算力池:Slurm+TensorFlow轻量级调度实践

发布时间:2026/10/24 8:39:50
从闲置GPU到共享算力池:Slurm+TensorFlow轻量级调度实践 1. 从一台闲置GPU服务器说起为什么我们要自建算力池实验室角落里那台装了四张显卡的服务器大半时间都在吃灰。偶尔有人跑个训练任务也是SSH上去手动nvidia-smi看一眼哪张卡空着然后CUDA_VISIBLE_DEVICES2 python train.py一把梭。这种人肉调度的模式在只有三五个人的时候还能凑合一旦团队扩到十几个人问题就全冒出来了有人占着卡跑小demo有人排了一下午队结果发现卡被占了还有人训练到一半OOM把别人的进程一起带崩。这个项目的出发点很朴素——把散落在各处的GPU算力聚成一个共享资源池让做科研协作的团队成员能像用内部WiFi一样按需申请、按量使用、用完释放。核心不是造一个多先进的调度系统而是用尽量低的运维成本把TensorFlow训练任务的算力分配这件事从靠喊变成靠系统。适合谁来参考这篇内容如果你所在的课题组、实验室或小团队有几张到几十张GPU成员以跑TensorFlow实验为主没有专职运维又不想一上来就上Kubernetes那套重家伙那这套思路基本可以直接抄。我会把选型逻辑、部署细节、踩过的坑和后期维护经验都摊开讲重点放在为什么这么选而不是照着敲命令。需要先说明一点下面涉及的所有主机名、IP、路径、团队规模都是虚构代称实际部署时按你自己的环境替换即可。整套方案的核心组件都是开源且社区活跃的不存在什么独门秘方关键在于组合方式和参数取舍。2. 调度层选型为什么最终没上K8s而选了Slurm2.1 三种主流路线的真实对比自建算力池调度层是第一个要拍板的事。市面上常见的有三条路Kubernetes GPU Operator、Slurm、以及各种轻量级自研脚本。我前后试过前两种最后落在Slurm上原因不是它最先进而是它最匹配。维度Kubernetes方案Slurm方案自研脚本方案上手成本高需要懂容器编排中配置文件为主低但后期维护爆炸GPU隔离依赖device plugin原生支持GRES靠环境变量易冲突多用户配额需额外组件内置QOS/账户体系基本没有故障恢复强但排查链路长够用日志直观全靠人适合规模50卡以上且有专人5到100卡5卡以下K8s的问题不在于不好而在于杀鸡用牛刀。我们团队没有容器化传统成员提交的是python train.py这种裸脚本硬塞进Pod里反而增加了调试成本——出问题时你得先分清是代码问题、镜像问题还是调度问题。Slurm则天然贴合HPC场景sbatch提交、squeue查看、scancel取消这套交互对做科研的人几乎没有学习门槛。2.2 Slurm的GRES机制到底解决了什么Slurm管理GPU靠的是GRESGeneric Resource Scheduling直译就是通用资源调度。它的工作方式很直接在每个计算节点上声明我有几张什么型号的卡调度器据此分配任务启动时通过CUDA_VISIBLE_DEVICES把分到的卡暴露给进程。这里有个容易被忽略的细节GRES的配置必须和节点实际的GPU拓扑一致。我踩过一次坑节点上插了四张卡但gres.conf里只写了Namegpu Count2结果调度器以为只有两张卡另外两张永远分不出去白白浪费。排查时slurmd -C能看到真实硬件但配置文件的错误它不会主动报只能靠sinfo -o %N %G对比。配置的核心就两行写在gres.conf里# /etc/slurm/gres.conf NodeNamenode01 Namegpu Typertx4090 File/dev/nvidia[0-3] NodeNamenode02 Namegpu Typea100 File/dev/nvidia[0-1]Type字段很有用它让调度器能区分不同型号的卡。做科研时经常有这个实验必须用A100的大显存的需求有了Type提交任务时就能写--gresgpu:a100:1精确指定避免被分到不合适的卡上。2.3 账户与QOS让资源分配有据可依多人共享最怕的就是公地悲剧。Slurm的**账户Account和QOSQuality of Service**体系正好治这个病。我的做法是按课题组或项目建Account每个Account下挂成员用户再给不同Account配不同的QOS限制。# 创建一个账户 sacctmgr add account lab_vision Description视觉组 Organizationlab # 给账户设QOS限制最多同时用8张卡单任务最长72小时 sacctmgr add qos vision_qos GrpTRESgres/gpu8 MaxWall72:00:00 # 把用户挂到账户下 sacctmgr add user alice Accountlab_vision这样配下来视觉组再多人同时提交总量也不会超过8张卡不会把整个池子吃干。MaxWall限制单任务时长防止有人挂个死循环占着卡不放。实测下来这套配额机制比任何口头约定都管用因为它是系统强制的不靠自觉。提示QOS的GrpTRES是组总量限制不是单用户限制。如果想让每个成员也有上限需要再配一层MaxTRESPerUser两者叠加才完整。3. TensorFlow任务在Slurm上的提交姿势3.1 从裸命令到sbatch脚本的改造成员原来习惯直接敲python train.py上了Slurm之后要改成sbatch提交。这个转变一开始有阻力但用顺了之后大家都说好——因为任务不再依赖终端会话关掉电脑训练照样跑。一个典型的TensorFlow训练脚本模板长这样#!/bin/bash #SBATCH --job-nameresnet_train #SBATCH --partitiongpu #SBATCH --gresgpu:1 #SBATCH --cpus-per-task8 #SBATCH --mem32G #SBATCH --time24:00:00 #SBATCH --outputlogs/%j.out #SBATCH --errorlogs/%j.err # 加载环境 module load cuda/12.1 source /opt/conda/etc/profile.d/conda.sh conda activate tf_env # 关键让TensorFlow只看到分到的卡 export CUDA_VISIBLE_DEVICES$CUDA_VISIBLE_DEVICES # 防止TF吃满所有显存 export TF_FORCE_GPU_ALLOW_GROWTHtrue python train.py --epochs 100 --batch_size 64这里有几个细节值得展开。--gresgpu:1是向调度器申请一张卡Slurm分配后会自动设置CUDA_VISIBLE_DEVICES所以脚本里不需要手动指定卡号这也是共享池比人肉调度优雅的地方——你永远不用关心哪张卡空着。TF_FORCE_GPU_ALLOW_GROWTHtrue这行是我强烈建议加的。TensorFlow默认会一次性占满整张卡的显存哪怕你的模型只需要2G。在共享环境里这是灾难一张24G的卡被一个2G的任务占满别人就没法用了。开启allow growth后显存按需增长一张卡上并行跑两三个小任务成为可能。3.2 多卡训练的资源申请方式做科研难免要跑多卡训练。Slurm下有两种模式单节点多卡和多节点多卡。单节点最简单--gresgpu:4一次申请四张TensorFlow的MirroredStrategy会自动发现所有可见的卡。import tensorflow as tf strategy tf.distribute.MirroredStrategy() print(f检测到 {strategy.num_replicas_in_sync} 个GPU设备) with strategy.scope(): model build_model() model.compile(optimizeradam, losssparse_categorical_crossentropy)多节点就复杂一些需要配--nodes2 --ntasks-per-node1 --gresgpu:4然后用tf.distribute.MultiWorkerMirroredStrategy配合环境变量TF_CONFIG。这块坑比较多我建议除非模型真的需要跨节点否则优先在单节点内堆卡通信开销小得多。3.3 交互式调试别让调试也排队有个现实问题写代码阶段需要反复试错如果每次调试都走sbatch排队效率极低。Slurm提供了srun和salloc做交互式申请我的经验是给调试单独开一个小配额。# 申请一个交互式会话1张卡2小时 srun --partitiongpu --gresgpu:1 --time2:00:00 --pty bash进去之后就是一个带GPU的shell可以随便跑python、看显存、调代码退出即释放。为了防止有人拿交互式会话当免费算力长期占用我在QOS里给交互式任务单独设了更短的MaxWall比如4小时到点自动踢。注意交互式会话里跑的训练任务会话一断进程就没了。真正要跑的训练还是得走sbatchsrun只用来调试。4. 存储与环境的共享设计4.1 共享存储算力池的隐形地基算力池搭好之后第二个瓶颈往往不是GPU而是存储。如果每个节点都是本地盘那用户的数据就得手动同步训练脚本里写死本地路径换节点就崩。解决办法是挂一个共享文件系统让所有节点看到同一份数据。我们用的是NFS虽然性能不是最强但胜在简单稳定、零学习成本。关键是把几类数据分开挂载挂载点用途特点/shared/datasets公共数据集只读为主容量大/shared/home用户家目录每人独立配额/shared/scratch临时训练数据高速定期清理/shared/scratch这个设计特别重要。训练过程中会产生大量checkpoint和中间文件如果全写到家目录很快就把配额撑爆。scratch盘给足空间但设定期清理策略比如30天没访问的文件自动删逼着大家把重要结果及时归档。NFS的性能调优有个参数值得调rsize和wsize。默认值偏保守改成rsize1048576,wsize1048576之后大文件读取速度能提升明显。这个改动在/etc/fstab里加就行# /etc/fstab 中的NFS挂载项 storage:/shared /shared nfs defaults,rsize1048576,wsize1048576,hard,intr 0 04.2 环境隔离Conda还是容器TensorFlow的版本兼容性是出了名的麻烦不同项目要不同版本的TF、CUDA、cuDNN。共享环境里如果大家共用一个Python迟早打起来。我的方案是Conda为主容器为辅。Conda负责日常的Python环境隔离每个用户自己建env互不干扰。节点上装好统一的CUDA驱动驱动是全局的不能隔离但CUDA Toolkit和cuDNN通过Conda装到各自环境里。这样用户A用TF 2.10 CUDA 11.2用户B用TF 2.15 CUDA 12.1都能在同一张卡上跑。# 用户自己创建环境 conda create -n tf215 python3.11 conda activate tf215 pip install tensorflow[and-cuda]2.15容器则用在需要完全复现的场景比如论文投稿前要固定整个软件栈。这时候用Singularity现在叫Apptainer比Docker更合适因为它在HPC环境里不需要root权限能直接读宿主机的GPU驱动。# 用Singularity跑一个容器化的训练 singularity exec --nv tf215.sif python train.py--nv这个flag是关键它把宿主机的GPU驱动透传进容器容器里就能直接用卡。这套组合下来日常用Conda图方便要复现用容器图严谨两不耽误。4.3 数据集缓存别让IO拖了后腿做视觉或NLP的数据集动辄几十上百G每次训练都从NFS读IO很容易成为瓶颈。我的做法是在每个计算节点的本地SSD上做一层缓存训练前把数据同步到本地训练时读本地盘。# 训练脚本开头加一段数据同步 if [ ! -d /local_cache/imagenet ]; then rsync -a /shared/datasets/imagenet/ /local_cache/imagenet/ fi这段逻辑可以封装成一个函数用户脚本里调一下就行。实测下来ImageNet这种规模的数据集从NFS读和从本地SSD读训练速度能差出20%到30%尤其是小文件多的时候差距更明显。5. 监控、告警与日常运维5.1 用Prometheus Grafana盯住GPU算力池上线只是开始日常运维才是长期活。最基础的监控是GPU利用率、显存占用、温度、功耗这几个指标。NVIDIA提供了dcgm-exporter能把GPU指标暴露成Prometheus格式再用Grafana画图。# 启动dcgm-exporter dcgm-exporter --collect-interval 5000配好之后Grafana上能实时看到每张卡的利用率曲线。这个面板的价值在于一眼就能发现哪张卡被占着但利用率长期为0——这通常意味着有人申请了卡却没真正在跑或者任务卡死了。发现这种情况就可以主动联系用户确认及时释放资源。5.2 告警规则只报真正需要人管的监控面板看多了会麻木真正有用的是告警。我配了几条规则只报那些必须人工介入的情况GPU温度持续超过85度可能是散热出问题需要检查机房空调节点失联超过5分钟可能是宕机需要重启磁盘使用率超过90%需要清理或扩容某用户连续7天占用GPU但利用率低于5%可能是僵尸任务告警走内部消息通道不搞花里胡哨的。关键是阈值要调准报得太频繁大家就无视了报得太少又漏掉真问题。我调了两周才把阈值调到合适。5.3 定期巡检清单运维这事靠人记不如靠清单。我整理了一份每周巡检的checklist检查项命令关注点节点状态sinfo有无down节点队列积压squeue有无长期排队任务磁盘空间df -h各挂载点余量GPU健康nvidia-smi -qECC错误、温度僵尸任务sacct超长运行任务这份清单执行下来大概十分钟但能提前发现大部分隐患。比如ECC错误单比特错误可以纠正双比特错误就得换卡了早发现早处理。6. 那些让我熬夜的坑与最终解法6.1 CUDA_VISIBLE_DEVICES被覆盖的诡异问题有段时间用户反馈明明申请了1张卡TensorFlow却看到了全部4张。排查了半天发现是用户脚本里自己写了export CUDA_VISIBLE_DEVICES0,1,2,3把Slurm设置的值覆盖了。Slurm分配卡是通过设置这个环境变量实现的用户一旦手动覆盖隔离就失效了。解法有两个层面一是在文档里明确写不要手动设置CUDA_VISIBLE_DEVICES二是在sbatch模板里加一行unset CUDA_VISIBLE_DEVICES再重新从Slurm的环境变量里取。更彻底的做法是用cgroup限制设备访问但那配置复杂权衡下来还是靠约定加模板约束。6.2 显存碎片导致的假性OOM另一个坑是显存碎片。TensorFlow的显存分配器在长时间运行后会产生碎片明明nvidia-smi显示还有几个G空闲但新任务就是分配不出来报OOM。这个问题在共享环境里特别烦因为一张卡上可能先后跑过好几个任务。缓解办法是开启TF_GPU_ALLOCATORcuda_malloc_async用异步分配器减少碎片。另外在QOS里限制单卡同时运行的任务数别让一张卡上挤太多进程。实测下来一张24G的卡同时跑不超过2个任务比较稳妥。6.3 节点时钟不同步引发的认证失败这个坑最隐蔽。有次新加了一个节点用户提交任务总是失败报认证错误。查了半天发现是新节点的系统时钟和登录节点差了十几分钟导致认证token过期。NTP服务没配好。# 确保所有节点时间同步 timedatectl set-ntp true chronyc sourcesHPC集群里时间同步是基础设施但很容易被忽略。新节点上线前一定要检查NTP状态否则会出现各种莫名其妙的认证、调度问题。6.4 用户误删共享数据的恢复共享存储最大的风险是误删。有人一个rm -rf把公共数据集删了全组遭殃。NFS本身没有回收站删了就没了。我的补救方案是给关键目录做定期快照用rsync每天凌晨同步一份到备份盘。# 每日备份脚本 rsync -a --delete /shared/datasets/ /backup/datasets_$(date %Y%m%d)/保留最近7天的快照误删了还能找回。这个成本不高但关键时刻能救命。另外权限上做文章公共数据集目录设成只读只有管理员能写从源头减少误操作。7. 算力池跑顺之后的一些体会整套系统跑了大半年从最初的四张卡扩到现在的二十多张成员从五个人到二十多人中间经历了不少调整。回头看最值得分享的经验不是某个技术细节而是几个判断。第一调度系统的复杂度要和团队规模匹配。五个人用Slurm是杀鸡用牛刀五十个人用脚本是自找麻烦。我们卡在中间Slurm刚好。如果哪天扩到上百张卡可能真得上K8s但那是以后的事现在不提前背这个包袱。第二配额机制比技术优化更能提升整体效率。GPU利用率上不去很多时候不是技术问题而是管理问题。有了QOS限制大家自然会珍惜申请到的资源不会随便占着卡不用。这比任何调度算法优化都立竿见影。第三文档和模板的价值被严重低估。我花了两天写的sbatch模板和常见问题文档省下了后面无数次的重复答疑。新人上手从配环境要一天变成半小时跑通第一个任务这个投入产出比极高。最后分享一个小心得给算力池起个内部名字做个简单的使用排行榜谁跑的任务多、谁的利用率高在组会上偶尔提一嘴。这种轻量的正向激励比硬性规定更能让大家自觉维护共享环境。技术系统终究是给人用的把人的因素考虑进去系统才能真正跑顺。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询