刚好我在两个不同的机构,分别部署过基于容器的,和基于普通 Slurm 的。一个机构刚好两种都体验了。
首先结论,对于我们组的大部分研究人员来说,全部投票 Slurm 。首先我们这大部分大学都是 Slurm ,很多人熟悉。其次容器对不熟悉 docker 的人来说,简直天书,完全不明白,为什么 container 关了我有些东西就不见了。而 Slurm 虽然上手有那么几条命令,看着挺复杂。但是一共也就那几条命令。另外就是 slurm 集群的所有操作,包括代码编写,调试,申请资源,等等基本都在登陆节点,而基于容器的通常都有个网页 UI 之类的,从那上面申请资源进行训练啥的。体验很割裂。而且基于容器的申请一个资源到正式运行代码,速度挺慢的,而 slurm 一秒就搞定了。综上,我们组当时体验了两个切换投票的时候,slurm 全票通过。
存储:我们用了 juicefs 和 seaweedfs 作为 S3 。运行挺好的。速度也很快。管理员需要研究研究 seaweedfs 的源码。文档较少。
Slurm:
1. 碎片化任务:容易解决。无非就是有的 1-2 GPUs 的调试任务,明明可以填空别的 mix 机器,却单独放到了一个 8x 节点上,导致这个 8x 节点无法进行 8 卡训练。这是因为 slurm 的最小 load 分配策略导致的,优先散布任务而不是紧凑分配任务。所以可以在命令比如 srun 外面包一层。在真正提交任务给 slurm 之前,优先寻找 slurm 里面的 mix 节点,然后分析哪个可以填空当前任务,如果找到了,附加参数 -w nodename 给这个任务即可。
2. 调试 GPU 利用率低:有的人喜欢 srun --pty bash 这样进去用交互的方式调试。极其浪费资源。因此通过 job_submit.lua 限制这类任务只能提交到特定分区,比如 dev 分区。该分区设置最大 job 时间比如 4 小时。同时还可以 sacctmgr 限制该分区每人最多用 2x 卡。另外,鼓励大家使用一次性资源申请调试任务。比如 srun -p xxx --gres=gpu:4 python
train.py 这样一样可以调试任务,而且申请到的 GPU 资源会在程序停止运行后立马释放,最大化共享。
3. 碎片资源利用:管理员可以写一个 job 提交脚本,通过分析集群资源 layout ,让用户的多机多卡任务适配。比如用户进行 8 卡训练,但是集群只有两个 4x 空闲机器。理论上此时也可以开始训练。所以我这边写了个脚本,自动完成 layout 分析,提供给用户一些可用的环境变量,比如 NODE_RANK ,LOCAL_RANK ,MASTER_ADDR ,MASTER_PORT ,就可以了。用户只需要设置好这些,无论是单机 8 卡,还是双机 4 卡,都能跑起来。提交任务的时候只需一条命令 slurm-submit --num_gpus=8 python
train.py ,脚本自动去寻找机器。
4. 交互式开发:同样可以做到。理论上用户并不可以直接访问 GPU 机器,因此需要在用户申请到 GPU 资源后,在 job 内启动 vscode server/jupyter server 。但是该服务在 GPU 机器上,用户无法访问,因此需要端口映射。用 frp 即可。管理员只需要基于 frp ,包一层,提供给用户一个命令,比如 forward_port 。用户运行这个命令之后,你的脚本里去调用 frpc ,然后把相应端口映射给登陆节点。用户只需访问 loginnode:port 即可访问上述服务。
5. 队列调度:slurm 的队列足够强大了,多个 qos 配置,包括资源抢占,以及 backfill 策略,都相当好用。相反我用过的两个基于容器的,包括开源的和大型公司私有的,完全比不上。