Streams, Memoria Unificada, etc. · 2016. 4. 13. · Streams – Hardware de Fermi 20! Los streams...
Transcript of Streams, Memoria Unificada, etc. · 2016. 4. 13. · Streams – Hardware de Fermi 20! Los streams...
1
Streams, Memoria Unificada, etc.
Clase 15
(thanks: Carlos Bederián [email protected]
IFEG-CONICET, FaMAF-UNC)
Motivación
2
! La GPU queda muy “lejos” del resto del sistema
– PCI Express 3.0 16X: 15.75 GB/s, bidireccional – Necesidad de ocultar esta latencia
! La GPU tiene poca memoria
– Necesidad de subir→procesar→bajar partes de sistemas grandes ! Si el kernel es computacionalmente intensivo (ej: MM),
puede operarse sin pérdida de performance
Motivación (cont.)
3
! Necesidad de concurrencia entre kernels dentro de una GPU
– A veces una GPU tiene demasiados recursos de ejecución para correr un único kernel
– Queremos poder correr múltiples kernels en paralelo
Kernels concurrentes
4
! Secuencial
! Cuatro en paralelo
cudaMemcpy(d_a,a,numBytes,cudaMemcpyHostToDevice);
some_kernel<<<1,N>>>(d_a)
cudaMemcpy(a,d_a,numBytes,cudaMemcpyDeviceToHost);
CPU - GPU Blocking
CPU - NON Blocking
Hasta ahora…
5
cudaMemcpy(d_a,a,numBytes,cudaMemcpyHostToDevice);some_kernel<<<1,N>>>(d_a)
some_cpu_work(a)cudaMemcpy(a,d_a,numBytes,cudaMemcpyDeviceToHost);
Overlaping CPU - GPU
6
Ocultamiento de latencia
7
Copia y cómputo (GeForce)
Secuencial
Doble copia y cómputo (>Tesla)
Streams
8
! Modelo de concurrencia dentro de la GPU
! Colas de tareas que se ejecutan secuencialmente en el orden que se despachan
! Todos los streams se ejecutan concurrentemente (si pueden)
Streams – Creación/Destrucción
9
cudaError_tcudaStreamCreate(cudaStream_t*s)
– Crea un nuevo stream y lo devuelve en *s
– Devuelve un cudaError_t como todo CUDA
! ¡Verificar el resultado! (nunca está de más recordarlo)
cudaStreamDestroy(cudaStream_tstream)
– Espera que stream termine su trabajo y lo destruye.
Streams – Ejecución
10
Encolar un kernel en un stream: kernel<<<grid,block,shared,stream>>>(...)
¡Parámetros de configuración nuevos!
shared: Memoria compartida definida en tiempo de ejecución
! Declaración en el kernel: extern__shared__Tbuf[];
! Por defecto: 0 bytes
stream: Stream donde encolar el kernel. ! Por defecto: Stream 0 (“null stream”)
– Creado automáticamente – Semántica distinta a la del resto de los streams
Streams – Memoria
11
! Encolar un memset: cudaMemset*Async(...,cudaStream_tstream)
! Encolar una transferencia: cudaMemcpy*Async(...,cudaStream_tstream)
OJO: La memoria del host debe ser pinned para que funcione
Pinned memory
12
Las copias asíncronas son manejadas por un DMA engine en la GPU ¡Pero el sistema operativo puede mudar las páginas de memoria!
CUDA provee funciones para usar memoria no paginable:
cudaMallocHost(void**ptr,size_tsize)
cudaFreeHost(void*ptr)
- Uso similar a cudaMalloc/cudaFree pero para memoria del host
- Copias más rápidas
¡Usar con precaución!
Streams – Sincronización
13
Esperar que termine un stream (como cudaDeviceSynchronize):
cudaStreamSynchronize(cudaStream_ts)
Consultar si un stream terminó (sin bloquear):
cudaError_tcudaStreamQuery(cudaStream_ts)
Streams – Eventos
14
! Esperar que termine un stream o todo el trabajo encolado en la GPU no es suficientemente fino
! Los eventos se introducen para cubrir esta necesidad – Marcadores que se insertan entre operaciones de un stream – Funciones de sincronización de eventos
Streams – Eventos (cont.)
15
Creación
cudaEvent_tevent;cudaEventCreate(&event);
Destrucción
cudaEventDestroy(event);
Inserción en un stream
cudaEventRecord(event,stream);
Streams – Eventos (cont.)
16
Ver si ocurrió un evento
cudaError_tstatus=cudaEventQuery(event);
Esperar que ocurra un evento
cudaEventSynchronize(event);
Hacer que un stream espere que se dé un evento antes de seguir:
cudaStreamWaitEvent(stream,event,0);
– Esto ocurre sin intervención del host
Streams – Cosas a evitar
17
Stream nulo
Sincroniza el device después de cada operación
Sincroniza el host después de cada operación, salvo algunas excepciones
Sincronización implícita cudaMallocHost/cudaHostAlloccudaMalloccudaMemcpy*/cudaMemset*(noAsync)
...
OJO: Estamos acostumbrados a esta falta de concurrencia
Streams – Ejemplo
18
cudaStream_tstream[3];
for(inti=0;i<3;++i)
cudaStreamCreate(&stream[i]);
for(inti=0;i<3;++i){
kernel_A<<<grid,block,0,stream[i]>>>(...);
kernel_B<<<grid,block,0,stream[i]>>>(...);
kernel_C<<<grid,block,0,stream[i]>>>(...);
}
cudaDeviceSynchronize();
for(inti=0;i<3;++i)
cudaStreamDestroy(stream[i]);
Streams – Ejemplo en Fermi
19
Streams – Hardware de Fermi
20
! Los streams son una abstracción del hardware subyacente
! Los streams se multiplexan a 3 colas en el hardware:
– Compute engine
– Device → Host engine
– Host → Device engine
! Las operaciones se ejecutan:
– En el orden que fueron despachadas
– Concurrentemente si pertenecen a distintos streams
KA1 < KA2 < KA3 = KB1 < KB2 < KB3 = KC1 < KC2 < KC3
! Fermi soporta hasta 16 grids activos concurrentes
Streams – Ejemplo (de nuevo)
21
cudaStream_tstream[3];
for(inti=0;i<3;++i)
cudaStreamCreate(&stream[i]);for(inti=0;i<3;++i)
kernel_A<<<grid,block,0,stream[i]>>>(...);
for(inti=0;i<3;++i)
kernel_B<<<grid,block,0,stream[i]>>>(...);
for(inti=0;i<3;++i)
kernel_C<<<grid,block,0,stream[i]>>>(...);
cudaDeviceSynchronize();
for(inti=0;i<3;++i)
cudaStreamDestroy(stream[i]);
Streams – Ejemplo (de nuevo)
22
Streams – Estrategias
23
No hay una solución buena para todos los problemas
Ver qué pasa en el profiler
Streams – Kepler
24
! SM 3.5 (GK110 / GK207)
– Una cola en hardware por stream
– Hasta 32 grids activos concurrentemente
– No hay dependencias ocultas entre streams
Fermi vs. Kepler
25
Hyper‐Q offers significant benefits for use in MPI‐based parallel computer systems. Legacy MPI‐based algorithms were often created to run on multi‐core CPU systems, with the amount of work assigned to each MPI process scaled accordingly. This can lead to a single MPI process having insufficient work to fully occupy the GPU. While it has always been possible for multiple MPI processes to share a GPU, these processes could become bottlenecked by false dependencies. Hyper‐Q removes those false dependencies, dramatically increasing the efficiency of GPU sharing across MPI processes.
Hyper‐Q working with CUDA Streams: In the Fermi model shown on the left, only (C,P) & (R,X) can run concurrently due to intra‐stream dependencies caused by the single hardware work queue. The Kepler Hyper‐Q model allows all streams to run concurrently using separate work queues.
Streams – Bibliotecas
26
Encolan sus kernels en el stream que se les configure
CUFFT cufftResultcufftSetStream(cufftHandleplan,cudaStream_tstream);
CUBLAS cublasStatus_tcublasSetStream(cublasHandle_thandle,cudaStream_tstream);
CUSPARSE cusparseStatus_tcusparseSetStream(cusparseHandle_th,cudaStream_tstream);
Unified Virtual Memory
27
Unified Virtual Memory
28
//allocomemoria
a=(float*)malloc(numBytes);
cudaMalloc(&d_a,numBytes);
//copiaH2D
cudaMemcpy(d_a,a,numBytes,cudaMemcpyHostToDevice);
//llamadoakernel
some_kernel<<<1,N>>>(d_a)
//copiaD2H
cudaMemcpy(a,d_a,numBytes,cudaMemcpyDeviceToHost);
//sigueelcódigo…
//allocomemoria
cudaMallocManaged(&a,numBytes);
//llamadoakernel
some_kernel<<<1,N>>>(a);
cudaDeviceSynchronize();
//sigueelcódigo…
Unified Virtual Memory
29
//variablesglobales'managed'
__device____managed__a[10];
__device____managed__x;
intmain(){
a[2]=3;
x=a[4];
//llamadoakernel
some_kernel<<<1,N>>>()
//sigueelcódigo…
}
Unified Virtual Memory
30
! GPU tiene acceso exclusivo durante la ejecución de un kernel.
! CPU y GPU no pueden acceder a memoria managed concurrentemente, ni siquiera si son variables distintas.
! Apéndice J de la CUDA C Programming Guide para más detalles.
Unified Virtual Memory: Streams
31
//variablesglobales'managed'
__device____managed__x;
__device____managed__intx,y=2;
__global__voidkernel(){
x=10;}
intmain(){
cudaStream_tstream1;
cudaStreamCreate(&stream1);
cudaStreamAttachMemAsync(stream1,&y,0,cudaMemAttachHost);
cudaDeviceSynchronize();//WaitforHostattachmenttooccur.
kernel<<<1,1,0,stream1>>>();//Note:Launchesintostream1.
y=20;//Success–akernelisrunningbut“y”
//hasbeenassociatedwithnostream.
return0;
}
Unified Virtual Memory: Deep copies
32
voidlaunch(dataElem*elem){
dataElem*d_elem;
char*d_name;
intnamelen=strlen(elem->name)+1;
//Allocatestorageforstructandname
cudaMalloc(&d_elem,sizeof(dataElem));
cudaMalloc(&d_name,namelen);
//Copyupeachpieceseparately,includingnew“name”pointervalue
cudaMemcpy(d_elem,elem,sizeof(dataElem),cudaMemcpyHostToDevice);
cudaMemcpy(d_name,elem->name,namelen,cudaMemcpyHostToDevice);
cudaMemcpy(&(d_elem->name),&d_name,sizeof(char*),cudaMemcpyHostToDevice);
//Finallywecanlaunchourkernel,butCPU&GPUusedifferentcopiesof“elem”
Kernel<<<...>>>(d_elem);
}
structdataElem{
intprop1;
intprop2;
char*name;
}
Unified Virtual Memory: Deep copies
33
voidlaunch(dataElem*elem){
Kernel<<<...>>>(elem);
}
structdataElem{
intprop1;
intprop2;
char*name;
}
Bibliografía
34
Justin Luitjens : Cuda Streams: Best Practices and common pitfalls- NVIDIA , GTC 2014
Steve Rennich, CUDA C/C++ Streams and Concurrency, GTC 2011
L. Nyland, S. Jones, Inside Kepler, GTC 2012
35
¿Che, y Thrust?
36
Thrust soporta streams