[Notas de entrada CUDA] Kernel CUDA e configuração de encadeamento

1. Função do kernel CUDA

A função chamada na GPU torna-se uma função do kernel CUDA (função do Kernel) e a função do kernel será executada por vários threads na GPU. Cada thread executará o código na função do kernel . Claro, devido aos diferentes números de thread, o caminho do código executado pode ser diferente.

(1) A frente da função é o identificador de declaração __global__, que indica que esta função pode ser executada na GPU. Deve-se ressaltar que, embora seja executado na GPU, ainda é chamado pelo lado da CPU

(2) Quando a função do kernel é chamada, o símbolo <<<...>>> precisa ser usado para especificar a configuração do thread

(3) As variáveis ​​internas do CUDA podem ser chamadas dentro da função do kernel, como threadIdx, blockDim, etc.

(4) A função do kernel é assíncrona ao código da CPU, ou seja, o controle retornará antes que a execução da função do kernel seja concluída, para que a CPU possa continuar a executar o seguinte código da CPU sem esperar a conclusão do kernel função

(5) A função kernel só pode acessar a memória do dispositivo. Como a função do kernel é executada no lado do dispositivo, ela só pode acessar a memória do lado do dispositivo.

(6) Deve retornar o tipo void. Sabemos que a função do kernel é iniciada pela CPU e executada na GPU. Os dados dentro da função do kernel estão localizados na GPU. Supondo que a função do kernel tenha um valor de retorno, o valor de retorno são os dados localizados na GPU e a CPU não pode receber esses dados diretamente. Portanto, a função kernel não tem valor de retorno.

(7) Funções de kernel não suportam parâmetros variáveis; funções de kernel não suportam variáveis ​​estáticas;
funções de kernel não suportam ponteiros de função;

(8) Na programação CUDA, além de __global__, os identificadores comumente usados ​​são: __device__. Funções com o identificador __device__ só podem ser executadas no segmento GPU e só podem ser chamadas no segmento GPU. Por exemplo, elas podem ser chamadas nas funções __global__ e __device__. __global__ e __device__ não podem ser usadas ao mesmo tempo.

(9) Outro identificador comumente usado é __host__: só pode ser executado no lado do host; só pode ser chamado no lado do host. Quando __host__ é usado sozinho, não há diferença na natureza e no uso dessa função em relação às funções comuns da CPU. Então, por que introduzir esse identificador neste caso? Podemos imaginar uma situação em que esperamos que uma função possa ser chamada tanto na CPU quanto na GPU, então declaramos essa função assim: host  device  funForCPUandGPU(args), então esta função pode ser executada na CPU Também pode ser executada na GPU.

2. Configuração do encadeamento

A maneira de chamar a função principal no lado do host é:

kernel<<<Dg, Db, Ns, S>>>(param list);

em,

  • Dg: tipo int ou tipo dim3 (x, y, z), usado para definir como o Bloco em uma grade é organizado, se for do tipo int, significa uma estrutura de organização unidimensional
  • Db: tipo int ou tipo dim3 (x, y, z), usado para definir como o Thread é organizado em um Bloco, se for do tipo int, significa uma estrutura de organização unidimensional
  • Ns: tipo size_t, pode ser padronizado e o padrão é 0; é usado para definir o tamanho máximo da memória compartilhada que pode ser alocada dinamicamente, além da memória compartilhada estaticamente alocada para cada bloco, e a unidade é byte. 0 significa que nenhuma alocação dinâmica é necessária.
  • S: tipo cudaStream_t, pode ser padronizado, o padrão é 0. Indica em qual fluxo a função do kernel está localizada.

Ao usar dim3tipos como:

dim3 grid(3,2,1), block(4,3,1);

 Isso significa que existem 3x2x1=6 blocos em uma grade e os métodos de arranjo nas três direções de (x, y, z) são 3, 2, 1 respectivamente; existem 4x3x1=12 threads em um bloco e em ( x, y) ,z) Os arranjos nas três direções são 4, 3, 1, respectivamente.

Ao usar o tipo int, significa um arranjo unidimensional, como:

kernel_name<<<5,8>>>(...);

Isso significa que existem 5 blocos em uma grade e os arranjos nas três direções de (x, y, z) são 5, 1, 1 respectivamente; existem 8 threads em um bloco e as três direções de (x, y, z) Os modos de arranjo nas duas direções são 8, 1, 1 respectivamente.
 

No CUDA, você pode usar variáveis ​​internas para obter o ID do thread e o ID do bloco:

  • threadIdx.[x, y, z] indica o número de Thread no Bloco
  • blockIdx.[x, y, z] indica o número do Bloco na Grade
  • blockDim.[x, y, z] representa a dimensão do Bloco, ou seja, a quantidade de Threads em cada direção do Bloco
  • gridDim.[x, y, z] indica a dimensão do Grid, ou seja, o número de Blocos em cada direção do Grid
     

Dê uma olhada em como threadId é calculado em diferentes dimensões:

#一维Grid 一维Block
kernel_name<<<4, 8>>>(...)
int threadId = blockIdx.x * blockDim.x + threadIdx.x = 2 * 4 + 1 = 9


#二维Grid 二维Block

dim grid(4,1,1), block(2,2,1);
kernel_name<<<grid, block>>>(...)

int blockId = blockIdx.x + blockId.y * gridDim.x;
int threadId = blockId * (blockDim.x * blockDim.y) + (threadIdx.y *blockDim.x) + threadIdx.x;

#三维Grid 三维Block

int blockId = blockIdx.x + blockIdx.y * gridDim.x + gridDim.x * gridDim.y * blockIdx.z;
int threadIc = blockId * (blockDim.x * blockDim.y * blockDim.z) 
                       + (threadIdx.z * (blockDim.x * blockDim.y)) 
                       + (threadIdx.y * blockDim.x) + threadIdx.x;   

Quando as dimensões da grade e do bloco são diferentes:

#一维Grid 二维Block

blockId = blockIdx.x 
threadId = blockIdx.x * blockDim.x * blockDim.y + threadIdx.y * blockDim.x + threadIdx.x


#一维Grid 三维Block
blockId = blockIdx.x 
threadId = blockIdx.x * blockDim.x * blockDim.y * blockDim.z 
                      + threadIdx.z * blockDim.y * blockDim.x 
                      + threadIdx.y * blockDim.x + threadIdx.x

#二维Grid 一维Block

int blockId = blockIdx.y * gridDim.x + blockIdx.x;  
int threadId = blockId * blockDim.x + threadIdx.x;

#二维Grid 三维Block
int blockId = blockIdx.x + blockIdx.y * gridDim.x;  
int threadId = blockId * (blockDim.x * blockDim.y * blockDim.z)  
                       + (threadIdx.z * (blockDim.x * blockDim.y))  
                       + (threadIdx.y * blockDim.x) + threadIdx.x;  

Se você deseja calcular o índice global (x, y) de cada thread no Grid multidimensional ao chamar a função do kernel , pode ser expresso da seguinte forma:

dim3 gridDim = (blocksPerGrid, blocksPerGrid);
dim3 blockDim = (threadsPerBlock, threadsPerBlock);

int x= blockIdx.x + blockId.x * gridDim.x;
int y= blockIdx.y + blockId.y * gridDim.y;


 

 

おすすめ

転載: blog.csdn.net/qq_34106574/article/details/128392585