Skip to content

Commit 35bfeca

Browse files
Added steps to install slinky on K8s and example training workload
1 parent 0640227 commit 35bfeca

6 files changed

Lines changed: 1185 additions & 0 deletions

File tree

slinky-example/Readme.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# Example Slinky Training Workload on Kubernetes
2+
3+
The following outlines steps to get up and running with Slinky on Kubernetes and running a simple image classification training workload to verify GPUs are accessible.
4+
5+
## Clone this repo and go into slinky folder
6+
7+
```bash
8+
git clone https://github.com/amd/ada.git
9+
cd slinky
10+
```
11+
12+
## Installing Slinky Prerequisites
13+
14+
The following steps for installing pre-requisites and installing Slinky have been taking from the SlinkProject/slinky-operator repo [quick-start guide](https://github.com/SlinkyProject/slurm-operator/blob/main/docs/quickstart.md)
15+
16+
```bash
17+
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
18+
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
19+
helm repo add bitnami https://charts.bitnami.com/bitnami
20+
helm repo add jetstack https://charts.jetstack.io
21+
helm repo update
22+
helm install cert-manager jetstack/cert-manager \
23+
--namespace cert-manager --create-namespace --set crds.enabled=true
24+
helm install prometheus prometheus-community/kube-prometheus-stack \
25+
--namespace prometheus --create-namespace --set installCRDs=true
26+
```
27+
28+
## Installing Slinky Operator
29+
30+
```bash
31+
helm install slurm-operator oci://ghcr.io/slinkyproject/charts/slurm-operator \
32+
--values=values-operator.yaml --version=0.1.0 --namespace=slinky --create-namespace
33+
```
34+
35+
Make sure the operator deployed successfully with:
36+
37+
```sh
38+
kubectl --namespace=slinky get pods
39+
```
40+
41+
Output should be similar to:
42+
43+
```sh
44+
NAME READY STATUS RESTARTS AGE
45+
slurm-operator-7444c844d5-dpr5h 1/1 Running 0 5m00s
46+
slurm-operator-webhook-6fd8d7857d-zcvqh 1/1 Running 0 5m00s
47+
```
48+
49+
## Installing Slurm Cluster
50+
51+
Build a Slurm docker image to be used for the Slurm compute node. See the [Dockerfile from the Slinky repo](https://github.com/SlinkyProject/containers/blob/main/schedmd/slurm/24.05/ubuntu24.04/Dockerfile) on how to create the base Docker image. This image will have to have ROCm and PyTorch added to it.
52+
53+
Once the image has been built and pushed to a repository update the `values-slurm.yaml` file to specify the compute node image you will be using:
54+
55+
```yaml
56+
# Slurm compute (slurmd) configurations.
57+
compute:
58+
#
59+
# -- (string)
60+
# Set the image pull policy.
61+
imagePullPolicy: IfNotPresent
62+
#
63+
# Default image for the nodeset pod (slurmd)
64+
# Each nodeset may override this setting.
65+
image:
66+
#
67+
# -- (string)
68+
# Set the image repository to use.
69+
repository: docker-registry/docker-repository/docker-image
70+
#
71+
# -- (string)
72+
# Set the image tag to use.
73+
# @default -- The Release appVersion.
74+
tag: image-tag
75+
```
76+
77+
Install the Slurm Cluster helm chart
78+
79+
```bash
80+
helm install slurm oci://ghcr.io/slinkyproject/charts/slurm \
81+
--values=values-slurm.yaml --version=0.1.0 --namespace=slurm --create-namespace
82+
```
83+
84+
Make sure the Slurm cluster deployed successfully with:
85+
86+
```sh
87+
kubectl --namespace=slurm get pods
88+
```
89+
90+
Output should be similar to:
91+
92+
```sh
93+
NAME READY STATUS RESTARTS AGE
94+
slurm-accounting-0 1/1 Running 0 5m00s
95+
slurm-compute-gpu-node 1/1 Running 0 5m00s
96+
slurm-controller-0 2/2 Running 0 5m00s
97+
slurm-exporter-7b44b6d856-d86q5 1/1 Running 0 5m00s
98+
slurm-mariadb-0 1/1 Running 0 5m00s
99+
slurm-restapi-5f75db85d9-67gpl 1/1 Running 0 5m00s
100+
```
101+
102+
## Prepping Compute Node
103+
104+
1. Get SLURM Compute Node Name
105+
106+
```bash
107+
SLURM_COMPUTE_POD=$(kubectl get pods -n slurm | grep ^slurm-compute-gpu-node | awk '{print $1}');echo $SLURM_COMPUTE_POD
108+
```
109+
110+
2. Add Slurm user to video and render group and create Slurm user home directory to Slrum Compute node
111+
112+
```bash
113+
kubectl exec -it -n slurm $SLURM_COMPUTE_POD -- bash -c "
114+
usermod -aG video,render slurm
115+
mkdir -p /home/slurm
116+
chown slurm:slurm /home/slurm"
117+
```
118+
119+
3. Copy PyTorch test script to Slurm compute node
120+
121+
```bash
122+
kubectl cp test.py slurm/$SLURM_COMPUTE_POD:/tmp/test.py
123+
```
124+
125+
4. Copy Fashion MNIST Image Classification Model Training script to Slurm compute node
126+
127+
```bash
128+
kubectl cp train_fashion_mnist.py slurm/$SLURM_COMPUTE_POD:/tmp/train_fashion_mnist.py
129+
```
130+
131+
5. Run test.py script on compute node to confirm GPUs are accessible
132+
133+
```bash
134+
kubectl exec -it slurm-controller-0 -n slurm -- srun python3 test.py
135+
```
136+
137+
6. Run single-GPU training script on compute node
138+
139+
```bash
140+
kubectl exec -it slurm-controller-0 -n slurm -- srun python3 train_fashion_mnist.py
141+
```
142+
143+
7. Run multi-GPU training script on compute node
144+
145+
```bash
146+
kubectl exec -it slurm-controller-0 -n slurm -- srun apptainer exec --rocm --bind /tmp:/tmp torch_rocm.sif torchrun --standalone --nnodes=1 --nproc_per_node=8 --master-addr localhost train_mnist_distributed.py
147+
```
148+
149+
## Other Useful Slurm Commands
150+
151+
### Check Slurm Node Info
152+
153+
```bash
154+
kubectl exec -it slurm-controller-0 -n slurm -- sinfo
155+
```
156+
157+
### Check Job Queue
158+
159+
```bash
160+
kubectl exec -it slurm-controller-0 -n slurm -- squeue
161+
```
162+
163+
### Check Node Resources
164+
165+
```bash
166+
kubectl exec -it slurm-controller-0 -n slurm -- sinfo -N -o "%N %G"
167+
```

slinky-example/test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# run this command to check if the GPUs are available
2+
# srun -N 2 --gpus=16 -t 00:02:00 python3 test.py
3+
import torch
4+
5+
if torch.cuda.is_available():
6+
print(f"GPUs available: {torch.cuda.device_count()}")
7+
for i in range(torch.cuda.device_count()):
8+
print(f" - GPU {i}: {torch.cuda.get_device_name(i)}")
9+
print(f" - GPU {i} Pytorch and rocm version: {torch.__version__}")
10+
print(f" - GPU {i} Nccl version: {torch.cuda.nccl.version()}")
11+
else:
12+
print("No GPUs available.")
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import os
2+
3+
# Set the Torch Distributed env variables so the training function can be run locally in the Notebook.
4+
# See https://pytorch.org/docs/stable/elastic/run.html#environment-variables
5+
os.environ["RANK"] = "0"
6+
os.environ["LOCAL_RANK"] = "0"
7+
os.environ["WORLD_SIZE"] = "1"
8+
os.environ["MASTER_ADDR"] = "localhost"
9+
os.environ["MASTER_PORT"] = "1234"
10+
11+
def train_fashion_mnist():
12+
import torch
13+
import torch.distributed as dist
14+
import torch.nn.functional as F
15+
from torch import nn
16+
from torch.utils.data import DataLoader, DistributedSampler
17+
from torchvision import datasets, transforms
18+
19+
# Define the PyTorch CNN model to be trained
20+
class Net(nn.Module):
21+
def __init__(self):
22+
super(Net, self).__init__()
23+
self.conv1 = nn.Conv2d(1, 20, 5, 1)
24+
self.conv2 = nn.Conv2d(20, 50, 5, 1)
25+
self.fc1 = nn.Linear(4 * 4 * 50, 500)
26+
self.fc2 = nn.Linear(500, 10)
27+
28+
def forward(self, x):
29+
x = F.relu(self.conv1(x))
30+
x = F.max_pool2d(x, 2, 2)
31+
x = F.relu(self.conv2(x))
32+
x = F.max_pool2d(x, 2, 2)
33+
x = x.view(-1, 4 * 4 * 50)
34+
x = F.relu(self.fc1(x))
35+
x = self.fc2(x)
36+
return F.log_softmax(x, dim=1)
37+
38+
# Use NCCL if a GPU is available, otherwise use Gloo as communication backend.
39+
device, backend = ("cuda", "nccl") if torch.cuda.is_available() else ("cpu", "gloo")
40+
print(f"Using Device: {device}, Backend: {backend}")
41+
42+
# Setup PyTorch distributed.
43+
local_rank = int(os.getenv("LOCAL_RANK", 0))
44+
dist.init_process_group(backend=backend)
45+
print(
46+
"Distributed Training for WORLD_SIZE: {}, RANK: {}, LOCAL_RANK: {}".format(
47+
dist.get_world_size(),
48+
dist.get_rank(),
49+
local_rank,
50+
)
51+
)
52+
53+
# Create the model and load it into the device.
54+
device = torch.device(f"{device}:{local_rank}")
55+
model = nn.parallel.DistributedDataParallel(Net().to(device))
56+
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
57+
58+
59+
# Download FashionMNIST dataset only on local_rank=0 process.
60+
if local_rank == 0:
61+
dataset = datasets.FashionMNIST(
62+
"./data",
63+
train=True,
64+
download=True,
65+
transform=transforms.Compose([transforms.ToTensor()]),
66+
)
67+
dist.barrier()
68+
dataset = datasets.FashionMNIST(
69+
"./data",
70+
train=True,
71+
download=False,
72+
transform=transforms.Compose([transforms.ToTensor()]),
73+
)
74+
75+
76+
# Shard the dataset accross workers.
77+
train_loader = DataLoader(
78+
dataset,
79+
batch_size=100,
80+
sampler=DistributedSampler(dataset)
81+
)
82+
83+
# TODO(astefanutti): add parameters to the training function
84+
dist.barrier()
85+
for epoch in range(1, 10):
86+
model.train()
87+
88+
# Iterate over mini-batches from the training set
89+
for batch_idx, (inputs, labels) in enumerate(train_loader):
90+
# Copy the data to the GPU device if available
91+
inputs, labels = inputs.to(device), labels.to(device)
92+
# Forward pass
93+
outputs = model(inputs)
94+
loss = F.nll_loss(outputs, labels)
95+
# Backward pass
96+
optimizer.zero_grad()
97+
loss.backward()
98+
optimizer.step()
99+
100+
if batch_idx % 10 == 0 and dist.get_rank() == 0:
101+
print(
102+
"Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}".format(
103+
epoch,
104+
batch_idx * len(inputs),
105+
len(train_loader.dataset),
106+
100.0 * batch_idx / len(train_loader),
107+
loss.item(),
108+
)
109+
)
110+
111+
# Wait for the distributed training to complete
112+
dist.barrier()
113+
if dist.get_rank() == 0:
114+
print("Training is finished")
115+
116+
# Finally clean up PyTorch distributed
117+
dist.destroy_process_group()
118+
119+
# Run the training function locally.
120+
train_fashion_mnist()

0 commit comments

Comments
 (0)