使用CUDA进行剩余计算

时间:2014-05-11 20:55:11

标签: c++ cuda thrust cub

我有两个向量(oldvectornewvector)。我需要计算由以下伪代码定义的残差值:

residual = 0;
forall i : residual += (oldvector[i] - newvector[i])^2

目前,我正在使用两个CUDA Thrust操作进行计算,这些操作基本上是这样做的:

forall i : oldvector[i] = oldvector[i] - newvector[i]

后跟一个thrust::transform_reduce,其中一个正方形作为一元运算符,正在执行:

residual = 0;
forall i : residual += oldvector[i]^2;

这个问题显然是transform_reduce之前的全局内存中间存储。是否有更有效的方法来解决这两个步骤融合的问题?除了编写我自己的CUDA内核之外,还有其他选择吗?

我想到的一种方法是使用zip迭代器编写thrust::reduce。这样做的问题是运算符的返回类型必须与其输入的类型相同。根据我的说法,这意味着减少运算符将返回一个元组,这将意味着额外的添加。

如果我确实编写了一个简化CUDA内核,那么还原内核的CUDA 1.1示例是否有任何改进?

2 个答案:

答案 0 :(得分:3)

thrust::inner_product将在单个函数调用中执行此操作。您的原始想法也可以工作(将两个向量压缩并使用thrust::transform_reduce)此代码演示了两种方法:

#include <iostream>

#include <thrust/tuple.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/transform.h>
#include <thrust/device_vector.h>
#include <thrust/inner_product.h>
#include <thrust/functional.h>

#define N 2

struct zdiffsq{
template <typename Tuple>
  __host__ __device__ float operator()(Tuple a)
  {
    float result = thrust::get<0>(a) - thrust::get<1>(a);
    return result*result;
  }
};

struct diffsq{
  __host__ __device__ float operator()(float a, float b)
  {
    return (b-a)*(b-a);
  }
};

int main(){

  thrust::device_vector<float> oldvector(N);
  thrust::device_vector<float> newvector(N);
  oldvector[0] = 1.0f;  oldvector[1] = 2.0f;
  newvector[0] = 2.0f;  newvector[1] = 5.0f;

  float result = thrust::inner_product(oldvector.begin(), oldvector.end(), newvector.begin(), 0.0f, thrust::plus<float>(), diffsq());
  std::cout << "Result: " << result << std::endl;

  float result2 = thrust::transform_reduce(thrust::make_zip_iterator(thrust::make_tuple(oldvector.begin(), newvector.begin())), thrust::make_zip_iterator(thrust::make_tuple(oldvector.end(), newvector.end())), zdiffsq(), 0.0f, thrust::plus<float>());
  std::cout << "Result2: " << result2 << std::endl;
}

您还可以使用推力placeholders来研究消除内部产品示例中使用的仿函数定义。

即使您想编写自己的CUDA代码,现在对于常用算法(如并行缩减和排序)的标准建议是使用cub

是的,CUDA parallel reduction sampleaccompanying presentation仍然是快速并行缩减的基本介绍。

答案 1 :(得分:1)

Robert Crovella已回答了这个问题,并建议使用CUB

与Thrust不一致,CUB留下了性能关键参数,例如要使用的特定约简算法的选择以及用户可选择的未绑定并发度。可以调整这些参数,以便最大化特定体系结构和应用程序的性能。可以在编译时指定参数,从而避免运行时性能损失。

下面是一个关于如何使用CUB进行残差计算的完整工作示例。

#include <cub/cub.cuh>
#include <cuda.h>

#include "Utilities.cuh"

#include <iostream>

#define BLOCKSIZE           256
#define ITEMS_PER_THREAD    8

const int N = 4096;

/******************************/
/* TRANSFORM REDUCTION KERNEL */
/******************************/
__global__ void TransformSumKernel(const float * __restrict__ indata1, const float * __restrict__ indata2, float * __restrict__ outdata) {

    unsigned int tid = threadIdx.x + blockIdx.x * gridDim.x;

    // --- Specialize BlockReduce for type float. 
    typedef cub::BlockReduce<float, BLOCKSIZE * ITEMS_PER_THREAD> BlockReduceT;

    __shared__ typename BlockReduceT::TempStorage  temp_storage;

    float result;
    if(tid < N) result = BlockReduceT(temp_storage).Sum((indata1[tid] - indata2[tid]) * (indata1[tid] - indata2[tid]));

    if(threadIdx.x == 0) atomicAdd(outdata, result);

    return;
}

/********/
/* MAIN */
/********/
int main() {

    // --- Allocate host side space for 
    float *h_data1      = (float *)malloc(N * sizeof(float));
    float *h_data2      = (float *)malloc(N * sizeof(float));
    float *h_result     = (float *)malloc(sizeof(float));

    float *d_data1;     gpuErrchk(cudaMalloc(&d_data1, N * sizeof(float)));
    float *d_data2;     gpuErrchk(cudaMalloc(&d_data2, N * sizeof(float)));
    float *d_result;    gpuErrchk(cudaMalloc(&d_result, sizeof(float)));

    for (int i = 0; i < N; i++) {
        h_data1[i] = 1.f;
        h_data2[i] = 3.f;
    }

    gpuErrchk(cudaMemcpy(d_data1, h_data1, N * sizeof(float), cudaMemcpyHostToDevice));
    gpuErrchk(cudaMemcpy(d_data2, h_data2, N * sizeof(float), cudaMemcpyHostToDevice));

    TransformSumKernel<<<iDivUp(N, BLOCKSIZE), BLOCKSIZE>>>(d_data1, d_data2, d_result);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk(cudaMemcpy(h_result, d_result, sizeof(float), cudaMemcpyDeviceToHost));

    std::cout << "output: ";
    std::cout << h_result[0];
    std::cout << std::endl;

    gpuErrchk(cudaFree(d_data1));
    gpuErrchk(cudaFree(d_data2));
    gpuErrchk(cudaFree(d_result));

    return 0;
}