Ottimizza le prestazioni della GPU TensorFlow con TensorFlow Profiler

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

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ù delle tue GPU sono sottoutilizzate.

Se non conosci il Profiler:

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

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

Flusso di lavoro di ottimizzazione delle prestazioni

Questa guida illustra come eseguire il debug dei problemi di prestazioni a partire da 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. Verificare se la pipeline di input è un collo di bottiglia.
    2. Esegui il debug delle prestazioni di una GPU.
    3. Abilita la precisione mista (con fp16 (float16)) e opzionalmente abilita XLA .
  2. Ottimizza ed esegui il debug delle prestazioni sull'host singolo multi-GPU.

Ad esempio, se si utilizza una strategia di distribuzione TensorFlow per addestrare un modello su un singolo host con più GPU e si nota un utilizzo non ottimale della GPU, è necessario innanzitutto ottimizzare ed eseguire il debug delle prestazioni per una GPU prima di eseguire il debug del sistema multi-GPU.

Come base per ottenere codice efficiente sulle GPU, questa guida presuppone che tu stia già utilizzando tf.function . Le API Keras Model.compile e Model.fit utilizzeranno automaticamente la tf.function . Quando si scrive un ciclo di allenamento personalizzato con tf.GradientTape , fare riferimento a Prestazioni migliori con tf.function su come abilitare tf.function s.

Le sezioni successive discutono gli approcci suggeriti per ciascuno degli scenari precedenti per aiutare a identificare e correggere i colli di bottiglia delle prestazioni.

1. Ottimizza le prestazioni su una GPU

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

Il primo passaggio 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 livello superiore delle prestazioni del modello durante l'esecuzione del profilo, può fornire un'idea di quanto sia lontano il programma dallo scenario ideale.

TensorFlow Profiler Overview Page

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

  1. Quanta parte del tempo di passaggio deriva dall'effettiva esecuzione del dispositivo
  2. La percentuale di operazioni effettuate sul dispositivo rispetto all'host
  3. Quanti kernel usano 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 del visualizzatore di tracce che dovresti 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 fare in modo che le operazioni siano in esecuzione sulla GPU accanto a ogni Stream , che fanno riferimento ai flussi CUDA. Ogni flusso viene utilizzato per attività specifiche. In questa traccia, Stream#118 viene utilizzato per avviare kernel di calcolo e copie da dispositivo a dispositivo. Stream#119 viene utilizzato per la copia da host a dispositivo e 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 pochissimi intervalli. Sono disponibili copie minime da host a dispositivo ( Stream #119 ) e da dispositivo a host ( Stream #120 ), nonché intervalli minimi tra i passaggi. Quando esegui Profiler per il tuo programma, potresti non essere in grado di identificare queste caratteristiche ideali nella tua visualizzazione di 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 potenziali azioni se la tua pipeline di input contribuisce in modo significativo al tempo di passaggio:

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

Inoltre, fare riferimento alle best practice per l'ottimizzazione della pipeline dei dati di input .

2. Eseguire il debug delle prestazioni di una GPU

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

1. Analizza le lacune tra i passaggi

Un'osservazione comune quando il tuo programma non funziona in modo ottimale sono le lacune tra le fasi dell'allenamento. Nell'immagine della vista della traccia in basso, c'è un grande divario tra i passaggi 8 e 9, il che significa che la GPU è inattiva durante quel periodo.

image

Se il tuo visualizzatore di tracce mostra grandi divari tra i passaggi, questo potrebbe essere un'indicazione che il tuo programma è vincolato all'input. In tal caso dovresti fare riferimento alla sezione precedente sul debug della pipeline di input se non l'hai già fatto.

Tuttavia, anche con una pipeline di input ottimizzata, è ancora possibile avere degli intervalli tra la fine di un passaggio e l'inizio di un altro a causa della contesa del thread della CPU. tf.data utilizza thread in background per parallelizzare l'elaborazione della pipeline. Questi thread possono 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 della GPU vengano avviati dai propri thread dedicati e non vengano accodati dietro il lavoro di tf.data .

Gli intervalli tra i passaggi possono anche essere causati da calcoli di parametri, callback Keras o operazioni al di fuori di tf.function eseguiti sull'host. Queste operazioni non hanno le stesse prestazioni delle 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 si notano ancora delle lacune tra i passaggi nel visualizzatore di traccia, è necessario 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 tracce (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é ogni passaggio. Quando si utilizza il metodo Model.compile nell'API tf.keras , l'impostazione del flag steps_per_execution lo fa automaticamente. Per cicli di formazione personalizzati, usa tf.while_loop .

2. Ottieni un maggiore utilizzo del dispositivo

1. Piccoli kernel GPU e ritardi di avvio del kernel host

L'host accoda i kernel da eseguire sulla GPU, ma è coinvolta una latenza (circa 20-40 μs) prima che i kernel vengano effettivamente eseguiti sulla GPU. In un caso ideale, l'host accoda abbastanza kernel sulla GPU in modo tale che la GPU trascorra la maggior parte del suo tempo in esecuzione, piuttosto che aspettare che l'host accoda più kernel.

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

image

Il visualizzatore di tracce per questo stesso programma mostra piccole lacune tra i kernel in cui l'host è impegnato nell'avvio dei 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 richiedono 2,77 secondi. Pertanto, ogni kernel è di circa 21,9 μs, che è molto piccolo (più o meno nello stesso periodo della latenza di avvio) e può potenzialmente causare ritardi nell'avvio 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 le operazioni vettoriali o usa una dimensione batch più grande per far lavorare di più ogni kernel lanciato, il che manterrà la GPU occupata più a lungo.
  • Assicurati di utilizzare tf.function per creare grafici TensorFlow, in modo da non eseguire le operazioni in una modalità desiderosa pura. Se stai usando Model.fit (al contrario di un ciclo di formazione personalizzato con tf.GradientTape ), allora tf.keras.Model.compile lo farà automaticamente per te.
  • Unisci i kernel usando XLA con tf.function(jit_compile=True) o auto-clustering. Per maggiori dettagli, vai alla sezione Abilita precisione mista e XLA di seguito per scoprire come abilitare XLA per ottenere prestazioni più elevate. Questa funzione può portare a un elevato utilizzo del dispositivo.
2. Posizionamento operativo TensorFlow

La pagina di panoramica del Profiler mostra la percentuale di operazioni piazzate sull'host rispetto al dispositivo (puoi anche verificare il posizionamento di operazioni specifiche guardando il visualizzatore di tracce . Come nell'immagine qui sotto, 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 dovrebbe essere posizionata sulla GPU.

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

Si noti che in alcuni casi, anche se si specifica un'operazione da posizionare su un particolare dispositivo, la sua implementazione potrebbe ignorare questa condizione (esempio: tf.unique ). Anche per il training su GPU singola, la specifica di una strategia di distribuzione, ad esempio tf.distribute.OneDeviceStrategy , può comportare un posizionamento più deterministico delle operazioni sul dispositivo.

Uno dei motivi per cui la maggior parte delle operazioni è posizionata sulla GPU è impedire copie di memoria eccessive 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 copiatura eccessiva è illustrato nella vista di traccia di seguito sugli stream GPU n. 167 , n. 168 e n. 169 .

image

Queste copie a volte possono danneggiare le prestazioni se bloccano l'esecuzione dei kernel della 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'operazione. In questi casi, è utile guardare le operazioni 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 consiste nell'aumentare l'efficienza dei kernel GPU utilizzando Tensor Core o fondendo le operazioni.

1. Utilizzare i Tensor Core

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

È possibile utilizzare le statistiche del kernel della GPU di TensorBoard per visualizzare quali kernel della GPU sono idonei per Tensor Core e quali kernel utilizzano Tensor Core. L'abilitazione fp16 (vedere la sezione Abilitazione della precisione mista di seguito) è un modo per fare in modo che i kernel General Matrix Multiply (GEMM) del programma (matmul ops) utilizzino il 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 i kernel efficienti per le GPU, fare riferimento alla guida alle prestazioni del deep learning NVIDIA® .

2. Fusibile op

Usa tf.function(jit_compile=True) per fondere operazioni più piccole per formare kernel più grandi che portano a significativi miglioramenti 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 opzionali che puoi eseguire per migliorare ulteriormente le prestazioni. L'approccio suggerito è di abilitarli uno per uno e verificare che i benefici in termini di prestazioni siano quelli attesi.

1. Abilita la precisione mista

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

Assicurati che le dimensioni matrice/tensore soddisfino i requisiti per chiamare i kernel che usano 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).

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

Segui le migliori pratiche di seguito per massimizzare i vantaggi in termini di prestazioni della precisione fp16 .

1. Usa kernel FP16 ottimali

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

image

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

2. Ridimensionamento dinamico e statico delle perdite

Il ridimensionamento della perdita è necessario quando si utilizza fp16 per prevenire l'underflow dovuto alla bassa precisione. Esistono due tipi di ridimensionamento delle perdite, dinamico e statico, entrambi spiegati in modo più dettagliato nella guida Mixed Precision . È possibile utilizzare il criterio 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 operazioni condizionali aggiuntive eseguite sull'host e causare lacune che saranno visibili tra i passaggi nel visualizzatore di tracce. D'altra parte, il ridimensionamento della perdita statica non ha tali spese generali e può essere un'opzione migliore in termini di prestazioni con il catch necessario per specificare il valore di scala della perdita statica corretto.

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

Come passaggio finale per ottenere le migliori prestazioni con una singola GPU, puoi sperimentare con l'abilitazione di XLA, che fonderà le operazioni e porterà a un migliore utilizzo del dispositivo e a un footprint di memoria inferiore. Per i 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 (off), 1 o 2 . Un livello più alto è più aggressivo e può ridurre il parallelismo e utilizzare più memoria. Impostare il valore su 1 se si hanno limitazioni di memoria. Si noti che XLA non funziona bene per i modelli con forme tensoriali di input variabili poiché il compilatore XLA dovrebbe continuare a compilare i kernel ogni volta che incontra nuove forme.

2. Ottimizza le prestazioni sull'host singolo multi-GPU

L'API tf.distribute.MirroredStrategy può essere utilizzata per scalare il training 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 , Utilizzare una GPU e Utilizzare TPU e il tutorial Formazione distribuita con Keras .)

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

Quando si passa dall'allenamento 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 dell'host. A causa di questo sovraccarico, non avrai un'esatta velocità 2x se passi da 1 a 2 GPU, ad esempio.

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

image

Il seguente elenco di controllo ti aiuterà a ottenere prestazioni migliori durante l'ottimizzazione delle prestazioni nello scenario multi-GPU:

  1. Cerca di massimizzare la dimensione del batch, che porterà a un maggiore utilizzo del dispositivo e ad ammortizzare i costi di comunicazione tra più GPU. L'utilizzo del profiler della memoria consente di avere un'idea di quanto il programma sia vicino al picco di utilizzo della memoria. Si noti che mentre una dimensione batch più elevata può 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. Quindi, dopo (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 di traccia del tuo programma per eventuali chiamate AllReduce non necessarie, poiché ciò comporta una sincronizzazione su tutti i dispositivi. Nella vista della traccia mostrata sopra, AllReduce viene eseguito tramite il kernel NCCL e c'è solo una chiamata NCCL su ciascuna GPU per i gradienti in ogni passaggio.
  4. Verifica la presenza di operazioni di copia D2H, H2D e D2D non necessarie che possono essere ridotte a icona.
  5. Controlla il tempo di passaggio per assicurarti che ogni replica stia facendo lo stesso lavoro. Ad esempio, può succedere che una GPU (in genere, GPU0 ) sia sottoscritta in eccesso perché l'host finisce erroneamente per lavorarci di più.
  6. Infine, controlla il passaggio di addestramento su tutte le GPU nella visualizzazione di traccia per eventuali operazioni in esecuzione in sequenza. Questo di solito accade quando il tuo programma include dipendenze di controllo da una GPU all'altra. In passato, il debug delle prestazioni in questa situazione è stato risolto caso per caso. Se osservi questo comportamento nel tuo programma, segnala un problema con GitHub con le immagini della tua visualizzazione di traccia.

1. Ottimizza il gradiente AllReduce

Durante l'allenamento con una strategia sincrona, ogni dispositivo riceve una parte dei dati di input.

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

Ciascuna GPU prima concatena 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 costi generali.

Il tempo per AllReduce dovrebbe essere approssimativamente lo stesso di:

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

Questo calcolo è utile come controllo rapido per capire se le prestazioni ottenute durante l'esecuzione di un processo di addestramento 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 .

Si noti che ogni 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 ottenere questo risultato consiste nell'utilizzare una dimensione del batch più elevata poiché la dimensione del batch influisce sul tempo di passaggio, ma non influisce sul sovraccarico di comunicazione.

2. Contesa 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 su tutti i dispositivi.

Tuttavia, quando ci sono molte operazioni indipendenti che la CPU può programmare 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 un'inclinazione o un ridimensionamento negativo, che può influire negativamente sulle prestazioni.

Il visualizzatore di tracce di seguito mostra l'overhead quando la CPU sfalsa il kernel della GPU viene avviato in modo inefficiente, poiché la GPU1 è inattiva e quindi inizia a eseguire le operazioni dopo l'avvio di GPU2 .

image

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

image

Se si verifica questo tipo di sfalsamento dei kernel GPU nella visualizzazione di traccia del programma, l'azione consigliata è:

  • Impostare 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, tale numero può essere modificato impostando la variabile di ambiente TensorFlow TF_GPU_THREAD_COUNT sul numero di thread desiderato.