Impostazione e Verifica di NVIDIA cuTile Python in Colab
In questo tutorial ci immergiamo nel mondo della programmazione GPU con NVIDIA cuTile Python, un'interfaccia avanzata per creare efficientemente kernal CUDA in Python. Iniziamo installando le dipendenze necessarie, verificando la disponibilità del GPU, del driver e del CUDA, per poi eseguire i primi test.
Per avviare il notebook in Google Colab, segui i seguenti passaggi per preparare l’ambiente:
- Installa PyTorch, NumPy, Pandas, e Matplotlib con il comando:
pip install torch numpy pandas matplotlib
Procediamo quindi al tentativo di installazione di cuTile Python. Esso potrebbe non funzionare in alcuni runtime di Colab a causa di problemi di compatibilità del driver NVIDIA. Per verificare la corretta installazione, esegui il comando:
pip install "cuda-tile[tileiras]"
Successivamente, verifichiamo la presenza di PyTorch e se CUDA è disponibile eseguendo i seguenti comandi:
python --versionnvidia-smi
Se il runtime supporta correttamente cuTile, i comandi dovrebbero restituire informazioni dettagliate sulla GPU e su CUDA. Se invece mancano i requisiti minimi, il notebook continuerà utilizzando una versione alternativa basata su PyTorch.
Utilità per il Timing, la Convalida e la Segnalazione delle Prestazioni
Per garantire un esecuzione corretta, testabile e confrontabile dei nostri kernal su GPU, abbiamo definito alcune utility utili:
sync: una funzione che sincronizza l’esecuzione della GPU.benchmark: una funzione che effettua dei test ripetuti per misurare il timing dell’esecuzione.showresulttable: una funzione che presenta i risultati in forma tabulare.assert_close: una funzione per verificare la correttezza delle operazioni confrontandole con l'output atteso.
Esempio di utilizzo di benchmark per un’operazione:
result = benchmark(fn, label="Addizione Matriciale CuTile")
showresulttable([result], "Risultato benchmark")
Creazione di Kernal CuTile per Addizione Vettoriale
I kernal per la GPU funzionano basandosi su operazioni eseguite su tile di dati. Qui mostriamo l'implementazione per l’addizione vettoriale di due array.
Ecco il codice per il kernal diretto:
@ct.kernel
- Carica i due vettori
aebsuddividendoli in tile di dimensioniTILE. - Esegue l’addizione vettoriale
atile+btilenel modulo GPU. - Salva l’output in
c.
def cutilevecadddirectkernel(a, b, c, TILE: ConstInt):
bid = ct.bid(0)
a_tile = ct.load(a, index=(bid,), shape=(TILE,))
b_tile = ct.load(b, index=(bid,), shape=(TILE,))
ctile = atile + b_tile
ct.store(c, index=(bid,), tile=c_tile)
Una variante simile, detta gather-kernel, utilizza offset precompilati per accedere direttamente ai dati:
def cutilevecaddgatherkernel(a, b, c, TILE: ConstInt):
offsets = bid * TILE + ct.arange(TILE, dtype=torch.int32)
a_tile = ct.gather(a, offsets)
b_tile = ct.gather(b, offsets)
ctile = atile + b_tile
ct.scatter(c, offsets, c_tile)
La seconda versione potrebbe essere preferita se i dati non sono accessibili in modo diretto in base alla tile.
Kernal CuTile per L’Addizione Matriciale
Per l’addizione matriciale in GPU, i kernal di cuTile gestiscono le dimensioni in due direzioni (M per righe e N per colonne).
- Le righe sono determinate da
bidme sono suddivise in tiles di dimensioniTILEM. - Le colonne sono determinate da
bidne divise in tiles di dimensioniTILEN. - Con un offset calcolato da
bidm * TILEM + ct.arange(TILE_M), accediamo direttamente ai dati.
Ecco il codice per l’addizione matriciale:
def cutilematrixaddgatherkernel(a, b, c, TILEM: ConstInt, TILEN: ConstInt):
bid_m = ct.bid(0)
bids_n = ct.bids(1)
rows = bidm * TILEM + ct.arange(TILE_M, dtype=torch.int32)
cols = bidn * TILEN + ct.arange(TILE_N, dtype=torch.int32)
rows = rows[:, None]
cols = cols[None, :]
a_tile = ct.gather(a, (rows, cols))
b_tile = ct.gather(b, (rows, cols))
ctile = atile + b_tile
ct.scatter(c, (rows, cols), c_tile)
Kernal CuTile per la Moltiplicazione Matriciale (MatMul)
La moltiplicazione di matrici è un caso complesso che richiede operazioni di accumulo su un terzo asse (K). Vediamo come il modello di tiled si adatta a questa operazione:
- Utilizziamo un loop su
kper accumulare i risultati di moltiplicazioni parziali. - I dati delle matrici
AeBvengono caricati in tiles separate. - Utilizziamo
ct.mma(Multiply-Accumulate) per eseguire l’operazione in parallelo. - L’output viene immagazzinato in
Cin modo ordinato.
Ecco il codice:
def cutilematmulkernel(A, B, C, TM: ConstInt, TN: ConstInt, TK: ConstInt):
bid_m = ct.bid(0)
bid_n = ct.bid(1)
numtilesk = ct.num_tiles(A, axis=1, shape=(TM, TK))
acc = ct.full((TM, TN), 0, dtype=ct.float32)
compute_dtype = ct.float32 if A.dtype == ct.float32 else A.dtype
zero_pad = ct.PaddingMode.ZERO
for k in range(numtilesk):
atile = ct.load(A, index=(bidm, k), shape=(TM, TK), paddingmode=zeropad).astype(compute_dtype)
b