multiplicação de matrizes em cuda
DESCRIPTION
Exemplos de Multiplicação de Matrizes em CUDA.TRANSCRIPT
Multiplicação de Matrizes em CUDA
Divino César SoaresPontifícia Universidade Católica de Goiás (CMP/PUC-GO)
O Problema
• Duas matrizes de entrada: A e B. Que são quadradas e possuem os mesmos valores para suas dimensões: LARGURA x LARGURA.
• Gerar uma matriz resultado C com as mesmas dimensões das matrizes A e B.
• Cada elemento (i, j) da matriz C é o produto (interno) da linha i de A pela coluna j de B.
• Para cada elemento (i, j) de C:
for (k=1; k<=LARGURA; k++)C[i][j] += (A[i][k] * B[k][j]);
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
21 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 1k = 1
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
33 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 1k = 2
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
54 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 1k = 3
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 1k = 4
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 12 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 2k = 1
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 48 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 2k = 2
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 72 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 2k = 3
1 2 3 4
1
2
3
4
Implementação Sequencialvoid multiplica(int *A[], int *B[], int *C[]) {
for (int i=1; i<=LARGURA; i++) {for (int j=1; j<=LARGURA; j++) {
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
}}
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 99 0 0
0 0 0 0
0 0 0 0
0 0 0 0
• Variáveis:L = 4
i = 1j = 2k = 4
1 2 3 4
1
2
3
4
Estrutura da Solução1. Alocar memória na GPU.
2. Copia dados de entrada. Da CPU para a GPU.
3. Configura execução. Número de threads e blocos.
4. Copia resultados.
cudaMalloc((void **)&A_d, size_A);cudaMalloc((void **)&B_d, size_B);cudaMalloc((void **)&C_d, size_C);
cudaMemcpy(A_d, A, size_A, cudaMemcpyHostToDevice);cudaMemcpy(B_d, B, size_B , cudaMemcpyHostToDevice);cudaMemcpy(C_d, C, size_C , cudaMemcpyHostToDevice);
dim3 gride(X, Y)dim3 bloco(Z, W, K)meu_kernel<<<gride, bloco>>>(A, B, C);
cudaMemcpy(C, C_d, size_C , cudaMemcpyDeviceToHost);
Primeira Abordagem
Kernel 1dim3 gride(1, 1)dim3 bloco(4, 4, 1)
dim3 gride(2, 1)dim3 bloco(4, 4, 1)
dim3 gride(1, 1)dim3 bloco(30, 30, 1)
Gride
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Gride Gride
<< Launch error >>>
Bloco com 600 threadsBloco 0
Bloco 0 Bloco 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Kernel 1dim3 gride(1, 1)dim3 bloco(LARGURA, LARGURA, 1)
Gride
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Bloco 0
LARGURA
LARGU
RA
Kernel 1dim3 gride(1, 1)dim3 bloco(LARGURA, LARGURA, 1)
Gride
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Bloco 0
LARGURA
LARGU
RA
__global__ void mulGpu(int *A[], int *B[], int *C[]) {int i = threadIdx.x;int j = threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 1: Multiplicação na GPU
Kernel 1
__global__ void mulGpu(int *A[], int *B[], int *C[]) {int i = threadIdx.x;int j = threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 1: Multiplicação na GPU
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 2 3 4
1
2
3
4
Instante de tempo t=0
Kernel 1
__global__ void mulGpu(int *A[], int *B[], int *C[]) {int i = threadIdx.x;int j = threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 1: Multiplicação na GPU
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
21 12 9 0
35 20 15 0
49 28 21 0
7 4 3 0
1 2 3 4
1
2
3
4
Instante de tempo t=1
Kernel 1
__global__ void mulGpu(int *A[], int *B[], int *C[]) {int i = threadIdx.x;int j = threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 1: Multiplicação na GPU
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
12 36 24 4
18 54 36 6
24 72 48 8
18 54 36 42
1 2 3 4
1
2
3
4
Instante de tempo t=2
Kernel 1
__global__ void mulGpu(int *A[], int *B[], int *C[]) {int i = threadIdx.x;int j = threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 1: Multiplicação na GPU
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 99 72 73
123 159 87 88
105 138 81 40
89 134 63 70
1 2 3 4
1
2
3
4
Instante de tempo t=L
Vantagens/Desvantagens
• Vantagem em relação a sequencial: 1. cada elemento de C é calculado em paralelo.
• Desvantagens desta abordagem:1. Restrição do formato das matrizes. Elas devem ser quadradas.2. Restrição da quantidade de elementos em cada matriz. Menor que 512.3. Usa apenas a memória global da GPU. A memória global apresenta grande latência.4. Apenas um bloco de threads, com poucas threads. Tamanho do maior bloco 22 x 22.5. Os mesmos dados são buscados várias vezes da memória.
Resultado: Subutilização dos recursos da GPU.
Segunda Abordagem
Kernel 2dim3 gride(2, 2)dim3 bloco(15, 15, 1)
dim3 gride(1, 1)dim3 bloco(30, 30, 1)
GrideGride
<< Launch error >>>
Bloco com 600 threads
Bloco 0, 0 Bloco 0, 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Bloco 1, 0 Bloco 1, 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Kernel 2
225 threads por bloco.Total de 900 threads.
dim3 gride(2, 2)dim3 bloco(15, 15, 1)
Gride
Bloco 0, 0 Bloco 0, 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Bloco 1, 0 Bloco 1, 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Kernel 2dim3 gride(2, 2)dim3 bloco(15, 15, 1)
Gride
Bloco 0, 0 Bloco 0, 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
Bloco 1, 0 Bloco 1, 1
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
0,0 0,1 0,2 0,3
1,0 1,1 1,2 1,3
2,0 2,1 2,2 2,3
3,0 3,1 3,2 3,3
__global__ void mulGpu2(int *A[], int *B[], int *C[]) {int i = blockIdx.x * SUB_LARGURA + threadIdx.x;int j = blockIdx.y * SUB_LARGURA + threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 2: Multiplicação na GPU
Kernel 2
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
1 2 3 4
1
2
3
4
__global__ void mulGpu2(int *A[], int *B[], int *C[]) {int i = blockIdx.x * SUB_LARGURA + threadIdx.x;int j = blockIdx.y * SUB_LARGURA + threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 2: Multiplicação na GPU
Kernel 2
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
21 12 0 0
35 20 0 0
0 0 0 0
0 0 0 0
1 2 3 4
1
2
3
4
__global__ void mulGpu2(int *A[], int *B[], int *C[]) {int i = blockIdx.x * SUB_LARGURA + threadIdx.x;int j = blockIdx.y * SUB_LARGURA + threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 2: Multiplicação na GPU
Kernel 2
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
12 36 0 0
18 54 0 0
0 0 21 0
0 0 3 0
1 2 3 4
1
2
3
4
__global__ void mulGpu2(int *A[], int *B[], int *C[]) {int i = blockIdx.x * SUB_LARGURA + threadIdx.x;int j = blockIdx.y * SUB_LARGURA + threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 2: Multiplicação na GPU
Kernel 2
7 4 3 0
3 9 6 1
7 8 1 5
2 3 4 6
3 4 3 9
5 6 8 7
7 8 4 2
1 6 8 4
72 99 72 73
123 159 87 88
105 138 81 40
89 134 63 70
1 2 3 4
1
2
3
4
Instante de tempo t=L __global__ void mulGpu2(int *A[], int *B[], int *C[]) {
int i = blockIdx.x * SUB_LARGURA + threadIdx.x;int j = blockIdx.y * SUB_LARGURA + threadIdx.y;
for (int k=1; k<=LARGURA; k++) {C[i][j] += (A[i][k] * B[k][j]);
}}
Kernel 2: Multiplicação na GPU
Vantagens/Desvantagens
• Vantagem em relação a sequencial: 1. cada elemento de C é calculado em paralelo.
• Vantagens em relação a primeira abordagem: 1. Matrizes de tamanho arbitrário.2. Quantidade maior de blocos, permite melhor utilização dos recursos da GPU.
• Desvantagens desta abordagem:1. Restrição do formato das matrizes. Elas devem ser quadradas.2. Usa apenas a memória global da GPU. A memória global apresenta grande latência.3. Os mesmos dados são buscados várias vezes da memória.
Resultado: Muito tempo gasto esperando transferência de dados.
Dúvidas?