我有两个向量(oldvector
和newvector
)。我需要计算由以下伪代码定义的残差值:
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示例是否有任何改进?
答案 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 sample和accompanying 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;
}