TensorFlow/fr: Difference between revisions
No edit summary |
No edit summary |
||
(145 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
<languages /> | <languages /> | ||
[[Category:Software]] | [[Category:Software]] [[Category:AI and Machine Learning]] | ||
[https://www.tensorflow.org/ TensorFlow] est une bibliothèque logicielle | [https://www.tensorflow.org/ TensorFlow] est une bibliothèque logicielle <i>open source</i> d'apprentissage machine. | ||
Si vous voulez porter un programme TensorFlow sur une de nos grappes, nous vous recommandons de prendre connaissance du [[Tutoriel Apprentissage machine|tutoriel sur l'apprentissage machine]]. | |||
==Installation== | ==Installation== | ||
Les directives suivantes servent à installer TensorFlow dans votre répertoire | Les directives suivantes servent à installer TensorFlow dans votre répertoire <i>home</i> à l'aide des ([http://pythonwheels.com/ <i>wheels</i> Python ]) qui se trouvent dans <code>/cvmfs/soft.computecanada.ca/custom/python/wheelhouse/</code>. | ||
<br /> | <br /> | ||
Le | Le wheel TensorFlow sera installé dans un [[Python/fr#Créer_et_utiliser_un_environnement_virtuel|environnement virtuel Python]] avec la commande <code>pip</code>. | ||
< | |||
<tabs> | |||
<tab name="TF 2.x"> | |||
Chargez les modules requis par TensorFlow; dans certains cas, d'autres modules pourraient être requis (par exemple CUDA). | |||
{{Command2|module load python/3}} | |||
Créez un nouvel environnement Python. | |||
{{Command2|virtualenv --no-download tensorflow}} | |||
Activez le nouvel environnement. | |||
{{Command2|source tensorflow/bin/activate}} | |||
Installez TensorFlow dans votre nouvel environnement virtuel en utilisant la commande suivante. | |||
{{Command2|prompt=(tensorflow) [name@server ~]$ | |||
|pip install --no-index tensorflow{{=}}{{=}}2.8}} | |||
</tab> | |||
<tab name="TF 1.x"> | |||
Chargez les modules requis par TensorFlow. TF 1.x requiert StdEnv/2018. | |||
<b>Remarque : TF 1.x n'est pas disponible sur Narval, puisque cette grappe n'offre pas StdEnv/2018.</b> | |||
{{Command2|module load StdEnv/2018 python/3}} | |||
{{Command2|module load python/3 | |||
Créez un nouvel environnement Python. | Créez un nouvel environnement Python. | ||
Line 22: | Line 41: | ||
{{Command2|source tensorflow/bin/activate}} | {{Command2|source tensorflow/bin/activate}} | ||
Installez TensorFlow dans votre nouvel environnement virtuel en utilisant une des commandes suivantes, dépendant de si vous avez besoin d'utiliser un GPU. | |||
< | <b>N'installez pas</b> le paquet <code>tensorflow</code> sans le suffixe <code>_cpu</code> ou <code>_gpu</code> car il existe des problèmes de compatibilité avec d'autres bibliothèques. | ||
=== CPU seulement === | === CPU seulement === | ||
{{Command2|prompt=(tensorflow) [name@server ~]$ | {{Command2|prompt=(tensorflow) [name@server ~]$ | ||
|pip install --no-index | |pip install --no-index tensorflow_cpu{{=}}{{=}}1.15.0}} | ||
=== GPU === | |||
{{Command2|prompt=(tensorflow) [name@server ~]$ | {{Command2|prompt=(tensorflow) [name@server ~]$ | ||
|pip install --no-index | |pip install --no-index tensorflow_gpu{{=}}{{=}}1.15.0}} | ||
</tab> | |||
</tabs> | |||
=== Le paquet R === | === Le paquet R === | ||
Line 47: | Line 66: | ||
Lancez R. | Lancez R. | ||
{{Command2|prompt=(tensorflow)_[name@server ~]$|R}} | {{Command2|prompt=(tensorflow)_[name@server ~]$|R}} | ||
En R, installez le paquet devtools, puis TensorFlow | En R, installez le paquet devtools, puis TensorFlow. | ||
<syntaxhighlight lang="r"> | <syntaxhighlight lang="r"> | ||
install.packages('devtools', repos='https://cloud.r-project.org') | install.packages('devtools', repos='https://cloud.r-project.org') | ||
Line 53: | Line 72: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Vous pouvez maintenant procéder. N'appelez pas <code>install_tensorflow()</code> en R puisque TensorFlow est déjà installé dans votre environnement virtuel avec < | Vous pouvez maintenant procéder. N'appelez pas <code>install_tensorflow()</code> en R puisque TensorFlow est déjà installé dans votre environnement virtuel avec <code>pip</code>. Pour utiliser TensorFlow tel qu'installé dans votre environnement virtuel, entrez les commandes suivantes en R, après que l'environnement est activé. | ||
<syntaxhighlight lang="r"> | <syntaxhighlight lang="r"> | ||
Line 80: | Line 99: | ||
}} | }} | ||
Le script Python se lit | Le script Python se lit | ||
<tabs> | |||
<tab name="TF 2.x"> | |||
{{File | {{File | ||
|name=tensorflow-test.py | |name=tensorflow-test.py | ||
Line 85: | Line 107: | ||
|contents= | |contents= | ||
import tensorflow as tf | import tensorflow as tf | ||
node1 = tf.constant(3.0, | node1 = tf.constant(3.0) | ||
node2 = tf.constant(4.0) | node2 = tf.constant(4.0) | ||
print(node1, node2) | |||
print(node1 + node2) | |||
}} | |||
</tab> | |||
<tab name="TF 1.x"> | |||
{{File | |||
|name=tensorflow-test.py | |||
|lang="python" | |||
|contents= | |||
import tensorflow as tf | |||
node1 = tf.constant(3.0) | |||
node2 = tf.constant(4.0) | |||
print(node1, node2) | print(node1, node2) | ||
sess = tf.Session() | sess = tf.Session() | ||
print(sess.run( | print(sess.run(node1 + node2)) | ||
}} | }} | ||
Une fois la tâche | </tab> | ||
</tabs> | |||
Une fois la tâche terminée, ce qui devrait nécessiter moins d'une minute, un fichier de sortie avec un nom semblable à <code>cdr116-122907.out</code> devrait être généré. Le contenu de ce fichier serait similaire à ce qui suit; il s'agit d'exemples de messages TensorFlow et il est possible que vous en ayez d'autres. | |||
<tabs> | |||
<tab name="TF 2.x"> | |||
{{File | |||
|name=cdr116-122907.out | |||
|lang="text" | |||
|contents= | |||
2017-07-10 12:35:19.491097: I tensorflow/core/common_runtime/gpu/gpu_device.cc:961] DMA: 0 | |||
2017-07-10 12:35:19.491156: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0: Y | |||
2017-07-10 12:35:19.520737: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-PCIE-12GB, pci bus id: 0000:82:00.0) | |||
tf.Tensor(3.0, shape=(), dtype=float32) tf.Tensor(4.0, shape=(), dtype=float32) | |||
tf.Tensor(7.0, shape=(), dtype=float32) | |||
}} | |||
</tab> | |||
<tab name="TF 1.x"> | |||
{{File | {{File | ||
|name=cdr116-122907.out | |name=cdr116-122907.out | ||
|lang="text" | |lang="text" | ||
|contents= | |contents= | ||
2017-07-10 12:35:19.491097: I tensorflow/core/common_runtime/gpu/gpu_device.cc:961] DMA: 0 | 2017-07-10 12:35:19.491097: I tensorflow/core/common_runtime/gpu/gpu_device.cc:961] DMA: 0 | ||
2017-07-10 12:35:19.491156: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0: Y | 2017-07-10 12:35:19.491156: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0: Y | ||
2017-07-10 12:35:19.520737: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-PCIE-12GB, pci bus id: 0000:82:00.0) | 2017-07-10 12:35:19.520737: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-PCIE-12GB, pci bus id: 0000:82:00.0) | ||
Tensor("Const:0", shape=(), dtype=float32) Tensor("Const_1:0", shape=(), dtype=float32) | Tensor("Const:0", shape=(), dtype=float32) Tensor("Const_1:0", shape=(), dtype=float32) | ||
7.0 | |||
}} | }} | ||
</tab> | |||
</tabs> | |||
TensorFlow fonctionne sur tous les types de nœuds GPU. Pour la recherche de grande envergure en apprentissage profond ou en apprentissage machine, il est fortement recommandé d'utiliser le type de nœuds <i>GPU large</i> de Cedar. Ces nœuds sont équipés de 4 x P100-PCIE-16Go avec [http://developer.download.nvidia.com/devzone/devcenter/cuda/docs/GPUDirect_Technology_Overview.pdf GPUDirect P2P] entre chaque paire. Pour plus d'information, consultez [[Using GPUs with Slurm/fr|cette page]]. | |||
==Suivi== | ==Suivi== | ||
Line 160: | Line 166: | ||
===TensorBoard=== | ===TensorBoard=== | ||
TensorFlow propose la suite d'outils de visualisation [https://www.tensorflow.org/programmers_guide/summaries_and_tensorboard TensorBoard] qui lit les événements TensorFlow et modélise les fichiers. Pour savoir comment créer ces fichiers, consultez [https://www.tensorflow.org/programmers_guide/summaries_and_tensorboard#serializing_the_data TensorBoard tutorial on summaries]. | TensorFlow propose la suite d'outils de visualisation [https://www.tensorflow.org/programmers_guide/summaries_and_tensorboard TensorBoard] qui lit les événements TensorFlow et modélise les fichiers. Pour savoir comment créer ces fichiers, consultez [https://www.tensorflow.org/programmers_guide/summaries_and_tensorboard#serializing_the_data TensorBoard tutorial on summaries]. | ||
Sachez toutefois que TensorBoard exige trop de puissance de calcul pour être exécuté sur un nœud de connexion. Nous vous recommandons de l'exécuter dans la même tâche que le processus TensorFlow. Pour ce faire, lancez TensorBoard en arrière-plan en l'appelant avant le script Python, en y ajoutant le caractère (<code>&</code>). | |||
# Your SBATCH arguments here | |||
tensorboard --logdir=/tmp/your_log_dir --host 0.0.0.0 --load_fast false & | |||
python train.py # example | |||
Pour accéder TensorBoard avec un fureteur une fois que la tâche est en cours, il faut créer un lien entre votre ordinateur et le nœud sur lequel TensorFlow et TensorBoard sont exécutés. Pour ce faire, vous avez besoin du <i>hostname</i> du nœud de calcul sur lequel le serveur TensorFlow se trouve. Pour le trouver, faites afficher la liste de vos tâches avec la commande <code>sq</code> et repérez la tâche; le <i>hostname</i> est la valeur qui se trouve dans la colonne NODELIST. | |||
Pour créer | Pour créer la connexion, lancez la commande sur votre ordinateur local. | ||
{{Command2|prompt=[name@my_computer ~]$ | {{Command2|prompt=[name@my_computer ~]$ | ||
|ssh -N -f -L localhost:6006:computenode:6006 userid@cluster.computecanada.ca}} | |ssh -N -f -L localhost:6006:computenode:6006 userid@cluster.computecanada.ca}} | ||
Remplacez <code>computenode</code> par le | Remplacez <code>computenode</code> par le <i>hostname</i> obtenu à l'étape précédente; <code>userid</code> par votre nom d'utilisateur de l'Alliance et; <code>cluster</code> par le <i>hostname</i> de la grappe, soit <code>beluga</code>, <code>cedar</code>, <code>graham</code>, etc. Si le port 6006 était déjà utilisé, tensorboard va en utiliser un autre (p. ex. 6007, 6008...). | ||
Une fois que la connexion est établie, allez à [http://localhost:6006 http://localhost:6006]. | Une fois que la connexion est établie, allez à [http://localhost:6006 http://localhost:6006]. | ||
==Utiliser plusieurs GPU== | ==Utiliser plusieurs GPU== | ||
Il existe plusieurs méthodes de gestion des variables, les plus communes étant | |||
===TensorFlow 1.x=== | |||
Il existe plusieurs méthodes de gestion des variables, les plus communes étant <i>Parameter Server</i> et <i>Replicated</i>. | |||
*Nous allons utiliser [https://github.com/tensorflow/benchmarks ce code] pour illustrer les diverses méthodes; vous pouvez l'adapter à vos besoins spécifiques. | *Nous allons utiliser [https://github.com/tensorflow/benchmarks ce code] pour illustrer les diverses méthodes; vous pouvez l'adapter à vos besoins spécifiques. | ||
===Parameter Server=== | ====Parameter Server==== | ||
La copie maîtresse des variables est enregistrée sur un serveur de paramètres. En | La copie maîtresse des variables est enregistrée sur un serveur de paramètres. En entraînement distribué, les serveurs de paramètres sont des processus distincts dans chacun des appareils. À chaque étape, chacune des tours obtient du serveur de paramètres une copie des variables et y retourne ses gradients. | ||
Les paramètres peuvent être enregistrés sur un CPU | Les paramètres peuvent être enregistrés sur un CPU | ||
Line 213: | Line 203: | ||
</pre> | </pre> | ||
===Replicated=== | ====Replicated==== | ||
Chaque GPU possède sa propre copie des variables. Les gradients sont copiés sur toutes les tours par | Chaque GPU possède sa propre copie des variables. Les gradients sont copiés sur toutes les tours par agrégation du contenu des appareils ou par un algorithme <i>all reduce</i> (dépendant de la valeur du paramètre all_reduce_spec). | ||
Avec la méthode ''all reduce'' par défaut ː | Avec la méthode ''all reduce'' par défaut ː | ||
Line 234: | Line 224: | ||
Les méthodes se comportent différemment selon les modèles; nous vous recommandons fortement de tester vos modèles avec toutes les méthodes sur les différents types de nœuds GPU. | Les méthodes se comportent différemment selon les modèles; nous vous recommandons fortement de tester vos modèles avec toutes les méthodes sur les différents types de nœuds GPU. | ||
===Étalonnage (''benchmarks'')=== | ====Étalonnage (''benchmarks'')==== | ||
Les résultats ont été obtenus avec TensorFlow v1.5 (CUDA9 et cuDNN 7) sur Graham et Cedar avec un seul GPU et plusieurs GPU et des méthodes différentes de gestion des variables; voyez [https://github.com/tensorflow/benchmarks TensorFlow Benchmarks]. | Les résultats ont été obtenus avec TensorFlow v1.5 (CUDA9 et cuDNN 7) sur Graham et Cedar avec un seul GPU et plusieurs GPU et des méthodes différentes de gestion des variables; voyez [https://github.com/tensorflow/benchmarks TensorFlow Benchmarks]. | ||
*ResNet-50 | *ResNet-50 | ||
Line 240: | Line 230: | ||
{| class="wikitable" | {| class="wikitable" | ||
|- | |- | ||
! Type de nœud !! | ! Type de nœud !! 1 GPU !! Nombre de GPU !! ps,cpu !! ps, gpu !! répliqué !! répliqué, xring !! répliqué, pscpu !! répliqué, nccl | ||
|- | |- | ||
| Graham, | | Graham, <i>GPU base</i>|| 171.23||2 || 93.31 || <b>324.04</b> || 318.33 || 316.01 || 109.82 || 315.99 | ||
|- | |- | ||
| Cedar | | Cedar <i>GPU Base</i> || 172.99|| 4 || <b>662.65</b> ||595.43 || 616.02 || 490.03|| 645.04 || 608.95 | ||
|- | |- | ||
| Cedar, | | Cedar, <i>GPU Large</i> || 205.71 ||4 || 673.47 || 721.98 || <b>754.35</b> || 574.91 || 664.72 || 692.25 | ||
|} | |} | ||
Line 256: | Line 246: | ||
! Type de nœud !! 1 GPU !! Nombre de GPU !! ps,cpu !! ps, gpu !! répliqué !! répliqué, xring !! répliqué, pscpu !! répliqué, nccl | ! Type de nœud !! 1 GPU !! Nombre de GPU !! ps,cpu !! ps, gpu !! répliqué !! répliqué, xring !! répliqué, pscpu !! répliqué, nccl | ||
|- | |- | ||
| Graham, | | Graham, <i>GPU Base</i>|| 115.89||2 || 91.29 || 194.46 || 194.43 || 203.83 || 132.19 || <b>219.72</b> | ||
|- | |- | ||
| Cedar, | | Cedar, <i>GPU Base</i> || 114.77 ||4 || 232.85 || 280.69 || 274.41 || 341.29 || 330.04 || <b>388.53</b> | ||
|- | |- | ||
| Cedar, | | Cedar, <i>GPU Large</i> || 137.16 ||4 || 175.20 || 379.80 ||336.72 || 417.46 || 225.37 || <b>490.52</b> | ||
|} | |} | ||
===TensorFlow 2.x=== | |||
À l'instar de TensorFlow 1.x, TensorFlow 2.x offre des stratégies différentes pour utiliser plusieurs GPU avec l'API de haut niveau <code>tf.distribute</code>. Dans les sections qui suivent, nous montrons des exemples de code pour chacune des stratégies avec Keras. Pour plus d'information, consultez la [https://www.tensorflow.org/api_docs/python/tf/distribute documentation officielle de TensorFlow]. | |||
====Stratégie miroir==== | |||
=====Nœud unique===== | |||
{{File | |||
|name=tensorflow-singleworker.sh | |||
|lang="bash" | |||
|contents= | |||
#!/bin/bash | |||
#SBATCH --nodes 1 | |||
#SBATCH --gres=gpu:4 | |||
#SBATCH --mem=8G | |||
#SBATCH --time=0-00:30 | |||
#SBATCH --output=%N-%j.out | |||
module load python/3 | |||
virtualenv --no-download $SLURM_TMPDIR/env | |||
source $SLURM_TMPDIR/env/bin/activate | |||
pip install --no-index tensorflow | |||
export NCCL_BLOCKING_WAIT=1 #Set this environment variable if you wish to use the NCCL backend for inter-GPU communication. | |||
python tensorflow-singleworker.py | |||
}} | |||
Le script Python <code>tensorflow-singleworker.py</code> a le format | |||
{{File | |||
|name=tensorflow-singleworker.py | |||
|lang="python" | |||
|contents= | |||
import tensorflow as tf | |||
import numpy as np | |||
import argparse | |||
parser = argparse.ArgumentParser(description='cifar10 classification models, tensorflow MirroredStrategy test') | |||
parser.add_argument('--lr', default=0.001, help='') | |||
parser.add_argument('--batch_size', type=int, default=256, help='') | |||
args = parser.parse_args() | |||
strategy = tf.distribute.MirroredStrategy() | |||
with strategy.scope(): | |||
model = tf.keras.Sequential() | |||
model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same', | |||
input_shape=(32,32,3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Conv2D(32, (3, 3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) | |||
model.add(tf.keras.layers.Dropout(0.25)) | |||
model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same')) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Conv2D(64, (3, 3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) | |||
model.add(tf.keras.layers.Dropout(0.25)) | |||
model.add(tf.keras.layers.Flatten()) | |||
model.add(tf.keras.layers.Dense(512)) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Dropout(0.5)) | |||
model.add(tf.keras.layers.Dense(10)) | |||
model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), | |||
optimizer=tf.keras.optimizers.SGD(learning_rate=args.lr),metrics=['accuracy']) | |||
### This next line will attempt to download the CIFAR10 dataset from the internet if you don't already have it stored in ~/.keras/datasets. | |||
### Run this line on a login node prior to submitting your job, or manually download the data from | |||
### https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz, rename to "cifar-10-batches-py.tar.gz" and place it under ~/.keras/datasets | |||
(x_train, y_train),_ = tf.keras.datasets.cifar10.load_data() | |||
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(args.batch_size) | |||
model.fit(dataset, epochs=2) | |||
}} | |||
=====Nœuds multiples===== | |||
La syntaxe pour utiliser des GPU distribués sur plusieurs nœuds ressemble beaucoup au cas du nœud simple; la différence principale est l'emploi de <code>MultiWorkerMirroredStrategy()</code>. Ici, nous utilisons <code>SlurmClusterResolver()</code> pour dire à TensorFlow d'obtenir par Slurm l'information sur la tâche plutôt que d'assigner manuellement un nœud principal et des nœuds secondaires (''workers''), par exemple. Nous devons aussi ajouter <code>CommunicationImplementation.NCCL</code> à la stratégie de distribution pour indiquer que nous voulons utiliser la bibliothèque NCCL de NVIDIA pour les communications entre les GPU. Ceci n'était pas nécessairement le cas pour un nœud simple puisque NCCL se trouve par défaut avec <code>MirroredStrategy()</code>. | |||
{{File | |||
|name=tensorflow-multiworker.sh | |||
|lang="bash" | |||
|contents= | |||
#!/bin/bash | |||
#SBATCH --nodes 2 # Request 2 nodes so all resources are in two nodes. | |||
#SBATCH --gres=gpu:2 # Request 2 GPU "generic resources”. You will get 2 per node. | |||
#SBATCH --ntasks-per-node=2 # Request 1 process per GPU. You will get 1 CPU per process by default. Request more CPUs with the "cpus-per-task" parameter if your input pipeline can handle parallel data-loading/data-transforms | |||
#SBATCH --mem=8G | |||
#SBATCH --time=0-00:30 | |||
#SBATCH --output=%N-%j.out | |||
srun -N $SLURM_NNODES -n $SLURM_NNODES config_env.sh | |||
module load gcc/9.3.0 cuda/11.8 | |||
export NCCL_BLOCKING_WAIT=1 #Set this environment variable if you wish to use the NCCL backend for inter-GPU communication. | |||
export XLA_FLAGS=--xla_gpu_cuda_data_dir=$CUDA_HOME | |||
srun launch_training.sh | |||
}} | |||
où <code>config_env.sh</code> a la forme | |||
{{File | |||
|name=config_env.sh | |||
|lang="bash" | |||
|contents= | |||
#!/bin/bash | |||
module load python | |||
virtualenv --no-download $SLURM_TMPDIR/ENV | |||
source $SLURM_TMPDIR/ENV/bin/activate | |||
pip install --upgrade pip --no-index | |||
pip install --no-index tensorflow | |||
echo "Done installing virtualenv!" | |||
}} | |||
Le script <code>launch_training.sh</code> a la forme | |||
{{File | |||
|name=launch_training.sh | |||
|lang="bash" | |||
|contents= | |||
#!/bin/bash | |||
source $SLURM_TMPDIR/ENV/bin/activate | |||
python tensorflow-multiworker.py | |||
}} | |||
Le script Python <code>tensorflow-multiworker.py</code> a la forme suivante : | |||
{{File | |||
|name=tensorflow-multiworker.py | |||
|lang="python" | |||
|contents= | |||
import tensorflow as tf | |||
import numpy as np | |||
import argparse | |||
parser = argparse.ArgumentParser(description='cifar10 classification models, tensorflow MultiWorkerMirrored test') | |||
parser.add_argument('--lr', default=0.001, help='') | |||
parser.add_argument('--batch_size', type=int, default=256, help='') | |||
args = parser.parse_args() | |||
cluster_config = tf.distribute.cluster_resolver.SlurmClusterResolver() | |||
comm_options = tf.distribute.experimental.CommunicationOptions(implementation=tf.distribute.experimental.CommunicationImplementation.NCCL) | |||
strategy = tf.distribute.MultiWorkerMirroredStrategy(cluster_resolver=cluster_config, communication_options=comm_options) | |||
with strategy.scope(): | |||
model = tf.keras.Sequential() | |||
model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same', | |||
input_shape=(32,32,3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Conv2D(32, (3, 3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) | |||
model.add(tf.keras.layers.Dropout(0.25)) | |||
model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same')) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Conv2D(64, (3, 3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) | |||
model.add(tf.keras.layers.Dropout(0.25)) | |||
model.add(tf.keras.layers.Flatten()) | |||
model.add(tf.keras.layers.Dense(512)) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Dropout(0.5)) | |||
model.add(tf.keras.layers.Dense(10)) | |||
model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), | |||
optimizer=tf.keras.optimizers.SGD(learning_rate=args.lr),metrics=['accuracy']) | |||
### This next line will attempt to download the CIFAR10 dataset from the internet if you don't already have it stored in ~/.keras/datasets. | |||
### Run this line on a login node prior to submitting your job, or manually download the data from | |||
### https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz, rename to "cifar-10-batches-py.tar.gz" and place it under ~/.keras/datasets | |||
(x_train, y_train),_ = tf.keras.datasets.cifar10.load_data() | |||
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(args.batch_size) | |||
model.fit(dataset, epochs=2) | |||
}} | |||
====Horovod==== | |||
[https://horovod.readthedocs.io/en/latest/summary_include.html Horovod] est une bibliothèque d'apprentissage profond distribué pour TensorFlow, Keras, PyTorch et Apache MXNet. Nous reprenons le même tutoriel que ci-dessus, cette fois-ci avec Horovod. | |||
{{File | |||
|name=tensorflow-horovod.sh | |||
|lang="bash" | |||
|contents= | |||
#!/bin/bash | |||
#SBATCH --nodes 1 | |||
#SBATCH --gres=gpu:2 # Request 2 GPU "generic resources”. You will get 2 per node. | |||
#SBATCH --ntasks-per-node=2 # Request 1 process per GPU. You will get 1 CPU per process by default. Request more CPUs with the "cpus-per-task" parameter if your input pipeline can handle parallel data-loading/data-transforms | |||
#SBATCH --mem=8G | |||
#SBATCH --time=0-00:30 | |||
#SBATCH --output=%N-%j.out | |||
module load StdEnv/2020 | |||
module load python/3.8 | |||
virtualenv --no-download $SLURM_TMPDIR/env | |||
source $SLURM_TMPDIR/env/bin/activate | |||
pip install --no-index tensorflow==2.5.0 horovod | |||
export NCCL_BLOCKING_WAIT=1 #Set this environment variable if you wish to use the NCCL backend for inter-GPU communication. | |||
srun python tensorflow-horovod.py | |||
}} | |||
{{File | |||
|name=tensorflow-horovod.py | |||
|lang="python" | |||
|contents= | |||
import tensorflow as tf | |||
import numpy as np | |||
import horovod.tensorflow.keras as hvd | |||
import argparse | |||
parser = argparse.ArgumentParser(description='cifar10 classification models, tensorflow horovod test') | |||
parser.add_argument('--lr', default=0.001, help='') | |||
parser.add_argument('--batch_size', type=int, default=256, help='') | |||
args = parser.parse_args() | |||
hvd.init() | |||
gpus = tf.config.experimental.list_physical_devices('GPU') | |||
tf.config.experimental.set_visible_devices(gpus[hvd.local_rank()], 'GPU') | |||
model = tf.keras.Sequential() | |||
model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same', | |||
input_shape=(32,32,3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Conv2D(32, (3, 3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) | |||
model.add(tf.keras.layers.Dropout(0.25)) | |||
model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same')) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Conv2D(64, (3, 3))) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) | |||
model.add(tf.keras.layers.Dropout(0.25)) | |||
model.add(tf.keras.layers.Flatten()) | |||
model.add(tf.keras.layers.Dense(512)) | |||
model.add(tf.keras.layers.Activation('relu')) | |||
model.add(tf.keras.layers.Dropout(0.5)) | |||
model.add(tf.keras.layers.Dense(10)) | |||
optimizer = tf.keras.optimizers.SGD(learning_rate=args.lr) | |||
optimizer = hvd.DistributedOptimizer(optimizer) | |||
model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True), | |||
optimizer=optimizer,metrics=['accuracy']) | |||
callbacks = [ | |||
hvd.callbacks.BroadcastGlobalVariablesCallback(0), | |||
] | |||
### This next line will attempt to download the CIFAR10 dataset from the internet if you don't already have it stored in ~/.keras/datasets. | |||
### Run this line on a login node prior to submitting your job, or manually download the data from | |||
### https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz, rename to "cifar-10-batches-py.tar.gz" and place it under ~/.keras/datasets | |||
(x_train, y_train),_ = tf.keras.datasets.cifar10.load_data() | |||
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(args.batch_size) | |||
model.fit(dataset, epochs=2, callbacks=callbacks, verbose=2) # verbose=2 to avoid printing a progress bar to *.out files. | |||
}} | |||
==Créer des points de contrôle== | |||
Peu importe le temps que dure l'exécution de votre code, une bonne habitude à prendre est de créer des points de contrôle pendant l'entraînement. Un point de contrôle vous donne le portrait de votre modèle à un moment précis du processus d'entraînement (après un certain nombre d'itérations ou d'époques); le portrait est enregistré sur disque et vous pourrez le récupérer par la suite. Ceci est pratique pour diviser en petites tâches une tâche qui doit avoir un long temps d'exécution, ce qui pourrait faire qu'elles soient être allouées plus rapidement à une grappe. C'est aussi un bon moyen d'éviter de perdre votre travail en cas d'erreurs inattendues ou de panne du matériel. | |||
===Avec Keras=== | |||
Pour créer un point de contrôle dans un entraînement avec <code>keras</code>, nous recommandons le paramètre <code>callbacks</code> de la méthode <code>model.fit()</code>. Dans l'exemple suivant, nous demandons à TensorFlow de créer un point de contrôle à la fin de chacune des époques d'entraînement. | |||
callbacks = [tf.keras.callbacks.ModelCheckpoint(filepath="./ckpt",save_freq="epoch")] # Make sure the path where you want to create the checkpoint exists | |||
model.fit(dataset, epochs=10 , callbacks=callbacks) | |||
Pour plus d'information, consultez la [https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/ModelCheckpoint documentation officielle de TensorFlow]. | |||
===Avec une boucle d'entraînement personnalisée=== | |||
Voyez la [https://www.tensorflow.org/guide/checkpoint#writing_checkpoints documentation officielle de TensorFlow]. | |||
==Opérateurs personnalisés== | |||
Dans le cadre de votre recherche, vous pourriez avoir besoin d'utiliser [https://github.com/Yang7879/3D-BoNet du code pour tirer avantage des opérateurs personnalisés] qui ne sont pas inclus dans les distributions de TensorFlow, ou même vouloir [https://www.tensorflow.org/guide/create_op créer vos propres opérateurs personnalisés]. Dans les deux cas, vos opérateurs personnalisés doivent être compilés avant que vous soumettiez la tâche. Suivez les étapes ci-dessous. | |||
Créez d'abord un [[Python/fr|environnement virtuel Python]] et installez une version de TensorFlow compatible avec vos opérateurs personnalisés. Allez ensuite au répertoire qui contient le code source des opérateurs et utilisez les commandes qui suivent selon la version que vous avez installée. | |||
===TensorFlow <= 1.4.x=== | |||
Si votre opérateur personnalisé <b>peut prendre en charge</b> un GPU : | |||
{{Commands | |||
| module load cuda/<version> | |||
| nvcc <operator>.cu -o <operator>.cu.o -c -O2 -DGOOGLE_CUDA{{=}}1 -x cu -Xcompiler -fPI | |||
| g++ -std{{=}}c++11 <operator>.cpp <operator>.cu.o -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I/<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public -I /usr/local/cuda-<version>/include -lcudart -L /usr/local/cuda-<version>/lib64/ | |||
}} | |||
Si votre opérateur personnalisé <b>ne peut pas prendre en charge</b> un GPU : | |||
{{Commands | |||
| g++ -std{{=}}c++11 <operator>.cpp -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I/<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public | |||
}} | |||
===TensorFlow > 1.4.x=== | |||
Si votre opérateur personnalisé <b>peut prendre en charge</b> un GPU : | |||
{{Commands | |||
| module load cuda/<version> | |||
|nvcc <operator>.cu -o <operator>.cu.o -c -O2 -DGOOGLE_CUDA{{=}}1 -x cu -Xcompiler -fPI | |||
|g++ -std{{=}}c++11 <operator>.cpp <operator>.cu.o -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I /usr/local/cuda-<version>/include -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-<version>/lib64/ -L /<path to python virtual env>/lib/python<version>/site-packages/tensorflow -ltensorflow_framework | |||
}} | |||
Si votre opérateur personnalisé <b>ne peut pas prendre en charge</b> un GPU : | |||
{{Commands | |||
|g++ -std{{=}}c++11 <operator>.cpp -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public -L /<path to python virtual env>/lib/python<version>/site-packages/tensorflow -ltensorflow_framework | |||
}} | |||
==Dépannage== | ==Dépannage== | ||
Line 270: | Line 627: | ||
<code>OMP: Error #15: Initializing libiomp5.so, but found libiomp5.so already initialized. </code> | <code>OMP: Error #15: Initializing libiomp5.so, but found libiomp5.so already initialized. </code> | ||
Ceci se produit quand la bibliothèque TensorFlow essaie de charger une version de OMP incompatible avec la version. | Ceci se produit quand la bibliothèque TensorFlow essaie de charger une version de OMP incompatible avec la version du système. Pour contourner ceci : | ||
{{Commands| | {{Commands|prompt=(tf_skimage_venv) name@server $ | ||
prompt=(tf_skimage_venv) name@server $ | |||
|cd tf_skimage_venv | |cd tf_skimage_venv | ||
|export LIBIOMP_PATH{{=}}$(strace python -c 'from skimage.transform import AffineTransform' 2>&1 {{!}} grep -v ENOENT {{!}} grep -ohP -e '(?<{{=}}")[^"]+libiomp5.so(?{{=}}")' {{!}} xargs realpath) | |export LIBIOMP_PATH{{=}}$(strace python -c 'from skimage.transform import AffineTransform' 2>&1 {{!}} grep -v ENOENT {{!}} grep -ohP -e '(?<{{=}}")[^"]+libiomp5.so(?{{=}}")' {{!}} xargs realpath) | ||
|find -path '*_solib_local*' -name libiomp5.so -exec ln -sf $LIBIOMP_PATH {} \; | |find -path '*_solib_local*' -name libiomp5.so -exec ln -sf $LIBIOMP_PATH {} \; | ||
}} | }} | ||
L'installation de la bibliothèque TensorFlow pourra alors utiliser libiomp5.so. | |||
===libcupti.so=== | ===libcupti.so=== | ||
Line 299: | Line 656: | ||
Pour solutionner ces erreurs, accédez au répertoire indiqué dans le message (soit <code>[...]/_U@mkl_Ulinux_S_S_Cmkl_Ulibs_Ulinux___Uexternal_Smkl_Ulinux_Slib</code>) et lancez la commande | Pour solutionner ces erreurs, accédez au répertoire indiqué dans le message (soit <code>[...]/_U@mkl_Ulinux_S_S_Cmkl_Ulibs_Ulinux___Uexternal_Smkl_Ulinux_Slib</code>) et lancez la commande | ||
{{Command| prompt=[name@server ...Ulinux_Slib] $ |ln -sf $(cat libiomp5.so) libiomp5.so}} | {{Command|prompt=[name@server ...Ulinux_Slib] $ |ln -sf $(cat libiomp5.so) libiomp5.so}} | ||
Le fichier texte sera remplacé par le bon lien symbolique. | Le fichier texte sera remplacé par le bon lien symbolique. | ||
==Contrôle du nombre de CPU et de fils== | |||
===TensorFlow 1.x=== | |||
Les paramètres de configuration <code>device_count</code>, <code>intra_op_parallelism_threads</code> et <code>inter_op_parallelism_threads</code> ont un effet sur le nombre de fils utilisés par TensorFlow; ces paramètres peuvent être définis comme suit à l'instanciation d'une session : | |||
tf.Session(config=tf.ConfigProto(device_count={'CPU': num_cpus}, intra_op_parallelism_threads=num_intra_threads, inter_op_parallelism_threads=num_inter_threads)) | |||
Si par exemple vous voulez exécuter plusieurs instances en parallèle dans un seul nœud, vous pourriez devoir utiliser de plus petites valeurs et même diminuer jusqu'à <code>1</code>. | |||
===TensorFlow 2.x=== | |||
Puisque les sessions ne sont plus utilisées, la configuration des fils se fait comme suit : | |||
tf.config.threading.set_inter_op_parallelism_threads(num_threads) | |||
tf.config.threading.set_intra_op_parallelism_threads(num_threads) | |||
Il ne semble pas possible de définir un nombre de CPU depuis la version 2.1. | |||
==Problèmes connus== | |||
Un bogue s'est introduit dans l'implémentation Keras de Tensorflow après la version 2.8.3. Il affecte la performance des layers d'augmentation des données <i>tf.keras.layers.Random</i> (comme <i>tf.keras.layers.RandomRotation</i>, <i>tf.keras.layers.RandomTranslation</i>, etc.). Le processus d'entraînement est ralenti d'un facteur de plus de 100. <b>Ce bogue a été corrigé dans la version 2.12. |
Latest revision as of 19:36, 15 July 2024
TensorFlow est une bibliothèque logicielle open source d'apprentissage machine.
Si vous voulez porter un programme TensorFlow sur une de nos grappes, nous vous recommandons de prendre connaissance du tutoriel sur l'apprentissage machine.
Installation
Les directives suivantes servent à installer TensorFlow dans votre répertoire home à l'aide des (wheels Python ) qui se trouvent dans /cvmfs/soft.computecanada.ca/custom/python/wheelhouse/
.
Le wheel TensorFlow sera installé dans un environnement virtuel Python avec la commande pip
.
Chargez les modules requis par TensorFlow; dans certains cas, d'autres modules pourraient être requis (par exemple CUDA).
[name@server ~]$ module load python/3
Créez un nouvel environnement Python.
[name@server ~]$ virtualenv --no-download tensorflow
Activez le nouvel environnement.
[name@server ~]$ source tensorflow/bin/activate
Installez TensorFlow dans votre nouvel environnement virtuel en utilisant la commande suivante.
(tensorflow) [name@server ~]$ pip install --no-index tensorflow==2.8
Chargez les modules requis par TensorFlow. TF 1.x requiert StdEnv/2018.
Remarque : TF 1.x n'est pas disponible sur Narval, puisque cette grappe n'offre pas StdEnv/2018.
[name@server ~]$ module load StdEnv/2018 python/3
Créez un nouvel environnement Python.
[name@server ~]$ virtualenv --no-download tensorflow
Activez le nouvel environnement.
[name@server ~]$ source tensorflow/bin/activate
Installez TensorFlow dans votre nouvel environnement virtuel en utilisant une des commandes suivantes, dépendant de si vous avez besoin d'utiliser un GPU.
N'installez pas le paquet tensorflow
sans le suffixe _cpu
ou _gpu
car il existe des problèmes de compatibilité avec d'autres bibliothèques.
CPU seulement
(tensorflow) [name@server ~]$ pip install --no-index tensorflow_cpu==1.15.0
GPU
(tensorflow) [name@server ~]$ pip install --no-index tensorflow_gpu==1.15.0
Le paquet R
Pour utiliser TensorFlow en R, suivez les directives données ci-dessus pour créer un environnement virtuel et y installer TensorFlow. Suivez ensuite cette procédure ː
Chargez les modules requis.
[name@server ~]$ module load gcc r
Activez votre environnement virtuel Python.
[name@server ~]$ source tensorflow/bin/activate
Lancez R.
(tensorflow)_[name@server ~]$ R
En R, installez le paquet devtools, puis TensorFlow.
install.packages('devtools', repos='https://cloud.r-project.org')
devtools::install_github('rstudio/tensorflow')
Vous pouvez maintenant procéder. N'appelez pas install_tensorflow()
en R puisque TensorFlow est déjà installé dans votre environnement virtuel avec pip
. Pour utiliser TensorFlow tel qu'installé dans votre environnement virtuel, entrez les commandes suivantes en R, après que l'environnement est activé.
library(tensorflow)
use_virtualenv(Sys.getenv('VIRTUAL_ENV'))
Soumettre une tâche TensorFlow avec un GPU
Soumettez une tâche TensorFlow ainsi
[name@server ~]$ sbatch tensorflow-test.sh
Le script contient
#!/bin/bash
#SBATCH --gres=gpu:1 # request GPU "generic resource"
#SBATCH --cpus-per-task=6 # maximum CPU cores per GPU request: 6 on Cedar, 16 on Graham.
#SBATCH --mem=32000M # memory per node
#SBATCH --time=0-03:00 # time (DD-HH:MM)
#SBATCH --output=%N-%j.out # %N for node name, %j for jobID
module load cuda cudnn
source tensorflow/bin/activate
python ./tensorflow-test.py
Le script Python se lit
import tensorflow as tf
node1 = tf.constant(3.0)
node2 = tf.constant(4.0)
print(node1, node2)
print(node1 + node2)
import tensorflow as tf
node1 = tf.constant(3.0)
node2 = tf.constant(4.0)
print(node1, node2)
sess = tf.Session()
print(sess.run(node1 + node2))
Une fois la tâche terminée, ce qui devrait nécessiter moins d'une minute, un fichier de sortie avec un nom semblable à cdr116-122907.out
devrait être généré. Le contenu de ce fichier serait similaire à ce qui suit; il s'agit d'exemples de messages TensorFlow et il est possible que vous en ayez d'autres.
2017-07-10 12:35:19.491097: I tensorflow/core/common_runtime/gpu/gpu_device.cc:961] DMA: 0
2017-07-10 12:35:19.491156: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0: Y
2017-07-10 12:35:19.520737: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-PCIE-12GB, pci bus id: 0000:82:00.0)
tf.Tensor(3.0, shape=(), dtype=float32) tf.Tensor(4.0, shape=(), dtype=float32)
tf.Tensor(7.0, shape=(), dtype=float32)
2017-07-10 12:35:19.491097: I tensorflow/core/common_runtime/gpu/gpu_device.cc:961] DMA: 0
2017-07-10 12:35:19.491156: I tensorflow/core/common_runtime/gpu/gpu_device.cc:971] 0: Y
2017-07-10 12:35:19.520737: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1030] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-PCIE-12GB, pci bus id: 0000:82:00.0)
Tensor("Const:0", shape=(), dtype=float32) Tensor("Const_1:0", shape=(), dtype=float32)
7.0
TensorFlow fonctionne sur tous les types de nœuds GPU. Pour la recherche de grande envergure en apprentissage profond ou en apprentissage machine, il est fortement recommandé d'utiliser le type de nœuds GPU large de Cedar. Ces nœuds sont équipés de 4 x P100-PCIE-16Go avec GPUDirect P2P entre chaque paire. Pour plus d'information, consultez cette page.
Suivi
Il est possible de se connecter à un nœud sur lequel une tâche est en cours pour y exécuter des processus. On peut ainsi faire le suivi des ressources utilisées par TensorFlow et visualiser le déroulement de l'entraînement. Pour des exemples, consultez Surveillance d'une tâche en cours.
TensorBoard
TensorFlow propose la suite d'outils de visualisation TensorBoard qui lit les événements TensorFlow et modélise les fichiers. Pour savoir comment créer ces fichiers, consultez TensorBoard tutorial on summaries.
Sachez toutefois que TensorBoard exige trop de puissance de calcul pour être exécuté sur un nœud de connexion. Nous vous recommandons de l'exécuter dans la même tâche que le processus TensorFlow. Pour ce faire, lancez TensorBoard en arrière-plan en l'appelant avant le script Python, en y ajoutant le caractère (&
).
# Your SBATCH arguments here tensorboard --logdir=/tmp/your_log_dir --host 0.0.0.0 --load_fast false & python train.py # example
Pour accéder TensorBoard avec un fureteur une fois que la tâche est en cours, il faut créer un lien entre votre ordinateur et le nœud sur lequel TensorFlow et TensorBoard sont exécutés. Pour ce faire, vous avez besoin du hostname du nœud de calcul sur lequel le serveur TensorFlow se trouve. Pour le trouver, faites afficher la liste de vos tâches avec la commande sq
et repérez la tâche; le hostname est la valeur qui se trouve dans la colonne NODELIST.
Pour créer la connexion, lancez la commande sur votre ordinateur local.
[name@my_computer ~]$ ssh -N -f -L localhost:6006:computenode:6006 userid@cluster.computecanada.ca
Remplacez computenode
par le hostname obtenu à l'étape précédente; userid
par votre nom d'utilisateur de l'Alliance et; cluster
par le hostname de la grappe, soit beluga
, cedar
, graham
, etc. Si le port 6006 était déjà utilisé, tensorboard va en utiliser un autre (p. ex. 6007, 6008...).
Une fois que la connexion est établie, allez à http://localhost:6006.
Utiliser plusieurs GPU
TensorFlow 1.x
Il existe plusieurs méthodes de gestion des variables, les plus communes étant Parameter Server et Replicated.
- Nous allons utiliser ce code pour illustrer les diverses méthodes; vous pouvez l'adapter à vos besoins spécifiques.
Parameter Server
La copie maîtresse des variables est enregistrée sur un serveur de paramètres. En entraînement distribué, les serveurs de paramètres sont des processus distincts dans chacun des appareils. À chaque étape, chacune des tours obtient du serveur de paramètres une copie des variables et y retourne ses gradients.
Les paramètres peuvent être enregistrés sur un CPU
python tf_cnn_benchmarks.py --variable_update=parameter_server --local_parameter_device=cpu
ou sur un GPU
python tf_cnn_benchmarks.py --variable_update=parameter_server --local_parameter_device=gpu
Replicated
Chaque GPU possède sa propre copie des variables. Les gradients sont copiés sur toutes les tours par agrégation du contenu des appareils ou par un algorithme all reduce (dépendant de la valeur du paramètre all_reduce_spec).
Avec la méthode all reduce par défaut ː
python tf_cnn_benchmarks.py --variable_update=replicated
Xring --- utilisez un global ring reduction pour tous les tenseurs :
python tf_cnn_benchmarks.py --variable_update=replicated --all_reduce_spec=xring
Pscpu --- utilisez CPU at worker 0 pour réduire tous les tenseurs :
python tf_cnn_benchmarks.py --variable_update=replicated --all_reduce_spec=pscpu
NCCL --- utilisez NCCL pour réduire localement tous les tenseurs :
python tf_cnn_benchmarks.py --variable_update=replicated --all_reduce_spec=nccl
Les méthodes se comportent différemment selon les modèles; nous vous recommandons fortement de tester vos modèles avec toutes les méthodes sur les différents types de nœuds GPU.
Étalonnage (benchmarks)
Les résultats ont été obtenus avec TensorFlow v1.5 (CUDA9 et cuDNN 7) sur Graham et Cedar avec un seul GPU et plusieurs GPU et des méthodes différentes de gestion des variables; voyez TensorFlow Benchmarks.
- ResNet-50
Lots de 32 par GPU et parallélisation des données (les résultats sont en images par seconde).
Type de nœud | 1 GPU | Nombre de GPU | ps,cpu | ps, gpu | répliqué | répliqué, xring | répliqué, pscpu | répliqué, nccl |
---|---|---|---|---|---|---|---|---|
Graham, GPU base | 171.23 | 2 | 93.31 | 324.04 | 318.33 | 316.01 | 109.82 | 315.99 |
Cedar GPU Base | 172.99 | 4 | 662.65 | 595.43 | 616.02 | 490.03 | 645.04 | 608.95 |
Cedar, GPU Large | 205.71 | 4 | 673.47 | 721.98 | 754.35 | 574.91 | 664.72 | 692.25 |
- VGG-16
Lots de 32 par GPU et parallélisation des données (les résultats sont en images par seconde).
Type de nœud | 1 GPU | Nombre de GPU | ps,cpu | ps, gpu | répliqué | répliqué, xring | répliqué, pscpu | répliqué, nccl |
---|---|---|---|---|---|---|---|---|
Graham, GPU Base | 115.89 | 2 | 91.29 | 194.46 | 194.43 | 203.83 | 132.19 | 219.72 |
Cedar, GPU Base | 114.77 | 4 | 232.85 | 280.69 | 274.41 | 341.29 | 330.04 | 388.53 |
Cedar, GPU Large | 137.16 | 4 | 175.20 | 379.80 | 336.72 | 417.46 | 225.37 | 490.52 |
TensorFlow 2.x
À l'instar de TensorFlow 1.x, TensorFlow 2.x offre des stratégies différentes pour utiliser plusieurs GPU avec l'API de haut niveau tf.distribute
. Dans les sections qui suivent, nous montrons des exemples de code pour chacune des stratégies avec Keras. Pour plus d'information, consultez la documentation officielle de TensorFlow.
Stratégie miroir
Nœud unique
#!/bin/bash
#SBATCH --nodes 1
#SBATCH --gres=gpu:4
#SBATCH --mem=8G
#SBATCH --time=0-00:30
#SBATCH --output=%N-%j.out
module load python/3
virtualenv --no-download $SLURM_TMPDIR/env
source $SLURM_TMPDIR/env/bin/activate
pip install --no-index tensorflow
export NCCL_BLOCKING_WAIT=1 #Set this environment variable if you wish to use the NCCL backend for inter-GPU communication.
python tensorflow-singleworker.py
Le script Python tensorflow-singleworker.py
a le format
import tensorflow as tf
import numpy as np
import argparse
parser = argparse.ArgumentParser(description='cifar10 classification models, tensorflow MirroredStrategy test')
parser.add_argument('--lr', default=0.001, help='')
parser.add_argument('--batch_size', type=int, default=256, help='')
args = parser.parse_args()
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same',
input_shape=(32,32,3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(32, (3, 3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same'))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(64, (3, 3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(512))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(10))
model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=tf.keras.optimizers.SGD(learning_rate=args.lr),metrics=['accuracy'])
### This next line will attempt to download the CIFAR10 dataset from the internet if you don't already have it stored in ~/.keras/datasets.
### Run this line on a login node prior to submitting your job, or manually download the data from
### https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz, rename to "cifar-10-batches-py.tar.gz" and place it under ~/.keras/datasets
(x_train, y_train),_ = tf.keras.datasets.cifar10.load_data()
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(args.batch_size)
model.fit(dataset, epochs=2)
Nœuds multiples
La syntaxe pour utiliser des GPU distribués sur plusieurs nœuds ressemble beaucoup au cas du nœud simple; la différence principale est l'emploi de MultiWorkerMirroredStrategy()
. Ici, nous utilisons SlurmClusterResolver()
pour dire à TensorFlow d'obtenir par Slurm l'information sur la tâche plutôt que d'assigner manuellement un nœud principal et des nœuds secondaires (workers), par exemple. Nous devons aussi ajouter CommunicationImplementation.NCCL
à la stratégie de distribution pour indiquer que nous voulons utiliser la bibliothèque NCCL de NVIDIA pour les communications entre les GPU. Ceci n'était pas nécessairement le cas pour un nœud simple puisque NCCL se trouve par défaut avec MirroredStrategy()
.
#!/bin/bash
#SBATCH --nodes 2 # Request 2 nodes so all resources are in two nodes.
#SBATCH --gres=gpu:2 # Request 2 GPU "generic resources”. You will get 2 per node.
#SBATCH --ntasks-per-node=2 # Request 1 process per GPU. You will get 1 CPU per process by default. Request more CPUs with the "cpus-per-task" parameter if your input pipeline can handle parallel data-loading/data-transforms
#SBATCH --mem=8G
#SBATCH --time=0-00:30
#SBATCH --output=%N-%j.out
srun -N $SLURM_NNODES -n $SLURM_NNODES config_env.sh
module load gcc/9.3.0 cuda/11.8
export NCCL_BLOCKING_WAIT=1 #Set this environment variable if you wish to use the NCCL backend for inter-GPU communication.
export XLA_FLAGS=--xla_gpu_cuda_data_dir=$CUDA_HOME
srun launch_training.sh
où config_env.sh
a la forme
#!/bin/bash
module load python
virtualenv --no-download $SLURM_TMPDIR/ENV
source $SLURM_TMPDIR/ENV/bin/activate
pip install --upgrade pip --no-index
pip install --no-index tensorflow
echo "Done installing virtualenv!"
Le script launch_training.sh
a la forme
#!/bin/bash
source $SLURM_TMPDIR/ENV/bin/activate
python tensorflow-multiworker.py
Le script Python tensorflow-multiworker.py
a la forme suivante :
import tensorflow as tf
import numpy as np
import argparse
parser = argparse.ArgumentParser(description='cifar10 classification models, tensorflow MultiWorkerMirrored test')
parser.add_argument('--lr', default=0.001, help='')
parser.add_argument('--batch_size', type=int, default=256, help='')
args = parser.parse_args()
cluster_config = tf.distribute.cluster_resolver.SlurmClusterResolver()
comm_options = tf.distribute.experimental.CommunicationOptions(implementation=tf.distribute.experimental.CommunicationImplementation.NCCL)
strategy = tf.distribute.MultiWorkerMirroredStrategy(cluster_resolver=cluster_config, communication_options=comm_options)
with strategy.scope():
model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same',
input_shape=(32,32,3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(32, (3, 3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same'))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(64, (3, 3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(512))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(10))
model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=tf.keras.optimizers.SGD(learning_rate=args.lr),metrics=['accuracy'])
### This next line will attempt to download the CIFAR10 dataset from the internet if you don't already have it stored in ~/.keras/datasets.
### Run this line on a login node prior to submitting your job, or manually download the data from
### https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz, rename to "cifar-10-batches-py.tar.gz" and place it under ~/.keras/datasets
(x_train, y_train),_ = tf.keras.datasets.cifar10.load_data()
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(args.batch_size)
model.fit(dataset, epochs=2)
Horovod
Horovod est une bibliothèque d'apprentissage profond distribué pour TensorFlow, Keras, PyTorch et Apache MXNet. Nous reprenons le même tutoriel que ci-dessus, cette fois-ci avec Horovod.
#!/bin/bash
#SBATCH --nodes 1
#SBATCH --gres=gpu:2 # Request 2 GPU "generic resources”. You will get 2 per node.
#SBATCH --ntasks-per-node=2 # Request 1 process per GPU. You will get 1 CPU per process by default. Request more CPUs with the "cpus-per-task" parameter if your input pipeline can handle parallel data-loading/data-transforms
#SBATCH --mem=8G
#SBATCH --time=0-00:30
#SBATCH --output=%N-%j.out
module load StdEnv/2020
module load python/3.8
virtualenv --no-download $SLURM_TMPDIR/env
source $SLURM_TMPDIR/env/bin/activate
pip install --no-index tensorflow==2.5.0 horovod
export NCCL_BLOCKING_WAIT=1 #Set this environment variable if you wish to use the NCCL backend for inter-GPU communication.
srun python tensorflow-horovod.py
import tensorflow as tf
import numpy as np
import horovod.tensorflow.keras as hvd
import argparse
parser = argparse.ArgumentParser(description='cifar10 classification models, tensorflow horovod test')
parser.add_argument('--lr', default=0.001, help='')
parser.add_argument('--batch_size', type=int, default=256, help='')
args = parser.parse_args()
hvd.init()
gpus = tf.config.experimental.list_physical_devices('GPU')
tf.config.experimental.set_visible_devices(gpus[hvd.local_rank()], 'GPU')
model = tf.keras.Sequential()
model.add(tf.keras.layers.Conv2D(32, (3, 3), padding='same',
input_shape=(32,32,3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(32, (3, 3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Conv2D(64, (3, 3), padding='same'))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Conv2D(64, (3, 3)))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2)))
model.add(tf.keras.layers.Dropout(0.25))
model.add(tf.keras.layers.Flatten())
model.add(tf.keras.layers.Dense(512))
model.add(tf.keras.layers.Activation('relu'))
model.add(tf.keras.layers.Dropout(0.5))
model.add(tf.keras.layers.Dense(10))
optimizer = tf.keras.optimizers.SGD(learning_rate=args.lr)
optimizer = hvd.DistributedOptimizer(optimizer)
model.compile(loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
optimizer=optimizer,metrics=['accuracy'])
callbacks = [
hvd.callbacks.BroadcastGlobalVariablesCallback(0),
]
### This next line will attempt to download the CIFAR10 dataset from the internet if you don't already have it stored in ~/.keras/datasets.
### Run this line on a login node prior to submitting your job, or manually download the data from
### https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz, rename to "cifar-10-batches-py.tar.gz" and place it under ~/.keras/datasets
(x_train, y_train),_ = tf.keras.datasets.cifar10.load_data()
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(args.batch_size)
model.fit(dataset, epochs=2, callbacks=callbacks, verbose=2) # verbose=2 to avoid printing a progress bar to *.out files.
Créer des points de contrôle
Peu importe le temps que dure l'exécution de votre code, une bonne habitude à prendre est de créer des points de contrôle pendant l'entraînement. Un point de contrôle vous donne le portrait de votre modèle à un moment précis du processus d'entraînement (après un certain nombre d'itérations ou d'époques); le portrait est enregistré sur disque et vous pourrez le récupérer par la suite. Ceci est pratique pour diviser en petites tâches une tâche qui doit avoir un long temps d'exécution, ce qui pourrait faire qu'elles soient être allouées plus rapidement à une grappe. C'est aussi un bon moyen d'éviter de perdre votre travail en cas d'erreurs inattendues ou de panne du matériel.
Avec Keras
Pour créer un point de contrôle dans un entraînement avec keras
, nous recommandons le paramètre callbacks
de la méthode model.fit()
. Dans l'exemple suivant, nous demandons à TensorFlow de créer un point de contrôle à la fin de chacune des époques d'entraînement.
callbacks = [tf.keras.callbacks.ModelCheckpoint(filepath="./ckpt",save_freq="epoch")] # Make sure the path where you want to create the checkpoint exists model.fit(dataset, epochs=10 , callbacks=callbacks)
Pour plus d'information, consultez la documentation officielle de TensorFlow.
Avec une boucle d'entraînement personnalisée
Voyez la documentation officielle de TensorFlow.
Opérateurs personnalisés
Dans le cadre de votre recherche, vous pourriez avoir besoin d'utiliser du code pour tirer avantage des opérateurs personnalisés qui ne sont pas inclus dans les distributions de TensorFlow, ou même vouloir créer vos propres opérateurs personnalisés. Dans les deux cas, vos opérateurs personnalisés doivent être compilés avant que vous soumettiez la tâche. Suivez les étapes ci-dessous.
Créez d'abord un environnement virtuel Python et installez une version de TensorFlow compatible avec vos opérateurs personnalisés. Allez ensuite au répertoire qui contient le code source des opérateurs et utilisez les commandes qui suivent selon la version que vous avez installée.
TensorFlow <= 1.4.x
Si votre opérateur personnalisé peut prendre en charge un GPU :
[name@server ~]$ module load cuda/<version>
[name@server ~]$ nvcc <operator>.cu -o <operator>.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPI
[name@server ~]$ g++ -std=c++11 <operator>.cpp <operator>.cu.o -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I/<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public -I /usr/local/cuda-<version>/include -lcudart -L /usr/local/cuda-<version>/lib64/
Si votre opérateur personnalisé ne peut pas prendre en charge un GPU :
[name@server ~]$ g++ -std=c++11 <operator>.cpp -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I/<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public
TensorFlow > 1.4.x
Si votre opérateur personnalisé peut prendre en charge un GPU :
[name@server ~]$ module load cuda/<version>
[name@server ~]$ nvcc <operator>.cu -o <operator>.cu.o -c -O2 -DGOOGLE_CUDA=1 -x cu -Xcompiler -fPI
[name@server ~]$ g++ -std=c++11 <operator>.cpp <operator>.cu.o -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I /usr/local/cuda-<version>/include -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public -lcudart -L /usr/local/cuda-<version>/lib64/ -L /<path to python virtual env>/lib/python<version>/site-packages/tensorflow -ltensorflow_framework
Si votre opérateur personnalisé ne peut pas prendre en charge un GPU :
[name@server ~]$ g++ -std=c++11 <operator>.cpp -o <operator>.so -shared -fPIC -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include -I /<path to python virtual env>/lib/python<version>/site-packages/tensorflow/include/external/nsync/public -L /<path to python virtual env>/lib/python<version>/site-packages/tensorflow -ltensorflow_framework
Dépannage
scikit-image
Si vous utilisez la bibliothèque scikit-image, vous pourriez recevoir l'erreur
OMP: Error #15: Initializing libiomp5.so, but found libiomp5.so already initialized.
Ceci se produit quand la bibliothèque TensorFlow essaie de charger une version de OMP incompatible avec la version du système. Pour contourner ceci :
(tf_skimage_venv) name@server $ cd tf_skimage_venv
(tf_skimage_venv) name@server $ export LIBIOMP_PATH=$(strace python -c 'from skimage.transform import AffineTransform' 2>&1 | grep -v ENOENT | grep -ohP -e '(?<=")[^"]+libiomp5.so(?=")' | xargs realpath)
(tf_skimage_venv) name@server $ find -path '*_solib_local*' -name libiomp5.so -exec ln -sf $LIBIOMP_PATH {} \;
L'installation de la bibliothèque TensorFlow pourra alors utiliser libiomp5.so.
libcupti.so
Certaines fonctions de suivi de TensorFlow utilisent la bibliothèque libcupti.so; si cette dernière n'est pas disponible, l'erreur suivante pourrait survenir :
I tensorflow/stream_executor/dso_loader.cc:142] Couldn't open CUDA library libcupti.so.9.0. LD_LIBRARY_PATH: /usr/local/cuda-9.0/lib64
La solution est d'exécuter les commandes suivantes avant l'exécution du script.
[name@server ~]$ module load cuda/9.0.xxx
[name@server ~]$ export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CUDA_HOME/extras/CUPTI/lib64/
Remplacez xxx par la version appropriée de CUDA que vous pouvez trouver avec module av cuda
.
libiomp5.so invalid ELF header
Le fichier objet partagé libiomp5.so
est quelquefois par erreur installé en tant que fichier texte, ce qui peut produire des erreurs comme ceci :
/home/username/venv/lib/python3.6/site-packages/tensorflow/python/../../_solib_local/_U@mkl_Ulinux_S_S_Cmkl_Ulibs_Ulinux___Uexternal_Smkl_Ulinux_Slib/libiomp5.so: invalid ELF header
Pour solutionner ces erreurs, accédez au répertoire indiqué dans le message (soit [...]/_U@mkl_Ulinux_S_S_Cmkl_Ulibs_Ulinux___Uexternal_Smkl_Ulinux_Slib
) et lancez la commande
[name@server ...Ulinux_Slib] $ ln -sf $(cat libiomp5.so) libiomp5.so
Le fichier texte sera remplacé par le bon lien symbolique.
Contrôle du nombre de CPU et de fils
TensorFlow 1.x
Les paramètres de configuration device_count
, intra_op_parallelism_threads
et inter_op_parallelism_threads
ont un effet sur le nombre de fils utilisés par TensorFlow; ces paramètres peuvent être définis comme suit à l'instanciation d'une session :
tf.Session(config=tf.ConfigProto(device_count={'CPU': num_cpus}, intra_op_parallelism_threads=num_intra_threads, inter_op_parallelism_threads=num_inter_threads))
Si par exemple vous voulez exécuter plusieurs instances en parallèle dans un seul nœud, vous pourriez devoir utiliser de plus petites valeurs et même diminuer jusqu'à 1
.
TensorFlow 2.x
Puisque les sessions ne sont plus utilisées, la configuration des fils se fait comme suit :
tf.config.threading.set_inter_op_parallelism_threads(num_threads) tf.config.threading.set_intra_op_parallelism_threads(num_threads)
Il ne semble pas possible de définir un nombre de CPU depuis la version 2.1.
Problèmes connus
Un bogue s'est introduit dans l'implémentation Keras de Tensorflow après la version 2.8.3. Il affecte la performance des layers d'augmentation des données tf.keras.layers.Random (comme tf.keras.layers.RandomRotation, tf.keras.layers.RandomTranslation, etc.). Le processus d'entraînement est ralenti d'un facteur de plus de 100. Ce bogue a été corrigé dans la version 2.12.