Выделить 2D массив на память устройства в CUDA
Как выделить и перенести (на хост и с хоста) 2D-массивы в память устройства в Cuda?
4 ответа
Я нашел решение этой проблемы. Мне не нужно было выравнивать массив.
Встроенный cudaMallocPitch()
Функция сделала работу. И я мог бы передать массив и с устройства, используя cudaMemcpy2D()
функция.
Например
cudaMallocPitch((void**) &array, &pitch, a*sizeof(float), b);
Это создает двумерный массив размером a * b с шагом, передаваемым в качестве параметра.
Следующий код создает двумерный массив и перебирает элементы. Он легко компилируется, вы можете использовать его.
#include<stdio.h>
#include<cuda.h>
#define height 50
#define width 50
// Device code
__global__ void kernel(float* devPtr, int pitch)
{
for (int r = 0; r < height; ++r) {
float* row = (float*)((char*)devPtr + r * pitch);
for (int c = 0; c < width; ++c) {
float element = row[c];
}
}
}
//Host Code
int main()
{
float* devPtr;
size_t pitch;
cudaMallocPitch((void**)&devPtr, &pitch, width * sizeof(float), height);
kernel<<<100, 512>>>(devPtr, pitch);
return 0;
}
Свести это: сделать его одномерным. Посмотрите, как это сделано здесь
Код вашего устройства может быть быстрее. Попробуйте использовать темы больше.
__global__ void kernel(float* devPtr, int pitch)
{
int r = threadIdx.x;
float* row = (float*)((char*)devPtr + r * pitch);
for (int c = 0; c < width; ++c) {
float element = row[c];
}
}
Затем вы рассчитываете распределение блоков и потоков так, чтобы каждый поток имел дело с одним элементом.
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <cuda.h>
#define MAX_ITER 1000000
#define MAX 100 //maximum value of the matrix element
#define TOL 0.000001
// Generate a random float number with the maximum value of max
float rand_float(int max){
return ((float)rand()/(float)(RAND_MAX)) * max;
}
__global__ void kernel(float **device_2Darray1, float **device_2Darray2, float **device_2Darray3, int rows, int cols) {
// Calculate the row index
int row = blockIdx.y * blockDim.y + threadIdx.y;
// Calculate the column index
int col = blockIdx.x * blockDim.x + threadIdx.x;
// Check if the thread is within the array bounds
if (row < rows && col < cols) {
// Perform the computation
device_2Darray3[row][col] = device_2Darray1[row][col] + device_2Darray2[row][col];
}
}
int main(int argc, char *argv[]){
float **host_2Darray;
float **device_2Darray;
int rows = 10; // or whatever value you want
int cols = 10; // or whatever value you want
// allocate memory for the host
host_2Darray = (float**)malloc(rows * sizeof(float*));
for(int i = 0; i < rows; i++){
host_2Darray[i] = (float*)malloc(cols * sizeof(float));
for(int j = 0; j < cols; j++){
host_2Darray[i][j] = rand_float(MAX);
}
}
// allocate memory for the device
cudaMalloc((void***)&device_2Darray, rows * sizeof(float*));
for(int i = 0; i < rows; i++){
cudaMalloc((void**)&device_2Darray[i], cols * sizeof(float));
}
// copy host memory to device
for(int i = 0; i < rows; i++){
cudaMemcpy(device_2Darray[i], host_2Darray[i], cols * sizeof(float), cudaMemcpyHostToDevice);
}
// call the kernel
dim3 threadsPerBlock(16, 16);
dim3 blocksPerGrid((rows + threadsPerBlock.x - 1) / threadsPerBlock.x,
(cols + threadsPerBlock.y - 1) / threadsPerBlock.y);
kernel<<<blocksPerGrid, threadsPerBlock>>>(device_2Darray, rows, cols);
// copy device memory back to host
for(int i = 0; i < rows; i++){
cudaMemcpy(host_2Darray[i], device_2Darray[i], cols * sizeof(float), cudaMemcpyDeviceToHost);
}
// free device memory
for(int i = 0; i < rows; i++){
cudaFree(device_2Darray[i]);
}
cudaFree(device_2Darray);
// free host memory
for(int i = 0; i < rows; i++){
free(host_2Darray[i]);
}
free(host_2Darray);
return 0;
}
Метод создания 2D-массивов в CUDA сложнее, чем у 1D-массива, поскольку память устройства (GPU) линейна. Фактически мы создаем массив указателей (каждый указатель указывает на одномерный массив), отсюда и двойные указатели.
Что касается глобальной функции ядра void: простая операция, такая как поэлементное добавление двух 2D-массивов. Вы можете использовать BlockIdx и BlockDim вместе с ThreadIdx для вычисления глобального индекса потока, а затем использовать этот индекс, чтобы указать, за какой элемент массива отвечает этот поток. В этом примере BlockIdx.y и BlockIdx.x дают индекс текущего потока внутри блока в направлениях y и x. Вы можете представить это как трехуровневую иерархию: сетка -> блок -> поток.
Вам нужно будет настроить остальную часть вашего кода, чтобы выделить и инициализировать второй массив, выделить выходной массив, скопировать выходные данные обратно на хост и т. д.
Не забудьте также изменить вызов ядра, чтобы передать правильные аргументы. Предполагается, что часть работы вы проделаете самостоятельно, например, интеграцию концепций, поскольку никто не имеет доступа к вашему полному коду и тому, как все это работает вместе как система.