为什么将函数传递给内核会导致数据变得不可变?

时间:2018-08-21 16:55:22

标签: c++ templates cmake cuda functor

我已经将我的项目简化为相关的代码。真正困扰我的部分是这不会产生任何错误。 无论如何,我有一个结构体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值设置为函数的结果。然后它将y值加1。

因此,请尝试一下。完整的源文件(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文件。最后,我已经解决了这些问题。

在我的机器上,它可以编译并正常运行。这是我的输出:

./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内存却变得不可变?我认为这是某种错误,但是它可以编译并运行而不会发出警告或错误。

现在按承诺运行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)

2 个答案:

答案 0 :(得分:2)

问题是您的代码正在宿主代码中创建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对此概念进行了初步介绍。

根据您的最终意图,可能有许多种方法可以修复代码,但是一种类似于上述介绍/链接的方法可能看起来像这样:

$ 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代码时遇到麻烦,我通常建议您确保执行proper CUDA error checking并使用cuda-memcheck运行代码。即使您不理解输出,对于尝试帮助您的人也将很有用。

答案 1 :(得分:1)

确实,如@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 ""
  )