本文介绍了从集群创建到作业调度的全过程,适用于需要在SLURM集群中运行容器化任务的用户。
步骤一:创建集群
本文使用的集群配置示例如下,未提及的参数请根据需要填写。
配置项
配置
集群配置
系列
标准版
部署模式
公共云集群
集群类型
SLURM
管理节点
实例规格:采用ecs.r7.xlarge实例规格,该规格配置为4 vCPU,32 GiB内存。
镜像:centos_7_6_x64_20G_alibase_20211130.vhd
计算节点与队列
队列节点数
初始节点
1
。节点间互联
VPC网络
实例规格组
实例规格:采用ecs.gn7i-c56g1.14xlarge实例规格,该规格配置为56 vCPU、346 GiB内存。
重要需使用支持GPU的实例规格。更多内容,请参见实例规格族。
镜像:centos_7_6_x64_20G_alibase_20211130.vhd
共享文件存储
/home 集群挂载目录
默认情况下,管理节点的
/home
和/opt
将挂载文件系统,作为共享存储目录。/opt 集群挂载目录
软件与服务组件
待安装软件
选择docker。
可安装服务组件
登录节点:
实例规格:采用ecs.r7.xlarge实例规格,该规格配置为4 vCPU,32 GiB内存。
镜像:centos_7_6_x64_20G_alibase_20211130.vhd。
本文中以usertest用户为例。
步骤二:搭建基础软件环境
为计算节点绑定弹性公网IP。具体操作,请参见弹性公网IP。
下载并安装CUDA。
下载CUDA安装包。
cd /opt wget https://developer.download.nvidia.com/compute/cuda/12.4.1/local_installers/cuda_12.4.1_550.54.15_linux.run
安装CUDA。
yum install -y git sh /opt/cuda_12.4.1_550.54.15_linux.run
显示如下图所示时,说明CUDA已安装。
配置环境变量。
echo 'export PATH=/usr/local/cuda-12.4/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc source ~/.bashrc
查看NVIDIA CUDA工具包和GPU驱动的安装状态及版本信息。
# NVIDIA CUDA编译驱动程序的版本信息 nvcc --version # GPU的详细状态信息 nvidia-smi
显示如下图所示时,说明CUDA和GPU驱动正常。
安装并配置 NVIDIA Container Toolkit。
distribution=$(. /etc/os-release;echo $ID$VERSION_ID) curl -s -L https://nvidia.github.io/libnvidia-container/stable/rpm/nvidia-container-toolkit.repo | sudo tee /etc/yum.repos.d/nvidia-container-toolkit.repo sudo yum install -y nvidia-container-toolkit sudo systemctl restart docker
下载并安装Singularity。
Singularity是一个容器化工具,它允许在不改变用户环境的情况下运行容器,常用于HPC环境。
cd /opt wget https://public-ehs.oss-cn-hangzhou.aliyuncs.com/softwares/packages/CentOS_7.2_64/singularity-3.8.3-1.el7.x86_64.rpm yum install -y /opt/singularity-3.8.3-1.el7.x86_64.rpm
创建作业依赖数据。
拉取PyTorch容器镜像。
docker pull ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/pytorch:2.4.0-cuda12.1.1-py310-alinux3.2104
使用
usertest
用户,创建main.py
文件 。vim /home/usertest/main.py
main.py
文件脚本内容如下。# -*- coding: utf-8 -*- import torch import torchvision import torchvision.transforms as transforms from torch import nn from torch.utils.data import DataLoader from torch.optim import SGD class SimpleNet(nn.Module): def __init__(self): super(SimpleNet, self).__init__() self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1) self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) self.fc1 = nn.Linear(64 * 8 * 8, 128) self.fc2 = nn.Linear(128, 10) self.pool = nn.MaxPool2d(2, 2) self.relu = nn.ReLU() self.dropout = nn.Dropout(0.5) def forward(self, x): x = self.pool(self.relu(self.conv1(x))) x = self.pool(self.relu(self.conv2(x))) x = x.view(-1, 64 * 8 * 8) x = self.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Using device: {device}") transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) ]) train_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform) test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform) train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False) model = SimpleNet().to(device) criterion = nn.CrossEntropyLoss() optimizer = SGD(model.parameters(), lr=0.001, momentum=0.9) num_epochs = 10 for epoch in range(num_epochs): model.train() running_loss = 0.0 for i, data in enumerate(train_loader, 0): inputs, labels = data inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() if i % 100 == 99: print(f"[{epoch + 1}, {i + 1}] loss: {running_loss / 100:.3f}") running_loss = 0.0 model.eval() correct = 0 total = 0 with torch.no_grad(): for data in test_loader: images, labels = data images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() print(f"Accuracy on test set: {100 * correct / total:.2f}%") print("Training finished.")
步骤三:调度作业
调度Docker作业
通过E-HPC Portal提交作业
提交NCCL作业。
在顶部导航栏,选择任务管理,在页面上方,单击submitter,在创建作业页面,填写作业计算节点数为
1
,任务数为2
,Gpu数为1
。作业脚本内容如下。
使用命令
docker images
获取镜像名称和版本号,替换第三行your_image
。#!/bin/bash image="your_image" run_cmd="python main.py" share_dir="/home/usertest/:/root" # cleanup docker handle function cleanup { echo "Caught signal, stopping Docker container: " $SLURM_JOB_NAME docker ps -q --filter label=$SLURM_JOB_NAME | xargs -r docker stop docker ps -qa --filter label=$SLURM_JOB_NAME | xargs -r docker rm } trap cleanup SIGINT SIGTERM cleanup # start docker # docker pull $image docker run \ --label $SLURM_JOB_NAME \ --gpus "device=0" \ -v $share_dir \ $image \ /bin/bash -c "$run_cmd" & # wait to complete wait cleanup
查询作业。
进入任务管理页面,可以查询作业列表,包含作业状态,作业操作等。更多内容,请参见查询作业。
通过命令行提交作业
通过命令行提交作业。具体操作,请参见SLURM。
作业脚本内容如下。
使用命令
docker images
获取镜像名称和版本号,替换第十三行your_image
。#!/bin/bash #SBATCH --job-name=tf_sample_job #SBATCH --nodes=1 #SBATCH --ntasks=2 #SBATCH --gpus-per-task=1 #SBATCH --time=01:00:00 #SBATCH --partition=comp #SBATCH --output=tf_sample_job_%j.out #SBATCH --error=tf_sample_job_%j.err # 定义变量 image="your_image" run_cmd="python main.py" share_dir="/home/usertest/:/root" # 清理 Docker 进程 function cleanup { echo "Caught signal, stopping Docker container: " $SLURM_JOB_NAME docker ps -q --filter label=$SLURM_JOB_NAME | xargs -r docker stop docker ps -qa --filter label=$SLURM_JOB_NAME | xargs -r docker rm } trap cleanup SIGINT SIGTERM cleanup # 启动 Docker 容器 docker pull $image docker run \ --label $SLURM_JOB_NAME \ --gpus "device=$CUDA_VISIBLE_DEVICES" \ -v $share_dir \ $image \ /bin/bash -c "$run_cmd" & # 等待任务完成 wait cleanup
通过slurm查询作业。
使用
squeue
命令可以查询当前正在运行和排队中的作业列表。squeue
使用
sacct
命令可以查询作业的历史记录,包括已完成的作业。sacct
调度Singularity作业
Docker2Singularity。
为了简化Singularity镜像管理,可以复用云上Docker镜像仓库,根据以下操作步骤,对镜像进行格式转换。
# 方案1:通过local docker package转换sif镜像 [root@compute006 opt]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/pytorch 2.4.0-cuda12.1.1-py310-alinux3.2104 19301a07d7fd 4 months ago 6.33GB [root@compute006 opt]# docker save -o docker.tar 19301a07d7fd [root@compute006 opt]# ll docker.tar -rw------- 1 root root 3021202432 2月 12 15:03 docker.tar [root@compute006 opt]# singularity build pytorch.sif docker-archive:///opt/docker.tar # 方案2:通过docker容器仓库build sif镜像 singularity build xx.sif docker://ac2-registry.cn-hangzhou.cr.aliyuncs.com/ac2/pytorch:2.4.0-cuda12.1.1-py310-alinux3.2104
通过E-HPC Portal提交作业
提交NCCL作业。
在顶部导航栏,选择任务管理,在页面上方,单击submitter,在创建作业页面,填写作业计算节点数为
1
,任务数为2
,Gpu数为1
。作业脚本内容如下。
image=/opt/pytorch.sif run_cmd="python main.py" share_dir="/home/usertest/:/root" singularity exec --nv --bind $share_dir $image $run_cmd
查询作业。
进入任务管理页面,可以查询作业列表,包含作业状态,作业操作等。更多内容,请参见查询作业。
通过命令行提交作业
通过命令行提交作业。具体操作,请参见SLURM。
作业脚本内容如下。
#!/bin/bash #SBATCH --job-name=singularity_TF #SBATCH --output=output.log #SBATCH --nodes=1 #SBATCH --gres=gpu:1 #SBATCH --partition=container #SBATCH --ntasks=2 image=/opt/pytorch.sif run_cmd="python main.py" share_dir="/home/usertest/:/root" singularity exec --nv --bind $share_dir $image $run_cmd
通过slurm查询作业。
使用
squeue
命令可以查询当前正在运行和排队中的作业列表。squeue
使用
sacct
命令可以查询作业的历史记录,包括已完成的作业。sacct