Я создаю некоторые буферы устройств, которые я пытаюсь передать простому ядру, используя API-интерфейс драйвера cuda. Я создаю три буфера устройств и сохраняю их в std::vector.
std::vector<void *> kernel_arguments;
std::vector<float> a = {2};
std::vector<float> b = {3};
for (auto &input : {a, b}) {
CUdeviceptr ptr;
cuMemAlloc(&ptr, input.size()*sizeof(float));
cuMemcpyHtoD(ptr, input.data(), input.size()*sizeof(float));
kernel_arguments.push_back(reinterpret_cast<void *> (&ptr));
}
std::vector<float> c(1);
for (auto &output : {c}) {
CUdeviceptr ptr;
cuMemAlloc(&ptr, output.size()*sizeof(float));
kernel_arguments.push_back(reinterpret_cast<void *> (&ptr));
}
CUresult result = cuLaunchKernel(function, 1, 1, 1,
1024, 1, 1, 0, stream,
kernel_arguments.data(), NULL)
const char *error;
cuGetErrorString(result, &error);
std::cout << result << " " << error << std::end;
result = cuStreamSynchronize(stream);
cuGetErrorString(result, &error);
std::cout << result << " " << error << std::end;
Функция ядра представляет собой простое ядро сложения с тремя аргументами.
__global__ void add_kernel(
float *i_1,
float *i_2,
float *o_3) {
const size_t index = blockIdx.x*blockDim.x + threadIdx.x;
if (index < 1) {
printf("index %d\n", index);
printf("%p\n", i_1);
printf("%f\n", *i_1);
const float r_1 = i_1[index];
printf("%p\n", i_2);
printf("%f\n", *i_2);
const float r_2 = i_2[index];
const float r_3 = r_1 + r_2;
o_3[index] = r_3;
}
}
Запустив это, я получаю результат.
0 no error
index 0
0x14cf4c400200
3.000000
0x14cf4c400200
3.000000
700 an illegal memory access was encountered
Почему я получаю одно и то же значение указателя для первого и второго аргументов и почему кажется, что буфер моего второго устройства оказывается в первом аргументе?
Эта методология работает, когда вы возвращаете обратно значение, расположенное в стеке, но не когда вы возвращаете обратно адрес расположения в стеке — это не меняется от одной итерации цикла for к другой:
for (auto &input : {a, b}) {
CUdeviceptr ptr; // a stack variable
cuMemAlloc(&ptr, input.size()*sizeof(float));
cuMemcpyHtoD(ptr, input.data(), input.size()*sizeof(float));
kernel_arguments.push_back(reinterpret_cast<void *> (&ptr)); //**
}
// ptr is out of scope here
Это объясняет, почему первый и второй параметры ссылаются на ваш второй входной аргумент ядра (i_2, 3).
В противном случае, когда я создаю полный код вокруг того, что вы показали, я не получаю никакой ошибки 700 (однако нажатие/использование адреса переменной стека также приведет к UB/незаконному доступу, как только эта переменная выйдет размаха.)
Вот пример (измененный из vectorAddDrv примера кода) с изменением вашего цикла распределения (то есть перезапись значения стека на каждой итерации), чтобы решить эту проблему:
$ cat vectorAddDrv.cpp
// Includes
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <cstring>
#include <cuda.h>
// includes, project
#include <helper_cuda_drvapi.h>
#include <helper_functions.h>
// includes, CUDA
#include <builtin_types.h>
#include <vector>
using namespace std;
// Variables
CUdevice cuDevice;
CUcontext cuContext;
CUmodule cuModule;
CUfunction vecAdd_kernel;
//define input fatbin file
#ifndef FATBIN_FILE
#define FATBIN_FILE "vectorAdd_kernel64.fatbin"
#endif
// Host code
int main(int argc, char **argv)
{
// Initialize
checkCudaErrors(cuInit(0));
cuDevice = findCudaDeviceDRV(argc, (const char **)argv);
// Create context
checkCudaErrors(cuCtxCreate(&cuContext, 0, cuDevice));
// first search for the module path before we load the results
string module_path;
std::ostringstream fatbin;
if (!findFatbinPath(FATBIN_FILE, module_path, argv, fatbin))
{
exit(EXIT_FAILURE);
}
else
{
printf("> initCUDA loading module: <%s>\n", module_path.c_str());
}
if (!fatbin.str().size())
{
printf("fatbin file empty. exiting..\n");
exit(EXIT_FAILURE);
}
// Create module from binary file (FATBIN)
checkCudaErrors(cuModuleLoadData(&cuModule, fatbin.str().c_str()));
// Get function handle from module
checkCudaErrors(cuModuleGetFunction(&vecAdd_kernel, cuModule, "VecAdd_kernel"));
// your code, modified
std::vector<void *> kernel_arguments;
std::vector<float> a = {2};
std::vector<float> b = {3};
for (auto &input : {a, b}) {
CUdeviceptr *ptr = new CUdeviceptr;
cuMemAlloc(ptr, input.size()*sizeof(float));
cuMemcpyHtoD(*ptr, input.data(), input.size()*sizeof(float));
kernel_arguments.push_back(ptr);
}
std::vector<float> c(1);
for (auto &output : {c}) {
CUdeviceptr *ptr = new CUdeviceptr;
cuMemAlloc(ptr, output.size()*sizeof(float));
kernel_arguments.push_back(ptr);
}
CUresult result = cuLaunchKernel(vecAdd_kernel, 1, 1, 1,
1024, 1, 1, 0, NULL,
kernel_arguments.data(), NULL);
const char *error;
cuGetErrorString(result, &error);
std::cout << result << " " << error << std::endl;
checkCudaErrors(cuCtxSynchronize());
cuGetErrorString(result, &error);
std::cout << result << " " << error << std::endl;
for (auto &c : kernel_arguments) cuMemFree(*(reinterpret_cast<CUdeviceptr *>(c))); // this works since all of the kernel arguments in this case happen to be CUdeviceptr
exit(EXIT_SUCCESS);
}
$ nvcc -I/usr/local/cuda/samples/common/inc -o test vectorAddDrv.cpp -lcuda
$ compute-sanitizer ./test
========= COMPUTE-SANITIZER
> Using CUDA Device [0]: Tesla V100-PCIE-32GB
> findModulePath found file at <./vectorAdd_kernel64.fatbin>
> initCUDA loading module: <./vectorAdd_kernel64.fatbin>
0 no error
index 0
0x7f8023c00000
2.000000
0x7f8023c00200
3.000000
0 no error
========= ERROR SUMMARY: 0 errors
$
Я уже просмотрел их и сопоставил, что они делают, только используя вектор C++ вместо массива C.