From e23f5d071b1655d40c944eb8ad77f7a51d390ac6 Mon Sep 17 00:00:00 2001 From: LucaCorra02 Date: Wed, 4 Feb 2026 15:46:20 +0100 Subject: [PATCH 1/3] update --- chapters/Lezione6.typ | 97 +++++++++++++++++++++++++++++++++++++++++++ main.typ | 3 ++ 2 files changed, 100 insertions(+) create mode 100644 chapters/Lezione6.typ diff --git a/chapters/Lezione6.typ b/chapters/Lezione6.typ new file mode 100644 index 0000000..2138896 --- /dev/null +++ b/chapters/Lezione6.typ @@ -0,0 +1,97 @@ +#import "../template.typ": * + += Lezione 6 + +Lettura sequenziale permette di sfurttare la località della cache. + +== Trasferimento di memoria + +GPU esegue una copia locked della memoria del sistema. Passiamo dalla gestione di una memoria paginabile (dato non stabile nel tempo) possiamo gestire sulal GPU della memoria non paginabile (bloccata). + +Ci sono delle API che permettono di allocare su HOST memoria non paginabile, una volta allocata possiamo fare trasferimenti asincroni. + +Vorremo togliere lo stage intermedio, fissiamo esplicitamente la memoria, aumantando la velocità del DMA. + +``` memcopyasync``` è la primitiva + +=== Concurency overlap + +Uno dei vantaggi è una concurincy overlap. Costruiamo degli stream indipendenti, ci possono essere trasferimenti su uno stream e esecuzioni in parallelo. IL DMA è unico, non posso fare trasferimenti paralleli ma posso far lavorare contemporaneamente CPU e DMA. + +Problema, se allocassimo troppa pined memory potremmo andare a togliere un sacco di risorse. Se un singolo processo usa tante risorse, influisce sul tutto il sistema. + +=== Gestione pined memory +//AGGIUNGRE MATRICI CON PIN MEMORY + +== Stream + +Uno stream è un unità di concorrenza in CUDA. Generare e gestire una coda FIFO di operazioni. Sono operazioni in sequenza dentro uno stream. Il vantaggio è che li stream sono indipendenti tra di loro (trasferimento e calcolo assieme) dipende dalla capacità della GPU. + +Per usare uno stream, basta passarlo al kernell. Una volta creato uso uno stream con il suo identificatore. + +Si sposano bene con la pinned memory. Nel momento in cui faccio il trasferimento posso decire lo stream su cui va. + +Esistono due stream: +- Default o nulla $"ID" = 0$ +- Stream non nullo $"ID" > 0$ +- Stream bullo, blocca tutti gli altri li stream, non può andare in concomitanza. + +#nota()[ + Il default stream di default si sincronizza con gli altri stream. +] + +Può aumentare l'occupancy. Nel momento in cui abbiamo taglie piccole di griglie, possono occupare solo alcuni SM, in vece di senqualizzare le cose l'idea è che arrivano diversi processi(griglie) e vengono gestiti parallelamente su stream diversi. Le applicazioni lavorano in maniera concorrente, se stiamo all'interno della stessa applicazione abbiamo il parallelismo. + +``` cudaDeviceSynchronize``` si ripecuote a livello di sistema per tutti gli stream. é la sincronizzazione più _forte_ che abbiamo. +//Mettere immagine old vs new + + +#attenzione()[ + La creazione di uno stream deve avvenire prima del suo utilizzo. Di solito gli stream vengono creati e distrutti con un loop +] + +é possibile anche sincronizzare il singolo stream con l'host e non solo gli stream tra di loro. + +QUERY = non bloccante, ma ci permette di indagare lo stato di uno stream, per capire ad esempio quante operazioni ha eseguito. + +//Aggiugnere somam di vettori con stream + +=== cuda pinned e stream + +La memoria pined veiene allocata con ``` cuda.pinned.array(shape,dtype)```. +```py +arr = np.arrange(1_000_000, dtype=np.float32) +with cuda.pinned(arr): + d_arr = cuda.to.device(arr) + #kernel + out = cuda.device.to.host(d_out) + + +``` +L'array a viene pinnato e usato nel contesto come pinned. + +Quando non usare pined: +- No uso di memoria GPU +- Non ci sono vantaggi oggettivi +- Troppi passaggi frequenti tra pinned e unpinned. + +// capire cos'è +``` cuda.managed_array(shape,dtype)``` da usare con attenzione. + +Uno stream si crea come ``` stream=cuda.stream()``` è indipendente dallo stream di default (non nullo). Per passarlo ad un kernel basta passarlo come parametro al kernel. + +//aggiungere esempio pipeline loop in cuda +```py + + for i in range(num_streams): + strat = i * chunk_size + end = start + cunk_size + + #H2D copy + cuda.to_device(h_in[start:end], to=d_buffers[i], stream=stream[i]) + + scale_kernel[blocks, threads, stream[i]](d_buffers[i],210) +``` +Se ho lanciato un loop sulle operazioni di stream, devo lanciare un syncronize in modo tale che tutto il lavoro su GPU sia finito metto un sincronize rispetto all'host. + + diff --git a/main.typ b/main.typ index 03046f3..03f9b24 100644 --- a/main.typ +++ b/main.typ @@ -36,3 +36,6 @@ #include "chapters/Lezione2-Gerarchia-Thread.typ" #include "chapters/Lezione3-Warp.typ" #include "chapters/Lezione4.typ" + + +#include "chapters/Lezione6.typ" From 347a3e0f0ebaf0aff2be02e02dd532236ceef224 Mon Sep 17 00:00:00 2001 From: Matteo Zagheno Date: Wed, 4 Feb 2026 15:50:13 +0100 Subject: [PATCH 2/3] autoformat --- chapters/Lezione6.typ | 48 +++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/chapters/Lezione6.typ b/chapters/Lezione6.typ index 2138896..9549e88 100644 --- a/chapters/Lezione6.typ +++ b/chapters/Lezione6.typ @@ -2,7 +2,7 @@ = Lezione 6 -Lettura sequenziale permette di sfurttare la località della cache. +Lettura sequenziale permette di sfurttare la località della cache. == Trasferimento di memoria @@ -10,55 +10,55 @@ GPU esegue una copia locked della memoria del sistema. Passiamo dalla gestione d Ci sono delle API che permettono di allocare su HOST memoria non paginabile, una volta allocata possiamo fare trasferimenti asincroni. -Vorremo togliere lo stage intermedio, fissiamo esplicitamente la memoria, aumantando la velocità del DMA. +Vorremo togliere lo stage intermedio, fissiamo esplicitamente la memoria, aumantando la velocità del DMA. ``` memcopyasync``` è la primitiva === Concurency overlap -Uno dei vantaggi è una concurincy overlap. Costruiamo degli stream indipendenti, ci possono essere trasferimenti su uno stream e esecuzioni in parallelo. IL DMA è unico, non posso fare trasferimenti paralleli ma posso far lavorare contemporaneamente CPU e DMA. +Uno dei vantaggi è una concurincy overlap. Costruiamo degli stream indipendenti, ci possono essere trasferimenti su uno stream e esecuzioni in parallelo. IL DMA è unico, non posso fare trasferimenti paralleli ma posso far lavorare contemporaneamente CPU e DMA. -Problema, se allocassimo troppa pined memory potremmo andare a togliere un sacco di risorse. Se un singolo processo usa tante risorse, influisce sul tutto il sistema. +Problema, se allocassimo troppa pined memory potremmo andare a togliere un sacco di risorse. Se un singolo processo usa tante risorse, influisce sul tutto il sistema. === Gestione pined memory -//AGGIUNGRE MATRICI CON PIN MEMORY +//AGGIUNGRE MATRICI CON PIN MEMORY == Stream -Uno stream è un unità di concorrenza in CUDA. Generare e gestire una coda FIFO di operazioni. Sono operazioni in sequenza dentro uno stream. Il vantaggio è che li stream sono indipendenti tra di loro (trasferimento e calcolo assieme) dipende dalla capacità della GPU. +Uno stream è un unità di concorrenza in CUDA. Generare e gestire una coda FIFO di operazioni. Sono operazioni in sequenza dentro uno stream. Il vantaggio è che li stream sono indipendenti tra di loro (trasferimento e calcolo assieme) dipende dalla capacità della GPU. -Per usare uno stream, basta passarlo al kernell. Una volta creato uso uno stream con il suo identificatore. +Per usare uno stream, basta passarlo al kernell. Una volta creato uso uno stream con il suo identificatore. -Si sposano bene con la pinned memory. Nel momento in cui faccio il trasferimento posso decire lo stream su cui va. +Si sposano bene con la pinned memory. Nel momento in cui faccio il trasferimento posso decire lo stream su cui va. -Esistono due stream: +Esistono due stream: - Default o nulla $"ID" = 0$ - Stream non nullo $"ID" > 0$ -- Stream bullo, blocca tutti gli altri li stream, non può andare in concomitanza. +- Stream bullo, blocca tutti gli altri li stream, non può andare in concomitanza. #nota()[ - Il default stream di default si sincronizza con gli altri stream. + Il default stream di default si sincronizza con gli altri stream. ] -Può aumentare l'occupancy. Nel momento in cui abbiamo taglie piccole di griglie, possono occupare solo alcuni SM, in vece di senqualizzare le cose l'idea è che arrivano diversi processi(griglie) e vengono gestiti parallelamente su stream diversi. Le applicazioni lavorano in maniera concorrente, se stiamo all'interno della stessa applicazione abbiamo il parallelismo. +Può aumentare l'occupancy. Nel momento in cui abbiamo taglie piccole di griglie, possono occupare solo alcuni SM, in vece di senqualizzare le cose l'idea è che arrivano diversi processi(griglie) e vengono gestiti parallelamente su stream diversi. Le applicazioni lavorano in maniera concorrente, se stiamo all'interno della stessa applicazione abbiamo il parallelismo. -``` cudaDeviceSynchronize``` si ripecuote a livello di sistema per tutti gli stream. é la sincronizzazione più _forte_ che abbiamo. +``` cudaDeviceSynchronize``` si ripecuote a livello di sistema per tutti gli stream. é la sincronizzazione più _forte_ che abbiamo. //Mettere immagine old vs new #attenzione()[ - La creazione di uno stream deve avvenire prima del suo utilizzo. Di solito gli stream vengono creati e distrutti con un loop + La creazione di uno stream deve avvenire prima del suo utilizzo. Di solito gli stream vengono creati e distrutti con un loop ] -é possibile anche sincronizzare il singolo stream con l'host e non solo gli stream tra di loro. +é possibile anche sincronizzare il singolo stream con l'host e non solo gli stream tra di loro. -QUERY = non bloccante, ma ci permette di indagare lo stato di uno stream, per capire ad esempio quante operazioni ha eseguito. +QUERY = non bloccante, ma ci permette di indagare lo stato di uno stream, per capire ad esempio quante operazioni ha eseguito. //Aggiugnere somam di vettori con stream -=== cuda pinned e stream +=== cuda pinned e stream -La memoria pined veiene allocata con ``` cuda.pinned.array(shape,dtype)```. +La memoria pined veiene allocata con ``` cuda.pinned.array(shape,dtype)```. ```py arr = np.arrange(1_000_000, dtype=np.float32) with cuda.pinned(arr): @@ -68,17 +68,17 @@ with cuda.pinned(arr): ``` -L'array a viene pinnato e usato nel contesto come pinned. +L'array a viene pinnato e usato nel contesto come pinned. -Quando non usare pined: +Quando non usare pined: - No uso di memoria GPU - Non ci sono vantaggi oggettivi -- Troppi passaggi frequenti tra pinned e unpinned. +- Troppi passaggi frequenti tra pinned e unpinned. // capire cos'è -``` cuda.managed_array(shape,dtype)``` da usare con attenzione. +``` cuda.managed_array(shape,dtype)``` da usare con attenzione. -Uno stream si crea come ``` stream=cuda.stream()``` è indipendente dallo stream di default (non nullo). Per passarlo ad un kernel basta passarlo come parametro al kernel. +Uno stream si crea come ``` stream=cuda.stream()``` è indipendente dallo stream di default (non nullo). Per passarlo ad un kernel basta passarlo come parametro al kernel. //aggiungere esempio pipeline loop in cuda ```py @@ -92,6 +92,6 @@ Uno stream si crea come ``` stream=cuda.stream()``` è indipendente dallo stream scale_kernel[blocks, threads, stream[i]](d_buffers[i],210) ``` -Se ho lanciato un loop sulle operazioni di stream, devo lanciare un syncronize in modo tale che tutto il lavoro su GPU sia finito metto un sincronize rispetto all'host. +Se ho lanciato un loop sulle operazioni di stream, devo lanciare un syncronize in modo tale che tutto il lavoro su GPU sia finito metto un sincronize rispetto all'host. From 482b15f3621011d6e762d0754dc4699b7a621b72 Mon Sep 17 00:00:00 2001 From: Matteo Zagheno Date: Wed, 4 Feb 2026 17:02:09 +0100 Subject: [PATCH 3/3] modifiche lezione 6 --- chapters/Lezione6.typ | 135 ++++++++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 44 deletions(-) diff --git a/chapters/Lezione6.typ b/chapters/Lezione6.typ index 9549e88..037ca4f 100644 --- a/chapters/Lezione6.typ +++ b/chapters/Lezione6.typ @@ -1,97 +1,144 @@ #import "../template.typ": * -= Lezione 6 - -Lettura sequenziale permette di sfurttare la località della cache. += Stream e gestione della memoria == Trasferimento di memoria -GPU esegue una copia locked della memoria del sistema. Passiamo dalla gestione di una memoria paginabile (dato non stabile nel tempo) possiamo gestire sulal GPU della memoria non paginabile (bloccata). - -Ci sono delle API che permettono di allocare su HOST memoria non paginabile, una volta allocata possiamo fare trasferimenti asincroni. - -Vorremo togliere lo stage intermedio, fissiamo esplicitamente la memoria, aumantando la velocità del DMA. - -``` memcopyasync``` è la primitiva - -=== Concurency overlap - -Uno dei vantaggi è una concurincy overlap. Costruiamo degli stream indipendenti, ci possono essere trasferimenti su uno stream e esecuzioni in parallelo. IL DMA è unico, non posso fare trasferimenti paralleli ma posso far lavorare contemporaneamente CPU e DMA. +Quando la GPU richiede un trasferimento di dati dall'host i dati si devono trovare necessariamente in una porzione di memoria non paginabile (pinned). Se così non fosse nel caso di uno swap il trasferimento fallirebbe. + +Per ovviare a questo problema quando trasferiamo dati (ad esempio con `cudaMemcpy`) viene allocato un buffer di memoria non paginabile dove vengono copiati i dati prima di essere trasferiti. + +In alternativa possiamo direttamente allocare sull'host memoria non paginabile, una volta allocata possiamo fare trasferimenti asincroni e aumentando anche la velocità del trasferimento evitando il buffer di pinned memory intermedio (vedremo più avanti come). + +=== Concurrency overlap + +Uno dei vantaggi è la concurrency overlap. Tramite stream indipendenti abbiamo flussi di esecuzione paralleli: mentre uno stream sta trasferendo dei dati un'altro potrebbe star eseguendo un task. Lo stesso vale per la CPU, possiamo proseguire con l'esecuzione del nostro programma mentre trasferiamo dati. + +// TODO: sistemare il disegno + +#{ + import fletcher: edge, node + figure( + fletcher.diagram( + node-stroke: 1pt, + spacing: (0.1cm, 1cm), + node((0, 0), [*Esecuzione Sincrona*], shape: rect, fill: none, stroke: none), + node((0.3, 1), [Copy A], fill: orange, stroke: black, width: 1.8cm, height: 0.9cm), + node((1, 1), [Copy B], fill: orange, stroke: black, width: 1.8cm, height: 0.9cm), + node((2.7, 1), [Kernel], fill: green, stroke: black, width: 2.5cm, height: 0.9cm), + node((7.2, 1), [Copy C], fill: orange, stroke: black, width: 1.8cm, height: 0.9cm), + edge((-0.3, 1), (9.2, 1), "->", stroke: 2pt), + ), + caption: [Somma di vettori $C = A + B$ con trasferimenti sequenziali], + ) + + figure( + fletcher.diagram( + node-stroke: 1pt, + spacing: (0.6cm, 0.9cm), + node((0, 0), [*Esecuzione con Stream*], shape: rect, fill: none, stroke: none), + + // Generazione degli stream tramite loop + ..for i in range(4) { + let y = i + 1 + let offset = i * 0.7 + ( + node((-0.5, y), [Stream #(i + 1)], fill: none, stroke: none), + node((0.8 + offset, y), [A], fill: orange, stroke: black, width: 0.9cm, height: 0.7cm), + node((1.5 + offset, y), [B], fill: orange, stroke: black, width: 0.9cm, height: 0.7cm), + node((2.7 + offset, y), [v_s], fill: green, stroke: black, width: 1.3cm, height: 0.7cm), + node((4.3 + offset, y), [C], fill: orange, stroke: black, width: 0.9cm, height: 0.7cm), + edge((0, y), (5.2 + offset, y), "->", stroke: 2pt), + ) + }, + ), + caption: [Partizionamento dei vettori e sovrapposizione di trasferimento e computazione tramite stream], + ) +} -Problema, se allocassimo troppa pined memory potremmo andare a togliere un sacco di risorse. Se un singolo processo usa tante risorse, influisce sul tutto il sistema. +#attenzione()[ + Il DMA è unico, non posso fare trasferimenti paralleli ma posso far lavorare contemporaneamente CPU e DMA. + Bisogna anche fare attenzione a limitare le allocazioni di pinned memory, siccome non può essere spostata può influire sul tutto il sistema. +] === Gestione pined memory //AGGIUNGRE MATRICI CON PIN MEMORY == Stream -Uno stream è un unità di concorrenza in CUDA. Generare e gestire una coda FIFO di operazioni. Sono operazioni in sequenza dentro uno stream. Il vantaggio è che li stream sono indipendenti tra di loro (trasferimento e calcolo assieme) dipende dalla capacità della GPU. - -Per usare uno stream, basta passarlo al kernell. Una volta creato uso uno stream con il suo identificatore. +Uno stream è un'unità di concorrenza in CUDA. Ogni stream è una coda FIFO di operazioni. Ogni stream è indipendente dagli altri. L'esecuzione di uno stream è sempre asincrona rispetto all'host. -Si sposano bene con la pinned memory. Nel momento in cui faccio il trasferimento posso decire lo stream su cui va. +Un pattern comune è utilizzarli per i trasferimenti di memoria. Assegno il trasferimento a uno stream (utilizzando la pinned memory sull'host) e ad un altro assegno un task. Esistono due stream: - Default o nulla $"ID" = 0$ - Stream non nullo $"ID" > 0$ -- Stream bullo, blocca tutti gli altri li stream, non può andare in concomitanza. -#nota()[ - Il default stream di default si sincronizza con gli altri stream. +Il default stream si comporta così: +- Se si lancia un kernel nel default stream aspetterà che tutti gli altri stream finiscano. +- Se si lancia un kernel in un altro stream aspetterò che il default finisca. + +#attenzione()[ + Possiamo avere due comportamenti diversi per lo stream di default: + - `--default-stream legacy (or noflag)` lo stream si sincronizza con gli altri stream. È il comportamento descritto in precedenza. Da qui in avanti per il default stream si assume questo comportamento. + - `--default-stream per-thread` lo stream non si sincronizza con gli altri. ] -Può aumentare l'occupancy. Nel momento in cui abbiamo taglie piccole di griglie, possono occupare solo alcuni SM, in vece di senqualizzare le cose l'idea è che arrivano diversi processi(griglie) e vengono gestiti parallelamente su stream diversi. Le applicazioni lavorano in maniera concorrente, se stiamo all'interno della stessa applicazione abbiamo il parallelismo. +Gli stream possono essere usati per aumentare l'occupancy: Ad esempio se il nostro task occupa solo alcuni SM, in vece di sequenzializzare l'esecuzione posso suddividere il problema o eseguire direttamente task diversi su più stream gestiti parallelamente. -``` cudaDeviceSynchronize``` si ripecuote a livello di sistema per tutti gli stream. é la sincronizzazione più _forte_ che abbiamo. -//Mettere immagine old vs new +```cudaDeviceSynchronize``` sincronizza tutti gli stream. È la sincronizzazione più _forte_ che abbiamo. +//TODO Mettere immagine old vs new #attenzione()[ - La creazione di uno stream deve avvenire prima del suo utilizzo. Di solito gli stream vengono creati e distrutti con un loop + La creazione di uno stream deve avvenire prima del suo utilizzo. ] -é possibile anche sincronizzare il singolo stream con l'host e non solo gli stream tra di loro. +È possibile anche sincronizzare il singolo stream con l'host. -QUERY = non bloccante, ma ci permette di indagare lo stato di uno stream, per capire ad esempio quante operazioni ha eseguito. +Possiamo effettuare delle query sullo stream per sapere se ha completato le operazioni che gli erano state assegnate oppure no. //Aggiugnere somam di vettori con stream -=== cuda pinned e stream -La memoria pined veiene allocata con ``` cuda.pinned.array(shape,dtype)```. + +=== Pinned memory e Stream + +La memoria pined viene allocata con `cuda.pinned.array(shape,dtype)`. ```py arr = np.arrange(1_000_000, dtype=np.float32) with cuda.pinned(arr): d_arr = cuda.to.device(arr) #kernel out = cuda.device.to.host(d_out) - - ``` -L'array a viene pinnato e usato nel contesto come pinned. -Quando non usare pined: -- No uso di memoria GPU -- Non ci sono vantaggi oggettivi +Quando non usare pinned memory: +- Non uso di memoria GPU // TODO: ??? +- Non ci sono vantaggi oggettivi // TODO: ??? - Troppi passaggi frequenti tra pinned e unpinned. -// capire cos'è -``` cuda.managed_array(shape,dtype)``` da usare con attenzione. - -Uno stream si crea come ``` stream=cuda.stream()``` è indipendente dallo stream di default (non nullo). Per passarlo ad un kernel basta passarlo come parametro al kernel. +Uno stream si crea come `stream=cuda.stream()`. È indipendente dallo stream di default (id non nullo). Per lanciare un kernel sullo stream basta passarlo come parametro. //aggiungere esempio pipeline loop in cuda ```py - for i in range(num_streams): - strat = i * chunk_size - end = start + cunk_size + start = i * chunk_size + end = start + chunk_size #H2D copy cuda.to_device(h_in[start:end], to=d_buffers[i], stream=stream[i]) scale_kernel[blocks, threads, stream[i]](d_buffers[i],210) ``` -Se ho lanciato un loop sulle operazioni di stream, devo lanciare un syncronize in modo tale che tutto il lavoro su GPU sia finito metto un sincronize rispetto all'host. +Essendo le operazioni con gli stream asincrone bisogna ricordarsi di sincronizzare alla fine. + +=== Managed memory + +Esiste anche un'altro modo per spostare i dati tra GPU e CPU: la managed memory. +È un'astrazione che permette a CPU e GPU di operare in maniera trasparente su una stessa porzione di memoria. In questo caso il driver si occupa di tenere sincronizzati i dati tra CPU e GPU. Può essere una buona soluzione in alcuni casi ma può anche causare overhead significativi e non permette di avere un controllo granulare sui trasferimenti di memoria. +```py +cuda.managed_array(shape,dtype) #da usare con attenzione. +```