Aide à protéger la Grande barrière de corail avec tensorflow sur Kaggle Rejoignez Défi

Optimisez les performances du GPU TensorFlow avec TensorFlow Profiler

Aperçu

Ce guide vous montrera comment utiliser TensorFlow Profiler avec TensorBoard pour mieux comprendre et tirer le meilleur parti de vos GPU, et déboguer lorsqu'un ou plusieurs de vos GPU sont sous-utilisés.

Si vous êtes nouveau dans le profileur :

Gardez à l'esprit que le déchargement des calculs vers 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 en raison de :

  • 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 :

  1. Optimisez et déboguez les performances sur un GPU :
    1. Vérifiez si le pipeline d'entrée est un goulot d'étranglement.
    2. Déboguez les performances d'un GPU.
    3. Activer précision mixte (avec fp16 (float16)) et éventuellement permettre XLA .
  2. Optimisez et déboguez les performances sur l'hôte unique multi-GPU.

Par exemple, si vous utilisez une tensorflow stratégie de distribution pour former un modèle sur un hôte unique avec plusieurs processeurs graphiques et l' utilisation des avis suboptimale GPU, vous devez d' abord optimiser et déboguer les performances pour un GPU avant le débogage du système multi-GPU.

En tant que base pour obtenir le code sur les GPU performant, ce guide suppose que vous utilisez déjà tf.function . Le Keras Model.compile et Model.fit API utiliseront tf.function automatiquement sous le capot. Lors de l' écriture d' une boucle de formation sur mesure avec tf.GradientTape , reportez - vous à la Meilleure performance avec tf.function sur la façon de permettre tf.function s.

Les sections suivantes traitent des approches suggérées pour chacun des scénarios ci-dessus afin d'identifier et de corriger les goulots d'étranglement des performances.

1. Optimisez les performances sur un GPU

Dans un cas idéal, votre programme doit avoir une utilisation élevée du GPU, une communication minimale du CPU (l'hôte) au GPU (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 GPU.

Profiler TensorBoard la page -qui montre une vue de niveau supérieur de la façon dont votre modèle réalisé au cours d' une exécution peut donner une idée profil de la distance de votre programme est le scénario idéal.

TensorFlow Profiler Overview Page

Les chiffres clés auxquels prêter attention à la page d'aperçu sont :

  1. Combien de temps d'étape provient de l'exécution réelle de l'appareil
  2. Le pourcentage d'opérations placées sur l'appareil par rapport à l'hôte
  3. Combien de grains utilisent fp16

Atteindre des performances optimales signifie maximiser ces chiffres dans les trois cas. Pour obtenir une compréhension approfondie de votre programme, vous devez être familier avec Profiler TensorBoard visionneuse de trace . Les sections ci-dessous présentent certains modèles de visionneuse de trace courants 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 s'exécutant sur un GPU. Des sections Ops Nom tensorflow Portée et tensorflow, on peut identifier différentes parties du modèle, comme le passage vers l' avant, la fonction de perte, le calcul passe arrière / gradient, et la mise à jour de poids de l' optimiseur. Vous pouvez également les opérations en cours d' exécution sur le GPU à côté de chaque flux, qui se réfèrent à des flux CUDA. Chaque flux est utilisé pour des tâches spécifiques. Dans cette trace, Stream # 118 est utilisé pour lancer des noyaux et des copies de calcul de dispositif à dispositif. STREAM # 119 est utilisé pour la copie hôte à l' appareil et flux # 120 pour le dispositif de copie hôte.

La trace ci-dessous montre les caractéristiques communes d'un modèle performant.

image

Par exemple, la ligne de temps de calcul GPU (flux # 118) semble « occupé » avec des écarts très peu. Il y a des copies minimales de l' hôte vers un périphérique (flux # 119) et de l' appareil à l' hôte (flux # 120), ainsi que les lacunes minimales 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 suivi. 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 du GPU consiste à déterminer si votre programme est lié à l'entrée. La meilleure façon de comprendre cela est d'utiliser le Profiler analyseur-pipeline d'entrée , sur TensorBoard, qui donne un aperçu du temps passé dans la canalisation d'entrée.

image

Vous pouvez effectuer les actions potentielles suivantes si votre pipeline d'entrée contribue de manière significative au temps de pas :

  • Vous pouvez utiliser le tf.data Spécifiques Guide pour apprendre comment 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 aléatoirement qui ne nécessitent aucun pré-traitement. Voici un exemple d'utilisation de cette technique pour un modèle de 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. Le seul surcoût dans le cas des données synthétiques sera dû à la copie des données d'entrée qui peut à nouveau être préchargée et optimisée.

De plus, se référer aux meilleures pratiques pour optimiser la conduite de données d'entrée .

2. Déboguer les performances d'un GPU

Plusieurs facteurs peuvent contribuer à une faible utilisation du GPU. Voici quelques scénarios fréquemment observés lorsque l'on regarde le spectateur de trace et les solutions possibles.

1. Analyser les écarts entre les étapes

Une observation courante lorsque votre programme ne fonctionne pas de manière optimale est l'écart 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.

image

Si votre visionneuse 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 de conflits de threads CPU. tf.data utilise des fils de fond pour paralléliser le traitement de pipeline. Ces threads peuvent interférer avec l'activité côté hôte GPU qui se produit au début de chaque étape, telle que la copie de données ou la planification d'opérations GPU.

Si vous remarquez de grandes lacunes du côté hôte, qui planifie ces ops 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 fils dévoués, et ne sont pas mis en attente derrière tf.data travail.

Les écarts entre les étapes peuvent également être causées par des calculs métriques, callbacks KERAS, ou ops en dehors de tf.function qui fonctionnent 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 trace, 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 se trouvent également dans la visionneuse de trace (à la fois côté périphérique et côté 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. Lorsque vous utilisez la compile méthode dans la tf.keras API, réglage du experimental_steps_per_execution drapeau fait automatiquement. Pour boucles de formation sur mesure, l' utilisation tf.while_loop .

2. Obtenez une utilisation plus élevée des appareils

1. Petits noyaux GPU et délais de lancement du noyau hôte

L'hôte met les noyaux en file d'attente pour qu'ils soient exécutés sur le GPU, mais il y a une latence (environ 20-40 μs) impliquée 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 le plus clair de son temps à s'exécuter, plutôt que d'attendre que l'hôte mette en file d'attente plus de noyaux.

Le Profiler page d' aperçu des spectacles TensorBoard combien de temps le GPU a été ralenti en raison de l' attente sur l'hôte pour les noyaux de lancement. Dans l'image ci-dessous, le GPU est inactif pendant environ 10 % du temps d'attente du lancement des noyaux.

image

La visionneuse de trace pour ce même programme montre petites lacunes entre les noyaux où l'hôte est des noyaux de lancement occupés sur le GPU.

image

En lançant de nombreuses petites opérations sur le GPU (comme un ajout scalaire, par exemple), l'hôte peut ne pas suivre le GPU. Le Stats tensorflow outil TensorBoard pour les mêmes spectacles Profil 126,224 opérations Mul prenant 2,77 secondes. Ainsi, chaque noyau fait environ 21,9 s, ce qui est très faible (à 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.

image

Si votre spectateur trace montre de nombreuses petites lacunes 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 importante pour que chaque noyau lancé fasse plus de travail, ce qui gardera le GPU occupé plus longtemps.
  • Assurez- vous que vous utilisez tf.function pour créer des graphiques tensorflow, de sorte que vous n'êtes pas en cours d' exécution ops dans un mode pur avide. Si vous utilisez Model.fit (comme s'opposer à une boucle de formation sur mesure avec tf.GradientTape ), puis tf.keras.Model.compile fera automatiquement pour vous.
  • Noyaux fusible à l' aide XLA avec tf.function(jit_compile=True) ou auto-regroupement. Pour plus de détails, allez à la option Activer la précision mixte et XLA section ci - dessous pour savoir comment activer XLA obtenir de meilleures performances. Cette fonctionnalité peut entraîner une utilisation élevée de l'appareil.
2. Placement des opérations TensorFlow

Le profileur page de vous les montre le pourcentage d'ops placé sur l'hôte par rapport à l'appareil (vous pouvez également vérifier la mise en place d'opérations spécifiques en regardant le spectateur de trace . Comme dans l'image ci - dessous, vous voulez que le pourcentage d'opérations sur l'hôte être très petit par rapport à l'appareil.

image

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 tenseurs dans votre modèle sont assignés à, ensemble tf.debugging.set_log_device_placement(True) comme la première déclaration de votre programme.

Notez que dans certains cas, même si vous spécifiez un op à placer sur un dispositif particulier, sa mise en œuvre pourrait passer outre cette condition ( par exemple: tf.unique ). Même pour la formation GPU unique, en spécifiant une stratégie de distribution, comme tf.distribute.OneDeviceStrategy , peut entraîner un positionnement 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'éviter les copies mémoire excessives entre l'hôte et le périphérique (des copies mémoire pour les données d'entrée/sortie du modèle entre l'hôte et le périphérique sont attendues). Un exemple de copie excessive est démontrée dans la vue ci - dessous trace sur les flux GPU # 167, # 168 et # 169.

image

Ces copies peuvent parfois nuire aux performances si elles bloquent l'exécution des noyaux GPU. Mémoire opérations de copie dans la trace viewer ont plus d' informations sur les opérations qui sont la source de ces tenseurs copiés, mais il pourrait ne pas être toujours facile d'associer un memCopy avec op. Dans ces cas, il est utile de regarder les opérations à proximité pour vérifier si la copie 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 à augmenter l'efficacité des noyaux GPU en utilisant des cœurs Tensor ou des opérations de fusion.

1. Utiliser des cœurs tenseurs

Processeurs graphiques modernes NVIDIA® sont spécialisés Cores Tensor qui peuvent améliorer considérablement les performances des noyaux admissibles.

Vous pouvez utiliser des TensorBoard statistiques du noyau GPU pour visualiser les noyaux qui GPU sont Tensor de base admissibles, et qui utilisent les noyaux Cores Tensor. Activation fp16 (voir la section Activation de précision mixte ci - dessous) est une façon de rendre votre Multiply du programme général Matrix (GEMM) noyaux (ops matmul) utilisent le Tensor de base. Les noyaux GPU utilisent les noyaux Tenseur efficacement lorsque la précision est FP16 et 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 faire des noyaux efficace pour processeurs graphiques, reportez - vous à la performances NVIDIA® apprentissage en profondeur guide.

2. Opérations de fusible

Utilisez tf.function(jit_compile=True) pour fusionner les petites ops pour former de plus grands noyaux conduisant à des gains de performance significatifs. Pour en savoir plus, consultez le XLA guide.

3. Activer la précision mixte et XLA

Après avoir suivi les étapes ci-dessus, l'activation de la précision mixte et du XLA sont deux étapes facultatives que vous pouvez suivre pour améliorer davantage les performances. L'approche suggérée est de les activer un par un et de vérifier que les avantages en termes de performances sont ceux attendus.

1. Activer la précision mixte

La tensorflow précision mixte montre de guidage comment activer fp16 précision sur les GPU. Activer AMP sur les GPU NVIDIA® utiliser Tensor Cores et de réaliser jusqu'à 3 fois speedups ensemble lorsque comparé à l' utilisation fp32 (float32) précision sur Volta et les nouvelles architectures GPU.

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 cœurs Tensor.

Suivez les meilleures pratiques ci - dessous pour maximiser les avantages de performance de fp16 précision.

1. Utilisez des noyaux fp16 optimaux

Avec fp16 activé, votre matrice de noyaux programme de multiplications (GEMM), doivent utiliser la correspondante fp16 version qui utilise les Cores Tensor. Cependant, dans certains cas, cela ne se produit pas et que vous ne ressentez pas le speedup attendu de permettre fp16 , que votre programme revient à la mise en œuvre inefficace à la place.

image

Le noyau GPU page de spectacles qui ops sont admissibles et Tensor de base qui utilisent les noyaux effectivement l'efficacité Tensor de base. Le Guide NVIDIA® sur la performance de l' apprentissage en profondeur contient des suggestions supplémentaires sur la façon de tirer parti de Tensor Cores. De plus, les avantages de l' utilisation fp16 montrera également dans les noyaux qui étaient auparavant la mémoire liée, comme maintenant les opérations prendront la moitié du temps.

2. Échelle de perte dynamique vs statique

Mise à l' échelle de perte est nécessaire lors de l' utilisation fp16 pour éviter underflow raison de la faible précision. Il existe deux types de mise à l' échelle de perte, dynamique et statique, plus en détail les deux sont expliqués dans le guide de précision mixte . Vous pouvez utiliser la mixed_float16 stratégie pour activer automatiquement mise à l' échelle des pertes au sein de 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 la capture dont vous avez besoin pour spécifier la valeur d'échelle de perte statique correcte.

2. Activez XLA avec tf.function(jit_compile=True) ou la mise en cluster automatique

Comme étape finale 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 la façon de permettre XLA dans votre programme avec tf.function(jit_compile=True) ou auto-regroupement, consultez le XLA guide.

Vous pouvez régler le niveau global de JIT à -1 (off), 1 ou 2 . Un niveau plus élevé est plus agressif et peut réduire le parallélisme et utiliser plus de mémoire. Réglez la valeur 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' tf.distribute.MirroredStrategy API peut être utilisée pour la formation de modèle à l'échelle d'un GPU à plusieurs processeurs graphiques sur un seul hôte. (Pour en savoir plus sur la façon de faire de la formation distribuée avec tensorflow, reportez - vous à la formation distribuée avec tensorflow , utilisation d' un GPU et utilisation PUT guides et la formation distribuée avec Keras tutoriel.)

Bien que la transition d'un GPU à plusieurs GPU doive idéalement être évolutive, 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 par gradient et une utilisation accrue des threads de l'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 la surcharge de communication supplémentaire lors de l'entraînement sur plusieurs GPU. Il y a une surcharge pour concaténer les gradients, les communiquer entre les répliques et les diviser avant de procéder à la mise à jour du poids.

image

La liste de contrôle suivante vous aidera à obtenir de meilleures performances lors de l'optimisation des performances dans le scénario multi-GPU :

  1. 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. Utilisation de la profileur de mémoire aide à obtenir une idée de fermer votre programme est d'utilisation de la mémoire de pointe. 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.
  2. 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.
  3. 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 trace ci - dessus, la AllReduce se fait via le NCCL noyau, et il n'y a qu'un seul appel NCCL sur chaque GPU pour les gradients de chaque étape.
  4. Vérifiez les opérations de copie D2H, H2D et D2D inutiles qui peuvent être minimisées.
  5. 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 que l' un GPU (généralement, GPU0 ) est sursouscrite parce que l'hôte se termine par erreur en mettant plus de travail là - dessus.
  6. 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, un problème relatif GitHub avec des images de votre point de 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 dans 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 de gradient sur chaque dispositif, et avant que l'optimiseur met à jour les coefficients de pondération du modèle.

Chaque GPU premier concatène les gradients à travers les couches du modèle, les communique à travers les GPU utilisant 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 poids de votre modèle. Idéalement, ce processus devrait se dérouler en même temps sur tous les GPU pour éviter toute surcharge.

Le temps d'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 d'entraînement distribuée sont celles attendues ou si vous devez effectuer un débogage supplémentaire des performances. Vous pouvez obtenir le nombre de paramètres dans votre modèle de Model.summary .

On notera que chaque paramètre de modèle est de 4 octets depuis tensorflow utilise fp32 (float32) pour communiquer des gradients. Même lorsque vous avez fp16 activé, NCCL AllReduce utilise fp32 paramètres.

Pour profiter des avantages de la mise à l'échelle, le pas-temps doit être beaucoup plus élevé par rapport à ces frais généraux. Une façon d'y parvenir est d'utiliser une taille de lot plus élevée car la taille de lot affecte le temps d'étape, mais n'a pas d'impact sur la surcharge de communication.

2. Conflit de thread hôte GPU

Lors de l'exécution de plusieurs GPU, le travail du CPU consiste à garder tous les appareils occupés en lançant efficacement des noyaux de GPU sur tous 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 beaucoup 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 distorsion ou une mise à l'échelle négative, ce qui peut affecter négativement les performances.

La visionneuse de trace ci - dessous montre le noyau de tête lorsque le GPU de la CPU lancements inefficacement, comme GPU1 est inactif, puis commence à courir après ops GPU2 a commencé.

image

La vue de trace pour les spectacles d'accueil que l'hôte lance sur les noyaux GPU2 avant de les lancer sur GPU1 (notez que les ci - dessous tf_Compute* ops ne sont pas représentatives de threads CPU).

image

Si vous rencontrez ce genre de décalage des noyaux GPU dans la vue de trace de votre programme, l'action recommandée est de :

  • Définissez la variable d'environnement tensorflow TF_GPU_THREAD_MODE à gpu_private . Cette variable d'environnement indiquera à l'hôte de garder les threads pour un GPU privés.
  • Par défaut, TF_GPU_THREAD_MODE=gpu_private définit le nombre de threads à 2, ce qui est suffisant dans la plupart des cas. Toutefois, ce nombre peut être modifié en réglant la variable d'environnement tensorflow TF_GPU_THREAD_COUNT au nombre de fils désiré.