operaciones con matrices · 2015. 4. 29. · jerarquía de memorias registros = 8.0 tb/seg...
TRANSCRIPT
Operaciones con matrices
Clase 7, 29/04/2015http://fisica.cab.cnea.gov.ar/gpgpu/index.php/en/icnpg/clases
cp -a /share/apps/codigos/alumnos_icnpg2015/Matrices .
Jerarquía de memorias
● Registros = 8.0 TB/seg● Shared/L1 = 1.6 TB/seg● Global = 190 GB/seg● Mapped = 8.0 GB/seg
● Otras memorias: texturas, constante
Shared memory
● Puede considerarse como una caché programable (yo decido qué guardar y cuándo hacerlo).
● Es privada a cada bloque (48 kB máx) .
● Se encuentra distribuída en bancos de memoria.
● En fermi hay 32 bancos de 32 bits de ancho (128 bytes).
kernel<<<dimGrid, dimBlock,shared_memory>>>
Shared memory - Bancos
● Cada banco de memoria puede realizar una única operación de lectura/escritura.
● En Fermi tenemos 32 bancos de 4 bytes.● En Kepler tenemos 32 bancos de 8 bytes.
● El ancho de banda de cada banco es 32 bits por cada 2 ciclos de clock.
char, signed char, unsigned char 8 bits (1 byte)
short, short int, signed short, signed short int, unsigned shor, unsigned shor int, int, signed int
16 bits (2 bytes)
long, long int, signed long, signed long int, unsigned long, unsigned long int
32 bits (4 bytes)
(singed/unsigned) long long (int) 64 bits (8 bytes)
float 32 bits (4 bytes)
double 64 bits (8 bytes)
long double >80bits (12 / 16 bytes)
Ante la duda... printf(“%d\n”,sizeof(data_type));
Warps
● 1 warp = 32 threads, con un modelo SIMD. ● threadIdx.x es consecutivo a cada warp. ● Para grillas 2D los threads se serializan y se dividen en warps. Un bloque de 8x8
threads se divide en 2 warps con indexado:
● Warp 0 = (0,0), (1,0) … (7,0) … (0,3) … (7,3) ● Warp 1 = (0,4), (1,4) … (7,4) ... (0,5) … (7,7)
● Los accesos a memoria se realizan por warp (>= Fermi).
● Hay divergencia si dentro de un warp se ejecutan caminos distintos.
Transposición de una matríz
int tidx = threadIdx.x + blockDim.x * blockIdx.x;int tidy = threadIdx.y + blockDim.y * blockIdx.y;
int inputIndex = tidx + size * tidy;int outputIndex = tidy + size * tidx;
outMatrix[outputIndex] = inpMatrix[inputIndex]; Lecturas con acceso coalescido.
Escrituras con strides muy grandes.
Transposición de una matríz – Un poco mejor
La carga de datos es totalmente coalescida desde la matriz de entrada a un TILE de memoria compartida.
La escritura de datos es totalmente coalescida también.
Existe un conflicto de banco de memoria compartida. Cada thread accede a una palabra distinta del Mismo banco de memoria compartida.
__shared__ FLOAT tile[TILE_DIM][TILE_DIM];
unsigned int tidx = blockIdx.x * TILE_DIM + threadIdx.x;
unsigned int tidy = blockIdx.y * TILE_DIM + threadIdx.y;
unsigned int index_input = tidx + (tidy)*size;
tidx = blockIdx.y * TILE_DIM + threadIdx.x;
tidy = blockIdx.x * TILE_DIM + threadIdx.y;
unsigned int index_output = tidx + (tidy)*size;
for (int i=0; i<TILE_DIM; i+=ROWS)
tile[threadIdx.y+i][threadIdx.x] = input[index_input+i*size];
__syncthreads();
for (int i=0; i<TILE_DIM; i+=ROWS)
output[index_output+i*size] = tile[threadIdx.x][threadIdx.y+i];
Transposición de una matríz – Un poco mejor
Transposición de una matríz – Un poco mejor
TILE_DIM
Transposición de una matríz – Todavía mejor
Podemos evitar el conflicto de banco agregando un pequeño offset al TILE de memoria compartida.
__shared__ FLOAT tile[TILE_DIM][TILE_DIM+1];
TILE_DIM + 1
Veamos algunos ejemplos: transpose.
Multiplicación de matrices
Dadas dos matrices:A: hA x wAB: hB x wB
C = A x B: hA x wB
wA
hA
wB
hB
Algoritmo en CPU
void matrixMult(const float *A, const float *B, int hA, int wA, int wB, float *out){
for (int i = 0; i < hA; i++){
for(int j = 0; j < wB; j++){
float aux = 0.0f;
for (int k = 0; k < wA; k++){
aux += A[i * wA + j] * B[k * wB + j];
} C[i * wB + j] = aux;
} }}
Un kernel básico
__global__ void matrixMultiply(float *A, float *B, float *C, int hA, int wA, int BRows, int wB) { int row = threadIdx.y + blockDim.y * blockIdx.y;
int col = threadIdx.x + blockDim.x * blockIdx.x;
if ((row < hA) && (col < wB))
{ float sum = 0;
for (int i = 0; i < wA; i++)
{ sum += A[row * wA + i] * B[col + i * wB];
} C[row * wB + col] = sum;
} }
Lecturas con acceso coalescido.
Lecturas poco eficientes.
Cada thread lee una fila y una columna de A y B.
Un kernel un poco mejor
Se divide la matriz C en bloques de tamaño BLOCK_WIDTH.
Se utiliza una grilla dividida en bloques de tamaño BLOCK_WIDTH
Cada bloque del Grid calcula un bloque de C.
Para calcular un bloque de C necesito una ciertacantidad de columnas de B y filas de A.
Las mismas se cargan en memoria compartida.
Un kernel un poco mejor__global__ void matrixMultiply(float *A, float *B, float *C, int hA, int wA, int hB, int wB) {{ __shared__ float dA[TILE_WIDTH][TILE_WIDTH]; __shared__ float dB[TILE_WIDTH][TILE_WIDTH];
int row = threadIdx.y + blockDim.y * blockIdx.y; int col = threadIdx.x + blockDim.x * blockIdx.x; int tx = threadIdx.x; int ty = threadIdx.y; float value = 0; for (int i = 0; i < blockDim.x; i++) { if ((row < hA) && (i * TILE_WIDTH + tx < wA)) { dA[ty][tx] = A[row * wA + i * TILE_WIDTH + tx]; } else { dA[ty][tx] = 0.0; } if ((i * TILE_WIDTH + ty < wA) && (col < wB)) { dB[ty][tx] = B[(i * TILE_WIDTH + ty) * wB + col]; } else { dB[ty][tx] = 0.0; } __syncthreads(); for (int j = 0; j < TILE_WIDTH; j++) { value += dA[ty][j] * dB[j][tx]; } __syncthreads(); } if (row < hA && col < wB){ C[row * wB + col] = value; }}
Veamos un ejemplo: multiplicación
Referencias
● An Efficient Matrix Transpose in CUDA C/C++ - http://devblogs.nvidia.com/parallelforall/efficient-matrix-transpose-cuda-cc/
● Bank conflicts in shared memory in Cuda - http://cuda-programming.blogspot.com.ar/2013/02/bank-conflicts-in-shared-memory-in-cuda.html
● Optimizing Matrix Transpose in CUDA - http://docs.nvidia.com/cuda/samples/6_Advanced/transpose/doc/MatrixTranspose.pdf
● Memory optimizations - http://on-demand.gputechconf.com/gtc-express/2011/presentations/NVIDIA_GPU_Computing_Webinars_CUDA_Memory_Optimization.pdf