用CUDA计算相应行矩阵之间的欧几里德距离

时间:2013-10-11 18:10:50

标签: cuda parallel-processing gpu

我有一个非常简单的算法,可以计算两个矩阵的相应行之间的平方欧几里德距离。我有以下代码,但遗憾的是它不会返回不同矩阵大小的正确结果。更具体地说,它适用于大小为2000x4500x42500x2600x81000x8100x8的矩阵,但它不起作用对于大小为2500x32500x5400x3100x3100x101000x101000x12,{{1 },500x12

任何人都可以帮助我吗?我想手动完成,而不使用任何优化的库,因为我想了解线程管理。

500x14

内核调用是:

__global__ void cudaEuclid( float* A, float* B, float* C, int rows, int cols )
    {
        int i, squareeucldist = 0;
        int r = blockDim.x * blockIdx.x + threadIdx.x; // rows
        int c = blockDim.y * blockIdx.y + threadIdx.y; // cols 
        extern __shared__ float sdata[];
        //int r = blockIdx.y; int c = threadIdx.x;
        if( r < rows && c < cols  ){

            //C[r + rows*c] = ( A[r + rows*c] - B[r + rows*c] ) * ( A[r + rows*c] - B[r + rows*c] );


            sdata[threadIdx.x] = ( A[r + rows*c] - B[r + rows*c] ) * ( A[r + rows*c] - B[r + rows*c] );

            __syncthreads();

            // contiguous range pattern
            for(int offset = blockDim.x / 2;
                offset > 0;
                offset >>= 1)
            {
                if(threadIdx.x < offset)
                {
                    // add a partial sum upstream to our own
                    sdata[threadIdx.x] += sdata[threadIdx.x + offset];
                }

                // wait until all threads in the block have
                // updated their partial sums
                __syncthreads();
            }

            // thread 0 writes the final result
            if(threadIdx.x == 0)
            {
                C[r] = sdata[0];
            }

        }

    }

PS :我想提一下我发布了一个类似的问题,但从一开始就不清楚,讨论是迷失方向的。虽然Tom提出了一个非常有用的建议,即将来对于优化实现非常实用,但我需要一些更手工制作的东西。最后,我发这篇文章的原因是因为我不想让相关的帖子更复杂。感谢。

2 个答案:

答案 0 :(得分:1)

事实上,当m * 2^n足够小时,您的代码仅适用于n。您可能希望更详细地阅读第14页的以下幻灯片,

http://docs.nvidia.com/cuda/samples/6_Advanced/reduction/doc/reduction.pdf

并考虑以下问题

  1. blockDim.x等于3或5时会发生什么;
  2. blockDim.xcols不是2的幂时,如何正确执行并行缩减;
  3. 为什么减少结果小于预期;
  4. sdata[]中的哪些元素未添加到最终总和中;
  5. 如果blockDim.x为5时将cols和涂抹大小设置为2 ^ 3,结果是否正确;
  6. 在q5的情况下,如何处理smem[5..7]
  7. 中的额外3个元素空间

    尝试使用笔和纸张逐步模拟运行for循环。

答案 1 :(得分:1)

虽然OP不想使用优化的库来回答他的问题,但帖子有一个有用的标题,其他用户可以发现在没有手写内核的情况下解决问题很有用。

我很好奇,并且在使用CUDA Thrust时考虑了这个问题。我最终得到了下面的代码,它使用thrust::reduce_by_key来计算两个矩阵的同源行之间的距离。

#include <thrust\device_vector.h>
#include <thrust\transform_reduce.h>
#include <thrust\sequence.h>
#include <thrust\random.h>
#include <thrust\gather.h>
#include <thrust\extrema.h>

using namespace thrust::placeholders;

/****************************************************/
/* POWER DIFFERENCE FUNCTOR FOR EUCLIDEAN DISTANCES */
/****************************************************/
struct PowerDifference {
    __host__ __device__ float operator()(const float& a, const float& b) const { return pow(a - b, 2); }
};

/*******************/
/* EXPAND OPERATOR */
/*******************/
template <typename InputIterator1, typename InputIterator2, typename OutputIterator>
OutputIterator expand(InputIterator1 first1,
                      InputIterator1 last1,
                      InputIterator2 first2,
                      OutputIterator output)
{
    typedef typename thrust::iterator_difference<InputIterator1>::type difference_type;

    difference_type input_size  = thrust::distance(first1, last1);
    difference_type output_size = thrust::reduce(first1, last1);

    // scan the counts to obtain output offsets for each input element
    thrust::device_vector<difference_type> output_offsets(input_size, 0);
    thrust::exclusive_scan(first1, last1, output_offsets.begin()); 

    // scatter the nonzero counts into their corresponding output positions
    thrust::device_vector<difference_type> output_indices(output_size, 0);
    thrust::scatter_if(thrust::counting_iterator<difference_type>(0), thrust::counting_iterator<difference_type>(input_size),
                       output_offsets.begin(), first1, output_indices.begin());

    // compute max-scan over the output indices, filling in the holes
    thrust::inclusive_scan(output_indices.begin(), output_indices.end(), output_indices.begin(), thrust::maximum<difference_type>());

    // gather input values according to index array (output = first2[output_indices])
    OutputIterator output_end = output; thrust::advance(output_end, output_size);
    thrust::gather(output_indices.begin(), output_indices.end(), first2, output);

    // return output + output_size
    thrust::advance(output, output_size);

    return output;
}

/********/
/* MAIN */
/********/
int main()
{
    /**************************/
    /* SETTING UP THE PROBLEM */
    /**************************/

    const int N     = 10;           // --- Number of vector elements
    const int Nvec  = 20;           // --- Number of vectors for each matrix

    // --- Random uniform integer distribution between 0 and 100
    thrust::default_random_engine rng;
    thrust::uniform_int_distribution<int> dist(0, 20);

    // --- Matrix allocation and initialization
    thrust::device_vector<float> d_matrix1(Nvec * N);
    thrust::device_vector<float> d_matrix2(Nvec * N);
    for (size_t i = 0; i < d_matrix1.size(); i++) d_matrix1[i] = (float)dist(rng);
    for (size_t i = 0; i < d_matrix2.size(); i++) d_matrix2[i] = (float)dist(rng);

    printf("\n\nFirst matrix\n");
    for(int i = 0; i < Nvec; i++) {
        std::cout << " [ ";
        for(int j = 0; j < N; j++)
            std::cout << d_matrix1[i * N + j] << " ";
        std::cout << "]\n";
    }

    printf("\n\nSecond matrix\n");
    for(int i = 0; i < Nvec; i++) {
        std::cout << " [ ";
        for(int j = 0; j < N; j++)
            std::cout << d_matrix2[i * N + j] << " ";
        std::cout << "]\n";
    }

    /****************************************************************************/
    /* CALCULATING THE EUCLIDEAN DISTANCES BETWEEN THE ROWS OF THE TWO MATRICES */
    /****************************************************************************/
    // --- Creating the indices for the reduction by key
    thrust::device_vector<int> d_sequence(Nvec);
    thrust::device_vector<int> d_indices(Nvec * N);
    thrust::device_vector<int> d_counts(Nvec, N);
    thrust::sequence(d_sequence.begin(), d_sequence.begin() + Nvec);
    expand(d_counts.begin(), d_counts.end(), d_sequence.begin(), d_indices.begin());

    printf("\n\nSecond matrix\n");
    for(int i = 0; i < Nvec; i++) {
        std::cout << " [ ";
        for(int j = 0; j < N; j++)
            std::cout << d_indices[i * N + j] << " ";
        std::cout << "]\n";
    }

    thrust::device_vector<float> d_squared_differences(Nvec * N);

    thrust::transform(d_matrix1.begin(), d_matrix1.end(), d_matrix2.begin(), d_squared_differences.begin(), PowerDifference());

    thrust::device_vector<float> d_norms(Nvec);
    thrust::reduce_by_key(d_indices.begin(), d_indices.end(), d_squared_differences.begin(), d_indices.begin(), d_norms.begin());

    printf("\n\ndnorms\n");
    for(int i = 0; i < Nvec; i++) {
            std::cout << d_norms[i] << " ";
    }

    return 0; 
}