Multi-GPU ja monisolmuinen koneoppiminen
Tämä opas selittää, miten hyödyntää useita GPU:ita ja useita solmuja koneoppimissovelluksissa CSC:n supertietokoneilla. Se on osa meidän koneoppimisopasta.
Ensiksi selitämme yleiset periaatteet, kuten yksi- ja monisolmuiset työt sekä mekanismit monien prosessien suorittamiseen. Sen jälkeen käsittelemme joitakin yleisiä ohjelmistokehityksen ympäristöjä ja miten niitä käytetään CSC:n supertietokoneilla: PyTorch DDP:n, DeepSpeedin sekä lyhyesti Horovodin ja TensorFlow'n
tf.distribute.Strategy
.
Solmut ja tehtävät
GPU-solmut
Jokaisella erillisellä GPU-solmulla (eli klusterin yksittäisellä tietokoneella) on kiinteä määrä GPU:ita. Puhtissa ja Mahtissa on 4 GPU:ta solmua kohden, ja LUMIssa 8 GPU:ta solmua kohden. (Teknisesti LUMI-solmulla on 4 GPU-korttia, mutta 8 GPU-piirilevyä.) Koko supertietokoneella voi olla kymmeniä tai jopa tuhansia GPU-solmuja. Katso GPU:lla kiihdytetty koneoppiminen lisätietoja varten, erityisesti taulukko, joka kuvaa eri GPU:ida CSC:n eri supertietokoneissa, saattaa olla mielenkiintoinen.
Jos tarvitset 1-4 GPU:ta (tai 1-8 LUMIssa), sinun tulisi aina varata yksisolmuinen työ. Jos tarvitset yli 4 GPU:ta (tai 8 LUMIssa), sinun pitää varata monisolmuinen työ. Vaikka on teknisesti mahdollista varata, esim. kaksi GPU:ta yhdestä solmusta ja kaksi toista solmusta, tätä ei suositella kuin testaustarkoituksiin, sillä kommunikointi solmujen välillä on aina hitaampaa kuin solmun sisällä.
MPI-tehtävät
Kun rinnakkaistetaan useille GPU:ille, on yleistä allokoida erillinen CPU-prosessi jokaisen GPU:n kommunikointia varten. Tämä per-GPU-tehtävien jako voidaan hoitaa ohjelman itsensä toimesta, esimerkiksi käyttämällä Pythonin multiprocessing-kirjastoa erillisten prosessien suorittamiseen. Toinen tapa on käyttää Slurmin MPI-toiminnallisuutta suorittamaan useita MPI-tehtäviä. Se, käytetäänkö MPI-tehtäviä, riippuu usein käytetystä ohjelmistokehyksestä.
Slurm-esimerkit
Tässä osiossa annamme Slurm-eräskriptin esimerkkejä yksi- tai monisolmuisten töiden suorittamiseksi, MPI:lla tai ilman sitä.
Huomautus
Varmista, että koodisi tosiasiallisesti hyödyntää useita GPU:ita, sillä tämä vaatii yleensä joitakin muutoksia ohjelmaan. Pelkkä useamman GPU:n varaaminen ei riitä!
Voit seurata, käyttääkö ohjelmasi kaikkia varattuja GPU:ita samoilla mekanismeilla, jotka on kuvattu GPU:lla kiihdytetyn koneoppimisoppassa. Ainoa ero on, että sinun pitäisi nyt nähdä useamman kuin yhden GPU:n tilastot.
Yksi solmua käyttävä ajo 2 GPU:lla, ei MPI
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpusmall
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=64
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:a100:2
srun python3 myprog.py <options>
Huomaa, että Mahtissa gpusmall
-osaston maksimimäärä on 2 GPU:ta. 3 tai 4 GPU:ta tai useampaa varten sinun täytyy käyttää gpumedium
-osastoa.
Yllä oleva esimerkki on helposti muutettavissa käyttämään enemmän kuin 2 GPU:ta muuttamalla --gres
-asetus (Puhti ja Mahti) tai --gpus-per-node
-asetus (LUMI) -parametrissa olevaa numeroa. Enimmäismäärä yksisolmuiselle työlle on 4 GPU:ta (Puhti ja Mahti) tai 8 GPU:ta (LUMI).
Jos lisäät GPU:iden määrää, saatat haluta myös lisätä varattujen CPU-ydinten lukumäärää ja muistimäärää. Esimerkeissämme olemme käyttäneet nyrkkisääntöä varata CPU-ytimet ja muisti samassa suhteessa kuin GPU:iden määrä.
Esimerkiksi Puhtissa on 4 GPU:ta, 40 CPU-ydintä ja 384 GB muistia solmua kohden. Jokaista GPU:ta kohden varaisimme näin ollen 10 CPU-ydintä ja noin 95 G muistia (muistille pyöristämme hieman alaspäin, koska yksiköt eivät ole niin tarkkoja).
LUMI:ssa käytä enintään 7 CPU-ydintä ja 60 Gt/GPU.
Yksi solmu käyttämällä kaikkia GPU:ita, käyttäen MPI
Monisolmu kahdella täydellä solmulla, ei MPI:tä
Huomaa, että --gres
-asetus määrittää aina yksisolmuisten GPU:iden määrän, jopa monisolmuskenaariossa. Joten jos varaamme 8 GPU:ta kahdelle solmulle Puhtissa, se tarkoittaa 4 GPU:ta jokaiselle solmulle, eli --gres=gpu:v100:4
.
Monisolmu kahdella täydellä solmulla käyttäen MPI:tä
Saatavilla olevat kehykset
On olemassa useita kehyksiä monigpu- ja monisolmuiseen koneoppimiseen. Jotkin kehykset ovat tiiviisti yhdistettyjä tiettyyn kehykseen, kuten PyTorchin DistributedDataParallel
, DeepSpeed tai TensorFlow'n tf.distribute.Strategy
, kun taas toiset ovat yleisempiä, kuten Horovod.
Riippumatta siitä, minkä kehyksen valitset, kiinnitä huomiota työn suorittamismenetelmään. Esimerkiksi Horovodia käytettäessä on yleistä käyttää MPI:tä, kun taas DeepSpeed voidaan konfiguroida käyttämään MPI:tä tai omaa rinnakkaismekanismiaan. Joissakin kehyksissä suoritusmekanismi voi myös vaihdella sen mukaan, onko kyseessä yksisolmuinen vai monisolmuinen työ.
Kaikkien kehysten tulisi käyttää NCCL:tä (NVIDIA) tai RCCL:ää (AMD) nopeaan GPU-laitteiden väliseen viestintään, vaikka MPI:tä käytettäisiinkin yhteyksien muodostamiseen.
PyTorch DDP
PyTorch
distributed,
ja erityisesti DistributedDataParallel
(DDP), tarjoaa mukavan tavan
suorittaa monigpu- ja monisolmuista PyTorch-työtä. Valitettavasti
PyTorch-dokumentaatio on ollut jossain määrin puutteellista tällä
alueella, ja verkosta löytyvät esimerkit voivat usein olla
vanhentuneita. Siksi, jotta DDP:n käyttö CSC:n supertietokoneilla
olisi helpompaa, olemme luoneet joukon esimerkkejä, miten
suorittaa yksinkertaisia DDP-töitä klusterissa.
Esimerkeissä käytämme
rendezvous
-mekanismia kommunikaation asettamiseen solmujen välille, ei MPI:tä.
Esimerkki Slurm-erätyöstä, jolla ajetaan PyTorch DDP:tä yhdellä täydellä solmulla:
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpu
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=40
#SBATCH --mem=320G
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:v100:4
module purge
module load pytorch
srun python3 -m torch.distributed.run --standalone --nnodes=1 --nproc_per_node=4 \
myprog.py <options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpumedium
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=128
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:a100:4
module purge
module load pytorch
srun python3 -m torch.distributed.run --standalone --nnodes=1 --nproc_per_node=4 \
myprog.py <options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=small-g
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=56
#SBATCH --gpus-per-node=8
#SBATCH --mem=480G
#SBATCH --time=1:00:00
module purge
module use /appl/local/csc/modulefiles
module load pytorch
srun python3 -m torch.distributed.run --standalone --nnodes=1 --nproc_per_node=8 \
myprog.py <options>
Esimerkki PyTorch DDP:n ajamisesta kahdella täydellä solmulla:
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpu
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=40
#SBATCH --mem=320G
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:v100:4
export RDZV_HOST=$(hostname)
export RDZV_PORT=29400
module purge
module load pytorch
srun python3 -m torch.distributed.run \
--nnodes=$SLURM_JOB_NUM_NODES \
--nproc_per_node=4 \
--rdzv_id=$SLURM_JOB_ID \
--rdzv_backend=c10d \
--rdzv_endpoint="$RDZV_HOST:$RDZV_PORT" \
myprog.py <options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpumedium
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=128
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:a100:4
export RDZV_HOST=$(hostname)
export RDZV_PORT=29400
module purge
module load pytorch
srun python3 -m torch.distributed.run \
--nnodes=$SLURM_JOB_NUM_NODES \
--nproc_per_node=4 \
--rdzv_id=$SLURM_JOB_ID \
--rdzv_backend=c10d \
--rdzv_endpoint="$RDZV_HOST:$RDZV_PORT" \
myprog.py <options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=small-g
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=56
#SBATCH --gpus-per-node=8
#SBATCH --mem=480G
#SBATCH --time=1:00:00
export RDZV_HOST=$(hostname)
export RDZV_PORT=29400
module purge
module use /appl/local/csc/modulefiles
module load pytorch
srun python3 -m torch.distributed.run \
--nnodes=$SLURM_JOB_NUM_NODES \
--nproc_per_node=8 \
--rdzv_id=$SLURM_JOB_ID \
--rdzv_backend=c10d \
--rdzv_endpoint="$RDZV_HOST:$RDZV_PORT" \
myprog.py <options>
Jos et käytä CSC:n kehittämää PyTorch-modulia LUMIssa, sinun täytyy ehkä asettaa ympäristömuuttuja NCCL_SOCKET_IFNAME=hsn
ja muutamia muita asetuksia parhaan suorituskyvyn saamiseksi. Katso PyTorch-esimerkki LUMI-dokumentaatiosta kaikkien tarvittavien ympäristömuuttujien saamiseksi. Nämä asetetaan automaattisesti CSC:n PyTorch-modulissa.
Jos muunnet vanhaa PyTorch-ohjelmaa, on muutamia toimenpiteitä, joita sinun täytyy tehdä:
-
Alusta
init_process_group()
:llä, esimerkiksi: -
Kääri mallisi
DistributedDataParallel
:llä: -
Käytä
DistributedSampler
:iaDataLoader
:issasi:
Käyttökelpoinen esimerkki Puhtille löytyy meidän pytorch-ddp-examples
-arkistostamme:
- mnist_ddp.py näyttää Python-koodin yksinkertaisen CNN-mallin opettamiseen MNIST-datalla käyttäen PyTorch DDP:tä
- run-ddp-gpu4.sh sisältää Slurm-skriptin harjoituksen suorittamiseksi 4 GPU:lla yksittäisellä solmulla
- run-ddp-gpu8.sh näyttää saman kahdelle täydelle solmulle, yhteensä 8 GPU:lle
PyTorch Lightning DDP:n kanssa
PyTorch Lightning on suosittu korkean tason kehys, joka on suunniteltu helpottamaan PyTorchin käyttöä. Monigpu- ja monisolmuisten töiden suorittaminen Lightningin kanssa on melko helppoa. Jos haluat muuntaa nykyisen PyTorch-ohjelmasi Lightningiksi, viittaamme viralliseen PyTorch Lightning -ohjeeseen.
Suosittelemme käyttämään DistributedDataParallel (DDP): tä monigpu- ja monisolmuiseen käyttöön. Sinun tarvitsee vain lisätä nämä vaihtoehdot Lightning Traineriin:
trainer = pl.Trainer(devices=args.gpus,
num_nodes=args.nodes,
accelerator='gpu',
strategy='ddp',
...)
Sinun täytyy antaa asianmukaiset arvot devices
(GPU:den määrä solmua kohden) ja num_nodes
. Suosittelemme antamaan nämä komentoriviparametreina:
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--gpus', default=1, type=int,
help='number of GPUs per node')
parser.add_argument('--nodes', default=1, type=int,
help='number of nodes')
# any other command line arguments here
args = parser.parse_args()
PyTorch Lightning Slurm-skripti yhden solmun käyttöön käyttäen kaikkia GPU:ita:
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=small-g
#SBATCH --nodes=1
#SBATCH --ntasks-per-node=8
#SBATCH --cpus-per-task=7
#SBATCH --gpus-per-node=8
#SBATCH --mem=480G
#SBATCH --time=1:00:00
module purge
module use /appl/local/csc/modulefiles
module load pytorch
srun python3 myprog.py --gpus=8 --nodes=1 <options>
PyTorch Lightning Slurm-skripti kahtena täydellisenä solmuna kaikkia GPU:ita käyttäen:
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=small-g
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=8
#SBATCH --cpus-per-task=7
#SBATCH --gpus-per-node=8
#SBATCH --mem=480G
#SBATCH --time=1:00:00
module purge
module use /appl/local/csc/modulefiles
module load pytorch
srun python3 myprog.py --gpus=8 --nodes=2 <options>
Accelerate
Hugging Facen Accelerate on suosittu kehys suurten kielimallien kouluttamiseen, ja sen avulla huomattavasti monimutkaisempien koulutusalgojen, kuten FSDP:n, käyttäminen on hyvin helppoa. Acceleratella työn suorittaminen poikkeaa hieman PyTorch DDP:stä, koska meidän täytyy käyttää acceleraten käynnistäjää ja lisäksi tarjota Accelerate-konfiguraatiotiedosto.
Toimiva esimerkki LLM:ien hienosäädöstä löytyy tästä GitHub
repositoriosta
(tutustu tiedostoihin, jotka päättyvät -accelerate.sh
). Katso myös
oppaan kenttä käyttämisestä supertietokoneilla.
Esimerkki Accelerate käyttämisestä kaikilla GPU:illa yhdellä solmulla:
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpu
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=40
#SBATCH --mem=320G
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:v100:4
module purge
module load pytorch
srun accelerate launch \
--config_file=accelerate_config.yaml \
--num_processes=4 \
--num_machines=1 \
--machine_rank=0 \
myprog.py <options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpumedium
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=128
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:a100:4
module purge
module load pytorch
srun accelerate launch \
--config_file=accelerate_config.yaml \
--num_processes=4 \
--num_machines=1 \
--machine_rank=0 \
myprog.py <options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=small-g
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=56
#SBATCH --mem=480G
#SBATCH --time=1:00:00
#SBATCH --gpus-per-node=8
module purge
module use /appl/local/csc/modulefiles/
module load pytorch
srun accelerate launch \
--config_file=accelerate_config.yaml \
--num_processes=8 \
--num_machines=1 \
--machine_rank=0 \
myprog.py <options>
Esimerkki Accelerate suorittamisesta 2 täydellä solmulla (8 GPU:ta).
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpu
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=40
#SBATCH --mem=320G
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:v100:4
module purge
module load pytorch
GPUS_PER_NODE=4
NUM_PROCESSES=$(expr $SLURM_NNODES \* $GPUS_PER_NODE)
MAIN_PROCESS_IP=$(hostname -i)
RUN_CMD="accelerate launch \
--config_file=accelerate_config.yaml \
--num_processes=$NUM_PROCESSES \
--num_machines=$SLURM_NNODES \
--machine_rank=\$SLURM_NODEID \
--main_process_ip=$MAIN_PROCESS_IP \
myprog.py <options>"
srun bash -c "$RUN_CMD"
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpumedium
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=128
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:a100:4
module purge
module load pytorch
GPUS_PER_NODE=4
NUM_PROCESSES=$(expr $SLURM_NNODES \* $GPUS_PER_NODE)
MAIN_PROCESS_IP=$(hostname -i)
RUN_CMD="accelerate launch \
--config_file=accelerate_config.yaml \
--num_processes=$NUM_PROCESSES \
--num_machines=$SLURM_NNODES \
--machine_rank=\$SLURM_NODEID \
--main_process_ip=$MAIN_PROCESS_IP \
myprog.py <options>"
srun bash -c "$RUN_CMD"
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=small-g
#SBATCH --nodes=2
#SBATCH --ntasks-per-node=1
#SBATCH --cpus-per-task=56
#SBATCH --gpus-per-node=8
#SBATCH --mem=480G
#SBATCH --time=1:00:00
module purge
module use /appl/local/csc/modulefiles
module load pytorch
NUM_PROCESSES=$(expr $SLURM_NNODES \* $SLURM_GPUS_PER_NODE)
MAIN_PROCESS_IP=$(hostname -i)
RUN_CMD="accelerate launch \
--config_file=accelerate_config.yaml \
--num_processes=$NUM_PROCESSES \
--num_machines=$SLURM_NNODES \
--machine_rank=\$SLURM_NODEID \
--main_process_ip=$MAIN_PROCESS_IP \
myprog.py <options>"
srun bash -c "$RUN_CMD"
Huomaa jonkin verran kankea tapa määrittää komento \$SLURM_NODEID
-muuttujan kanssa paeta niin, että se arvioidaan vain kyseisellä
solmulla, missä se suoritetaan. Normaalisti kaikki muuttujat
arvioidaan ensimmäisellä solmulla, mutta \$SLURM_NODEID
:n tulee olla
eri arvo jokaisella solmulla, jotta hajautettu asetus toimisi
oikein.
Molemmat esimerkit käyttävät tätä accelerate_config.yaml
-tiedostoa:
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: MULTI_GPU
downcast_bf16: 'no'
gpu_ids: all
main_training_function: main
main_process_port: 29500
mixed_precision: bf16
num_processes: 1
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
Jos haluat käyttää FSDP:tä, käytä yksinkertaisesti Accelerate-konfiguraatiota, joka on samanlainen kuin tämä:
compute_environment: LOCAL_MACHINE
debug: false
distributed_type: FSDP
downcast_bf16: 'no'
fsdp_config:
fsdp_auto_wrap_policy: TRANSFORMER_BASED_WRAP
fsdp_backward_prefetch_policy: BACKWARD_PRE
fsdp_forward_prefetch: false
fsdp_cpu_ram_efficient_loading: true
fsdp_offload_params: false
fsdp_sharding_strategy: FULL_SHARD
fsdp_state_dict_type: SHARDED_STATE_DICT
fsdp_sync_module_states: true
fsdp_use_orig_params: true
gpu_ids: all
main_training_function: main
main_process_port: 29500
mixed_precision: bf16
num_processes: 1
rdzv_backend: static
same_network: true
tpu_env: []
tpu_use_cluster: false
tpu_use_sudo: false
use_cpu: false
Katso GitHub repositoriomme saadaksesi lisää esimerkkejä.
DeepSpeed
DeepSpeed on optimointiohjelmistopaketti PyTorchille, joka auttaa skaalaamaan sekä harjoittelua että mallien käyttöä suurten syväoppimismallien kanssa. DeepSpeed on tuettu PyTorchin modeleissa Puhtissa ja Mahtissa versiosta 1.10 alkaen. DeepSpeed ei ole vielä täysin tuettu LUMIssa.
Esimerkki DeepSpeedin käyttämisestä yhdellä täydellisellä solmulla käyttäen
deepspeed
-käynnistintä:
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpu
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=40
#SBATCH --mem=320G
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:v100:4
module purge
module load pytorch
srun apptainer_wrapper exec deepspeed myprog.py \
--deepspeed --deepspeed_config my_ds_config.json \
<further options>
#!/bin/bash
#SBATCH --account=<project>
#SBATCH --partition=gpumedium
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=128
#SBATCH --time=1:00:00
#SBATCH --gres=gpu:a100:4
module purge
module load pytorch
srun apptainer_wrapper exec deepspeed myprog.py \
--deepspeed --deepspeed_config my_ds_config.json \
<further options>
```bash
!/bin/bash
SBATCH --account=
SBATCH --partition=small-g
SBATCH --ntasks=1
SBATCH --cpus-per-task=56
SBATCH --gpus-per-node=8
SBATCH --mem=480G
SBATCH --time=1:00:00
module purge