InterCon 2017 - Processamento de imagem em larga escala com GPU - Thiago Avelino
Programação para GPU - dcce.ibilce.unesp.braleardo/cursos/hpc/gpus2020.pdf · &num_of_devices //...
Transcript of Programação para GPU - dcce.ibilce.unesp.braleardo/cursos/hpc/gpus2020.pdf · &num_of_devices //...
Programação para GPU
Aleardo Manacero Jr.
Introdução
O uso de GPUs em computação de alto desempenho começou com o desenvolvimento de APIs que permitissem a programação das placas gráficas em alto nível
Isso significa abandonar bibliotecas gráficas, como DirectX e usar primitivas apropriadas à paralelização de aplicações
Introdução
Três bibliotecas para programação de GPU:
CUDA
OpenCL
DirectCompute
Esse processo, infelizmente, depende da arquitetura de cada máquina
Arquitetura de GPUs
GPUs são dispositivos originalmente criados para acelerar o processamento gráfico
Sua arquitetura é massivamente paralela
Os elementos de processamento são direcionados para operações aritméticas
O alto grau de paralelismo dá seu desempenho
Arquitetura de GPUs
ATI Radeon 5870
Arquitetura de GPUs
ATI Radeon 5870
Arquitetura de GPUs
Nvidia Kepler
Arquitetura de GPUs
Nvidia Kepler
Programação
As diferenças de arquitetura tornam a programação sensível ao contexto
Um programa otimizado para uma GPU pode não funcionar bem em outra
O que fazer então?
Programação
Usar técnicas de particionamento do problema adequadas para a GPU a ser usada
Usar a biblioteca para paralelização adequada para a GPU a ser usada
openCL
Programação
OpenCL (Open Computing Language) é uma API para programação de GPUs (ou outros dispositivos) e sua interação com a CPU
Framework de três partes (linguagem, plataforma e execução)
Programação
Linguagem baseada em C99, com extensões e restrições adicionais
API de Plataforma com rotinas para tratar o sistema e seus recursos
API de execução gerencia objetos criados na execução de processos openCL
Kernel
Programas são divididos em dois componentes:
Host, que executa na CPU e envia kernels para as GPUs
Kernel, que executa nas GPUs, de modo paralelo (dados ou tarefas)
Kernel
Para a paralelização de kernel é preciso definir o número de dimensões do objeto a ser tratado
Cada elemento no objeto é chamado de work-item
O número de kernels a ser criado depende da dimensão global do objeto
Kernel
Pode haver aglomeração de work-itens em um work-group
Kernel
Pode haver aglomeração de work-itens em um work-group
Exemplo (paralelismo de dados)
SEQUENCIAL
void square(int n, const float *a,
float *result)
{ int i;
for (i=0; i<n; i++)
result[i] = a[i] * a[i];
}
PARALELO
kernel dp_square (const float *a,
float *result)
{ int id = get_global_id(0);
result[id] = a[id] * a[id];
}
// dp_square executes over “n” work-items
Host
Gerencia os recursos openCL, ou seja, gerencia:
Dispositivos
Rotinas (código) de kernel
Kernel
Memória
Memória
É gerenciada explicitamente pelo programa
Cuida das movimentações entre host e kernel
Se divide em quatro níveis dentro da GPU
Memória
Na GPU temos:
Global – R/W para todos itens e groups
Constante – R/W para host e R para itens
Local – R/W para itens no mesmo group
Privada – R/W para um item apenas
Memória
Execução de um programa
Envolve várias etapas:Buscar dispositivos no sistema
Criar contexto para associar dispositivos
Criar programas para execução nos dispositivos
Selecionar kernels para execução
Criar objetos de memória (host e dispositivo)
Transferir dados para dispositivos
Submeter kernels para fila de comandos
Copiar resultados para o host
Execução de um programa
Identificação das plataformas disponíveis
cl_platform_id platforms;cl_uint num_platforms;
// query for 1 available platformcl_int err = clGetPlatformIDs (
1, // the number of entries that can be added to platforms&platforms, // list of OpenCL found&num_platforms); // the number of OpenCL platforms found
// platforms are NVIDIA, INTEL, AMD, ...
Execução de um programa
Identificação dos dispositivos disponíveis
cl_device_id device_id;cl_uint num_of_devices;cl_int err;err = clGetDeviceIDs (platform_id, // platform_id retrieved from clGetPlatformIDsCL_DEVICE_TYPE_GPU, // device type to search for1, // number of id add to device_id list&device_id, // list of device ids&num_of_devices // number of compute devices found);
TYPE pode ser CPU, GPU, ACCELERATOR, DEFAULT, ALL
Execução de um programa
Criação de contexto
cl_context context; // context properties list - must be ended with 0properties[0]= CL_CONTEXT_PLATFORM; // tells platform to useproperties[1]= (cl_context_properties) platform_id;properties[2]= 0;context = clCreateContext (
properties, // list of context properties1, // num of devices in the device_id list&device_id, // the device id listNULL, // pointer to the error callback function (if required)NULL, // the argument data to pass to the callback function&err // the return code
);
Execução de um programa
Criação da fila de comandos
cl_command_queue command_queue;command_queue = clCreateCommandQueue (
context, // a valid contextdevice_id, // a valid device associated with the context0, // properties for the queue - not used here&err // the return code
);
Campo properties (0 no exemplo) pode ser:CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE CL_QUEUE_PROFILING_ENABLE
Execução de um programa
Criação de um programa
const char *ProgramSource ="__kernel void hello (__global float *input, __global float *output)\n"\"{\n"\" size_t id = get_global_id (0);\n"\" output[id] = input[id] * input[id];\n"\"}\n";
Neste exemplo o programa aparece na forma de string a ser lida pelo código do host
Execução de um programa
Criação de um programa
cl_program program;program = clCreateProgramWithSource (
Context, // a valid context1, // the number strings in the next parameter(const char **) &ProgramSource, // the array of stringsNULL, // the length of each string or can be NULL terminated&err // the error return code
);
Se o programa já for um executável trocar por clCreateProgramWithBinary
Execução de um programa
Criação do executável
err = clBuildProgram (program, // a valid program object0, // number of devices in the device listNULL, // device list – NULL means for all devicesNULL, // a string of build optionsNULL, // callback function when executable has been builtNULL // data arguments for the callback function
);
Execução de um programa
Criação do executável
err = clBuildProgram (program, // a valid program object0, // number of devices in the device listNULL, // device list – NULL means for all devicesNULL, // a string of build optionsNULL, // callback function when executable has been builtNULL // data arguments for the callback function
);
clGetBuildProgramInfo() permite recuperar o log da função acima, para eventuais erros de compilação
Execução de um programa
Criação de objetos kernel
cl_kernel kernel;
kernel = clCreateKernel(program, // a valid program object successfully built"hello", // the name of the kernel declared with __kernel&err // error return code
);
clCreateKernel retorna um valor diferente de zero se for bem sucedida ou zero se falhar (tipo de erro é retornado em “err”)
Execução de um programa
Erros podem ser:
CL_INVALID_PROGRAM — O programa não é um objeto válido
CL_INVALID_PROGRAM_EXECUTABLE — O programa não contém um executável construído com sucesso
CL_INVALID_KERNEL_NAME — Nome do kernel não foi localizado no programa objeto
CL_INVALID_VALUE — Nome do kernel é NULL.
CL_OUT_OF_HOST_MEMORY — Host não foi capaz de alocar recursos OpenCL
Execução de um programa
Definição de parâmetros do kernel
err = clSetKernelArg (kernel, // valid kernel object0, // the specific argument index of a kernelsizeof(cl_mem), // the size of the argument data&input_data // a pointer of data used as the argument
);
clGetKernelInfo() retorna informações sobre os parâmetros de um kernel
Execução de um programa
Colocando um kernel para execução
err = clEnqueueNDRangeKernel (command_queue, // valid command queuekernel, // valid kernel object1, // the work problem dimensionsNULL, // reserved for future revision - must be NULL&global, // work-items for each dimensionNULL, // work-group size for each dimension0, // number of event in the event listNULL, // list of events that needs to complete before this executesNULL // event object to return on completion
);
Execução de um programa
Para o comando anterior é preciso definir alguns parâmetros:
O número de dimensões pode ser 1, 2 ou 3
work-itens por dimensão precisa ser definida, como
size_t global[2]={512,512};
work-group por dimensão deve ser definido, como
size_t local[2]={8,8};
Execução de um programa
Terminando a execução (liberando recursos)
clReleaseMemObject(input);
clReleaseMemObject(output);
clReleaseProgram(program);
clReleaseKernel(kernel);
clReleaseCommandQueue(command_queue);
clReleaseContext(context);
Vetores em openCL
Vetores são definidos de forma explícita, como por exemplo int8 alfa;
Vetores em openCL
O número de elementos pode ser 2, 4, 8 ou 16
Os elementos podem ser indexados por valores hexadecimais ou letras (até 4 elem.)
float16 x;x.sa = 1.0f;
float4 c;c.x = 1.0f; c.y = 1.0f; c.z = 1.0f; c.w = 1.0f;
Vetores em openCL
O acesso ainda pode ser feito por grupos, como:
float4 f = (float4) (1.0f, 2.0f, 3.0f, 4.0f);float2 low, high;float2 o, e;
low = f.lo; // returns f.xy (1.0f, 2.0f)high = f.hi; // returns f.zw (3.0f, 4.0f)
o = f.odd; // returns f.yw (2.0f, 4.0f)e = f.even; // returns f.xz (1.0f, 3.0f)
Vetores em openCL
Casting é feito por funções de conversão, apesar de tipos escalares também aceitarem casting clássico
int4 i;float4 f = convert_float4(i); // int4 vector to float4
float f;int i = convert_int(f); // float scaler to an integer scaler
Exemplo em openCL
Multiplicação convencional de matrizes
Alguns trechos foram suprimidos (a geração dos conteúdos das matrizes, p.ex.)
O código do kernel está em arquivo lido pelo programa do host
As matrizes são tratadas como vetores
Host code openCL
#include <stdio.h> + <stdlib.h> + <string.h> + <math.h>#include <fcntl.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <stdbool.h>#include <CL/cl.h> // openCL headers
#define WA 1024#define HA 1024#define WB 1024
#define HB WA#define WC WB#define HC HA
Host code openCL
long LoadOpenCLKernel(char const* path, char **buf){ FILE *fp; size_t fsz; long off_end; int rc;
/* Open the file */ fp = fopen(path, "r"); if ( NULL == fp ) { return -1L; }
/* Seek to the end of the file */ rc = fseek(fp, 0L, SEEK_END); if ( 0 != rc ) { return -1L; }
Host code openCL
/* Byte offset to the end of the file (size) */ if ( 0 > (off_end = ftell(fp)) ) { return -1L; } fsz = (size_t)off_end;
/* Allocate a buffer to hold the whole file */ *buf = (char *) malloc( fsz+1); if ( NULL == *buf ) { return -1L; }
/* Rewind file pointer to start of file */ rewind(fp);
Host code openCL
/* Slurp file into buffer */ if( fsz != fread(*buf, 1, fsz, fp) ) { free(*buf); return -1L; }
/* Close the file */ if( EOF == fclose(fp) ) { free(*buf); return -1L; }
/* Make sure the buffer is NUL-terminated, just in case */ (*buf)[fsz] = '\0';
/* Return the file size */ return (long)fsz;}
Host code openCL
int main(int argc, char** argv){int err; // error code returned from api calls cl_device_id device_id; // compute device id cl_context context; // compute context cl_command_queue commands; // compute command queue cl_program program; // compute program cl_kernel kernel; // compute kernel
// OpenCL device memory for matrices cl_mem d_A; cl_mem d_B; cl_mem d_C;
Host code openCL
//Allocate host memory for matrices A and B unsigned int size_A = WA * HA; unsigned int mem_size_A = sizeof(float) * size_A; float* h_A = (float*) malloc(mem_size_A); unsigned int size_B = WB * HB; unsigned int mem_size_B = sizeof(float) * size_B; float* h_B = (float*) malloc(mem_size_B);
//Initialize host memory randomMemInit(h_A, size_A); randomMemInit(h_B, size_B); //Allocate host memory for the result C unsigned int size_C = WC * HC; unsigned int mem_size_C = sizeof(float) * size_C; float* h_C = (float*) malloc(mem_size_C);
Host code openCL
printf("Initializing OpenCL device...\n");
cl_uint dev_cnt = 0; clGetPlatformIDs(0, 0, &dev_cnt);
cl_platform_id platform_ids[100]; clGetPlatformIDs(dev_cnt, platform_ids, NULL);
// Connect to a compute device int gpu = 1; err = clGetDeviceIDs(platform_ids[0], gpu ? CL_DEVICE_TYPE_GPU : CL_DEVICE_TYPE_CPU, 1, &device_id, NULL); if (err != CL_SUCCESS) { printf("Error: Failed to create a device group!\n"); return EXIT_FAILURE; }
Host code openCL
// Create a compute context context = clCreateContext(0, 1, &device_id, NULL, NULL, &err); if (!context) { printf("Error: Failed to create a compute context!\n"); return EXIT_FAILURE; }
// Create a command commands commands = clCreateCommandQueue(context, device_id, 0, &err); if (!commands) { printf("Error: Failed to create a command commands!\n"); return EXIT_FAILURE; }
Host code openCL
// Create the compute program from the source file char *KernelSource; long lFileSize;
lFileSize = LoadOpenCLKernel("matrixmul_kernel.cl", &KernelSource); if( lFileSize < 0L ) { perror("File read failed"); return 1; }
program = clCreateProgramWithSource(context, 1, (const char **) & KernelSource, NULL, &err); if (!program) { printf("Error: Failed to create compute program!\n"); return EXIT_FAILURE; }
Host code openCL
// Build the program executable
err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL); if (err != CL_SUCCESS) { size_t len; char buffer[2048]; printf("Error: Failed to build program executable!\n"); clGetProgramBuildInfo(program, device_id, CL_PROGRAM_BUILD_LOG, sizeof(buffer), buffer, &len); printf("%s\n", buffer); exit(1); }
// Create the compute kernel in the program we wish to run
kernel = clCreateKernel(program, "matrixMul", &err); if (!kernel || err != CL_SUCCESS) { printf("Error: Failed to create compute kernel!\n"); exit(1); }
Host code openCL
// Create the input and output arrays in device memory for our calculation d_C = clCreateBuffer(context, CL_MEM_READ_WRITE, mem_size_A, NULL, &err); d_A = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, mem_size_A, h_A, &err); d_B = clCreateBuffer(context, CL_MEM_READ_WRITE | CL_MEM_COPY_HOST_PTR, mem_size_B, h_B, &err);
if (!d_A || !d_B || !d_C) { printf("Error: Failed to allocate device memory!\n"); exit(1); }
Host code openCL
printf("Running matrix multiplication for matrices A (%dx%d) and B (%dx%d) ...\n", WA,HA,WB,HB);
//Launch OpenCL kernel size_t localWorkSize[2], globalWorkSize[2];
int wA = WA; int wC = WC; err = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&d_C); err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&d_A); err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&d_B); err |= clSetKernelArg(kernel, 3, sizeof(int), (void *)&wA); err |= clSetKernelArg(kernel, 4, sizeof(int), (void *)&wC);
if (err != CL_SUCCESS) { printf("Error: Failed to set kernel arguments! %d\n", err); exit(1); }
Host code openCL
localWorkSize[0] = 16; localWorkSize[1] = 16; globalWorkSize[0] = 1024; globalWorkSize[1] = 1024; err = clEnqueueNDRangeKernel(commands, kernel, 2, NULL, globalWorkSize, localWorkSize, 0, NULL, NULL);
if (err != CL_SUCCESS) { printf("Error: Failed to execute kernel! %d\n", err); exit(1); } //Retrieve result from device err = clEnqueueReadBuffer(commands, d_C, CL_TRUE, 0, mem_size_C, h_C, 0, NULL, NULL);
Host code openCL
if (err != CL_SUCCESS) { printf("Error: Failed to read output array! %d\n", err); exit(1); }
printf("Matrix multiplication completed...\n");
//Shutdown and cleanup free(h_A); free(h_B); free(h_C);
clReleaseMemObject(d_A); clReleaseMemObject(d_C); clReleaseMemObject(d_B);
clReleaseProgram(program); clReleaseKernel(kernel); clReleaseCommandQueue(commands); clReleaseContext(context);
return 0;}
Kernel em openCL
// Arquivo matrixmul_kernel.cl
// OpenCL Kernel__kernel void matrixMul(__global float* C, __global float* A, __global float* B, int wA, int wB){ int tx = get_global_id(0); int ty = get_global_id(1); // value stores the element that is computed by the thread float value = 0;
Kernel em openCL
for (int k = 0; k < wA; ++k) { float elementA = A[ty * wA + k]; float elementB = B[k * wB + tx]; value += elementA * elementB; } // Write the matrix to device memory each // thread writes one element C[ty * wA + tx] = value;}
CUDA
Introdução
Criada para programação de GPUs da Nvidia
Programação mais simples do que openCL
Problema é ser restrita ao fabricante
Usaremos material da própria Nvidia