Ottimizza le prestazioni della GPU TensorFlow con TensorFlow Profiler

Panoramica

Questa guida ti mostrerà come utilizzare TensorFlow Profiler con TensorBoard per ottenere informazioni dettagliate e ottenere le massime prestazioni dalle tue GPU ed eseguire il debug quando una o più GPU sono sottoutilizzate.

Se sei nuovo al Profiler:

Tieni presente che scaricare i calcoli sulla GPU potrebbe non essere sempre vantaggioso, in particolare per i modelli di piccole dimensioni. Potrebbero esserci delle spese generali dovute a:

  • Trasferimento dati tra l'host (CPU) e il dispositivo (GPU); E
  • A causa della latenza coinvolta quando l'host avvia i kernel GPU.

Flusso di lavoro di ottimizzazione delle prestazioni

Questa guida descrive come eseguire il debug dei problemi di prestazioni iniziando con una singola GPU, per poi passare a un singolo host con più GPU.

Si consiglia di eseguire il debug dei problemi di prestazioni nel seguente ordine:

  1. Ottimizza ed esegui il debug delle prestazioni su una GPU:
    1. Controlla se la pipeline di input rappresenta un collo di bottiglia.
    2. Eseguire il debug delle prestazioni di una GPU.
    3. Abilita la precisione mista (con fp16 (float16)) e facoltativamente abilita XLA .
  2. Ottimizza ed esegui il debug delle prestazioni sul singolo host multi-GPU.

Ad esempio, se stai utilizzando una strategia di distribuzione TensorFlow per addestrare un modello su un singolo host con più GPU e noti un utilizzo non ottimale della GPU, dovresti prima ottimizzare ed eseguire il debug delle prestazioni per una GPU prima di eseguire il debug del sistema multi-GPU.

Come base per ottenere codice performante sulle GPU, questa guida presuppone che tu stia già utilizzando tf.function . Le API Keras Model.compile e Model.fit utilizzeranno tf.function automaticamente dietro le quinte. Quando scrivi un ciclo di training personalizzato con tf.GradientTape , fai riferimento a Prestazioni migliori con tf.function su come abilitare tf.function s.

Le sezioni successive illustrano gli approcci suggeriti per ciascuno degli scenari precedenti per identificare e risolvere i colli di bottiglia delle prestazioni.

1. Ottimizza le prestazioni su una GPU

Nel caso ideale, il programma dovrebbe avere un utilizzo elevato della GPU, una comunicazione minima tra CPU (l'host) e GPU (il dispositivo) e nessun sovraccarico dalla pipeline di input.

Il primo passo nell'analisi delle prestazioni è ottenere un profilo per un modello in esecuzione con una GPU.

La pagina di panoramica del Profiler di TensorBoard, che mostra una vista di primo livello del rendimento del tuo modello durante l'esecuzione del profilo, può fornire un'idea di quanto sia lontano il tuo programma dallo scenario ideale.

TensorFlow Profiler Overview Page

I numeri chiave a cui prestare attenzione nella pagina di panoramica sono:

  1. La parte del tempo del passaggio deriva dall'esecuzione effettiva del dispositivo
  2. La percentuale di operazioni effettuate sul dispositivo rispetto all'host
  3. Quanti kernel utilizzano fp16

Raggiungere prestazioni ottimali significa massimizzare questi numeri in tutti e tre i casi. Per ottenere una comprensione approfondita del tuo programma, dovrai avere familiarità con il visualizzatore di tracce Profiler di TensorBoard. Le sezioni seguenti mostrano alcuni modelli comuni di visualizzazione delle tracce da cercare durante la diagnosi dei colli di bottiglia delle prestazioni.

Di seguito è riportata un'immagine di una vista di traccia del modello in esecuzione su una GPU. Dalle sezioni TensorFlow Name Scope e TensorFlow Ops , puoi identificare diverse parti del modello, come il passaggio in avanti, la funzione di perdita, il calcolo del passaggio/gradiente all'indietro e l'aggiornamento del peso dell'ottimizzatore. Puoi anche avere le operazioni in esecuzione sulla GPU accanto a ciascun flusso , che si riferisce ai flussi CUDA. Ogni flusso viene utilizzato per attività specifiche. In questa traccia, il flusso n. 118 viene utilizzato per avviare i kernel di elaborazione e le copie da dispositivo a dispositivo. Lo stream#119 viene utilizzato per la copia da host a dispositivo e lo stream#120 per la copia da dispositivo a host.

La traccia seguente mostra le caratteristiche comuni di un modello performante.

image

Ad esempio, la sequenza temporale di calcolo della GPU ( Stream#118 ) sembra "occupata" con pochissime lacune. Sono presenti copie minime da host a dispositivo ( Stream #119 ) e da dispositivo a host ( Stream #120 ), nonché intervalli minimi tra i passaggi. Quando esegui il Profiler per il tuo programma, potresti non essere in grado di identificare queste caratteristiche ideali nella visualizzazione della traccia. Il resto di questa guida copre scenari comuni e come risolverli.

1. Eseguire il debug della pipeline di input

Il primo passaggio nel debug delle prestazioni della GPU è determinare se il programma è vincolato all'input. Il modo più semplice per capirlo è utilizzare l' analizzatore della pipeline di input di Profiler, su TensorBoard, che fornisce una panoramica del tempo trascorso nella pipeline di input.

image

Puoi intraprendere le seguenti azioni potenziali se la tua pipeline di input contribuisce in modo significativo al tempo del passaggio:

  • Puoi utilizzare la guida specifica tf.data per apprendere come eseguire il debug della pipeline di input.
  • Un altro modo rapido per verificare se la pipeline di input costituisce il collo di bottiglia consiste nell'utilizzare dati di input generati casualmente che non necessitano di pre-elaborazione. Ecco un esempio di utilizzo di questa tecnica per un modello ResNet. Se la pipeline di input è ottimale, dovresti ottenere prestazioni simili con dati reali e con dati casuali/sintetici generati. L'unico sovraccarico nel caso dei dati sintetici sarà dovuto alla copia dei dati di input che, ancora una volta, può essere precaricata e ottimizzata.

Inoltre, fare riferimento alle migliori pratiche per ottimizzare la pipeline dei dati di input .

2. Eseguire il debug delle prestazioni di una GPU

Esistono diversi fattori che possono contribuire a un basso utilizzo della GPU. Di seguito sono riportati alcuni scenari comunemente osservati quando si utilizza il visualizzatore di tracce e le potenziali soluzioni.

1. Analizzare gli spazi tra i passaggi

Un'osservazione comune quando il programma non viene eseguito in modo ottimale sono gli intervalli tra le fasi dell'allenamento. Nell'immagine della vista traccia qui sotto, c'è un ampio intervallo tra i passaggi 8 e 9, il che significa che la GPU è inattiva durante quel periodo.

image

Se il visualizzatore di tracce mostra ampi intervalli tra i passaggi, ciò potrebbe indicare che il programma è vincolato all'input. In tal caso dovresti fare riferimento alla sezione precedente sul debug della pipeline di input se non lo hai già fatto.

Tuttavia, anche con una pipeline di input ottimizzata, possono comunque verificarsi degli intervalli tra la fine di un passaggio e l'inizio di un altro a causa del conflitto dei thread della CPU. tf.data utilizza thread in background per parallelizzare l'elaborazione della pipeline. Questi thread potrebbero interferire con l'attività lato host della GPU che si verifica all'inizio di ogni passaggio, come la copia dei dati o la pianificazione delle operazioni della GPU.

Se noti grandi lacune sul lato host, che pianifica queste operazioni sulla GPU, puoi impostare la variabile di ambiente TF_GPU_THREAD_MODE=gpu_private . Ciò garantisce che i kernel GPU vengano avviati dai propri thread dedicati e non vengano messi in coda dietro il lavoro tf.data .

Gli intervalli tra i passaggi possono essere causati anche da calcoli metrici, callback Keras o operazioni esterne a tf.function eseguite sull'host. Queste operazioni non hanno prestazioni buone quanto le operazioni all'interno di un grafico TensorFlow. Inoltre, alcune di queste operazioni vengono eseguite sulla CPU e copiano i tensori avanti e indietro dalla GPU.

Se dopo aver ottimizzato la pipeline di input noti ancora degli spazi vuoti tra i passaggi nel visualizzatore di traccia, dovresti esaminare il codice del modello tra i passaggi e verificare se la disabilitazione di callback/metriche migliora le prestazioni. Alcuni dettagli di queste operazioni si trovano anche nel visualizzatore di traccia (sia lato dispositivo che host). La raccomandazione in questo scenario è di ammortizzare il sovraccarico di queste operazioni eseguendole dopo un numero fisso di passaggi anziché a ogni passaggio. Quando si utilizza il metodo Model.compile nell'API tf.keras , l'impostazione del steps_per_execution esegue questa operazione automaticamente. Per cicli di addestramento personalizzati, utilizzare tf.while_loop .

2. Ottieni un maggiore utilizzo del dispositivo

1. Kernel GPU di piccole dimensioni e ritardi nel lancio del kernel host

L'host accoda i kernel affinché vengano eseguiti sulla GPU, ma è necessaria una latenza (circa 20-40 μs) prima che i kernel vengano effettivamente eseguiti sulla GPU. In un caso ideale, l'host accoda un numero sufficiente di kernel sulla GPU in modo tale che la GPU trascorra la maggior parte del tempo in esecuzione, anziché attendere che l'host accoda più kernel.

La pagina di panoramica del Profiler su TensorBoard mostra per quanto tempo la GPU è rimasta inattiva a causa dell'attesa che l'host avviasse i kernel. Nell'immagine seguente, la GPU è inattiva per circa il 10% del tempo in attesa dell'avvio dei kernel.

image

Il visualizzatore di tracce per questo stesso programma mostra piccoli spazi tra i kernel in cui l'host è impegnato ad avviare i kernel sulla GPU.

image

Avviando molte piccole operazioni sulla GPU (come un'aggiunta scalare, ad esempio), l'host potrebbe non tenere il passo con la GPU. Lo strumento TensorFlow Stats in TensorBoard per lo stesso profilo mostra 126.224 operazioni Mul che impiegano 2,77 secondi. Pertanto, ciascun kernel dura circa 21,9 μs, che è molto piccolo (più o meno lo stesso tempo della latenza di lancio) e può potenzialmente provocare ritardi nel lancio del kernel host.

image

Se il tuo visualizzatore di tracce mostra molti piccoli intervalli tra le operazioni sulla GPU come nell'immagine sopra, puoi:

  • Concatena piccoli tensori e usa operazioni vettorizzate o usa una dimensione batch più grande per fare in modo che ogni kernel lanciato faccia più lavoro, il che manterrà la GPU impegnata più a lungo.
  • Assicurati di utilizzare tf.function per creare grafici TensorFlow, in modo da non eseguire operazioni in modalità pura entusiasmo. Se stai utilizzando Model.fit (in contrapposizione a un ciclo di addestramento personalizzato con tf.GradientTape ), tf.keras.Model.compile lo farà automaticamente per te.
  • Fondi i kernel utilizzando XLA con tf.function(jit_compile=True) o il clustering automatico. Per ulteriori dettagli, vai alla sezione Abilita precisione mista e XLA di seguito per scoprire come abilitare XLA per ottenere prestazioni più elevate. Questa funzionalità può comportare un utilizzo elevato del dispositivo.
2. Posizionamento operativo TensorFlow

La pagina di panoramica del Profiler mostra la percentuale di operazioni posizionate sull'host rispetto al dispositivo (puoi anche verificare il posizionamento di operazioni specifiche osservando il visualizzatore di tracce . Come nell'immagine seguente, vuoi la percentuale di operazioni sull'host essere molto piccolo rispetto al dispositivo.

image

Idealmente, la maggior parte delle operazioni ad alta intensità di calcolo dovrebbero essere collocate sulla GPU.

Per scoprire a quali dispositivi sono assegnati le operazioni e i tensori nel tuo modello, imposta tf.debugging.set_log_device_placement(True) come prima istruzione del tuo programma.

Tieni presente che in alcuni casi, anche se specifichi un'operazione da posizionare su un particolare dispositivo, la sua implementazione potrebbe sovrascrivere questa condizione (esempio: tf.unique ). Anche per l'addestramento su GPU singola, specificare una strategia di distribuzione, come tf.distribute.OneDeviceStrategy , può comportare un posizionamento più deterministico delle operazioni sul dispositivo.

Uno dei motivi per cui la maggior parte delle operazioni viene eseguita sulla GPU è impedire un numero eccessivo di copie di memoria tra l'host e il dispositivo (sono previste copie di memoria per i dati di input/output del modello tra host e dispositivo). Un esempio di copia eccessiva è dimostrato nella visualizzazione traccia di seguito sui flussi GPU #167 , #168 e #169 .

image

Queste copie a volte possono compromettere le prestazioni se bloccano l'esecuzione dei kernel GPU. Le operazioni di copia della memoria nel visualizzatore di tracce hanno più informazioni sulle operazioni che sono l'origine di questi tensori copiati, ma potrebbe non essere sempre facile associare un memCopy a un op. In questi casi, è utile controllare gli operatori nelle vicinanze per verificare se la copia della memoria avviene nella stessa posizione in ogni passaggio.

3. Kernel più efficienti sulle GPU

Una volta che l'utilizzo della GPU del tuo programma è accettabile, il passaggio successivo è esaminare l'aumento dell'efficienza dei kernel GPU utilizzando Tensor Core o fondendo le operazioni.

1. Utilizzare i nuclei tensoriali

Le moderne GPU NVIDIA® dispongono di Tensor Core specializzati che possono migliorare significativamente le prestazioni dei kernel idonei.

Puoi utilizzare le statistiche del kernel GPU di TensorBoard per visualizzare quali kernel GPU sono idonei per Tensor Core e quali kernel utilizzano Tensor Core. Abilitare fp16 (vedere la sezione Abilitazione di precisione mista di seguito) è un modo per fare in modo che i kernel GEMM (General Matrix Multiply) del programma (matmul ops) utilizzino Tensor Core. I kernel GPU utilizzano i Tensor Core in modo efficiente quando la precisione è fp16 e le dimensioni del tensore di input/output sono divisibili per 8 o 16 (per int8 ).

Per altri consigli dettagliati su come rendere efficienti i kernel per le GPU, fare riferimento alla guida alle prestazioni del deep learning NVIDIA® .

2. Operazioni fusibili

Utilizza tf.function(jit_compile=True) per fondere operazioni più piccole per formare kernel più grandi che portano a miglioramenti significativi delle prestazioni. Per saperne di più fare riferimento alla guida XLA .

3. Abilita precisione mista e XLA

Dopo aver seguito i passaggi precedenti, abilitare la precisione mista e XLA sono due passaggi facoltativi che puoi eseguire per migliorare ulteriormente le prestazioni. L'approccio suggerito è abilitarli uno per uno e verificare che i vantaggi in termini di prestazioni siano quelli previsti.

1. Abilita la precisione mista

La guida alla precisione mista di TensorFlow mostra come abilitare la precisione fp16 sulle GPU. Abilita AMP sulle GPU NVIDIA® per utilizzare Tensor Core e ottenere velocità complessive fino a 3 volte superiori rispetto all'utilizzo della sola precisione fp32 (float32) su Volta e sulle architetture GPU più recenti.

Assicurati che le dimensioni della matrice/tensore soddisfino i requisiti per la chiamata dei kernel che utilizzano Tensor Core. I kernel GPU utilizzano i Tensor Core in modo efficiente quando la precisione è fp16 e le dimensioni di input/output sono divisibili per 8 o 16 (per int8).

Tieni presente che con cuDNN v7.6.3 e versioni successive, le dimensioni di convoluzione verranno automaticamente riempite dove necessario per sfruttare i Tensor Core.

Seguire le migliori pratiche riportate di seguito per massimizzare i vantaggi prestazionali della precisione fp16 .

1. Utilizzare kernel fp16 ottimali

Con fp16 abilitato, i kernel GEMM (Matrix Multiplications) del tuo programma dovrebbero utilizzare la versione fp16 corrispondente che utilizza i Tensor Core. Tuttavia, in alcuni casi, ciò non accade e non si riscontra l'accelerazione prevista dall'abilitazione fp16 , poiché il programma ricorre invece a un'implementazione inefficiente.

image

La pagina delle statistiche del kernel GPU mostra quali operazioni sono idonee a Tensor Core e quali kernel utilizzano effettivamente l'efficiente Tensor Core. La guida NVIDIA® sulle prestazioni di deep learning contiene ulteriori suggerimenti su come sfruttare i Tensor Core. Inoltre, i vantaggi derivanti dall'utilizzo fp16 verranno mostrati anche nei kernel che in precedenza erano vincolati alla memoria, poiché ora le operazioni richiederanno la metà del tempo.

2. Scalabilità delle perdite dinamiche e statiche

Il ridimensionamento delle perdite è necessario quando si utilizza fp16 per prevenire l'underflow dovuto alla scarsa precisione. Esistono due tipi di ridimensionamento delle perdite, dinamico e statico, entrambi spiegati in maggior dettaglio nella guida Precisione mista . È possibile utilizzare la policy mixed_float16 per abilitare automaticamente il ridimensionamento delle perdite all'interno dell'ottimizzatore Keras.

Quando si tenta di ottimizzare le prestazioni, è importante ricordare che il ridimensionamento dinamico delle perdite può introdurre ulteriori operazioni condizionali eseguite sull'host e portare a lacune visibili tra i passaggi nel visualizzatore di traccia. D'altra parte, il ridimensionamento delle perdite statiche non presenta tali costi generali e può essere un'opzione migliore in termini di prestazioni con il problema che è necessario specificare il corretto valore di scala delle perdite statiche.

2. Abilita XLA con tf.function(jit_compile=True) o il clustering automatico

Come passaggio finale per ottenere le migliori prestazioni con una singola GPU, puoi sperimentare l'abilitazione di XLA, che unirà le operazioni e porterà a un migliore utilizzo del dispositivo e a un minore ingombro di memoria. Per dettagli su come abilitare XLA nel tuo programma con tf.function(jit_compile=True) o il clustering automatico, fai riferimento alla guida XLA .

È possibile impostare il livello JIT globale su -1 (disattivato), 1 o 2 . Un livello più alto è più aggressivo e può ridurre il parallelismo e utilizzare più memoria. Imposta il valore su 1 se hai limitazioni di memoria. Si noti che XLA non funziona bene per i modelli con forme di tensori di input variabili poiché il compilatore XLA dovrebbe continuare a compilare i kernel ogni volta che incontra nuove forme.

2. Ottimizza le prestazioni sul singolo host multi-GPU

L'API tf.distribute.MirroredStrategy può essere utilizzata per scalare l'addestramento del modello da una GPU a più GPU su un singolo host. (Per ulteriori informazioni su come eseguire la formazione distribuita con TensorFlow, fare riferimento alle guide Formazione distribuita con TensorFlow , Utilizzo di una GPU e Utilizzo di TPU e al tutorial Formazione distribuita con Keras .)

Sebbene la transizione da una GPU a più GPU dovrebbe idealmente essere scalabile immediatamente, a volte potresti riscontrare problemi di prestazioni.

Quando si passa dall'addestramento con una singola GPU a più GPU sullo stesso host, idealmente dovresti sperimentare il ridimensionamento delle prestazioni con solo il sovraccarico aggiuntivo della comunicazione gradiente e un maggiore utilizzo del thread host. A causa di questo sovraccarico, non avrai un aumento esatto della velocità 2x se, ad esempio, passi da 1 a 2 GPU.

La visualizzazione della traccia seguente mostra un esempio del sovraccarico di comunicazione aggiuntivo durante l'addestramento su più GPU. È necessario un sovraccarico per concatenare i gradienti, comunicarli tra le repliche e dividerli prima di eseguire l'aggiornamento del peso.

image

La seguente lista di controllo ti aiuterà a ottenere prestazioni migliori quando ottimizzi le prestazioni nello scenario multi-GPU:

  1. Prova a massimizzare la dimensione del batch, il che porterà a un maggiore utilizzo dei dispositivi e ad ammortizzare i costi di comunicazione su più GPU. L'uso del profiler della memoria aiuta a capire quanto il tuo programma è vicino al picco di utilizzo della memoria. Si noti che, sebbene una dimensione batch maggiore possa influire sulla convergenza, ciò è generalmente controbilanciato dai vantaggi in termini di prestazioni.
  2. Quando si passa da una singola GPU a più GPU, lo stesso host ora deve elaborare molti più dati di input. Pertanto, dopo il punto (1), si consiglia di ricontrollare le prestazioni della pipeline di input e assicurarsi che non si tratti di un collo di bottiglia.
  3. Controlla la sequenza temporale della GPU nella visualizzazione traccia del tuo programma per eventuali chiamate AllReduce non necessarie, poiché ciò si traduce in una sincronizzazione su tutti i dispositivi. Nella vista traccia mostrata sopra, AllReduce viene eseguito tramite il kernel NCCL ed è presente una sola chiamata NCCL su ciascuna GPU per i gradienti in ogni passaggio.
  4. Verificare la presenza di operazioni di copia D2H, H2D e D2D non necessarie che possono essere ridotte al minimo.
  5. Controlla il tempo del passaggio per assicurarti che ogni replica stia eseguendo lo stesso lavoro. Ad esempio, può succedere che una GPU (tipicamente GPU0 ) venga sovrascritta perché l'host finisce erroneamente per dedicarvi più lavoro.
  6. Infine, controlla la fase di addestramento su tutte le GPU nella visualizzazione traccia per eventuali operazioni eseguite in sequenza. Questo di solito accade quando il tuo programma include dipendenze di controllo da una GPU a un'altra. In passato, il debug delle prestazioni in questa situazione veniva risolto caso per caso. Se osservi questo comportamento nel tuo programma, invia un problema a GitHub con le immagini della tua visualizzazione di traccia.

1. Ottimizza il gradiente AllReduce

Quando si allena con una strategia sincrona, ogni dispositivo riceve una parte dei dati di input.

Dopo aver calcolato i passaggi avanti e indietro attraverso il modello, i gradienti calcolati su ciascun dispositivo devono essere aggregati e ridotti. Questo gradiente AllReduce avviene dopo il calcolo del gradiente su ciascun dispositivo e prima che l'ottimizzatore aggiorni i pesi del modello.

Ciascuna GPU concatena innanzitutto i gradienti tra i livelli del modello, li comunica tra le GPU utilizzando tf.distribute.CrossDeviceOps ( tf.distribute.NcclAllReduce è l'impostazione predefinita), quindi restituisce i gradienti dopo la riduzione per livello.

L'ottimizzatore utilizzerà questi gradienti ridotti per aggiornare i pesi del modello. Idealmente, questo processo dovrebbe avvenire contemporaneamente su tutte le GPU per evitare eventuali sovraccarichi.

Il tempo necessario per AllReduce dovrebbe essere approssimativamente lo stesso di:

(number of parameters * 4bytes)/ (communication bandwidth)

Questo calcolo è utile come controllo rapido per comprendere se le prestazioni ottenute durante l'esecuzione di un processo di formazione distribuito sono quelle previste o se è necessario eseguire un ulteriore debug delle prestazioni. Puoi ottenere il numero di parametri nel tuo modello da Model.summary .

Tieni presente che ciascun parametro del modello ha una dimensione di 4 byte poiché TensorFlow utilizza fp32 (float32) per comunicare i gradienti. Anche quando hai abilitato fp16 , NCCL AllReduce utilizza i parametri fp32 .

Per ottenere i vantaggi del ridimensionamento, il tempo di passaggio deve essere molto più elevato rispetto a questi costi generali. Un modo per raggiungere questo obiettivo è utilizzare una dimensione batch più elevata poiché la dimensione batch influisce sul tempo del passaggio, ma non influisce sul sovraccarico della comunicazione.

2. Conflitto del thread host della GPU

Quando si eseguono più GPU, il compito della CPU è mantenere occupati tutti i dispositivi avviando in modo efficiente i kernel GPU sui dispositivi.

Tuttavia, quando ci sono molte operazioni indipendenti che la CPU può pianificare su una GPU, la CPU può decidere di utilizzare molti dei suoi thread host per mantenere occupata una GPU e quindi avviare i kernel su un'altra GPU in un ordine non deterministico . Ciò può causare una distorsione o un ridimensionamento negativo, che può influire negativamente sulle prestazioni.

Il visualizzatore di tracce riportato di seguito mostra l'overhead quando la CPU scagliona l'avvio del kernel GPU in modo inefficiente, poiché GPU1 è inattiva e quindi inizia a eseguire operazioni dopo l'avvio di GPU2 .

image

La vista di traccia per l'host mostra che l'host sta avviando i kernel su GPU2 prima di avviarli su GPU1 (nota che le operazioni tf_Compute* riportate di seguito non sono indicative dei thread della CPU).

image

Se riscontri questo tipo di scaglionamento dei kernel GPU nella visualizzazione traccia del tuo programma, l'azione consigliata è quella di:

  • Imposta la variabile di ambiente TensorFlow TF_GPU_THREAD_MODE su gpu_private . Questa variabile di ambiente dirà all'host di mantenere privati ​​i thread per una GPU.
  • Per impostazione predefinita, TF_GPU_THREAD_MODE=gpu_private imposta il numero di thread su 2, che è sufficiente nella maggior parte dei casi. Tuttavia, quel numero può essere modificato impostando la variabile di ambiente TensorFlow TF_GPU_THREAD_COUNT sul numero desiderato di thread.