Compare commits

...
18 Commits
3 changed files with 75 additions and 19 deletions
+21 -2
View File
@@ -40,13 +40,32 @@ In this setting, the model will run on all 8 GPUs at once using DataParallel und
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2,3,4,5,6,7" os.environ["CUDA_VISIBLE_DEVICES"] = "0,1,2,3,4,5,6,7"
# DEFAULT
trainer = Trainer(gpus=[0,1,2,3,4,5,6,7]) trainer = Trainer(gpus=[0,1,2,3,4,5,6,7])
``` ```
--- ---
#### Multi-node #### Multi-node
COMING SOON. Multi-node training is easily done by specifying these flags.
```python
# train on 12*8 GPUs
trainer = Trainer(gpus=[0,1,2,3,4,5,6,7], nb_gpu_nodes=12)
```
In addition, make sure to set up your SLURM job correctly via the [SlurmClusterObject](https://williamfalcon.github.io/test-tube/hpc/SlurmCluster/). In particular, specify the number of tasks per node correctly.
```python
cluster = SlurmCluster(
hyperparam_optimizer=test_tube.HyperOptArgumentParser(),
log_path='/some/path/to/save',
)
# configure cluster
cluster.per_experiment_nb_nodes = 12
cluster.per_experiment_nb_gpus = 8
cluster.add_slurm_cmd(cmd='ntasks-per-node', value=8, comment='1 task per gpu')
```
--- ---
#### Self-balancing architecture #### Self-balancing architecture
+53 -16
View File
@@ -1,12 +1,12 @@
""" """
The trainer handles all the logic for running a val loop, training loop, distributing, etc... The trainer handles all the logic for running a val loop, training loop, distributing, etc...
""" """
from time import sleep
import subprocess import subprocess
import traceback import traceback
import warnings import warnings
import os import os
import pdb import pdb
import re
import torch import torch
from torch.utils.data.distributed import DistributedSampler from torch.utils.data.distributed import DistributedSampler
@@ -146,17 +146,24 @@ class Trainer(TrainerIO):
self.print_nan_grads = print_nan_grads self.print_nan_grads = print_nan_grads
self.data_parallel_device_ids = None self.data_parallel_device_ids = None
self.world_size = 1 self.world_size = 1
self.node_rank = 0
self.use_ddp = False self.use_ddp = False
self.use_dp = False self.use_dp = False
# gpus come in as a string. # gpus come in as a string.
# if gpus = -1 then use all available devices # if gpus = -1 then use all available devices
# otherwise, split the string using commas # otherwise, split the string using commas
if gpus is not None: if gpus is not None:
if gpus == '-1': if type(gpus) is list:
self.data_parallel_device_ids = list(range(0, torch.cuda.device_count())) self.data_parallel_device_ids = gpus
elif type(gpus) is str:
if gpus == '-1':
self.data_parallel_device_ids = list(range(0, torch.cuda.device_count()))
else:
self.data_parallel_device_ids = [int(x.strip()) for x in gpus.split(',')]
else: else:
self.data_parallel_device_ids = [int(x.strip()) for x in gpus.split(',')] raise Exception('gpus has to be a string or list of ids')
# set the correct cuda visible devices (using pci order) # set the correct cuda visible devices (using pci order)
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
@@ -170,6 +177,15 @@ class Trainer(TrainerIO):
self.use_dp = distributed_backend == 'dp' self.use_dp = distributed_backend == 'dp'
self.use_ddp = distributed_backend == 'ddp' self.use_ddp = distributed_backend == 'ddp'
# use ddp automatically if nb_gpu_nodes > 1
if nb_gpu_nodes > 1:
self.use_ddp = True
self.use_ddp = False
w = 'DataParallel does not support nb_gpu_nodes > 1. ' \
'Switching to DistributedDataParallel for you. ' \
'To silence this warning set distributed_backend=ddp'
warnings.warn(w)
# process info # process info
self.proc_rank = 0 self.proc_rank = 0
@@ -377,9 +393,14 @@ class Trainer(TrainerIO):
# whenever we have the correct number of tasks, we let slurm manage processes # whenever we have the correct number of tasks, we let slurm manage processes
# otherwise we launch the required number of processes # otherwise we launch the required number of processes
nb_slurm_tasks = int(os.environ['SLURM_NTASKS']) try:
nb_requested_gpus = len(self.data_parallel_device_ids) nb_slurm_tasks = int(os.environ['SLURM_NTASKS'])
is_slurm_managing_tasks = nb_slurm_tasks == nb_requested_gpus nb_requested_gpus = len(self.data_parallel_device_ids) * self.nb_gpu_nodes
is_slurm_managing_tasks = nb_slurm_tasks == nb_requested_gpus
except Exception as e:
# likely not on slurm, so set the slurm managed flag to false
is_slurm_managing_tasks = False
if is_slurm_managing_tasks: if is_slurm_managing_tasks:
task = int(os.environ['SLURM_LOCALID']) task = int(os.environ['SLURM_LOCALID'])
self.ddp_train(task, model) self.ddp_train(task, model)
@@ -388,6 +409,7 @@ class Trainer(TrainerIO):
You requested {nb_requested_gpus} GPUs but launched {nb_slurm_tasks} slurm tasks. You requested {nb_requested_gpus} GPUs but launched {nb_slurm_tasks} slurm tasks.
We will launch {nb_requested_gpus} processes for you. We will launch {nb_requested_gpus} processes for you.
We recommend you let slurm manage the processes by setting: --ntasks-per-node={nb_requested_gpus} We recommend you let slurm manage the processes by setting: --ntasks-per-node={nb_requested_gpus}
If you're not using SLURM, ignore this message!
""" """
warnings.warn(msg) warnings.warn(msg)
mp.spawn(self.ddp_train, nprocs=len(self.data_parallel_device_ids), args=(model, )) mp.spawn(self.ddp_train, nprocs=len(self.data_parallel_device_ids), args=(model, ))
@@ -443,9 +465,10 @@ class Trainer(TrainerIO):
# node rank using relative slurm id # node rank using relative slurm id
# otherwise default to node rank 0 # otherwise default to node rank 0
try: try:
node_rank = int(os.environ['SLURM_NODEID']) node_id = os.environ['SLURM_NODEID']
except KeyError as e: self.node_rank = int(node_id)
node_rank = 0 except Exception as e:
self.node_rank = 0
# recover original exp before went into process # recover original exp before went into process
# init in write mode only on proc 0 # init in write mode only on proc 0
@@ -453,10 +476,10 @@ class Trainer(TrainerIO):
self.experiment = self.experiment.get_non_ddp_exp() self.experiment = self.experiment.get_non_ddp_exp()
# show progbar only on prog_rank 0 # show progbar only on prog_rank 0
self.prog_bar = self.prog_bar and node_rank == 0 and gpu_nb == 0 self.prog_bar = self.prog_bar and self.node_rank == 0 and gpu_nb == 0
# determine which process we are and world size # determine which process we are and world size
self.proc_rank = node_rank * len(self.data_parallel_device_ids) + gpu_nb self.proc_rank = self.node_rank * len(self.data_parallel_device_ids) + gpu_nb
self.world_size = self.nb_gpu_nodes * len(self.data_parallel_device_ids) self.world_size = self.nb_gpu_nodes * len(self.data_parallel_device_ids)
# set up server using proc 0's ip address # set up server using proc 0's ip address
@@ -501,15 +524,29 @@ class Trainer(TrainerIO):
port = 12910 port = 12910
os.environ['MASTER_PORT'] = f'{port}' os.environ['MASTER_PORT'] = f'{port}'
root_node = self.__resolve_root_node_address()
os.environ['MASTER_ADDR'] = root_node
dist.init_process_group("nccl", rank=self.proc_rank, world_size=self.world_size)
def __resolve_root_node_address(self):
try: try:
root_node = os.environ['SLURM_NODELIST'].split(' ')[0] root_node = os.environ['SLURM_NODELIST'].split(' ')[0]
if '[' in root_node:
name = root_node.split('[')[0]
number = root_node.split(',')[0]
if '-' in number:
number = number.split('-')[0]
number = re.sub('[^0-9]', '', number)
root_node = name + number
except Exception as e: except Exception as e:
root_node = '127.0.0.2' root_node = '127.0.0.2'
os.environ['MASTER_ADDR'] = root_node return root_node
sleep(self.proc_rank*0.5)
dist.init_process_group("nccl", rank=self.proc_rank, world_size=self.world_size)
def __run_pretrain_routine(self, model): def __run_pretrain_routine(self, model):
""" """
+1 -1
View File
@@ -7,7 +7,7 @@ from setuptools import setup, find_packages
# http://blog.ionelmc.ro/2014/05/25/python-packaging/ # http://blog.ionelmc.ro/2014/05/25/python-packaging/
setup( setup(
name="pytorch-lightning", name="pytorch-lightning",
version='0.2.5.1', version='0.3.1',
description="The Keras for ML researchers using PyTorch", description="The Keras for ML researchers using PyTorch",
author="William Falcon", author="William Falcon",
author_email="waf2107@columbia.edu", author_email="waf2107@columbia.edu",