SparseCore è un processore a blocchi specializzato progettato per l'accelerazione ad alte prestazioni di carichi di lavoro che comportano accesso e calcolo irregolari e sparsi della memoria, in particolare su grandi set di dati archiviati nella memoria ad alta larghezza di banda (HBM). Sebbene eccella in attività come l'incorporamento di ricerche, le sue funzionalità si estendono all'accelerazione di una serie di altri workload dinamici e sparsi.
1. Introduzione a SparseCore
Caratteristiche architettoniche principali:
- Architettura a riquadri: comprende più riquadri di calcolo (ogni riquadro è un'unità Dataflow completa con memoria locale e unità di elaborazione proprie) che consentono l'elaborazione parallela.
- Esecuzione dinamica: supporta in modo nativo il flusso di controllo e gli accessi alla memoria dipendenti dai dati, fondamentali per i dati sparsi.
- Elaborazione vettoriale: utilizza attività con vettori piccoli (8 o 16 elementi, a seconda della versione hardware) per un calcolo efficiente.
- Controllo centralizzato: un unico sequencer SparseCore orchestra le attività in tutti i riquadri, garantendo operazioni sincronizzate.
- Supporto del riepilogo dei dati: include operazioni cross-lane specializzate utili per attività come l'ordinamento, il filtraggio e le somme dei prefissi.
- Gerarchia di memoria: sfrutta strategicamente la HBM per l'archiviazione di grandi set di dati e la memoria scratchpad locale (SPMEM) per l'organizzazione temporanea dei dati a cui si accede di frequente, riducendo significativamente la latenza della HBM.
Specifiche in sintesi:
| Attributo | TPU v4 | TPU v5p | Trillium |
|---|---|---|---|
| SparseCores/Chip | 4 | 4 | 2 |
| Tiles/SparseCore | 16 | 16 | 16 |
| Larghezza SIMD | 8 | 8 | 8 (F32) 16 (BF16) |
| Capacità HBM | 32 GiB | 96 GiB | 32 GiB |
2. Preelaborazione host SparseCore
Una preparazione efficace dei dati è fondamentale per le prestazioni di SparseCore ed è qui che il pre-elaborazione dell'host svolge un ruolo fondamentale. Comprende diverse funzionalità chiave:
- Trasformazione dei dati:
- Applica le trasformazioni necessarie ai dati di input non elaborati.
- Gestisci le trasformazioni degli ID, il che è particolarmente importante quando si tratta di impilare funzionalità o tabelle.
- Converti i dati di input nel formato sparso Coordinate (COO), descritto nella sezione seguente.
- Partiziona i dati per una distribuzione efficiente tra i diversi SparseCore disponibili sul chip.
- Convalida del limite:
- Assicurati che le caratteristiche dei dati di input (ad esempio, il numero di ID) siano conformi ai limiti operativi predefiniti di SparseCore, ad esempio
max_ids_per_partitionemax_unique_ids_per_partition. - Se i dati di input superano questi limiti, il livello di preelaborazione dell'host può tentare di segmentare i dati in mini-batch più piccoli che rientrano nei vincoli.
- Assicurati che le caratteristiche dei dati di input (ad esempio, il numero di ID) siano conformi ai limiti operativi predefiniti di SparseCore, ad esempio
- Data Transfer:
- Copia in modo efficiente i dati elaborati e convalidati nella memoria ad alta larghezza di banda (HBM) della TPU, rendendoli pronti per l'esecuzione di SparseCore.
Informazioni sull'impilamento delle tabelle:
L'impilamento delle tabelle è una tecnica di ottimizzazione significativa in cui più tabelle di incorporamento vengono combinate logicamente per migliorare l'efficienza della ricerca degli incorporamenti. Questo processo viene in genere gestito automaticamente dal framework ML sottostante.
- Stacking di funzionalità: si verifica quando più funzionalità distinte condividono la stessa tabella di incorporamento sottostante. Un esempio comune è l'utilizzo di un unico dizionario di incorporamento per varie caratteristiche categoriche come i codici postali di contesti diversi.
- Impilamento delle tabelle: in questo scenario, più tabelle di incorporamento distinte vengono impilate insieme. Le tabelle che condividono la stessa dimensione di embedding e la stessa configurazione dell'ottimizzatore vengono spesso raggruppate.
Il vantaggio principale dell'impilamento delle tabelle è la creazione di una dimensione batch effettiva più grande per le operazioni su queste tabelle impilate. Ciò riduce il sovraccarico di calcolo e può essere efficace per nascondere le latenze di comunicazione tra chip (ICI). Per prestazioni ottimali, è consigliabile un numero moderato di tabelle in pila (generalmente compreso tra 5 e 100).
3. Conversione in tensori COO
Prima che i dati possano essere elaborati da SparseCore, vengono comunemente convertiti in un formato di tensore sparso Coordinate (COO). Il formato COO è un modo per rappresentare in modo efficiente le matrici sparse, in genere utilizzando tre array:
row_ids: un array contenente gli indici di riga per ogni elemento diverso da zero. Nel contesto dell'elaborazione in batch, questo spesso corrisponde alla dimensione batch.col_ids: un array contenente gli indici delle colonne per ogni elemento diverso da zero. Per gli incorporamenti, spesso si tratta dei valori delle caratteristiche o degli ID.values(facoltativo): un array contenente i valori effettivi degli elementi diversi da zero alle coordinate (row,col) corrispondenti. Per i calcoli dei limiti (descritti in seguito) relativi ai conteggi degli ID, questi valori (incrementi) spesso non vengono presi in considerazione.
Esempio illustrativo:
Considera una matrice sparsa di input che rappresenta batch di ID:
[
[id_A], // Sample 0
[id_A, id_B, id_C], // Sample 1
[id_B, id_B, id_D], // Sample 2 (note duplicate id_B)
]
Dopo la conversione al formato COO (e potenzialmente dopo la deduplicazione degli ID all'interno dello stesso campione):
row_ids = [0, 1, 1, 1, 2, 2]
col_ids = [id_A, id_A, id_B, id_C, id_B, id_D]
Questa conversione è fondamentale per il modo in cui SparseCore elabora e distribuisce il lavoro.
In particolare, col_ids sono fondamentali per determinare a quale partizione SparseCore specifica appartiene un ID, consentendo uno sharding e una ricerca efficienti.
4. SparsecoreConfig: l'API di alto livello
API di incorporamento specifiche per framework:
- JAX: https://github.com/jax-ml/jax-tpu-embedding
- TensorFlow: https://www.tensorflow.org/recommenders/api_docs/python/tfrs/layers/embedding/TPUEmbedding
- Keras: https://keras.io/keras_rs/api/embedding_layers/distributed_embedding
SparsecoreConfig o meccanismi equivalenti come i flag XLA, funge da
interfaccia di alto livello per controllare un'ampia gamma di comportamenti di SparseCore. Una
comprensione approfondita di questi parametri è fondamentale per un'ottimizzazione efficace del rendimento
e per garantire il corretto funzionamento dei modelli.
disable_table_stacking: bool = False- Spiegazione: questo flag controlla se l'impilamento automatico delle tabelle impedisce al framework di impilare le tabelle, il che potrebbe comportare una riduzione delle prestazioni a causa dell'aumento degli overhead e di una minore capacità di nascondere la latenza dell'interconnessione inter-chip (ICI).
- Predefinito:
False(il che implica che l'impilamento delle tabelle è generalmente abilitato per impostazione predefinita laddove il framework lo supporta).
max_ids_per_chip_per_sample: int = 64- Spiegazione: questo parametro stabilisce un limite superiore globale per il numero totale di ID incorporamento che un singolo chip può elaborare da un campione nel batch di input, aggregato in tutte le tabelle. È un meccanismo per gestire le risorse a livello di chip, prima che vengano presi in considerazione limiti più granulari per tabella o per partizione. La regolazione di questo valore dipende in genere dalle caratteristiche specifiche del modello e dalla capacità complessiva del sistema.
- Predefinito:
64.
max_ids_per_table: Optional[Dict[str, int]] = None- Spiegazione: questo parametro specifica il numero massimo di
ID incorporamento (che possono includere duplicati) che possono essere elaborati per
ogni tabella logica, considerando tutte le sue partizioni in tutti i
SparseCore. Si tratta di un limite più ampio rispetto a
max_ids_per_partition. Se una tabellaTè suddivisa inPpartizioni, questo limite si applica alla somma degli ID indirizzati a tutte le partizioni P. Spesso è correlato amax_ids_per_partition_per_samplee alla dimensione complessiva del batch. - Impostazione: in genere configurata utilizzando un file dei limiti (ad esempio, utilizzando il flag
xla_sparse_core_max_ids_file), in cui è definitomax_ids_per_partition. Questo concetto a livello di tabella è un metodo per impostare i limiti a livello di partizione (max_idsemax_uniques). - Predefinito:
None(il valore può essere dedotto dai limiti per partizione o da altre configurazioni se non fornito esplicitamente).
- Spiegazione: questo parametro specifica il numero massimo di
ID incorporamento (che possono includere duplicati) che possono essere elaborati per
ogni tabella logica, considerando tutte le sue partizioni in tutti i
SparseCore. Si tratta di un limite più ampio rispetto a
max_unique_ids_per_table: Optional[Dict[str, int]] = None- Spiegazione: analogo a
max_ids_per_table, ma questo parametro specifica il numero massimo di ID univoci per ogni tabella logica. Si tratta di un'impostazione fondamentale per dimensionare correttamente i buffer sul dispositivo utilizzati nell'elaborazione degli ID univoci e nelle successive operazioni sui vettori. - Impostazione: definita comunemente anche in un file dei limiti o derivata da
max_unique_ids_per_partition_per_sample. - Predefinito:
None.
- Spiegazione: analogo a
allow_id_dropping: bool = False- Spiegazione: questo flag booleano controlla l'eliminazione degli ID quando il numero di
ID riscontrati nei dati di input (limiti osservati) supera i limiti
impostati durante la compilazione (ad esempio,
max_ids_per_partition).- Se
True: gli ID che causerebbero il superamento dei limiti vengono eliminati automaticamente. In genere, gli ID all'interno di una partizione vengono elaborati in ordine ordinato e qualsiasi ID che superi il limite del conteggio corrente per il mini-batch designato viene eliminato. Ciò consente al programma di continuare l'esecuzione, ma potrebbe avere un impatto negativo sull'accuratezza del modello. - Se
False: viene attivato un errore e il processo probabilmente terminerà se i limiti osservati superano i limiti compilati. Questo approccio garantisce l'elaborazione di tutti i dati, ma richiede una configurazione più conservativa dei limiti.
- Se
- Predefinito:
False(causando un errore in caso di overflow anziché l'eliminazione silenziosa dei dati).
- Spiegazione: questo flag booleano controlla l'eliminazione degli ID quando il numero di
ID riscontrati nei dati di input (limiti osservati) supera i limiti
impostati durante la compilazione (ad esempio,
initialize_tables_on_host: bool = True- Spiegazione: questo flag determina se le tabelle di incorporamento vengono
inizializzate sulla CPU host prima di essere trasferite successivamente alla
memoria ad alta larghezza di banda (HBM) della TPU. La prassi standard prevede l'inizializzazione delle tabelle sull'host. Se imposti questo valore su
True, segui questa convenzione. Se fosse impostato suFalse, implicherebbe un meccanismo di inizializzazione sul dispositivo, che potrebbe avere implicazioni diverse in termini di prestazioni o prerequisiti di inizializzazione specifici.
- Spiegazione: questo flag determina se le tabelle di incorporamento vengono
inizializzate sulla CPU host prima di essere trasferite successivamente alla
memoria ad alta larghezza di banda (HBM) della TPU. La prassi standard prevede l'inizializzazione delle tabelle sull'host. Se imposti questo valore su
enable_fast_table_initialization: bool = False- Spiegazione: inizializza le tabelle direttamente sulla TPU. In questo modo è possibile ridurre i tempi di avvio del modello.
5. Pipelining per le prestazioni
Il pipelining è una tecnica di ottimizzazione delle prestazioni che consente l'esecuzione simultanea di operazioni su TensorCore (TC) e SparseCore (SC). Sovrapponendo questi calcoli, la velocità effettiva complessiva può essere migliorata in modo significativo.
- Meccanismo: in un passaggio di addestramento standard che prevede ricerche di incorporamento sparse (gestite da SC) e calcoli di livelli densi (gestiti da TC), il pipelining consente a SC di lavorare alla sua parte del passaggio
i(ad esempio, passaggi in avanti o indietro) mentre TC elabora contemporaneamente una parte diversa dello stesso passaggioio anche parti di passaggi adiacenti comei-1oi+1. - Impatto sui gradienti: SparseCore potrebbe operare su gradienti "obsoleti".
Ad esempio, i gradienti calcolati durante la fase di backpropagation del
passaggio
ipotrebbero non essere completamente aggiornati e visibili al SC fino al passaggioi+2. - Compromesso tra prestazioni e valori numerici: questa esecuzione sovrapposta può portare ad accelerazioni sostanziali, potenzialmente fino a un miglioramento di 2 volte del tempo di esecuzione del passaggio del dispositivo. Tuttavia, le lievi modifiche ai valori numerici (embedding_weights) derivanti dall'utilizzo di gradienti obsoleti potrebbero influenzare il comportamento di convergenza del modello o l'accuratezza finale raggiunta. L'accettabilità di questo compromesso dipende molto dal modello e spesso richiede una convalida empirica.
- Control flag: il pipelining può essere controllato da
tf_xla_disable_full_embedding_pipelining. Se imposti questo flag sutrue, viene disattivato il pipelining completo (sovrapposizione del calcolo di TensorCore e SparseCore), mentre se lo imposti sufalse(o se la semantica del flag implica l'attivazione quando è false), viene attivato.
Flusso concettuale del pipelining:
Senza pipelining (flusso sequenziale semplificato):
Loop: SC/F_i -> TC/F_i -> TC/B_i -> SC/B_iCon il pipelining (flusso sovrapposto semplificato):
Time -> Step i: SC/F_i | TC/F_i | TC/B_i | SC/B_i Step i+1: SC/F_i+1| TC/F_i+1| TC/B_i+1| SC/B_i+1Nota: le fasi di pipelining effettive implementate nell'hardware e nel compilatore possono essere più complesse e spesso includono pre-loop, loop di esecuzione principali e post-loop per gestire le dipendenze dei dati e garantire la correttezza.
6. Il ruolo di XLA
XLA (Accelerated Linear Algebra) è il compilatore specifico per il dominio che traduce grafici computazionali di alto livello, in genere da framework come TensorFlow, in codice macchina altamente ottimizzato e personalizzato per le TPU. Ciò include la generazione delle istruzioni per le operazioni destinate a SparseCore.
Funzioni chiave nel contesto di SparseCore:
- Compilazione di operazioni sparse: XLA è responsabile della compilazione
delle operazioni di ricerca di incorporamento (come
SparseDenseMatmulOp) e di altri calcoli sparsi in programmi SparseCore eseguibili di basso livello. - Integrazione dei limiti: utilizza i limiti operativi configurati
(ad esempio
max_ids_per_partition,max_unique_ids_per_partition, spesso forniti tramite un file dei limiti specificato da flag comexla_sparse_core_max_ids_file) per determinare staticamente le dimensioni e allocare i buffer di memoria sul dispositivo, in particolare all'interno di SPMEM. - Ottimizzazioni mirate: XLA esegue una serie di ottimizzazioni progettate specificamente per l'architettura SparseCore. Questi possono includere la pianificazione delle istruzioni, le trasformazioni del layout della memoria e la fusione delle operazioni per massimizzare l'efficienza.
- Controllo tramite flag: molti aspetti del comportamento di SparseCore, dei parametri di ottimizzazione e delle strategie di ottimizzazione sono esposti e controllati tramite i flag XLA (ad esempio
xla_sparse_core_estimate_max_idsper la stima dei limiti oxla_sc_detect_nanper il debug).
Stato open source:
Al momento l'implementazione di Sparsecore è interna e viene eseguita utilizzando libtpu.so.
Segnalazione e diagnostica degli errori:
Gli errori di compilazione correlati alle configurazioni SparseCore o ai vincoli
delle risorse spesso si manifestano come errori di compilazione XLA:TPU. Questi messaggi
di errore possono fornire informazioni preziose su problemi come limiti impostati troppo
alti per la SPMEM disponibile o l'utilizzo di configurazioni non supportate.
7. Come si traducono i limiti in tabelle su SparseCore
Su SparseCore, i "limiti" sono parametri di configurazione fondamentali che si riferiscono principalmente a due impostazioni per partizione per ogni tabella partizionata (distribuita) tra gli SparseCore disponibili:
max_ids_per_partition: definisce il numero massimo di ID totali (inclusi i duplicati) che un singolo SparseCore deve inviare o elaborare per una partizione specifica di una determinata tabella in un singolo passaggio di calcolo.max_unique_ids_per_partition: definisce il numero massimo di ID univoci che un singolo SparseCore deve inviare o elaborare per un
Traduzione nel layout e nell'elaborazione della tabella fisica:
- Strategia di partizionamento delle tabelle: le tabelle di incorporamento vengono in genere "partizionate in base al modulo"
in tutti gli SparseCore del sistema. Ciò significa che ogni SparseCore diventa
responsabile di un sottoinsieme distinto del vocabolario (righe) di ogni tabella. Un
ID
jviene generalmente assegnato aSparseCore_kin base a una formula comek = j % num_total_sparse_cores. - Definizione di "partizione": in questo contesto, una "partizione" si riferisce al segmento specifico di una tabella di incorporamento per cui una singola SparseCore gestisce le ricerche.
- Allocazione del buffer SPMEM: questi limiti vengono utilizzati dal compilatore XLA per dimensionare e allocare staticamente i buffer all'interno della memoria scratchpad sul dispositivo (SPMEM). I buffer sono dimensionati in modo che tutti i dati necessari relativi agli
ID per una determinata partizione (fino ai limiti specificati di
max_idsemax_unique_ids) possano essere caricati in SPMEM per l'elaborazione. Ciò è particolarmente importante per i calcoli non elementwise, ad esempio la riduzione di ID duplicati all'interno di una partizione (ad esempio, quando si crea una rappresentazione CSR), in cui l'intero set di dati pertinente per gli ID della partizione deve essere facilmente disponibile nella memoria veloce. Limiti compilati e limiti osservati:
- Limiti osservati: il numero effettivo di ID rilevati per ogni partizione durante l'esecuzione, in base ai dati di input in fase di elaborazione.
- Se i limiti osservati superano i limiti compilati, possono verificarsi errori o l'eliminazione degli ID (se
allow_id_droppingè abilitato).
Calcolo dei limiti: il processo di determinazione dei limiti appropriati prevede un'attenta analisi della distribuzione dei dati di input. Per una determinata tabella (che chiameremo
T1, che potrebbe a sua volta far parte di una tabella in pila più grandeT):- Il batch di input (ad esempio, un
SparseTensor2D di forma[BatchSize, MaxSequenceLength]) viene inizialmente suddiviso tra gli SparseCore disponibili. Ad esempio, se un TensorCore è accoppiato a due SparseCore, ogni SparseCore potrebbe ricevere un sotto-batch di forma[BatchSize/2, MaxSequenceLength]. - Questo sottoinsieme viene quindi convertito nel formato COO, generando
row_idsecol_ids. - Gli ID duplicati all'interno dello stesso campione (ovvero le voci con lo stesso
row_idecol_id) vengono rimossi. - Per ogni
col_idunivoco rimanente (all'interno di un campione), il SparseCore di destinazione responsabile di questo ID viene determinato utilizzando la regola di sharding mod:target_sc_id = col_id % num_total_sparse_cores. - Viene mantenuto un conteggio del numero totale di ID (
ids_per_sparse_core[target_sc_id]++) e del numero di ID univoci (unique_ids_per_sparse_core[target_sc_id]++, dopo aver verificato l'univocità per queltarget_sc_idspecifico) destinati a ognitarget_sc_id. - Il
max_ids_per_partitionper la tabellaT1viene quindi impostato sumax(ids_per_sparse_core_array). - Analogamente, il
max_unique_ids_per_partitionper la tabellaT1è impostato sumax(unique_ids_per_sparse_core_array). - Se la tabella
T1è un componente di una tabella in pila, alle distribuzioni degli ID potrebbero essere applicate trasformazioni aggiuntive, come rotazioni o spostamenti, prima di sommare le statistiche di tutte le tabelle costituenti. Ciò contribuisce a bilanciare il carico tra i chip.
- Il batch di input (ad esempio, un
L'impostazione corretta di questi limiti è un compromesso: limiti inferiori possono potenzialmente portare a prestazioni migliori (in quanto è necessario elaborare meno dati per passaggio e la pressione SPMEM è ridotta), ma se impostati troppo bassi, possono comportare un mini-batching eccessivo o l'eliminazione indesiderata degli ID.
8. Come comunica ogni SparseCore
La comunicazione SparseCore, in particolare nel contesto dell'elaborazione di un elenco di ID per le ricerche di incorporamento, si basa su diversi meccanismi coordinati:
- Mod sharding and implicit routing:
- Le tabelle di incorporamento sono mod-shard in tutti gli SparseCore del sistema.
- Quando l'host fornisce un batch di dati di input (che viene successivamente preelaborato in formato COO, incluso
col_ids), il valorecol_idviene utilizzato per determinare quale SparseCore è responsabile di quell'ID specifico:target_sc_id = col_id % num_total_sparse_cores. - Ogni SparseCore riceve ed elabora solo il sottoinsieme di ID mappati alle partizioni del vocabolario assegnate. La fase di preelaborazione dell'host è fondamentale per preparare i dati in modo che ogni SparseCore possa identificare e utilizzare facilmente gli ID pertinenti.
- Distribuzione dei dati per host:
- La logica di preelaborazione dell'host partiziona il batch di input complessivo e distribuisce le parti pertinenti di
row_idsecol_ids(insieme a eventuali funzionalità o pesi associati, se applicabile) alla memoria (HBM) direttamente accessibile a ogni SparseCore o a una HBM condivisa da cui gli SparseCore recupereranno i dati richiesti.
- La logica di preelaborazione dell'host partiziona il batch di input complessivo e distribuisce le parti pertinenti di
- Elaborazione intra-SparseCore:
- Una volta ricevuto il set di ID designato per una determinata partizione della tabella, SparseCore esegue operazioni come la deduplicazione di questi ID e la raccolta dei vettori di incorporamento corrispondenti. Si tratta principalmente di calcoli locali eseguiti all'interno dei riquadri di SparseCore e che utilizzano la relativa SPMEM locale.
- Comunicazione Inter-SparseCore (All-to-All):
- Dopo la fase di elaborazione iniziale (ad esempio le ricerche di incorporamento), è possibile utilizzare un pattern di comunicazione "all-to-all" per combinare o ridistribuire i risultati tra gli SparseCore (ad esempio, prima di inserire le attivazioni in un livello TensorCore che prevede un input corrispondente a tutte le posizioni di campionamento originali). Questo è fondamentale per ricostruire l'insieme completo di attivazioni se il batch di input originale è stato distribuito per l'elaborazione parallela.
- Comunicazione con i Tensor Core:
- Gli SparseCore comunicano con i TensorCore per inviare attivazioni di incorporamento (durante il calcolo in avanti) e per ricevere gradienti (durante il calcolo all'indietro). Questa interazione è orchestrata dal programma compilato XLA e spesso coinvolge HBM come buffer intermedio. La strategia di pipelining (descritta in precedenza) influenza notevolmente la tempistica e la sincronizzazione di questa comunicazione SC-TC.
In sostanza, la "distribuzione" iniziale degli ID ai SparseCore appropriati viene gestita in gran parte dallo schema di sharding e dai passaggi di preelaborazione dell'host. La comunicazione successiva prevede che SparseCores operi sui propri dati locali, potenzialmente seguita da operazioni di comunicazione collettiva come all-to-all se i dati devono essere scambiati o riordinati a livello globale tra SparseCores prima di essere ulteriormente elaborati dai TensorCore.
9. Gestione della memoria SparseCore
Ogni SparseCore gestisce in modo efficiente diversi tipi di memoria per eseguire i calcoli:
- Memoria scratchpad (SPMEM):
- Natura: una SRAM locale relativamente piccola, ma molto veloce, disponibile esclusivamente per ogni SparseCore. È importante notare che SPMEM non è una cache; il suo utilizzo è gestito e orchestrato in modo esplicito dal compilatore XLA.
- Scopo: SPMEM viene utilizzato per "organizzare i dati in modo opportunistico". Sono inclusi input, output e risultati intermedi necessari per i calcoli SC in corso. L'organizzazione temporanea dei dati in SPMEM riduce significativamente l'elevata latenza in genere associata all'accesso a HBM.
- Dimensionamento: come descritto nella sezione "Limiti", i buffer SPMEM vengono dimensionati staticamente in fase di compilazione. Il dimensionamento si basa su parametri come
max_ids_per_partitionemax_unique_ids_per_partition. Questa allocazione statica garantisce che per qualsiasi operazione su una partizione di tabella (ad esempio la riduzione del CSR), tutti i dati necessari per gli ID della partizione (fino ai limiti definiti) possano essere inseriti in SPMEM. - Ottimizzazioni del compilatore: il compilatore XLA incorpora ottimizzazioni sofisticate per determinare con precisione la quantità di dati e gli elementi di dati specifici che devono essere organizzati in SPMEM per nascondere efficacemente la latenza HBM e massimizzare il rendimento.
- Vincolo di allocazione dinamica: il compilatore SparseCore al momento non supporta l'allocazione dinamica dello spazio di lavoro. Ciò evidenzia l'importanza fondamentale del dimensionamento statico attraverso la configurazione accurata dei limiti.
- Memoria a larghezza di banda elevata (HBM):
- Natura: una risorsa di memoria condivisa di grandi dimensioni accessibile a tutti gli SparseCore, i TensorCore e il sistema host. Le tabelle di incorporamento principali sono archiviate in HBM.
- Utilizzo dello stack: le operazioni SparseCore spesso richiedono spazio di archiviazione temporaneo in HBM per i risultati intermedi che non rientrano nella SPMEM limitata o devono essere passati tra le fasi più grandi della pipeline di elaborazione. L'utilizzo dello stack HBM durante i passaggi in avanti e indietro può essere stimato come segue:
- Stack HBM di propagazione in avanti (singola tabella) ≈ (2 *
feature_width+ 1) *max_unique_nz_per_row*logical_replica_count* 4 byte - Stack HBM di propagazione all'indietro (singola tabella) ≈ 3 *
feature_width*max_unique_nz_per_row*logical_replica_count* 4 byte
- Stack HBM di propagazione in avanti (singola tabella) ≈ (2 *
- Utilizzo dell'heap: l'HBM ospita anche l'heap, che viene gestito dall'host. L'heap memorizza dati come i pesi dei livelli densi, le costanti utilizzate dal modello e i dati di input precaricati. L'utilizzo dell'heap tende ad aumentare con il numero di passaggi per i quali l'host precarica i dati (controllato dal flag
maximum_parallel_iterations). Sebbene un maggior numero di prefetching possa migliorare il rendimento sovrapponendo i trasferimenti da host a dispositivo al calcolo del dispositivo, consuma anche più HBM. - Serializzazione per l'ottimizzazione HBM: il flag
xla_sc_num_serialized_tables_to_optimize_hbmfornisce un meccanismo per controllare quanti dati delle tabelle vengono mantenuti "live" nella memoria dello stack HBM in un determinato momento. L'aumento di questo numero serializza effettivamente l'elaborazione per più tabelle, il che può ridurre l'utilizzo massimo dello stack HBM, ma potrebbe influire sulle prestazioni a causa del parallelismo ridotto.
- Memoria vettoriale (VMEM):
- La VMEM è una memoria scratchpad locale utilizzata esclusivamente dal TensorCore (TC). Sebbene la VMEM non sia gestita direttamente da SparseCore, è parte integrante dell'ecosistema di memoria con cui interagisce SC, principalmente tramite TensorCore.
Strategia generale di gestione della memoria:
La strategia principale di gestione della memoria per SparseCore ruota attorno all'utilizzo della piccola e veloce SPMEM per i dati "caldi" che vengono elaborati attivamente da un riquadro SparseCore, riducendo al minimo gli accessi alla HBM più lenta. I limiti configurati sono il meccanismo principale per garantire che SPMEM non vada in overflow. La HBM viene utilizzata per archiviare tabelle di incorporamento di grandi dimensioni e dati temporanei che superano la capacità della SPMEM o devono essere condivisi tra diverse unità di elaborazione o fasi della pipeline. Il compilatore XLA è responsabile dell'orchestrazione di tutti gli spostamenti di dati e dell'allocazione dei buffer in base a questi principi architetturali e ai limiti configurati dall'utente.
10. Colli di bottiglia delle prestazioni e della memoria
Per ottenere prestazioni ottimali con SparseCore è necessario comprendere chiaramente i potenziali colli di bottiglia e come risolverli. Questi possono verificarsi sull'host, all'interno di SparseCore stesso o nella sua interazione con i TensorCore.
Colli di bottiglia delle prestazioni comuni:
- Collo di bottiglia dell'host:
- Problema: la CPU host potrebbe non riuscire a preelaborare i dati e a inviarli alla TPU abbastanza rapidamente, il che comporta un sottoutilizzo di SparseCore e TensorCore. Si tratta di un limite alle prestazioni frequente.
- Mitigazione: monitora l'utilizzo della CPU host e le metriche della pipeline di input. Ottimizza le routine di caricamento e preelaborazione dei dati lato host (consulta i suggerimenti per la conversione COO). Modifica il flag
maximum_parallel_iterationsper ottimizzare il precaricamento dei dati.
- Sincronizzazione TC/SC non ottimale (mancanza di pipeline):
- Problema: se il pipelining tra TensorCore e SparseCore è disattivato o non funziona in modo efficiente, un'unità potrebbe trascorrere molto tempo in attesa dell'altra, riducendo così la velocità effettiva complessiva del sistema.
- Mitigazione: assicurati che il pipelining sia abilitato (ad esempio,
tf_xla_disable_full_embedding_pipelining = falseo il suo equivalente).
- Colli di bottiglia indotti dai limiti:
- Problema:
- Limiti troppo bassi: possono attivare un mini-batching eccessivo (suddivisione dei batch di input in numerosi batch secondari più piccoli per rispettare i limiti ristretti). Sebbene ciò mantenga la correttezza, ogni mini-batch introduce un sovraccarico di elaborazione, rallentando potenzialmente l'esecuzione complessiva. Se
allow_id_droppingè true, limiti eccessivamente bassi possono anche portare all'eliminazione degli ID, il che influisce sull'accuratezza del modello. - Limiti troppo elevati (ma comunque adatti): anche se limiti molto elevati potrebbero impedire il mini-batching, potrebbero aumentare inutilmente la pressione SPMEM se le caratteristiche effettive dei dati raramente si avvicinano a questi valori di picco. Potrebbero anche portare a un utilizzo maggiore dello stack HBM rispetto a quanto strettamente necessario.
- Errori di compilazione: se i limiti configurati richiedono più stack SPMEM o HBM della memoria fisica disponibile, la compilazione non andrà a buon fine.
- Limiti troppo bassi: possono attivare un mini-batching eccessivo (suddivisione dei batch di input in numerosi batch secondari più piccoli per rispettare i limiti ristretti). Sebbene ciò mantenga la correttezza, ogni mini-batch introduce un sovraccarico di elaborazione, rallentando potenzialmente l'esecuzione complessiva. Se
- Mitigazione: assicurati che i limiti siano impostati correttamente.
- Problema:
- Asimmetria della distribuzione dei dati:
- Problema: se alcune partizioni SparseCore ricevono costantemente un numero di ID sproporzionatamente maggiore rispetto ad altre (a indicare una distribuzione degli ID non ottimale), questi SparseCore sovraccarichi diventeranno colli di bottiglia delle prestazioni.
- Mitigazione: il rimescolamento degli ID durante il processo di mini-batching può contribuire ad alleviare questo problema per le tabelle in pila, in particolare quelle con tabelle utente "calde". Analizza attentamente le distribuzioni degli ID per impostare limiti per tabella appropriati ed equilibrati.
- Problemi di impilamento delle tabelle:
- Problema:
- Troppe poche tabelle impilate: potrebbero non essere sufficienti per nascondere efficacemente la latenza ICI o ridurre adeguatamente i sovraccarichi di elaborazione.
- Troppe tabelle impilate: potrebbe comportare la creazione di tabelle logiche molto grandi che diventano difficili da gestire o potrebbero superare i limiti delle risorse disponibili.
- Mitigazione:
- Garantisci un numero ottimale di tabelle per l'impilamento. Una linea guida generale suggerisce un "punto ottimale" di 5-100 tabelle per l'impilamento.
- Problema:
- Numeri/quantizzazione inefficienti:
- Problema: l'utilizzo della precisione FP32 completa quando formati a precisione inferiore come BF16 o numeri interi quantizzati sarebbero sufficienti (e offrirebbero un calcolo più rapido) può rappresentare un collo di bottiglia delle prestazioni.
- Mitigazione: esplora le opzioni di precisione inferiore. Tuttavia, tieni presente che la quantizzazione stessa ha un sovraccarico e potrebbe richiedere un'attenta ottimizzazione dei parametri di quantizzazione per mantenere l'accuratezza del modello.
- Saturazione della larghezza di banda HBM:
- Problema: un movimento eccessivo di dati da e verso HBM, potenzialmente causato da larghezze delle funzionalità molto piccole (che comportano un overhead di padding elevato), pattern di accesso alla memoria inefficienti o un numero estremamente elevato di ricerche, può saturare la larghezza di banda HBM disponibile.
- Mitigazione: scalare il numero di TPU può contribuire a risolvere il problema della saturazione della larghezza di banda HBM.
Colli di bottiglia della memoria comuni:
- Overflow SPMEM (errore di compilazione):
- Problema: se
max_ids_per_partitionemax_unique_ids_per_partitionsono impostati su valori troppo elevati, il compilatore XLA potrebbe non essere in grado di allocare SPMEM sufficiente, causando errori di compilazione come:"Fixed size allocations (...) do not fit in TileSpmem (...)". Inoltre, se il termine(sample_count * feature_width) / kNumTiles(dovekNumTilesè il numero di riquadri per SC) è troppo grande per gli operandi di raccolta temporanea all'interno di SPMEM del riquadro, possono verificarsi errori come"Gather operand too large...". - Mitigazione: riduci le dimensioni del batch o aumenta il numero di chip utilizzati per l'elaborazione.
- Problema: se
- Overflow dello stack HBM (runtime o compilazione):
- Problema: se la combinazione di
feature_width,max_unique_nz_per_rowelogical_replica_countcomporta requisiti di memoria dello stack HBM che superano l'HBM disponibile, possono verificarsi errori di memoria insufficiente (OOM) in fase di runtime o durante la compilazione. - Mitigazione: regola il flag
xla_sc_num_serialized_tables_to_optimize_hbmper ridurre l'utilizzo dello stack HBM serializzando l'elaborazione delle tabelle (in genere a scapito delle prestazioni).
- Problema: se la combinazione di
- Esaurimento dell'heap HBM:
- Problema: causato principalmente da pesi di layer densi molto grandi, da numerose costanti memorizzate o dal prefetching degli input eccessivamente aggressivo (
maximum_parallel_iterationselevato). - Mitigazione: monitora l'utilizzo dell'heap utilizzando strumenti come XProf Memory Viewer.
- Problema: causato principalmente da pesi di layer densi molto grandi, da numerose costanti memorizzate o dal prefetching degli input eccessivamente aggressivo (
- Overhead di padding:
- Problema: le tabelle di incorporamento vengono riempite in modo da essere allineate a 32 byte (equivalenti a 8 numeri in virgola mobile) nella dimensione della funzionalità. Di conseguenza, le larghezze delle funzionalità ridotte (ad esempio, 1 float) comportano un overhead di padding significativo (ad esempio, 7/8 dello spazio buffer allocato è padding), con conseguente spreco di HBM. Anche la dimensione del vocabolario delle tabelle viene riempita in modo da essere un multiplo del numero di SparseCore nel sistema; tuttavia, questo impatto è in genere trascurabile per le tabelle con una dimensione del vocabolario sufficientemente elevata.
Fattori generali che influiscono sulle prestazioni e sulla memoria:
- Topologia: il numero di chip disponibili e la loro architettura di interconnessione.
- Dimensione batch: influisce direttamente su
sample_countper SparseCore, che a sua volta influenza il consumo di memoria e il carico di calcolo. - Formattazione dei dati: garantire un layout efficiente dei dati sul dispositivo è fondamentale per prestazioni ottimali.
11. Analizzare un profilo SparseCore
L'analisi di un profilo di rendimento è un passaggio fondamentale per identificare i colli di bottiglia e scoprire opportunità di ottimizzazione all'interno dei tuoi workload SparseCore.
- Ottenere una traccia:
- Utilizza strumenti di profilazione, come XProf, per acquisire una traccia di esecuzione dettagliata durante l'addestramento o l'inferenza del modello. Questa traccia fornirà una sequenza temporale delle operazioni che si verificano sull'host, sui TensorCore e sugli SparseCore.
- Esamina Trace Viewer (ad esempio, in XProf o TensorBoard):
- Attività dell'organizzatore: esamina attentamente l'attività dell'organizzatore. Esistono lacune significative nell'attività della TPU? Questi intervalli potrebbero indicare che l'host è un collo di bottiglia e non riesce a fornire i dati abbastanza rapidamente. Analizza il rendimento della pipeline di input.
- Attività di TensorCore (TC) e SparseCore (SC):
- Esamina le sequenze temporali di esecuzione sia per TC che per SC. Funzionano in parallelo, indicando un pipelining efficace? Oppure ci sono periodi prolungati in cui un'unità è inattiva, in attesa dell'altra?
- Identifica le operazioni che richiedono più tempo (operazioni con esecuzione più lunga) sia su SC che su TC.
- Gli output di traccia visiva (che spesso mostrano blocchi colorati che rappresentano diverse operazioni nel tempo, come
TPU:0 SparseCore 1 (pid 1005)) sono preziosi per identificare visivamente le operazioni dominanti e i periodi di inattività.
- Analisi del tempo di esecuzione dei passaggi: osserva il tempo di esecuzione dei passaggi complessivo e scopri come viene distribuito o suddiviso tra l'elaborazione dell'host, il calcolo SC e il calcolo TC.
- Analisi della memoria (XProf Memory Viewer):
- Utilizzo dell'heap: utilizza strumenti come la scheda "Visualizzatore memoria" di XProf per esaminare l'utilizzo dell'heap HBM. Questo può aiutarti a determinare se pesi, costanti o prefetching degli input eccessivamente aggressivi del modello di grandi dimensioni stanno consumando una quantità eccessiva di HBM. L'attivazione di flag come
--vmodule=best_fit_allocator=1potrebbe fornire log dell'utilizzo massimo dell'heap. - Utilizzo dello stack (indiretto): sebbene la profilazione diretta dello stack HBM possa essere complessa, se si verificano errori di esaurimento della memoria e l'utilizzo dell'heap sembra ragionevole, l'esaurimento dello stack HBM (spesso dovuto a limiti o larghezze delle funzionalità eccessivamente grandi) è un forte sospetto. Le formule fornite per l'utilizzo dello stack HBM possono aiutarti a stimare questo valore.
- Utilizzo dell'heap: utilizza strumenti come la scheda "Visualizzatore memoria" di XProf per esaminare l'utilizzo dell'heap HBM. Questo può aiutarti a determinare se pesi, costanti o prefetching degli input eccessivamente aggressivi del modello di grandi dimensioni stanno consumando una quantità eccessiva di HBM. L'attivazione di flag come
- Cerca pattern specifici:
- Mini-batching: se i limiti vengono superati di frequente, potresti notare prove di mini-batching nella traccia (ad esempio, un numero maggiore di operazioni SC più piccole del previsto per la dimensione del batch globale). Spesso può essere dedotto dai log o osservando i conteggi delle chiamate di determinate operazioni.
- Eliminazione degli ID: se l'eliminazione degli ID è attivata e in corso, i log di sistema potrebbero fornire indicazioni in merito. Questo sarebbe anche un chiaro segnale che i limiti configurati sono troppo restrittivi per i dati di input.
- Tempi di compilazione: tempi di ricompilazione prolungati, soprattutto se l'ottimizzazione basata sul feedback (FDO) è abilitata e i limiti vengono modificati di frequente, possono aumentare notevolmente il tempo di addestramento complessivo.
- Correlare con flag e configurazione:
- Collega il comportamento osservato nel profilo alle configurazioni di SparseCore (impostazioni nei file dei limiti, flag XLA). Ad esempio, se
xla_sc_num_serialized_tables_to_optimize_hbmè impostato su un valore elevato, potresti aspettarti prestazioni SC più lente, ma un consumo inferiore dello stack HBM.
- Collega il comportamento osservato nel profilo alle configurazioni di SparseCore (impostazioni nei file dei limiti, flag XLA). Ad esempio, se
- Processo iterativo:
- La profilazione è spesso un processo di perfezionamento iterativo. Apporta una modifica specifica (regola un limite, attiva o disattiva una funzionalità), acquisisci un nuovo profilo e confrontalo con il profilo precedente per vedere l'impatto della modifica.
12. Flag di debug generali
È possibile attivare diversi flag per facilitare il debug dei problemi relativi all'esecuzione di SparseCore. È importante notare che l'attivazione di questi controlli spesso comporta una penalità di rendimento e, pertanto, in genere devono essere disattivati per le esecuzioni di produzione.
- Controlli ID (fuori intervallo):
- Bandiera:
xla_sparse_core_enable_id_bound_check = true - Scopo: consente di eseguire controlli sul sistema host per rilevare se gli ID di incorporamento nei dati di input non rientrano nell'intervallo di vocabolario valido definito per una determinata tabella di incorporamento. In questo modo è possibile rilevare problemi relativi a dati di input errati o danneggiati.
- Bandiera:
- Controllo NaN:
- Bandiera:
xla_sc_detect_nan = true - Scopo: consente il rilevamento di valori NaN (Not a Number) all'interno dei dati in virgola mobile elaborati su SparseCore. Se viene rilevato un NaN negli input o negli output di vari passaggi del compilatore, questo flag genera un errore. Questi errori in genere forniscono informazioni su dove è stato rilevato il NaN.
- Bandiera:
- Controllo dei limiti (accesso alla memoria):
- Bandiera:
xla_sc_assert_level=bounds - Scopo: questo flag attiva uno strumento in stile ASAN (AddressSanitizer) che riscrive le istruzioni di accesso alla memoria (come i carichi/archiviazione VMEM e le operazioni DMA) per includere controlli dinamici. Questi controlli verificano se l'accesso alla memoria rientra nei limiti allocati della regione di memoria di destinazione.
- Comportamento: se viene rilevato un accesso alla memoria fuori dai limiti, l'esecuzione non andrà a buon fine.
- Attenzione: è possibile che questo controllo produca falsi positivi, ad esempio a causa di pattern di accesso complessi con stride che non sono completamente compresi dal controllo. Questa trasformazione viene applicata in una fase avanzata del processo di compilazione del backend.
- Bandiera:
- Buffer checker (corruzione della memoria):
- Flag:
xla_tpu_buffer_contents_sanitizer_config='cores_to_sanitize: [TC, SC_SCS, SC_TILE], sanitizer_mode: LOCAL_ONLY'xla_tpu_verify_launch_id_across_cores=true
- Scopo: questi flag contribuiscono a garantire che i buffer di memoria non vengano danneggiati o sovrascritti inavvertitamente da operazioni non correlate. Buffer Sanitizer controlla i contenuti dei buffer per verificare che non cambino in modo imprevisto.
- Flag:
13. Supporto della quantizzazione
SparseDenseMatmulOp di SparseCore è progettato per supportare le operazioni sulle tabelle di incorporamento utilizzando sia tipi di dati interi che a virgola mobile a 32 bit (FP32). Sebbene l'addestramento del modello venga in genere eseguito utilizzando la precisione FP32 per le tabelle di incorporamento, è possibile applicare la quantizzazione post-addestramento (PTQ). PTQ consente l'utilizzo di tipi di dati a precisione inferiore (come gli interi a 8 bit) per l'inferenza, il che può potenzialmente portare a prestazioni migliori e a un footprint di memoria ridotto.
Quantizzazione simulata:
SparseDenseMatmulOp può essere configurato per eseguire la "quantizzazione simulata". In questa modalità operativa, i vettori di incorporamento vengono prima quantizzati a una precisione inferiore e poi dequantizzati a una precisione superiore (ad esempio FP32) prima di essere utilizzati nei calcoli successivi. Questa tecnica consente di addestrare i modelli tenendo conto degli effetti del rumore di quantizzazione. L'addestramento con la quantizzazione simulata può migliorare l'accuratezza del modello finale quando viene quantizzato completamente per l'inferenza.
Attributi di configurazione per SparseDenseMatmulOp (per la quantizzazione):
quantization_config_num_buckets = 256- Questo attributo specifica il numero di bucket o livelli discreti in cui verrà quantizzato un numero in virgola mobile a 32 bit. Ad esempio, quando si esegue la quantizzazione a numeri interi a 8 bit, in genere si specificano 2^8 =256 bucket.
quantization_config_low = -X.X- Questo attributo definisce il valore minimo in virgola mobile nell'intervallo di quantizzazione. Tutti i valori di input inferiori a questo minimo specificato verranno troncati a questo valore minimo durante la quantizzazione.
quantization_config_high = Y.Y- Questo attributo definisce il valore massimo in virgola mobile nell'intervallo di quantizzazione. Qualsiasi valore di input superiore a questo massimo specificato verrà troncato a questo valore massimo durante la quantizzazione.
Interazione con numeri e pipeline:
Il comportamento numerico del modello può variare a seconda che sia attivato il pipelining tra TensorCore e SparseCore. Se il pipelining è attivo, i gradienti elaborati da SparseCore potrebbero essere "obsoleti" (da un'iterazione precedente). Questo può interagire con il processo di quantizzazione e potenzialmente influire sulla dinamica di addestramento del modello o sull'accuratezza finale.
14. Funzionalità in arrivo e miglioramenti recenti
L'ecosistema SparseCore è soggetto a sviluppo e miglioramento continui.
Roadmap:
- Mini-batching della dimensione del campione:
- Questa funzionalità è pensata per integrare le funzionalità di mini-batching della dimensione del vocabolario esistenti.
- Ciò consentirebbe un'ulteriore partizione degli input di incorporamento lungo la dimensione del campione. Ciò si otterrebbe introducendo loop sul dispositivo in grado di filtrare ed elaborare le ricerche da un sottoinsieme di campioni alla volta. Una funzionalità di questo tipo potrebbe essere utile per gestire conteggi di ID per campione molto grandi o per migliorare il bilanciamento del carico tra le unità di elaborazione.
- Supporto migliorato per l'incorporamento con meno di 8 numeri interi per riga (larghezza ridotta delle funzionalità):
- Il design attuale spesso utilizza un padding significativo per incorporare larghezze delle funzionalità inferiori a 8 float (che corrispondono a 32 byte). Questo padding può comportare uno spreco di HBM e risorse di calcolo potenzialmente sottoutilizzate. I miglioramenti futuri mirano a ridurre questa inefficienza per le tabelle con dimensioni delle funzionalità ridotte.
Miglioramenti recenti:
- Staging degli operandi di raccolta in HBM:
- Questa ottimizzazione contribuisce a ridurre la pressione sulla memoria scratchpad condivisa (SPMEM) consentendo di eseguire lo staging di alcuni input o output dell'operazione di raccolta nella HBM più grande.
- Riduzione dell'utilizzo della memoria dello stack:
- Sono stati implementati miglioramenti per ridurre il consumo di memoria dello stack HBM durante le operazioni SparseCore, idealmente senza influire negativamente sulle prestazioni o sul throughput complessivi.
Questi miglioramenti sono incentrati sul miglioramento delle prestazioni, dell'efficienza della memoria e della flessibilità operativa di SparseCore per una gamma ancora più ampia di carichi di lavoro di tipo sparse.