GPU (Graphics Processing Unit) - specjalistyczny układ scalony, który przyspiesza generowanie obrazów na ekranie komputera. Przeznaczone do wykonywania zadań związanych z grafiką komputerową, GPU składa się z wielu jednostek przetwarzających pracujących równocześnie.
Stosowane głównie w grach, aplikacjach multimedialnych i programach graficznych, GPU jest również używane w dziedzinie nauki i uczenia maszynowego. Jego architektura sprawia, że jest bardziej efektywne niż CPU w przetwarzaniu równoczesnych, prostych operacji.
CUDA (Compute Unified Device Architecture) jest to platforma obliczeniowa stworzona przez firmę NVIDIA do wykorzystania kart graficznych w celach ogólnych obliczeń równoległych. CUDA umożliwia programistom tworzenie aplikacji wykorzystujących duże ilości wątków, co pozwala na znaczne przyspieszenie czasu obliczeń w porównaniu z tradycyjnymi procesorami CPU.
TensorCores to specjalne jednostki obliczeniowe, które zostały zaprojektowane przez firmę NVIDIA w celu przyspieszenia obliczeń związanych z operacjami na macierzach i tensorach, takich jak mnożenie macierzy i konwolucje. TensorCores wykorzystują technologię float16, co pozwala na przyspieszenie czasu obliczeń bez utraty precyzji. Dzięki wykorzystaniu TensorCores, karty graficzne NVIDIA są szczególnie efektywne w obliczeniach związanych z uczeniem maszynowym i sztuczną inteligencją.
Tesla to seria kart graficznych wyprodukowana przez firmę NVIDIA, przeznaczona do zastosowań profesjonalnych, takich jak przetwarzanie danych, uczenie maszynowe, symulacje fizyczne, czy przetwarzanie obrazów. Pierwsze karty Tesla zostały wprowadzone na rynek w 2007 roku, a obecnie seria Tesla obejmuje kilka generacji kart graficznych, w tym między innymi akceleratory oferowane w ramach klastra BEM2:
Model i rok produkcji:
Dokładana specyfikacja na stronie Nvidia
Dokładana specyfikacja na stronie Nvidia
Karty te charakteryzują się różnymi parametrami, takimi jak ilość rdzeni CUDA, taktowanie GPU, pamięć RAM, czy przepustowość pamięci.
Całość wykonywana jest w jednym katalogu roboczym:
srun -c 2 -N 1 --mem=50gb --time=00:30:00 --pty /bin/bash
Należy sprawdzić limity na podanym QOS, za pomocą polecenia: sacctmgr show -P qos where name=<TU_PODAJ_QOS>
Zaleca się ustawiać --mem mniejszy niż w przykładzie np. 10/12gb, dla podanego przykładu wymagane jest jednak 50gb
APPTAINER_TMPDIR=/dev/shm/$SLURM_TASK_PID APPTAINER_CACHEDIR=/dev/shm/$SLURM_TASK_PID apptainer pull docker://pytorch/pytorch:latest
Zostanie stworzony plik pytorch_latest.sif
exit
torch_list_gpus.py
:# Wypisuje na ekran ilość GPU i ich nazwy
import torch
print(torch.cuda.device_count())
for i in range(torch.cuda.device_count()):
print(torch.cuda.get_device_name(i))
Linki do dokumentacji pytorch:
https://pytorch.org/docs/stable/generated/torch.cuda.device_count.html
https://pytorch.org/docs/stable/generated/torch.cuda.get_device_name.html
pythontest.sh
:#!/bin/bash
#SBATCH -N <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_WĘZŁÓW> # np. 1
#SBATCH -c <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_RDZENI_CPU> # np. 8
#SBATCH --mem=<TU_PODAJ_POTRZEBNĄ_ILOŚĆ_PAMIĘCI_RAM> # np. 4gb
#SBATCH --time=<TU_PODAJ_MAKSYMALNĄ_ILOŚĆ_CZASU_NA_WYKONANIE_ZADANIA> # format dd-hh:mm:ss np. 1-12:30:15
#SBATCH --job-name=<TU_PODAJ_NAZWĘ_ZADANIA> # np. example
#SBATCH -p <TU_PODAJ_NAZWĘ_PARTYCJI> # np. tesla
#SBATCH --gres=<TU_PODAJ_JAKIE_ZASOBY_GPU_POTRZEBUJESZ> #format gpu:${nazwa_zasobu}:${ilość} np. gpu:tesla:1
apptainer exec --nv pytorch_latest.sif python3 torch_list_gpus.py
sbatch pythontest.sh
1
Tesla P100-PCIE-16GB
srun -c 2 -N 1 --mem=50gb --time=00:30:00 --pty /bin/bash
Zaleca się ustawiać --mem mniejszy niż w przykładzie np. 10/12gb, dla podanego przykładu wymagane jest jednak 50gb
APPTAINER_TMPDIR=/dev/shm/$SLURM_TASK_PID APPTAINER_CACHEDIR=/dev/shm/$SLURM_TASK_PID apptainer pull docker://pytorch/pytorch:latest
Zostanie stworzony plik pytorch_latest.sif
exit
W przykładzie zaprezentowania działania tego rozwiązania skorzystano z udostępnionego wcześniej kodu na https://github.com/pytorch/examples/tree/main/distributed/ddp-tutorial-series . Dwa poniższe kody multinode.py i datautils.py zostały zaczerpnięte z tego właśnie źródła.
multinode.py
import torch
import torch.nn.functional as F
from torch.utils.data import Dataset, DataLoader
from datautils import MyTrainDataset
import torch.multiprocessing as mp
from torch.utils.data.distributed import DistributedSampler
from torch.nn.parallel import DistributedDataParallel as DDP
from torch.distributed import init_process_group, destroy_process_group
import os
def ddp_setup():
init_process_group(backend="nccl")
torch.cuda.set_device(int(os.environ["LOCAL_RANK"]))
class Trainer:
def __init__(
self,
model: torch.nn.Module,
train_data: DataLoader,
optimizer: torch.optim.Optimizer,
save_every: int,
snapshot_path: str,
) -> None:
self.local_rank = int(os.environ["LOCAL_RANK"])
self.global_rank = int(os.environ["RANK"])
self.model = model.to(self.local_rank)
self.train_data = train_data
self.optimizer = optimizer
self.save_every = save_every
self.epochs_run = 0
self.snapshot_path = snapshot_path
if os.path.exists(snapshot_path):
print("Loading snapshot")
self._load_snapshot(snapshot_path)
self.model = DDP(self.model, device_ids=[self.local_rank])
def _load_snapshot(self, snapshot_path):
loc = f"cuda:{self.local_rank}"
snapshot = torch.load(snapshot_path, map_location=loc)
self.model.load_state_dict(snapshot["MODEL_STATE"])
self.epochs_run = snapshot["EPOCHS_RUN"]
print(f"Resuming training from snapshot at Epoch {self.epochs_run}")
def _run_batch(self, source, targets):
self.optimizer.zero_grad()
output = self.model(source)
loss = F.cross_entropy(output, targets)
loss.backward()
self.optimizer.step()
def _run_epoch(self, epoch):
b_sz = len(next(iter(self.train_data))[0])
print(f"[GPU{self.global_rank}] Epoch {epoch} | Batchsize: {b_sz} | Steps: {len(self.train_data)}")
self.train_data.sampler.set_epoch(epoch)
for source, targets in self.train_data:
source = source.to(self.local_rank)
targets = targets.to(self.local_rank)
self._run_batch(source, targets)
def _save_snapshot(self, epoch):
snapshot = {
"MODEL_STATE": self.model.module.state_dict(),
"EPOCHS_RUN": epoch,
}
torch.save(snapshot, self.snapshot_path)
print(f"Epoch {epoch} | Training snapshot saved at {self.snapshot_path}")
def train(self, max_epochs: int):
for epoch in range(self.epochs_run, max_epochs):
self._run_epoch(epoch)
if self.local_rank == 0 and epoch % self.save_every == 0:
self._save_snapshot(epoch)
def load_train_objs():
train_set = MyTrainDataset(2048) # load your dataset
model = torch.nn.Linear(20, 1) # load your model
optimizer = torch.optim.SGD(model.parameters(), lr=1e-3)
return train_set, model, optimizer
def prepare_dataloader(dataset: Dataset, batch_size: int):
return DataLoader(
dataset,
batch_size=batch_size,
pin_memory=True,
shuffle=False,
sampler=DistributedSampler(dataset)
)
def main(save_every: int, total_epochs: int, batch_size: int, snapshot_path: str = "snapshot.pt"):
ddp_setup()
dataset, model, optimizer = load_train_objs()
train_data = prepare_dataloader(dataset, batch_size)
trainer = Trainer(model, train_data, optimizer, save_every, snapshot_path)
trainer.train(total_epochs)
destroy_process_group()
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='simple distributed training job')
parser.add_argument('total_epochs', type=int, help='Total epochs to train the model')
parser.add_argument('save_every', type=int, help='How often to save a snapshot')
parser.add_argument('--batch_size', default=32, type=int, help='Input batch size on each device (default: 32)')
args = parser.parse_args()
main(args.save_every, args.total_epochs, args.batch_size)
datautils.py
import torch
from torch.utils.data import Dataset
class MyTrainDataset(Dataset):
def __init__(self, size):
self.size = size
self.data = [(torch.rand(20), torch.rand(1)) for _ in range(size)]
def __len__(self):
return self.size
def __getitem__(self, index):
return self.data[index]
pytorch_torchrun.sh
Linie zaczynające się do
#SBATCH
są parametrami dla systemu zarządzani zasobami (SLURM).
#!/bin/bash
#SBATCH --job-name=multi-test #nazwa
#SBATCH --nodes=2 #ilość węzłów
#SBATCH --cpus-per-task=4 # ilość cpu na zadanie
#SBATCH --time=00:10:00 # maksymalny czas wykonania zadania
#SBATCH --mem=8gb # ilośc pamięci RAM
#SBATCH -p tesla # partycja
#SBATCH --gpus-per-node=1 #(ilość kart graficznych na węźle)
#ustalenie adressu ip headnode
nodes=( $( scontrol show hostnames $SLURM_JOB_NODELIST ))
nodes_array=($nodes)
head_node=${nodes_array[0]}
head_node_ip=$(srun --nodes=1 --ntasks=1 -t 1 -w "$head_node" hostname --ip-address -I | awk '{print $1}')
# uruchomienie zadania w kontenerze
srun singularity exec --nv \
pytorch_latest.sif \
torchrun --nnodes=$SLURM_NNODES \
--nproc_per_node=$SLURM_GPUS_PER_NODE \
--rdzv_id=$SLURM_JOB_ID \
--rdzv_backend=c10d \
--rdzv_endpoint=$head_node_ip:29500 multinode.py 50 10
sbatch pytorch_torchrun.sh
<output omitted>
[GPU1] Epoch 40 | Batchsize: 32 | Steps: 32
Epoch 40 | Training snapshot saved at snapshot.pt
[GPU0] Epoch 41 | Batchsize: 32 | Steps: 32
Epoch 40 | Training snapshot saved at snapshot.pt
[GPU1] Epoch 41 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 42 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 42 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 43 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 43 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 44 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 44 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 45 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 45 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 46 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 46 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 47 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 47 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 48 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 48 | Batchsize: 32 | Steps: 32
[GPU0] Epoch 49 | Batchsize: 32 | Steps: 32
[GPU1] Epoch 49 | Batchsize: 32 | Steps: 32
Całość wykonywana jest w jednym katalogu roboczym:
srun -c 2 -N 1 --mem=50gb --time=00:30:00 --pty /bin/bash
Należy sprawdzić limity na podanym QOS, za pomocą polecenia: sacctmgr show -P qos where name=<TU_PODAJ_QOS>
Zaleca się ustawiać --mem mniejszy niż w przykładzie np. 10/12gb, dla podanego przykładu wymagane jest jednak 50gb
APPTAINER_TMPDIR=/dev/shm/$SLURM_TASK_PID APPTAINER_CACHEDIR=/dev/shm/$SLURM_TASK_PID apptainer pull docker://tensorflow/tensorflow:latest-gpu
Zostanie stworzony plik tensorflow_latest-gpu.sif
3. Należy zakończyć zadanie interaktywne poleceniem:
exit
tf_list_gpus.py
:import tensorflow as tf
#Wypisanie ilości GPU
gpus = tf.config.list_physical_devices('GPU')
print(len(gpus))
#Wypisanie nazw GPU
for gpu in gpus:
device = tf.config.PhysicalDevice(name=gpu.name, device_type='GPU')
details = tf.config.experimental.get_device_details(device)
print(details['device_name'])
Linki do dokumentacji tensorflow:
https://www.tensorflow.org/api_docs/python/tf/config/list_physical_devices
https://www.tensorflow.org/api_docs/python/tf/config/PhysicalDevice
https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details
tensortest.sh
:#!/bin/bash
#SBATCH -N <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_WĘZŁÓW> # np. 1
#SBATCH -c <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_RDZENI_CPU> # np. 8
#SBATCH --mem=<TU_PODAJ_POTRZEBNĄ_ILOŚĆ_PAMIĘCI_RAM> # np. 4gb
#SBATCH --time=<TU_PODAJ_MAKSYMALNĄ_ILOŚĆ_CZASU_NA_WYKONANIE_ZADANIA> # format dd-hh:mm:ss np. 1-12:30:15
#SBATCH --job-name=<TU_PODAJ_NAZWĘ_ZADANIA> # np. example
#SBATCH -p <TU_PODAJ_NAZWĘ_PARTYCJI> # np. tesla
#SBATCH --gres=<TU_PODAJ_JAKIE_ZASOBY_GPU_POTRZEBUJESZ> #format gpu:${nazwa_zasobu}:${ilość} np. gpu:tesla:1
apptainer exec --nv tensorflow_latest-gpu.sif python3 tf_list_gpus.py
sbatch tensortest.sh
1
Tesla P100-PCIE-16GB
DOSTĘPNE MODUŁY NA PARTYCJACH
Poniższym poleceniem sprawdzamy dostępność modułu na konkretnej partycjitesla
- lista modułów na obu partycjach nie jest tożsama.
Aby sprawdzić dostępność możemy wywołać zadanie interaktywne np.
srun --pty --mem=1gb -N1 -c2 -t10 -p tesla --gres=gpu:tesla:1 /bin/bash
polecenie
module av
< > konkretny moduł np. Python - wypisuje dostępne wersje
(base) [wcss] user@hgx8 ~ > module av <Python>
------------------------------------------- /usr/local/easybuild/modules/all
Python/2.7.18-GCCcore-10.2.0 Python/2.7.18-GCCcore-10.3.0-bare Python/3.8.6-GCCcore-10.2.0 Python/3.9.5-GCCcore-10.3.0 Python/3.9.5-GCCcore-10.3.0-bare
Utworzenie nowego środowiska wirtualnego i włączenie zadania.
Dokumentacja virtualenv: https://man.e-science.pl/pl/kdm/oprogramowanie/virtualenv
Dokumentacja python venv: https://docs.python.org/3/library/venv.html
srun -c 2 -N 1 --mem=2gb --time=00:30:00 --pty /bin/bash
Należy sprawdzić limity na podanym QOS, za pomocą polecenia: sacctmgr show -P qos where name=<TU_PODAJ_QOS>
module load Python/3.10.4-GCCcore-11.3.0
python3 -m virtualenv myenv
source myenv/bin/activate
pip3 install torch torchvision torchaudio
exit
torch_list_gpus.py
:import torch
#Wypisuje ilość GPU
print(torch.cuda.device_count())
#Wypisuje nazwy GPU
for i in range(torch.cuda.device_count()):
print(torch.cuda.get_device_name(i))
Linki do dokumentacji pytorch:
https://pytorch.org/docs/stable/generated/torch.cuda.device_count.html#torch.cuda.device_count
https://pytorch.org/docs/stable/generated/torch.cuda.get_device_name.html
pytorchsbatch.sh
WAŻNE! Ustaw odpowiednią ilość rdzeni(domyślne ustawienia: c1)
#!/bin/bash
#SBATCH -N <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_WĘZŁÓW> # np. 1
#SBATCH -c <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_RDZENI_CPU> # np. 8
#SBATCH --mem=<TU_PODAJ_POTRZEBNĄ_ILOŚĆ_PAMIĘCI_RAM> # np. 4gb
#SBATCH --time=<TU_PODAJ_MAKSYMALNĄ_ILOŚĆ_CZASU_NA_WYKONANIE_ZADANIA> # format dd-hh:mm:ss np. 1-12:30:15
#SBATCH --job-name=<TU_PODAJ_NAZWĘ_ZADANIA> # np. example
#SBATCH -p <TU_PODAJ_NAZWĘ_PARTYCJI> # np. tesla
#SBATCH --gres=<TU_PODAJ_JAKIE_ZASOBY_GPU_POTRZEBUJESZ> #format gpu:${nazwa_zasobu}:${ilość} np. gpu:tesla:1
source /usr/local/sbin/modules.sh
# moduły wymagane do obsługi GPU.
module load Python/3.10.4-GCCcore-11.3.0
module load cuda
module load cudnn
source myenv/bin/activate
python torch_list_gpus.py
Linki do dokumentacji pytorch:
https://github.com/pytorch/pytorch#from-source
sbatch pytorchsbatch.sh
1
Tesla P100-PCIE-16GB
srun -c 2 -N 1 --mem=2gb --time=00:30:00 --pty /bin/bash
Należy sprawdzić limity na podanym QOS, za pomocą polecenia: sacctmgr show -P qos where name=<TU_PODAJ_QOS>
module load Python/3.10.4-GCCcore-11.3.0
python3 -m virtualenv myenv
source myenv/bin/activate
pip install tensorflow
exit
tf_list_gpus.py
:import tensorflow as tf
#Wypisanie ilości GPU
gpus = tf.config.list_physical_devices('GPU')
print(len(gpus))
#Wypisanie nazw GPU
for gpu in gpus:
device = tf.config.PhysicalDevice(name=gpu.name, device_type='GPU')
details = tf.config.experimental.get_device_details(device)
print(details['device_name'])
Linki do dokumentacji tensorflow:
https://www.tensorflow.org/api_docs/python/tf/config/list_physical_devices
https://www.tensorflow.org/api_docs/python/tf/config/PhysicalDevice
https://www.tensorflow.org/api_docs/python/tf/config/experimental/get_device_details
tensorsbatch.sh
WAŻNE! Ustaw odpowiednią ilość rdzeni(domyślne ustawienia: c1)
#!/bin/bash
#SBATCH -N <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_WĘZŁÓW> # np. 1
#SBATCH -c <TU_PODAJ_POTRZEBNĄ_ILOŚĆ_RDZENI_CPU> # np. 8
#SBATCH --mem=<TU_PODAJ_POTRZEBNĄ_ILOŚĆ_PAMIĘCI_RAM> # np. 4gb
#SBATCH --time=<TU_PODAJ_MAKSYMALNĄ_ILOŚĆ_CZASU_NA_WYKONANIE_ZADANIA> # format dd-hh:mm:ss np. 1-12:30:15
#SBATCH --job-name=<TU_PODAJ_NAZWĘ_ZADANIA> # np. example
#SBATCH -p <TU_PODAJ_NAZWĘ_PARTYCJI> # np. tesla
#SBATCH --gres=<TU_PODAJ_JAKIE_ZASOBY_GPU_POTRZEBUJESZ> #format gpu:${nazwa_zasobu}:${ilość} np. gpu:tesla:1
source /usr/local/sbin/modules.sh
# moduły wymagane do obsługi GPU.
module load Python/3.10.4-GCCcore-11.3.0
module load cuda
module load cudnn
source myenv/bin/activate
python tf_list_gpus.py
Link do dokumentacji tensorflow:
https://www.tensorflow.org/install/pip#software_requirements
sbatch tensorsbatch.sh
1
Tesla P100-PCIE-16GB