Disciplina de TICs 1 - Introdução a Programação em GPGPU
Transcrição
Disciplina de TICs 1 - Introdução a Programação em GPGPU
Introdução Múltiplos Stream Agendamento de Trabalho Referências Disciplina de TICs 1 - Introdução a Programação em GPGPU Prof. Tiago A. E. Ferreira Aula 11 - Streams (Parte II) Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Introdução Foi visto na aula passada que é possı́vel que a GPU controle uma certa porção de memória do Host. Para tanto é necessário utilizar uma memória não paginável no Host Também foi visto que é possı́vel gerar um stream em cuda como uso das memórias não pagináveis. Cada stream pode ser visto como uma fila processos a serem executados na GPU (de forma paralela) O que ainda não foi visto foi o uso de múltiplos stream. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Múltiplos Stream Na aula passada foi vista uma aplicação que utilizava o conceito de stream para a realização de processamento sobre a GPU Contudo, esta aplicação utilizava apenas um único stram. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Múltiplos Stream Na aula passada foi vista uma aplicação que utilizava o conceito de stream para a realização de processamento sobre a GPU Contudo, esta aplicação utilizava apenas um único stram. A ideia agora é adaptar tal aplicação para que sejam aplicados mais de um stream na realização do processamento sobre a GPU Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências A Aplicação Inicialmente ainda se verifica se a GPU tem suporte a avorlap Uma vez a GPU tenha este suporte, então a computação é quebrada em pedaços Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências A Aplicação Inicialmente ainda se verifica se a GPU tem suporte a avorlap Uma vez a GPU tenha este suporte, então a computação é quebrada em pedaços A ideia para se se melhorar a versão desta aplicação é baseada em dois pontos: A computação dos pedaços e a sobreposição de memória são reproduzidas com a execução do kernel Será realizado um esforço de tal forma que o stream 1 copie os buffers de entrada para a GPU e o stream 0 execute o kernel. Então, o stream 1 executa o kernel enquanto o stream 0 executa a cópia dos resultados para o host. Então, o sream 1 irá copiar seus resultados para o host enquanto o stream 0 começa a executar seu kernel sobre o próximo pedaço dos dados. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Linha do Tempo É assumido que cada caixa tem a mesma duração temporal Assim, é assumido que a GPU pode executar um kernel ao mesmo tempo que realiza cópia de memória memcpy = cudaMemcpyAscyn() As caixas vazias representa a espera de um stream de uma operação que não aceita sobreposição Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências A Sobreposição Dependendo a placa que se esteja trabalhando a capacidade de sobreposição pode ser alterada. Quando uma placa permite sobreposição, tem-se minimamente, Execução de um Kernel Uma Cópia de Memória Em placa mais modernas é possı́vel ter a sobreposição de, Execução de um Kernel Duas Cópias de Memória Uma cópia do Host para o Devide Uma cópia do Device para o Host Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Código do Kernel O código do Kernel fica inalterado #define N (1024*1024) #define FULL_DATA_SIZE (N*20) __global__ void kernel( int *a, int *b, int *c ) { int idx = threadIdx.x + blockIdx.x * blockDim.x; if (idx < N) { int idx1 = (idx + 1) % 256; int idx2 = (idx + 2) % 256; float as = (a[idx] + a[idx1] + a[idx2]) / 3.0f; float bs = (b[idx] + b[idx1] + b[idx2]) / 3.0f; c[idx] = (as + bs) / 2; } } Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Código do Kernel O código do Kernel fica inalterado #define N (1024*1024) #define FULL_DATA_SIZE (N*20) __global__ void kernel( int *a, int *b, int *c ) { int idx = threadIdx.x + blockIdx.x * blockDim.x; if (idx < N) { int idx1 = (idx + 1) % 256; int idx2 = (idx + 2) % 256; float as = (a[idx] + a[idx1] + a[idx2]) / 3.0f; float bs = (b[idx] + b[idx1] + b[idx2]) / 3.0f; c[idx] = (as + bs) / 2; } } O próximo passo é verificar se exite suporte a sobreposição Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Verificando a Sobreposição int main( void ) { cudaDeviceProp prop; int whichDevice; HANDLE_ERROR( cudaGetDevice( &whichDevice ) ); HANDLE_ERROR( cudaGetDeviceProperties( &prop, whichDevice ) ); if (!prop.deviceOverlap) { printf( "Device will not handle overlaps, so no " "speed up from streams\n" ); return 0; } cudaEvent_t start, stop; float elapsedTime; // start the timers HANDLE_ERROR( cudaEventCreate( &start ) ); HANDLE_ERROR( cudaEventCreate( &stop ) ); HANDLE_ERROR( cudaEventRecord( start, 0 ) ); Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Criação dos Streams O próximo passo é criar os dois streams da mesma forma como foi criado um único stream // initialize the streams cudaStream_t stream0, stream1; HANDLE_ERROR( cudaStreamCreate( &stream0 ) ); HANDLE_ERROR( cudaStreamCreate( &stream1 ) ); Ainda será assumido que se tem dois buffers de entrada e um único buffer de saı́da. Contudo, agora serão alocados dois conjuntos idênticos de buffers na GPU, de forma que cada stream possa independentemente trabalhar sobre os pedaços da entrada Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Alocações int *host_a, *host_b, *host_c; int *dev_a0, *dev_b0, *dev_c0; //GPU buffers for stream0 int *dev_a1, *dev_b1, *dev_c1; //GPU buffers for stream1 // allocate the memory on HANDLE_ERROR( cudaMalloc( HANDLE_ERROR( cudaMalloc( HANDLE_ERROR( cudaMalloc( HANDLE_ERROR( cudaMalloc( HANDLE_ERROR( cudaMalloc( HANDLE_ERROR( cudaMalloc( the GPU (void**)&dev_a0,N (void**)&dev_b0,N (void**)&dev_c0,N (void**)&dev_a1,N (void**)&dev_b1,N (void**)&dev_c1,N * * * * * * sizeof(int) sizeof(int) sizeof(int) sizeof(int) sizeof(int) sizeof(int) ) ) ) ) ) ) ); ); ); ); ); ); // allocate page-locked memory, used to stream HANDLE_ERROR(cudaHostAlloc( (void**)&host_a,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault )); HANDLE_ERROR(cudaHostAlloc( (void**)&host_b,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault )); HANDLE_ERROR(cudaHostAlloc( (void**)&host_c,FULL_DATA_SIZE*sizeof(int),cudaHostAllocDefault )); for (int i=0; i<FULL_DATA_SIZE; i++) { host_a[i] = rand(); host_b[i] = rand(); } Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Próximos Passos... Agora têm-se que correr o laço sobre os pedaços das entrada. Porém, agora estamos utilizando dois streams Logo se processa duas vezes mais dados por iteração do laço No stream0 são enfileirados as cópias assı́ncronas dos buffers de entrada a e b para a GPU, uma execução do kernel e uma cópia de volta para o buffer de saı́da c Após as operações no stream0, são enfileiradas operações idênticas para o próximo pedaço de dados, mas agora no stream1 Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Stream0 // now loop over full data, in bite-sized chunks for (int i=0; i<FULL_DATA_SIZE; i+= N*2) { // copy the locked memory to the device, async HANDLE_ERROR( cudaMemcpyAsync( dev_a0, host_a+i, N * sizeof(int), cudaMemcpyHostToDevice, stream0 ) ); HANDLE_ERROR( cudaMemcpyAsync( dev_b0, host_b+i, N * sizeof(int), cudaMemcpyHostToDevice, stream0 ) ); kernel<<<N/256,256,0,stream0>>>( dev_a0, dev_b0, dev_c0 ); // copy the data from device to locked memory HANDLE_ERROR( cudaMemcpyAsync( host_c+i, dev_c0, N * sizeof(int), cudaMemcpyDeviceToHost, stream0 ) ); Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Stream1 // copy the locked memory to the device, async HANDLE_ERROR( cudaMemcpyAsync( dev_a1, host_a+i+N, N * sizeof(int), cudaMemcpyHostToDevice, stream1 ) ); HANDLE_ERROR( cudaMemcpyAsync( dev_b1, host_b+i+N, N * sizeof(int), cudaMemcpyHostToDevice, stream1 ) ); kernel<<<N/256,256,0,stream1>>>( dev_a1, dev_b1, dev_c1 ); // copy the data from device to locked memory HANDLE_ERROR( cudaMemcpyAsync( host_c+i+N, dev_c1, N * sizeof(int), cudaMemcpyDeviceToHost, stream1 ) ); } Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Procedimentos O laço for () procede alternando os streams Após o término do laço for (), sincroniza-se a GPU com a CPU antes de se parar o cronômetro Desde que estamos trabalhando com dois streams, é necessário sincronizar ambos, HANDLE_ERROR( cudaStreamSynchronize( stream0 ) ); HANDLE_ERROR( cudaStreamSynchronize( stream1 ) ); Agora, para-se o cronômetro, mostra-se o tempo gasto e desaloca-se as memórias Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Finalizando HANDLE_ERROR( cudaEventRecord( stop, 0 ) ); HANDLE_ERROR( cudaEventSynchronize( stop ) ); HANDLE_ERROR( cudaEventElapsedTime( &elapsedTime, start, stop ) ); printf( "Time taken: %3.1f ms\n", elapsedTime ); // cleanup the streams and memory HANDLE_ERROR( cudaFreeHost( host_a ) ); HANDLE_ERROR( cudaFreeHost( host_b ) ); HANDLE_ERROR( cudaFreeHost( host_c ) ); HANDLE_ERROR( cudaFree( dev_a0 ) ); HANDLE_ERROR( cudaFree( dev_b0 ) ); HANDLE_ERROR( cudaFree( dev_c0 ) ); HANDLE_ERROR( cudaFree( dev_a1 ) ); HANDLE_ERROR( cudaFree( dev_b1 ) ); HANDLE_ERROR( cudaFree( dev_c1 ) ); HANDLE_ERROR( cudaStreamDestroy( stream0 ) ); HANDLE_ERROR( cudaStreamDestroy( stream1 ) ); return 0; } Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Testes Utilizando-se uma GeForce GTX 285, foi observado Processo com uma única stream Tempo gasto: 62 ms Processo com dois streams Tempo gasto: 61 ms Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Testes Utilizando-se uma GeForce GTX 285, foi observado Processo com uma única stream Tempo gasto: 62 ms Processo com dois streams Tempo gasto: 61 ms UH-oh!!!!!!!!!!!!!!!!!!!!!!!!! Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Testes Utilizando-se uma GeForce GTX 285, foi observado Processo com uma única stream Tempo gasto: 62 ms Processo com dois streams Tempo gasto: 61 ms UH-oh!!!!!!!!!!!!!!!!!!!!!!!!! Calma.... é preciso entendermos mais a respeito dos streams e como o drive e o hardware CUDA os encaram Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Agendamento de Trabalho na GPU Como programadores, temos uma noç ao de Stream como uma fila ordenada de invocações de processos. Contudo o hardware CUDA não tem noção dos Streams, sendo estes uma abstração! O hardware tem um ou mais engenhos para desempenhar as cópias de memória e um engenho para executar os kernels Tais engenhos enfileiram comandos independentemente um dos outros, resultando em um cenário do tipo agendamento de trabalhos. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Agendamento de Trabalhos A figura abaixo mostra como as operações têm sido agendadas Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Abstração vs. Hardware Assim, as noções do usuário programador e o hardware são ortogonais Cabe ao driver CUDA tentar agradar aos dois lados: o programador e o hardware Primeiramente, existe uma dependência importante na especificação da ordem na qual os operadores são adicionados no Stream Por exemplo, a cópia da memória a deve ocorrer antes da cópia da memória b que deve ocorrer antes da execução do kernel! Como como estas operações vão para engenhos diferentes do hardware, esta dependência é perdida. Este procedimento deve ser gerenciado pelo driver CUDA. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências O Driver CUDA O driver CUDA irá garantir a ordem de execução das tarefas, e no caso fará, Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Linha do Tempo Observando a linha do tempo dos processos! Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Otimizando o Processo Observe que o programador tem que saber como o hardware funciona para tentar tirar o maior proveito possı́vel. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Otimizando o Processo Observe que o programador tem que saber como o hardware funciona para tentar tirar o maior proveito possı́vel. Assim, seria possı́vel otimizar o processo de tal forma que a linha do tempo ficasse, Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Código Otimizado for (int i=0; i<FULL_DATA_SIZE; i+= N*2) { // enqueue copies of a in stream0 and stream1 HANDLE_ERROR( cudaMemcpyAsync( dev_a0, host_a+i,N * sizeof(int),cudaMemcpyHostToDevice, stream0 ) ); HANDLE_ERROR( cudaMemcpyAsync( dev_a1, host_a+i+N,N * sizeof(int),cudaMemcpyHostToDevice, stream1 ) ); // enqueue copies of b in stream0 and stream1 HANDLE_ERROR( cudaMemcpyAsync( dev_b0, host_b+i,N * sizeof(int),cudaMemcpyHostToDevice, stream0 ) ); HANDLE_ERROR( cudaMemcpyAsync( dev_b1, host_b+i+N,N * sizeof(int),cudaMemcpyHostToDevice, stream1 ) ); // enqueue kernels in stream0 and stream1 kernel<<<N/256,256,0,stream0>>>( dev_a0, dev_b0, dev_c0 ); kernel<<<N/256,256,0,stream1>>>( dev_a1, dev_b1, dev_c1 ); // enqueue copies of c from device to locked memory HANDLE_ERROR( cudaMemcpyAsync( host_c+i, dev_c0,N * sizeof(int),cudaMemcpyDeviceToHost, stream0 ) ); HANDLE_ERROR( cudaMemcpyAsync( host_c+i+N, dev_c1,N * sizeof(int),cudaMemcpyDeviceToHost, stream1 ) ); } Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Teste Com esta nova implementação o tempo gasto caio para 48 ms. Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Exercı́cio Implemente o problema apresentado com dois stream, tanto na versão naive como na versão otmizada Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU Introdução Múltiplos Stream Agendamento de Trabalho Referências Bibliografia Jason Sandres and Edward Kandrot. CUDA by exemple. An Introduction to General-Purpose GPU Programming. Addison-Wesley, 2011. Capı́tulo 10 Prof. Tiago A. E. Ferreira Disciplina de TICs 1 - Introdução a Programação em GPGPU