为什么将函数传递给内核会导致数据变得不可变?
Why does passing a function to a kernel cause data to become immutable?
我已将我的项目缩减为仅包含相关代码。真正困扰我的部分是这不会产生任何错误。
无论如何,我有一个结构 GpuData
struct GpuData { float x, y, z; };
我的目标是针对此结构启动一个内核,该内核接受一个函数并将该函数应用于该结构。
那么让我们看一个示例内核:
__global__ void StructFunctor(GpuData* in_dat, nvstd::function<float(void)> func) {
in_dat->x = func();
in_dat->y += T{1};
};
在这种情况下,内核被简化为非常简单的东西。它将 x 值设置为函数的结果。然后它会将 1 添加到 y 值。
所以让我们试试吧。一个完整的源文件(cuda_demo.cu
):
#include <iostream>
#include <nvfunctional>
struct GpuData { float x, y, z; };
__global__ void StructFunctor(GpuData* in_dat, nvstd::function<float(void)> func) {
in_dat->x = func();
in_dat->y += float{1};
};
int main(int argc, char** argv) {
GpuData c_dat {2, 3, 5};
std::cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
GpuData* g_dat;
cudaMalloc(&g_dat, sizeof(GpuData));
cudaMemcpy(g_dat, &c_dat, sizeof(GpuData), cudaMemcpyHostToDevice);
StructFunctor<<<1, 1>>>(g_dat, []()->float{return 1.0f;});
cudaMemcpy(&c_dat, g_dat, sizeof(GpuData), cudaMemcpyDeviceToHost);
std::cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
return 0;
}
好吧,如果我们真的要尝试,我们将需要 Cmake 文件。我把那些放在最后。
在我的机器上它编译并且 运行s 没有错误。这是我的输出:
./CudaDemo
Input x: 2 y: 3 z: 5
Output x: 2 y: 3 z: 5
他们变量根本没有被修改!但是如果我返回并注释掉 in_dat-> = func();
那么我会得到这个输出:
./CudaDemo
Input x: 2 y: 3 z: 5
Output x: 2 y: 4 z: 5
现在修改了y值!这是一个好的开始,但为什么当我尝试使用该函数时 gpu 内存变得不可变?我认为这是某种错误,但它编译并且 运行s 没有警告或错误。
现在按照承诺,cmake 文件到 运行 这个。
cmake_minimum_required(VERSION 3.8)
project(Temp LANGUAGES CXX CUDA)
set(CMAKE_CUDA_STANDARD 14)
add_executable(CudaDemo cuda_demo.cu)
set_property(TARGET CudaDemo PROPERTY CUDA_SEPARABLE_COMPILATION ON)
确实,正如@RobertCrovella 指出的那样,(仅主机)lambda 的地址不是有效的设备端地址,因此构造的 nvstd::function 不可调用)。当您尝试在内核中调用它时,您会收到错误消息。这是你的代码(嗯,我对你的代码的编辑),转换为使用正确的错误检查:
#include <iostream>
#include <nvfunctional>
#include <cuda/api_wrappers.h>
struct GpuData { float x, y, z; };
__global__ void StructFunctor(GpuData* in_dat, nvstd::function<float(void)> func) {
in_dat->x = func();
in_dat->y += float{1};
};
int main(int argc, char** argv) {
using std::cout; using std::endl;
GpuData c_dat {2, 3, 5};
cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << endl;
auto current_device = cuda::device::current::get();
auto g_dat = cuda::memory::device::make_unique<GpuData>(current_device);
cuda::memory::copy(g_dat.get(), &c_dat, sizeof(GpuData));
device.launch(StructFunctor, cuda::make_launch_config(1, 1),
g_dat.get(), []()->float { return 1.0f; });
cuda::outstanding_error::ensure_none(); // This is where we'll notice the error
cuda::memory::copy(&c_dat, g_dat.get(), sizeof(GpuData));
cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
}
当你运行这个时,你会得到:
Input x: 2 y: 3 z: 5
terminate called after throwing an instance of 'cuda::runtime_error'
what(): Synchronously copying data: an illegal memory access was encountered
Aborted
修复方法是:
#include <iostream>
#include <cuda/api_wrappers.h>
struct GpuData { float x, y, z; };
template <typename F>
__global__ void StructFunctor(GpuData* in_dat, F func) {
in_dat->x = func();
in_dat->y += float{1};
};
int main(int argc, char** argv) {
using std::cout; using std::endl;
GpuData c_dat {2, 3, 5};
cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << endl;
auto device = cuda::device::current::get();
auto g_dat = cuda::memory::device::make_unique<GpuData>(device);
cuda::memory::copy(g_dat.get(), &c_dat, sizeof(GpuData));
auto return_one = [] __device__ ()->float { return 1.0f; };
device.launch(StructFunctor<decltype(return_one)>, cuda::make_launch_config(1, 1), g_dat.get(), return_one);
cuda::outstanding_error::ensure_none();
cuda::memory::copy(&c_dat, g_dat.get(), sizeof(GpuData));
cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << endl;
}
要使用 CUDA API 包装器,请将其添加到您的 CMakeLists.txt
ExternalProject_Add(cuda-api-wrappers_project
PREFIX CMakeFiles/cuda-api-wrappers_project
TMP_DIR CMakeFiles/cuda-api-wrappers_project/tmp
STAMP_DIR CMakeFiles/cuda-api-wrappers_project/stamp
GIT_REPOSITORY git@github.com:eyalroz/cuda-api-wrappers.git
GIT_TAG 7e48712af95939361bf04e4f4718688795a319f9
UPDATE_COMMAND ""
SOURCE_DIR "${CMAKE_SOURCE_DIR}/cuda-api-wrappers"
BUILD_IN_SOURCE 1
INSTALL_COMMAND ""
)
问题是您的代码在主机代码中创建了一个 lambda(因此它会针对您指定的任何主机处理器进行编译),然后您正试图在设备代码中使用该已编译的 lambda。这行不通。如果你 运行 你的代码带有 cuda-memcheck
它表明一个错误可能采取几种形式之一,我看到一条消息 "Invalid PC",这意味着你的程序试图执行来自一个指令的指令无效位置:
$ cuda-memcheck ./t277
========= CUDA-MEMCHECK
Input x: 2 y: 3 z: 5
========= Invalid PC
========= at 0x00000048 in void StructFunctor<float>(GpuData<float>*, nvstd::function<float () (void)>)
========= by thread (0,0,0) in block (0,0,0)
========= Device Frame:void StructFunctor<float>(GpuData<float>*, nvstd::function<float () (void)>) (void StructFunctor<float>(GpuData<float>*, nvstd::function<float () (void)>) : 0x40)
========= Saved host backtrace up to driver entry point at kernel launch time
========= Host Frame:/lib64/libcuda.so.1 (cuLaunchKernel + 0x2cd) [0x2486ed]
========= Host Frame:./t277 [0x190b2]
========= Host Frame:./t277 [0x192a7]
在 CUDA 中,如果你想在设备代码中使用 lambda,你必须正确地修饰它,就像你打算在设备上执行的任何其他代码一样。虽然您可以找到许多其他示例,但已初步介绍了此概念。here。
可能有很多方法可以修复代码,具体取决于您的最终意图,但与上述 introduction/link 密切相关的方法可能如下所示:
$ cat t277.cu
#include <iostream>
template <typename T>
struct GpuData {
T x;
T y;
T z;
};
template <typename T, typename F>
__global__ void StructFunctor(GpuData<T>* in_dat, F f) {
in_dat->x = f();
in_dat->y += T{1};
};
int main(int argc, char** argv) {
GpuData<float> c_dat {2, 3, 5};
std::cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
GpuData<float>* g_dat;
cudaMalloc(&g_dat, sizeof(GpuData<float>));
cudaMemcpy(g_dat, &c_dat, sizeof(GpuData<float>), cudaMemcpyHostToDevice);
StructFunctor<float><<<1, 1>>>(g_dat, [] __host__ __device__ ()->float{return 1.0f;});
cudaMemcpy(&c_dat, g_dat, sizeof(GpuData<float>), cudaMemcpyDeviceToHost);
std::cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
return 0;
}
$ nvcc -std=c++11 t277.cu -o t277 --expt-extended-lambda
$ cuda-memcheck ./t277
========= CUDA-MEMCHECK
Input x: 2 y: 3 z: 5
Output x: 1 y: 4 z: 5
========= ERROR SUMMARY: 0 errors
$
(我添加到 lambda 的 __host__
装饰器在这种特殊情况下不是必需的,但 __device__
装饰器是必需的。)
请注意,我正在处理 the original code you posted,而不是@einpoklum
编辑到你的问题中的修改版本
在寻求他人帮助之前,如果您在使用 CUDA 代码时遇到问题,我通常建议您务必使用 cuda-memcheck
执行 proper CUDA error checking 和 运行 您的代码。即使您不理解输出,它也会对那些试图帮助您的人有用。
我已将我的项目缩减为仅包含相关代码。真正困扰我的部分是这不会产生任何错误。 无论如何,我有一个结构 GpuData
struct GpuData { float x, y, z; };
我的目标是针对此结构启动一个内核,该内核接受一个函数并将该函数应用于该结构。 那么让我们看一个示例内核:
__global__ void StructFunctor(GpuData* in_dat, nvstd::function<float(void)> func) {
in_dat->x = func();
in_dat->y += T{1};
};
在这种情况下,内核被简化为非常简单的东西。它将 x 值设置为函数的结果。然后它会将 1 添加到 y 值。
所以让我们试试吧。一个完整的源文件(cuda_demo.cu
):
#include <iostream>
#include <nvfunctional>
struct GpuData { float x, y, z; };
__global__ void StructFunctor(GpuData* in_dat, nvstd::function<float(void)> func) {
in_dat->x = func();
in_dat->y += float{1};
};
int main(int argc, char** argv) {
GpuData c_dat {2, 3, 5};
std::cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
GpuData* g_dat;
cudaMalloc(&g_dat, sizeof(GpuData));
cudaMemcpy(g_dat, &c_dat, sizeof(GpuData), cudaMemcpyHostToDevice);
StructFunctor<<<1, 1>>>(g_dat, []()->float{return 1.0f;});
cudaMemcpy(&c_dat, g_dat, sizeof(GpuData), cudaMemcpyDeviceToHost);
std::cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
return 0;
}
好吧,如果我们真的要尝试,我们将需要 Cmake 文件。我把那些放在最后。
在我的机器上它编译并且 运行s 没有错误。这是我的输出:
./CudaDemo
Input x: 2 y: 3 z: 5
Output x: 2 y: 3 z: 5
他们变量根本没有被修改!但是如果我返回并注释掉 in_dat-> = func();
那么我会得到这个输出:
./CudaDemo
Input x: 2 y: 3 z: 5
Output x: 2 y: 4 z: 5
现在修改了y值!这是一个好的开始,但为什么当我尝试使用该函数时 gpu 内存变得不可变?我认为这是某种错误,但它编译并且 运行s 没有警告或错误。
现在按照承诺,cmake 文件到 运行 这个。
cmake_minimum_required(VERSION 3.8)
project(Temp LANGUAGES CXX CUDA)
set(CMAKE_CUDA_STANDARD 14)
add_executable(CudaDemo cuda_demo.cu)
set_property(TARGET CudaDemo PROPERTY CUDA_SEPARABLE_COMPILATION ON)
确实,正如@RobertCrovella 指出的那样,(仅主机)lambda 的地址不是有效的设备端地址,因此构造的 nvstd::function 不可调用)。当您尝试在内核中调用它时,您会收到错误消息。这是你的代码(嗯,我对你的代码的编辑),转换为使用正确的错误检查:
#include <iostream>
#include <nvfunctional>
#include <cuda/api_wrappers.h>
struct GpuData { float x, y, z; };
__global__ void StructFunctor(GpuData* in_dat, nvstd::function<float(void)> func) {
in_dat->x = func();
in_dat->y += float{1};
};
int main(int argc, char** argv) {
using std::cout; using std::endl;
GpuData c_dat {2, 3, 5};
cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << endl;
auto current_device = cuda::device::current::get();
auto g_dat = cuda::memory::device::make_unique<GpuData>(current_device);
cuda::memory::copy(g_dat.get(), &c_dat, sizeof(GpuData));
device.launch(StructFunctor, cuda::make_launch_config(1, 1),
g_dat.get(), []()->float { return 1.0f; });
cuda::outstanding_error::ensure_none(); // This is where we'll notice the error
cuda::memory::copy(&c_dat, g_dat.get(), sizeof(GpuData));
cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
}
当你运行这个时,你会得到:
Input x: 2 y: 3 z: 5
terminate called after throwing an instance of 'cuda::runtime_error'
what(): Synchronously copying data: an illegal memory access was encountered
Aborted
修复方法是:
#include <iostream>
#include <cuda/api_wrappers.h>
struct GpuData { float x, y, z; };
template <typename F>
__global__ void StructFunctor(GpuData* in_dat, F func) {
in_dat->x = func();
in_dat->y += float{1};
};
int main(int argc, char** argv) {
using std::cout; using std::endl;
GpuData c_dat {2, 3, 5};
cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << endl;
auto device = cuda::device::current::get();
auto g_dat = cuda::memory::device::make_unique<GpuData>(device);
cuda::memory::copy(g_dat.get(), &c_dat, sizeof(GpuData));
auto return_one = [] __device__ ()->float { return 1.0f; };
device.launch(StructFunctor<decltype(return_one)>, cuda::make_launch_config(1, 1), g_dat.get(), return_one);
cuda::outstanding_error::ensure_none();
cuda::memory::copy(&c_dat, g_dat.get(), sizeof(GpuData));
cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << endl;
}
要使用 CUDA API 包装器,请将其添加到您的 CMakeLists.txt
ExternalProject_Add(cuda-api-wrappers_project
PREFIX CMakeFiles/cuda-api-wrappers_project
TMP_DIR CMakeFiles/cuda-api-wrappers_project/tmp
STAMP_DIR CMakeFiles/cuda-api-wrappers_project/stamp
GIT_REPOSITORY git@github.com:eyalroz/cuda-api-wrappers.git
GIT_TAG 7e48712af95939361bf04e4f4718688795a319f9
UPDATE_COMMAND ""
SOURCE_DIR "${CMAKE_SOURCE_DIR}/cuda-api-wrappers"
BUILD_IN_SOURCE 1
INSTALL_COMMAND ""
)
问题是您的代码在主机代码中创建了一个 lambda(因此它会针对您指定的任何主机处理器进行编译),然后您正试图在设备代码中使用该已编译的 lambda。这行不通。如果你 运行 你的代码带有 cuda-memcheck
它表明一个错误可能采取几种形式之一,我看到一条消息 "Invalid PC",这意味着你的程序试图执行来自一个指令的指令无效位置:
$ cuda-memcheck ./t277
========= CUDA-MEMCHECK
Input x: 2 y: 3 z: 5
========= Invalid PC
========= at 0x00000048 in void StructFunctor<float>(GpuData<float>*, nvstd::function<float () (void)>)
========= by thread (0,0,0) in block (0,0,0)
========= Device Frame:void StructFunctor<float>(GpuData<float>*, nvstd::function<float () (void)>) (void StructFunctor<float>(GpuData<float>*, nvstd::function<float () (void)>) : 0x40)
========= Saved host backtrace up to driver entry point at kernel launch time
========= Host Frame:/lib64/libcuda.so.1 (cuLaunchKernel + 0x2cd) [0x2486ed]
========= Host Frame:./t277 [0x190b2]
========= Host Frame:./t277 [0x192a7]
在 CUDA 中,如果你想在设备代码中使用 lambda,你必须正确地修饰它,就像你打算在设备上执行的任何其他代码一样。虽然您可以找到许多其他示例,但已初步介绍了此概念。here。
可能有很多方法可以修复代码,具体取决于您的最终意图,但与上述 introduction/link 密切相关的方法可能如下所示:
$ cat t277.cu
#include <iostream>
template <typename T>
struct GpuData {
T x;
T y;
T z;
};
template <typename T, typename F>
__global__ void StructFunctor(GpuData<T>* in_dat, F f) {
in_dat->x = f();
in_dat->y += T{1};
};
int main(int argc, char** argv) {
GpuData<float> c_dat {2, 3, 5};
std::cout << "Input x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
GpuData<float>* g_dat;
cudaMalloc(&g_dat, sizeof(GpuData<float>));
cudaMemcpy(g_dat, &c_dat, sizeof(GpuData<float>), cudaMemcpyHostToDevice);
StructFunctor<float><<<1, 1>>>(g_dat, [] __host__ __device__ ()->float{return 1.0f;});
cudaMemcpy(&c_dat, g_dat, sizeof(GpuData<float>), cudaMemcpyDeviceToHost);
std::cout << "Output x: " << c_dat.x << " y: " << c_dat.y << " z: " << c_dat.z << std::endl;
return 0;
}
$ nvcc -std=c++11 t277.cu -o t277 --expt-extended-lambda
$ cuda-memcheck ./t277
========= CUDA-MEMCHECK
Input x: 2 y: 3 z: 5
Output x: 1 y: 4 z: 5
========= ERROR SUMMARY: 0 errors
$
(我添加到 lambda 的 __host__
装饰器在这种特殊情况下不是必需的,但 __device__
装饰器是必需的。)
请注意,我正在处理 the original code you posted,而不是@einpoklum
编辑到你的问题中的修改版本在寻求他人帮助之前,如果您在使用 CUDA 代码时遇到问题,我通常建议您务必使用 cuda-memcheck
执行 proper CUDA error checking 和 运行 您的代码。即使您不理解输出,它也会对那些试图帮助您的人有用。