【问题标题】:CUDA 7.5 experimental __host__ __device__ lambdasCUDA 7.5 实验性 __host__ __device__ lambdas
【发布时间】:2015-12-10 14:42:31
【问题描述】:

我在experimental device lambdas 上玩了一下,它是在 CUDA 7.5 中引入并在此 blog post by Mark Harris 中推广的。

对于以下示例,我删除了很多不需要的东西来显示我的问题(我的实际实现看起来更好......)。

我尝试编写一个 foreach 函数,它可以根据模板参数对设备上的向量(每个元素 1 个线程)或主机(串行)上的向量进行操作。使用这个 foreach 函数,我可以轻松实现 BLAS 函数。作为一个例子,我使用为向量的每个分量分配一个标量(我在最后附上了完整的代码):

template<bool onDevice> void assignScalar( size_t size, double* vector, double a )
{
    auto assign = [=] __host__ __device__ ( size_t index ) { vector[index] = a; };
    if( onDevice )
    {
        foreachDevice( size, assign );
    }
    else
    {
        foreachHost( size, assign );
    }
}

但是,由于__host__ __device__ lambda:此代码给出了编译器错误:

lambda 的闭包类型(“lambda ->void”)不能用于 __global__ 函数模板实例化的模板参数类型,除非 lambda 在 __device__ 或 __global__ 函数中定义

如果我从 lambda 表达式中删除 __device__,我会得到相同的错误,如果我删除 __host__(只有 __device__ lambda),我不会收到编译错误,但在这种情况下,主机部分不会被执行。 ..

如果我将 lambda 分别定义为 __host____device__,则代码编译并按预期工作。

template<bool onDevice> void assignScalar2( size_t size, double* vector, double a )
{
    if( onDevice )
    {
        auto assign = [=] __device__ ( size_t index ) { vector[index] = a; };
        foreachDevice( size, assign );
    }
    else
    {
        auto assign = [=] __host__ ( size_t index ) { vector[index] = a; };
        foreachHost( size, assign );
    }
}

但是,这会引入代码重复,实际上使使用 lambdas 的整个想法在本示例中毫无用处。

有没有办法完成我想做的事情,或者这是实验功能中的错误?实际上,在programming guide 的第一个示例中明确提到了定义__host__ __device__ lambda。即使对于那个更简单的示例(仅从 lambda 返回一个常量值),我也找不到在主机和设备上使用 lambda 表达式的方法。

这里是完整代码,编译选项-std=c++11 --expt-extended-lambda:

#include <iostream>
using namespace std;

template<typename Operation> void foreachHost( size_t size, Operation o )
{
    for( size_t i = 0; i < size; ++i )
    {
        o( i );
    }
}

template<typename Operation> __global__ void kernel_foreach( Operation o )
{
    size_t index = blockIdx.x * blockDim.x + threadIdx.x;
    o( index );
}

template<typename Operation> void foreachDevice( size_t size, Operation o )
{
    size_t blocksize = 32;
    size_t gridsize = size/32;
    kernel_foreach<<<gridsize,blocksize>>>( o );
}

__global__ void printFirstElementOnDevice( double* vector )
{
    printf( "dVector[0] = %f\n", vector[0] );
}

void assignScalarHost( size_t size, double* vector, double a )
{
    auto assign = [=] ( size_t index ) { vector[index] = a; };
    foreachHost( size, assign );
}

void assignScalarDevice( size_t size, double* vector, double a )
{
    auto assign = [=] __device__ ( size_t index ) { vector[index] = a; };
    foreachDevice( size, assign );
}

// compile error:
template<bool onDevice> void assignScalar( size_t size, double* vector, double a )
{
    auto assign = [=]  __host__ __device__ ( size_t index ) { vector[index] = a; };
    if( onDevice )
    {
        foreachDevice( size, assign );
    }
    else
    {
        foreachHost( size, assign );
    }
}

// works:
template<bool onDevice> void assignScalar2( size_t size, double* vector, double a )
{
    if( onDevice )
    {
        auto assign = [=] __device__ ( size_t index ) { vector[index] = a; };
        foreachDevice( size, assign );
    }
    else
    {
        auto assign = [=] __host__ ( size_t index ) { vector[index] = a; };
        foreachHost( size, assign );
    }
}

int main()
{
    size_t SIZE = 32;

    double* hVector = new double[SIZE];
    double* dVector;
    cudaMalloc( &dVector, SIZE*sizeof(double) );

    // clear memory
    for( size_t i = 0; i < SIZE; ++i )
    {
        hVector[i] = 0;
    }
    cudaMemcpy( dVector, hVector, SIZE*sizeof(double), cudaMemcpyHostToDevice );

    assignScalarHost( SIZE, hVector, 1.0 );
    cout << "hVector[0] = " << hVector[0] << endl;

    assignScalarDevice( SIZE, dVector, 2.0 );
    printFirstElementOnDevice<<<1,1>>>( dVector );
    cudaDeviceSynchronize();

    assignScalar2<false>( SIZE, hVector, 3.0 );
    cout << "hVector[0] = " << hVector[0] << endl;

    assignScalar2<true>( SIZE, dVector, 4.0 );
    printFirstElementOnDevice<<<1,1>>>( dVector );
    cudaDeviceSynchronize();

//  assignScalar<false>( SIZE, hVector, 5.0 );
//  cout << "hVector[0] = " << hVector[0] << endl;
//
//  assignScalar<true>( SIZE, dVector, 6.0 );
//  printFirstElementOnDevice<<<1,1>>>( dVector );
//  cudaDeviceSynchronize();

    cudaError_t error = cudaGetLastError();
    if(error!=cudaSuccess)
    {
        cout << "ERROR: " << cudaGetErrorString(error);
    }
}

我使用的是 CUDA 7.5 的生产版本。

更新

我为 assignScalar 函数尝试了第三个版本:

template<bool onDevice> void assignScalar3( size_t size, double* vector, double a )
{
#ifdef __CUDA_ARCH__
#define LAMBDA_HOST_DEVICE __device__
#else
#define LAMBDA_HOST_DEVICE __host__
#endif

    auto assign = [=] LAMBDA_HOST_DEVICE ( size_t index ) { vector[index] = a; };
    if( onDevice )
    {
        foreachDevice( size, assign );
    }
    else
    {
        foreachHost( size, assign );
    }
}

编译运行没有错误,但是设备版本(assignScalar3&lt;true&gt;)没有执行。实际上,我认为__CUDA_ARCH__ 将始终未定义(因为函数不是__device__),但我明确检查了定义它的编译路径。

【问题讨论】:

  • 我认为该错误具有指导意义,它可能是文档中未明确说明的进一步实施限制。如果您按照报告错误的建议,并将assignScalar 模板函数标记为__host__ __device__,我认为您可以解决这个特定问题。这将引发编译器警告,可以安全地忽略这些警告,或者可以使用__CUDA_ARCH__ 宏来解决,以进行干净的编译。到那时,我认为您可能会偶然发现某种实现错误。我目前没有其他信息。
  • 我会说该错误具有误导性,因为如果您检查示例 assignScalar2,它是不正确的。那里的 lambda 以相同的方式使用,并且__device____global__ 函数中定义。
  • @RobertCrovella 正如你所说,制作 assignScalar 函数可以解决错误,但不是问题,因为该函数仅从主机调用(实际上,当我调用主机和设备 foreach 时,我听从建议)。但是你的评论让我想到了第三个版本,我将添加到问题中。
  • “该函数仅从主机调用”是我提到的实现错误。实际上设备版本开始被调用,但内核启动过程在运行时由于某种原因失败。如果您使用 gdb 和分析器四处寻找,您会发现这一点。
  • 第二,编程指南中的限制 6:“解析函数时,CUDA 编译器为该函数中的每个 __device__ lambda 分配一个计数器值。此计数器值用于替换命名类型传递给宿主编译器。因此,是否在函数中定义 __device__ lambda 不应取决于 __CUDA_ARCH__ 的特定值,或 __CUDA_ARCH__ 未定义。"

标签: c++ c++11 lambda cuda


【解决方案1】:

我试图通过问题中提供的示例完成的任务CUDA 7.5 无法实现,尽管它没有明确排除在实验性 lambda 支持的允许情况之外。

根据博文CUDA 8 Features Revealed,NVIDIA 宣布 CUDA Toolkit 8.0 将支持 __host__ __device__ lambdas 作为实验性功能。

我验证了我的示例适用于 CUDA 8 Release Candidate(Cuda 编译工具,版本 8.0,V8.0.26)。

这是我最后使用的代码,用nvcc -std=c++11 --expt-extended-lambda编译:

#include <iostream>
using namespace std;

template<typename Operation> __global__ void kernel_foreach( Operation o )
{
    size_t i = blockIdx.x * blockDim.x + threadIdx.x;
    o( i );
}

template<bool onDevice, typename Operation> void foreach( size_t size, Operation o )
{
    if( onDevice )
    {
        size_t blocksize = 32;
        size_t gridsize = size/32;
        kernel_foreach<<<gridsize,blocksize>>>( o );
    }
    else
    {
        for( size_t i = 0; i < size; ++i )
        {
            o( i );
        }
    }
}

__global__ void printFirstElementOnDevice( double* vector )
{
    printf( "dVector[0] = %f\n", vector[0] );
}

template<bool onDevice> void assignScalar( size_t size, double* vector, double a )
{
    auto assign = [=]  __host__ __device__ ( size_t i ) { vector[i] = a; };
    foreach<onDevice>( size, assign );
}

int main()
{
    size_t SIZE = 32;

    double* hVector = new double[SIZE];
    double* dVector;
    cudaMalloc( &dVector, SIZE*sizeof(double) );

    // clear memory
    for( size_t i = 0; i < SIZE; ++i )
    {
        hVector[i] = 0;
    }
    cudaMemcpy( dVector, hVector, SIZE*sizeof(double), cudaMemcpyHostToDevice );

    assignScalar<false>( SIZE, hVector, 3.0 );
    cout << "hVector[0] = " << hVector[0] << endl;

    assignScalar<true>( SIZE, dVector, 4.0 );
    printFirstElementOnDevice<<<1,1>>>( dVector );
    cudaDeviceSynchronize();

    cudaError_t error = cudaGetLastError();
    if(error!=cudaSuccess)
    {
        cout << "ERROR: " << cudaGetErrorString(error);
    }
}

【讨论】:

    猜你喜欢
    • 2016-01-18
    • 1970-01-01
    • 1970-01-01
    • 2020-10-29
    • 2021-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多