Aperçu
Ce guide vous montrera comment utiliser TensorFlow Profiler avec TensorBoard pour mieux comprendre et tirer le maximum de performances de vos GPU, et déboguer lorsqu'un ou plusieurs de vos GPU sont sous-utilisés.
Si vous êtes nouveau sur le profileur :
- Premiers pas avec TensorFlow Profiler : bloc-notes sur les performances du modèle de profil avec un exemple Keras et TensorBoard .
- Découvrez les différents outils et méthodes de profilage disponibles pour optimiser les performances de TensorFlow sur l'hôte (CPU) avec le guide Optimiser les performances de TensorFlow à l'aide du profileur .
Gardez à l'esprit que le déchargement des calculs sur le GPU peut ne pas toujours être bénéfique, en particulier pour les petits modèles. Il peut y avoir des frais généraux dus à :
- Transfert de données entre l'hôte (CPU) et l'appareil (GPU); et
- En raison de la latence impliquée lorsque l'hôte lance les noyaux GPU.
Workflow d'optimisation des performances
Ce guide explique comment déboguer les problèmes de performances en commençant par un seul GPU, puis en passant à un seul hôte avec plusieurs GPU.
Il est recommandé de déboguer les problèmes de performances dans l'ordre suivant :
- Optimisez et déboguez les performances sur un GPU :
- Vérifiez si le pipeline d'entrée est un goulot d'étranglement.
- Déboguer les performances d'un GPU.
- Activer la précision mixte (avec
fp16
(float16)) et éventuellement activer XLA .
- Optimisez et déboguez les performances sur l'hôte unique multi-GPU.
Par exemple, si vous utilisez une stratégie de distribution TensorFlow pour former un modèle sur un seul hôte avec plusieurs GPU et que vous remarquez une utilisation sous-optimale du GPU, vous devez d'abord optimiser et déboguer les performances d'un GPU avant de déboguer le système multi-GPU.
Comme référence pour obtenir un code performant sur les GPU, ce guide suppose que vous utilisez déjà tf.function
. Les API Keras Model.compile
et Model.fit
utiliseront automatiquement tf.function
sous le capot. Lors de l'écriture d'une boucle d'entraînement personnalisée avec tf.GradientTape
, reportez-vous à Meilleures performances avec tf.function pour savoir comment activer tf.function
s.
Les sections suivantes traitent des approches suggérées pour chacun des scénarios ci-dessus afin d'aider à identifier et à résoudre les goulots d'étranglement des performances.
1. Optimisez les performances sur un GPU
Dans un cas idéal, votre programme devrait avoir une utilisation élevée du processeur graphique, une communication minimale entre le processeur (l'hôte) et le processeur graphique (le périphérique) et aucune surcharge du pipeline d'entrée.
La première étape de l'analyse des performances consiste à obtenir un profil pour un modèle fonctionnant avec un seul GPU.
La page de présentation du profileur de TensorBoard, qui affiche une vue de haut niveau des performances de votre modèle lors d'une exécution de profil, peut donner une idée de la distance qui sépare votre programme du scénario idéal.
Les chiffres clés à prêter attention à la page de présentation sont :
- Combien de temps d'étape provient de l'exécution réelle de l'appareil
- Le pourcentage d'opérations placées sur l'appareil par rapport à l'hôte
- Combien de noyaux utilisent
fp16
Atteindre des performances optimales signifie maximiser ces chiffres dans les trois cas. Pour avoir une compréhension approfondie de votre programme, vous devrez vous familiariser avec le visualiseur de traces Profiler de TensorBoard. Les sections ci-dessous présentent certains modèles courants de visualiseur de trace que vous devez rechercher lors du diagnostic des goulots d'étranglement des performances.
Vous trouverez ci-dessous une image d'une vue de trace de modèle exécutée sur un GPU. Dans les sections TensorFlow Name Scope et TensorFlow Ops , vous pouvez identifier différentes parties du modèle, telles que la passe avant, la fonction de perte, le calcul de la passe/du gradient en arrière et la mise à jour du poids de l'optimiseur. Vous pouvez également exécuter les opérations sur le GPU à côté de chaque Stream , qui fait référence aux flux CUDA. Chaque flux est utilisé pour des tâches spécifiques. Dans cette trace, Stream#118 est utilisé pour lancer des noyaux de calcul et des copies d'appareil à appareil. Le flux n° 119 est utilisé pour la copie hôte vers périphérique et le flux n° 120 pour la copie périphérique vers hôte.
La trace ci-dessous montre les caractéristiques communes d'un modèle performant.
Par exemple, la chronologie de calcul GPU ( Stream#118 ) semble "occupée" avec très peu d'écarts. Il existe un minimum de copies d'un hôte à un appareil ( Stream #119 ) et d'un appareil à un hôte ( Stream #120 ), ainsi que des écarts minimes entre les étapes. Lorsque vous exécutez le profileur pour votre programme, vous ne pourrez peut-être pas identifier ces caractéristiques idéales dans votre vue de trace. Le reste de ce guide couvre les scénarios courants et comment les résoudre.
1. Déboguer le pipeline d'entrée
La première étape du débogage des performances GPU consiste à déterminer si votre programme est lié aux entrées. Le moyen le plus simple de comprendre cela consiste à utiliser l' analyseur de pipeline d'entrée du profileur , sur TensorBoard, qui fournit un aperçu du temps passé dans le pipeline d'entrée.
Vous pouvez effectuer les actions potentielles suivantes si votre pipeline d'entrée contribue de manière significative au temps d'étape :
- Vous pouvez utiliser le guide spécifique à
tf.data
pour apprendre à déboguer votre pipeline d'entrée. - Un autre moyen rapide de vérifier si le pipeline d'entrée est le goulot d'étranglement consiste à utiliser des données d'entrée générées de manière aléatoire qui ne nécessitent aucun prétraitement. Voici un exemple d'utilisation de cette technique pour un modèle ResNet. Si le pipeline d'entrée est optimal, vous devriez obtenir des performances similaires avec des données réelles et avec des données aléatoires/synthétiques générées. La seule surcharge dans le cas des données synthétiques sera due à la copie des données d'entrée qui, à nouveau, peut être prérécupérée et optimisée.
En outre, reportez-vous aux bonnes pratiques d'optimisation du pipeline de données d'entrée .
2. Déboguer les performances d'un GPU
Plusieurs facteurs peuvent contribuer à une faible utilisation du GPU. Vous trouverez ci-dessous quelques scénarios couramment observés lors de l'examen de la visionneuse de traces et des solutions potentielles.
1. Analysez les écarts entre les étapes
Une observation courante lorsque votre programme ne fonctionne pas de manière optimale est les écarts entre les étapes d'entraînement. Dans l'image de la vue de trace ci-dessous, il y a un grand écart entre les étapes 8 et 9, ce qui signifie que le GPU est inactif pendant ce temps.
Si votre visualiseur de trace affiche de grands écarts entre les étapes, cela peut indiquer que votre programme est lié à l'entrée. Dans ce cas, vous devez vous référer à la section précédente sur le débogage de votre pipeline d'entrée si vous ne l'avez pas déjà fait.
Cependant, même avec un pipeline d'entrée optimisé, vous pouvez toujours avoir des écarts entre la fin d'une étape et le début d'une autre en raison d'un conflit de threads CPU. tf.data
utilise des threads d'arrière-plan pour paralléliser le traitement du pipeline. Ces threads peuvent interférer avec l'activité côté hôte GPU qui se produit au début de chaque étape, comme la copie de données ou la planification d'opérations GPU.
Si vous remarquez de grandes lacunes du côté de l'hôte, qui planifie ces opérations sur le GPU, vous pouvez définir la variable d'environnement TF_GPU_THREAD_MODE=gpu_private
. Cela garantit que les noyaux GPU sont lancés à partir de leurs propres threads dédiés et ne sont pas mis en file d'attente derrière le travail de tf.data
.
Les écarts entre les étapes peuvent également être causés par des calculs de métriques, des rappels Keras ou des opérations en dehors de tf.function
qui s'exécutent sur l'hôte. Ces opérations n'ont pas d'aussi bonnes performances que les opérations à l'intérieur d'un graphique TensorFlow. De plus, certaines de ces opérations s'exécutent sur le CPU et copient les tenseurs dans les deux sens depuis le GPU.
Si, après avoir optimisé votre pipeline d'entrée, vous remarquez toujours des écarts entre les étapes dans la visionneuse de traces, vous devez examiner le code du modèle entre les étapes et vérifier si la désactivation des rappels/métriques améliore les performances. Certains détails de ces opérations figurent également dans le visualiseur de traces (côté périphérique et hôte). La recommandation dans ce scénario est d'amortir la surcharge de ces opérations en les exécutant après un nombre fixe d'étapes au lieu de chaque étape. Lors de l'utilisation de la méthode Model.compile
dans l'API tf.keras
, la définition de l'indicateur steps_per_execution
le fait automatiquement. Pour les boucles d'entraînement personnalisées, utilisez tf.while_loop
.
2. Atteindre une meilleure utilisation des appareils
1. Petits noyaux GPU et retards de lancement du noyau hôte
L'hôte met en file d'attente les noyaux à exécuter sur le GPU, mais il y a une latence (environ 20 à 40 μs) avant que les noyaux ne soient réellement exécutés sur le GPU. Dans un cas idéal, l'hôte met en file d'attente suffisamment de noyaux sur le GPU de sorte que le GPU passe la plupart de son temps à s'exécuter, plutôt que d'attendre que l'hôte mette en file d'attente plus de noyaux.
La page de présentation du profileur sur TensorBoard indique combien de temps le GPU a été inactif en raison de l'attente de l'hôte pour lancer les noyaux. Dans l'image ci-dessous, le GPU est inactif pendant environ 10 % du temps d'étape en attendant que les noyaux soient lancés.
La visionneuse de trace pour ce même programme montre de petits écarts entre les noyaux où l'hôte est occupé à lancer des noyaux sur le GPU.
En lançant un grand nombre de petites opérations sur le GPU (comme un ajout scalaire, par exemple), l'hôte peut ne pas suivre le GPU. L'outil TensorFlow Stats de TensorBoard pour le même profil affiche 126 224 opérations Mul prenant 2,77 secondes. Ainsi, chaque noyau est d'environ 21,9 μs, ce qui est très petit (à peu près au même moment que la latence de lancement) et peut potentiellement entraîner des retards de lancement du noyau hôte.
Si votre visualiseur de traces affiche de nombreux petits écarts entre les opérations sur le GPU, comme dans l'image ci-dessus, vous pouvez :
- Concaténez de petits tenseurs et utilisez des opérations vectorisées ou utilisez une taille de lot plus grande pour que chaque noyau lancé fasse plus de travail, ce qui occupera le GPU plus longtemps.
- Assurez-vous que vous utilisez
tf.function
pour créer des graphiques TensorFlow, afin que vous n'exécutiez pas d'opérations en mode impatient pur. Si vous utilisezModel.fit
(par opposition à une boucle d'entraînement personnalisée avectf.GradientTape
), alorstf.keras.Model.compile
le fera automatiquement pour vous. - Fusionnez les noyaux en utilisant XLA avec
tf.function(jit_compile=True)
ou la mise en cluster automatique. Pour plus de détails, consultez la section Activer la précision mixte et XLA ci-dessous pour savoir comment activer XLA pour obtenir de meilleures performances. Cette fonctionnalité peut entraîner une utilisation élevée de l'appareil.
2. Placement des opérations TensorFlow
La page de présentation du profileur vous montre le pourcentage d'opérations placées sur l'hôte par rapport à l'appareil (vous pouvez également vérifier le placement d'opérations spécifiques en regardant la visionneuse de trace . Comme dans l'image ci-dessous, vous voulez le pourcentage d'opérations sur l'hôte être très petit par rapport à l'appareil.
Idéalement, la plupart des opérations intensives en calcul devraient être placées sur le GPU.
Pour savoir à quels appareils les opérations et les tenseurs de votre modèle sont affectés, définissez tf.debugging.set_log_device_placement(True)
comme première instruction de votre programme.
Notez que dans certains cas, même si vous spécifiez un op à placer sur un périphérique particulier, son implémentation peut remplacer cette condition (exemple : tf.unique
). Même pour une formation GPU unique, la spécification d'une stratégie de distribution, telle que tf.distribute.OneDeviceStrategy
, peut entraîner un placement plus déterministe des opérations sur votre appareil.
L'une des raisons pour lesquelles la majorité des opérations sont placées sur le GPU est d'empêcher les copies de mémoire excessives entre l'hôte et l'appareil (des copies de mémoire pour les données d'entrée/sortie du modèle entre l'hôte et l'appareil sont attendues). Un exemple de copie excessive est illustré dans la vue de trace ci-dessous sur les flux GPU #167 , #168 et #169 .
Ces copies peuvent parfois nuire aux performances si elles empêchent l'exécution des noyaux GPU. Les opérations de copie de mémoire dans la visionneuse de trace contiennent plus d'informations sur les opérations qui sont à l'origine de ces tenseurs copiés, mais il n'est pas toujours facile d'associer un memCopy à une opération. Dans ces cas, il est utile de regarder les opérations à proximité pour vérifier si la copie de la mémoire se produit au même endroit à chaque étape.
3. Des noyaux plus efficaces sur les GPU
Une fois que l'utilisation du GPU de votre programme est acceptable, l'étape suivante consiste à chercher à augmenter l'efficacité des noyaux GPU en utilisant des cœurs Tensor ou en fusionnant des opérations.
1. Utiliser les noyaux tenseurs
Les GPU NVIDIA® modernes ont des cœurs Tensor spécialisés qui peuvent améliorer considérablement les performances des noyaux éligibles.
Vous pouvez utiliser les statistiques du noyau GPU de TensorBoard pour visualiser quels noyaux GPU sont éligibles à Tensor Core et quels noyaux utilisent Tensor Cores. L'activation fp16
(voir la section Activation de la précision mixte ci-dessous) est un moyen de faire en sorte que les noyaux General Matrix Multiply (GEMM) de votre programme (matmul ops) utilisent le Tensor Core. Les noyaux GPU utilisent efficacement les Tensor Cores lorsque la précision est fp16 et que les dimensions du tenseur d'entrée/sortie sont divisibles par 8 ou 16 (pour int8
).
Pour d'autres recommandations détaillées sur la façon de rendre les noyaux efficaces pour les GPU, reportez-vous au guide des performances d'apprentissage en profondeur NVIDIA® .
2. Opérations de fusible
Utilisez tf.function(jit_compile=True)
pour fusionner des opérations plus petites pour former des noyaux plus gros conduisant à des gains de performances significatifs. Pour en savoir plus, consultez le guide XLA .
3. Activer la précision mixte et XLA
Après avoir suivi les étapes ci-dessus, activer la précision mixte et XLA sont deux étapes facultatives que vous pouvez suivre pour améliorer encore les performances. L'approche suggérée consiste à les activer un par un et à vérifier que les avantages en termes de performances sont conformes aux attentes.
1. Activer la précision mixte
Le guide de précision TensorFlow Mixed montre comment activer la précision fp16
sur les GPU. Activez AMP sur les GPU NVIDIA® pour utiliser les cœurs Tensor et réalisez jusqu'à 3 fois des accélérations globales par rapport à l'utilisation de fp32
(float32) sur Volta et les architectures GPU plus récentes.
Assurez-vous que les dimensions de la matrice/du tenseur satisfont aux exigences d'appel des noyaux qui utilisent des cœurs de tenseur. Les noyaux GPU utilisent efficacement les Tensor Cores lorsque la précision est de fp16 et que les dimensions d'entrée/sortie sont divisibles par 8 ou 16 (pour int8).
Notez qu'avec cuDNN v7.6.3 et versions ultérieures, les dimensions de convolution seront automatiquement complétées si nécessaire pour tirer parti des noyaux Tensor.
Suivez les meilleures pratiques ci-dessous pour maximiser les avantages de performance de la précision fp16
.
1. Utilisez des noyaux fp16 optimaux
Lorsque fp16
activé, les noyaux de multiplication matricielle (GEMM) de votre programme doivent utiliser la version fp16
correspondante qui utilise les cœurs Tensor. Cependant, dans certains cas, cela ne se produit pas et vous ne rencontrez pas l'accélération attendue de l'activation de fp16
, car votre programme revient à l'implémentation inefficace à la place.
La page des statistiques du noyau GPU indique quelles opérations sont éligibles à Tensor Core et quels noyaux utilisent réellement le Tensor Core efficace. Le guide NVIDIA® sur les performances d'apprentissage en profondeur contient des suggestions supplémentaires sur la manière d'exploiter les cœurs Tensor. De plus, les avantages de l'utilisation de fp16
apparaîtront également dans les noyaux qui étaient auparavant liés à la mémoire, car les opérations prendront désormais la moitié du temps.
2. Mise à l'échelle dynamique ou statique des pertes
La mise à l'échelle des pertes est nécessaire lors de l'utilisation de fp16
pour éviter le sous-dépassement dû à une faible précision. Il existe deux types de mise à l'échelle des pertes, dynamique et statique, qui sont toutes deux expliquées plus en détail dans le guide Précision mixte . Vous pouvez utiliser la politique mixed_float16
pour activer automatiquement la mise à l'échelle des pertes dans l'optimiseur Keras.
Lorsque vous essayez d'optimiser les performances, il est important de se rappeler que la mise à l'échelle dynamique des pertes peut introduire des opérations conditionnelles supplémentaires qui s'exécutent sur l'hôte et entraîner des écarts qui seront visibles entre les étapes de la visionneuse de trace. D'un autre côté, la mise à l'échelle de la perte statique n'a pas de tels frais généraux et peut être une meilleure option en termes de performances avec le piège dont vous avez besoin pour spécifier la valeur correcte de l'échelle de perte statique.
2. Activez XLA avec tf.function(jit_compile=True) ou auto-clustering
Comme dernière étape pour obtenir les meilleures performances avec un seul GPU, vous pouvez expérimenter l'activation de XLA, qui fusionnera les opérations et conduira à une meilleure utilisation de l'appareil et à une empreinte mémoire réduite. Pour plus de détails sur l'activation de XLA dans votre programme avec tf.function(jit_compile=True)
ou la mise en cluster automatique, reportez-vous au guide XLA .
Vous pouvez définir le niveau JIT global sur -1
(désactivé), 1
ou 2
. Un niveau supérieur est plus agressif et peut réduire le parallélisme et utiliser plus de mémoire. Définissez la valeur sur 1
si vous avez des restrictions de mémoire. Notez que XLA ne fonctionne pas bien pour les modèles avec des formes de tenseur d'entrée variables car le compilateur XLA devrait continuer à compiler les noyaux chaque fois qu'il rencontre de nouvelles formes.
2. Optimisez les performances sur l'hôte unique multi-GPU
L'API tf.distribute.MirroredStrategy
peut être utilisée pour faire évoluer la formation de modèle d'un GPU à plusieurs GPU sur un seul hôte. (Pour en savoir plus sur la façon d'effectuer une formation distribuée avec TensorFlow, reportez-vous aux guides Formation distribuée avec TensorFlow , Utiliser un GPU et Utiliser des TPU et au didacticiel Formation distribuée avec Keras .)
Bien que la transition d'un GPU à plusieurs GPU doive idéalement être évolutive dès le départ, vous pouvez parfois rencontrer des problèmes de performances.
Lorsque vous passez d'une formation avec un seul GPU à plusieurs GPU sur le même hôte, vous devriez idéalement faire l'expérience de la mise à l'échelle des performances avec uniquement la surcharge supplémentaire de la communication de gradient et l'utilisation accrue du thread hôte. En raison de cette surcharge, vous n'aurez pas une accélération exacte de 2x si vous passez de 1 à 2 GPU, par exemple.
La vue de trace ci-dessous montre un exemple de surcharge de communication supplémentaire lors de la formation sur plusieurs GPU. Il y a une surcharge pour concaténer les gradients, les communiquer entre les répliques et les diviser avant de faire la mise à jour du poids.
La liste de contrôle suivante vous aidera à obtenir de meilleures performances lors de l'optimisation des performances dans le scénario multi-GPU :
- Essayez de maximiser la taille du lot, ce qui entraînera une utilisation plus élevée des appareils et amortira les coûts de communication entre plusieurs GPU. L'utilisation du profileur de mémoire permet d'avoir une idée de la proximité de votre programme par rapport au pic d'utilisation de la mémoire. Notez que même si une taille de lot plus élevée peut affecter la convergence, cela est généralement compensé par les avantages en termes de performances.
- Lors du passage d'un seul GPU à plusieurs GPU, le même hôte doit désormais traiter beaucoup plus de données d'entrée. Ainsi, après (1), il est recommandé de revérifier les performances du pipeline d'entrée et de s'assurer qu'il ne s'agit pas d'un goulot d'étranglement.
- Vérifiez la chronologie du GPU dans la vue de trace de votre programme pour tout appel AllReduce inutile, car cela entraîne une synchronisation sur tous les appareils. Dans la vue de trace illustrée ci-dessus, AllReduce est effectué via le noyau NCCL , et il n'y a qu'un seul appel NCCL sur chaque GPU pour les gradients à chaque étape.
- Vérifiez les opérations de copie D2H, H2D et D2D inutiles qui peuvent être minimisées.
- Vérifiez la durée de l'étape pour vous assurer que chaque réplique effectue le même travail. Par exemple, il peut arriver qu'un GPU (généralement,
GPU0
) soit sursouscrit parce que l'hôte finit par y consacrer plus de travail. - Enfin, vérifiez l'étape de formation sur tous les GPU dans votre vue de trace pour toutes les opérations qui s'exécutent de manière séquentielle. Cela se produit généralement lorsque votre programme inclut des dépendances de contrôle d'un GPU à un autre. Dans le passé, le débogage des performances dans cette situation était résolu au cas par cas. Si vous observez ce comportement dans votre programme, créez un problème GitHub avec des images de votre vue de trace.
1. Optimiser le dégradé AllReduce
Lors de l'entraînement avec une stratégie synchrone, chaque appareil reçoit une partie des données d'entrée.
Après avoir calculé les passages avant et arrière à travers le modèle, les gradients calculés sur chaque appareil doivent être agrégés et réduits. Ce gradient AllReduce se produit après le calcul du gradient sur chaque appareil et avant que l'optimiseur ne mette à jour les pondérations du modèle.
Chaque GPU concatène d'abord les gradients sur les couches de modèle, les communique entre les GPU à l'aide tf.distribute.CrossDeviceOps
( tf.distribute.NcclAllReduce
est la valeur par défaut), puis renvoie les gradients après réduction par couche.
L'optimiseur utilisera ces gradients réduits pour mettre à jour les pondérations de votre modèle. Idéalement, ce processus devrait se produire en même temps sur tous les GPU pour éviter tout surcoût.
Le temps de AllReduce devrait être approximativement le même que :
(number of parameters * 4bytes)/ (communication bandwidth)
Ce calcul est utile pour vérifier rapidement si les performances que vous obtenez lors de l'exécution d'une tâche de formation distribuée sont conformes aux attentes ou si vous devez effectuer un débogage supplémentaire des performances. Vous pouvez obtenir le nombre de paramètres de votre modèle à partir de Model.summary
.
Notez que chaque paramètre de modèle a une taille de 4 octets puisque TensorFlow utilise fp32
(float32) pour communiquer les gradients. Même lorsque fp16
activé, NCCL AllReduce utilise les paramètres fp32
.
Pour bénéficier des avantages de la mise à l'échelle, le temps de pas doit être beaucoup plus élevé par rapport à ces frais généraux. Une façon d'y parvenir consiste à utiliser une taille de lot plus élevée, car la taille du lot affecte le temps d'étape, mais n'a pas d'impact sur la surcharge de communication.
2. Conflit de thread d'hôte GPU
Lors de l'exécution de plusieurs GPU, le travail du CPU consiste à occuper tous les appareils en lançant efficacement les noyaux GPU sur les appareils.
Cependant, lorsqu'il y a beaucoup d'opérations indépendantes que le CPU peut programmer sur un GPU, le CPU peut décider d'utiliser un grand nombre de ses threads hôtes pour garder un GPU occupé, puis lancer des noyaux sur un autre GPU dans un ordre non déterministe. . Cela peut entraîner une inclinaison ou une mise à l'échelle négative, ce qui peut affecter négativement les performances.
La visionneuse de trace ci-dessous montre la surcharge lorsque le processeur échelonne le lancement inefficace du noyau GPU, car GPU1
est inactif, puis commence à exécuter des opérations après le démarrage de GPU2
.
La vue de trace de l'hôte montre que l'hôte lance les noyaux sur GPU2
avant de les lancer sur GPU1
(notez que les opérations tf_Compute*
ci-dessous ne sont pas indicatives des threads CPU).
Si vous rencontrez ce type d'échelonnement des noyaux GPU dans la vue de trace de votre programme, l'action recommandée consiste à :
- Définissez la variable d'environnement TensorFlow
TF_GPU_THREAD_MODE
surgpu_private
. Cette variable d'environnement indiquera à l'hôte de garder les threads pour un GPU privé. - Par défaut,
TF_GPU_THREAD_MODE=gpu_private
définit le nombre de threads sur 2, ce qui est suffisant dans la plupart des cas. Cependant, ce nombre peut être modifié en définissant la variable d'environnement TensorFlowTF_GPU_THREAD_COUNT
sur le nombre de threads souhaité.