使用CUDA压缩“稀疏数据”(CCL:连接组件标签减少)

时间:2015-03-01 18:10:48

标签: cuda gpu cudafy.net

我有一个500万的32位整数列表(实际上是2048 x 2560图像),它是90%零。非零单元格是完全不以任何方式顺序或连续的标签(例如2049,8195,1333400,34320923,4320932)(它是我们的定制连接组件标记CCL算法的输出)。我正在使用NVIDA Tesla K40,所以如果需要任何前缀扫描工作,我会喜欢它,它使用SHUFFLE,BALLOT或任何更高的CC功能。

我不需要一个完整的例子,只需要一些建议。

为了说明,这里有一个由我们的CCL算法标记的博客。

Labeled Blob

其他blob将具有不同的唯一标签(例如13282)。但是所有都将被零包围,并且呈椭圆形。 (我们优化了椭圆体的CCL,这就是我们不使用这些库的原因)。但是一个副作用是blob标签不是连续的数字。我们不关心它们的编号顺序,但是我们想要一个标记为#1的blob,另一个标记为#2,最后一个标记为#n,其中n是图像中blob的数量。

我的意思是Labled#1?我的意思是所有2242个细胞都应该用1代替。所有13282个细胞都有#2等。

CCL的最大blob数等于2048x2560。所以我们知道数组的大小。

实际上,罗伯特克罗韦拉已经在一天前给出了一个很好的答案。这不是确切的,但我现在看到如何应用答案。所以我不需要任何帮助。但他的时间和精力都非常慷慨,并要求我用例子重新写下这个问题,所以我就这样做了。

2 个答案:

答案 0 :(得分:4)

一种可能的方法是使用以下序列:

  1. thrust::transform - 将输入数据转换为全部1或0:

    0 27 42  0 18 99 94 91  0  -- input data
    0  1  1  0  1  1  1  1  0  -- this will be our "mask vector"
    
  2. thrust::inclusive_scan - 将掩码矢量转换为渐进序列:

    0  1  1  0  1  1  1  1  0  -- "mask" vector
    0  1  2  2  3  4  5  6  6  -- "sequence" vector
    
  3. 另一个thrust::transform来屏蔽非增加值:

    0  1  1  0  1  1  1  1  0  -- "mask" vector
    0  1  2  2  3  4  5  6  6  -- "sequence" vector
    -------------------------
    0  1  2  0  3  4  5  6  0  -- result of "AND" operation
    
  4. 请注意,我们可以将前两个步骤与thrust::transform_inclusive_scan合并,然后将第三个步骤作为thrust::transform执行,并使用稍微不同的转换函数。这种修改允许我们省去临时“掩码”向量的创建。

    这是一个完整的示例,显示了使用thrust::transform_inclusive_scan的“修改”方法:

    $ cat t635.cu
    #include <iostream>
    #include <stdlib.h>
    
    #include <thrust/device_vector.h>
    #include <thrust/host_vector.h>
    #include <thrust/transform.h>
    #include <thrust/transform_scan.h>
    #include <thrust/generate.h>
    #include <thrust/copy.h>
    
    
    #define DSIZE 20
    #define PCT_ZERO 40
    
    struct my_unary_op
    {
      __host__ __device__
      int operator()(const int data) const
      {
        return (!data) ?  0:1;}
    };
    
    struct my_binary_op
    {
      __host__ __device__
      int operator()(const int d1, const int d2) const
      {
        return (!d1) ? 0:d2;}
    };
    
    int main(){
    
    // generate DSIZE random 32-bit integers, PCT_ZERO% are zero
      thrust::host_vector<int> h_data(DSIZE);
      thrust::generate(h_data.begin(), h_data.end(), rand);
      for (int i = 0; i < DSIZE; i++)
        if ((rand()%100)< PCT_ZERO) h_data[i] = 0;
        else h_data[i] %= 1000;
      thrust::device_vector<int> d_data = h_data;
      thrust::device_vector<int> d_result(DSIZE);
      thrust::transform_inclusive_scan(d_data.begin(), d_data.end(), d_result.begin(), my_unary_op(), thrust::plus<int>());
      thrust::transform(d_data.begin(), d_data.end(), d_result.begin(), d_result.begin(), my_binary_op());
      thrust::copy(d_data.begin(), d_data.end(), std::ostream_iterator<int>(std::cout, ","));
      std::cout << std::endl;
      thrust::copy(d_result.begin(), d_result.end(), std::ostream_iterator<int>(std::cout, ","));
      std::cout << std::endl;
      return 0;
    }
    
    $ nvcc -o t635 t635.cu
    $ ./t635
    0,886,777,0,793,0,386,0,649,0,0,0,0,59,763,926,540,426,0,736,
    0,1,2,0,3,0,4,0,5,0,0,0,0,6,7,8,9,10,0,11,
    $
    

    在我看来,这个新信息响应更新,使问题更难以解决。可以想到直方图技术,但是对32位整数(标签)的占用范围没有任何限制,或者对数据集中特定标签可能重复的次数有任何限制,直方图技术似乎不切实际。这导致我考虑对数据进行排序。

    这样的方法应该有效:

    1. 使用thrust::sort对数据进行排序。
    2. 使用thrust::unique删除重复项。
    3. 现在删除重复项的已排序数据为我们提供了输出集[0,1,2,...]的排序。我们称之为“地图”。我们可以使用parallel binary-search technique将原始数据集中的每个标签转换为它的映射输出值。
    4. 这个过程对我来说似乎相当“昂贵”。我建议重新考虑上游标签操作,看看它是否可以重新设计,以产生更适合有效下游处理的数据集。

      无论如何,这是一个完全有效的例子:

      $ cat t635.cu
      #include <iostream>
      #include <stdlib.h>
      
      #include <thrust/device_vector.h>
      #include <thrust/host_vector.h>
      #include <thrust/transform.h>
      #include <thrust/generate.h>
      #include <thrust/sort.h>
      #include <thrust/unique.h>
      #include <thrust/copy.h>
      
      
      #define DSIZE 20
      #define PCT_ZERO 40
      #define RNG 10
      
      #define nTPB 256
      
      // sets idx to the index of the first element in a that is
      // equal to or larger than key
      
      __device__ void bsearch_range(const int *a, const int key, const unsigned len_a, unsigned *idx){
        unsigned lower = 0;
        unsigned upper = len_a;
        unsigned midpt;
        while (lower < upper){
          midpt = (lower + upper)>>1;
          if (a[midpt] < key) lower = midpt +1;
          else upper = midpt;
          }
        *idx = lower;
        return;
        }
      
      __global__ void find_my_idx(const int *a, const unsigned len_a,  int *my_data, int *my_idx, const unsigned len_data){
        unsigned idx = (blockDim.x * blockIdx.x) + threadIdx.x;
        if (idx < len_data){
          unsigned sp_a;
          int val = my_data[idx];
          bsearch_range(a, val, len_a, &sp_a);
          my_idx[idx] = sp_a;
          }
      }
      
      
      int main(){
      
      // generate DSIZE random 32-bit integers, PCT_ZERO% are zero
        thrust::host_vector<int> h_data(DSIZE);
        thrust::generate(h_data.begin(), h_data.end(), rand);
        for (int i = 0; i < DSIZE; i++)
          if ((rand()%100)< PCT_ZERO) h_data[i] = 0;
          else h_data[i] %= RNG;
        thrust::device_vector<int> d_data = h_data;
        thrust::device_vector<int> d_result = d_data;
        thrust::sort(d_result.begin(), d_result.end());
        thrust::device_vector<int> d_unique = d_result;
        int unique_size = thrust::unique(d_unique.begin(), d_unique.end()) - d_unique.begin();
        find_my_idx<<< (DSIZE+nTPB-1)/nTPB , nTPB >>>(thrust::raw_pointer_cast(d_unique.data()), unique_size, thrust::raw_pointer_cast(d_data.data()), thrust::raw_pointer_cast(d_result.data()), DSIZE);
      
        thrust::copy(d_data.begin(), d_data.end(), std::ostream_iterator<int>(std::cout, ","));
        std::cout << std::endl;
        thrust::copy(d_result.begin(), d_result.end(), std::ostream_iterator<int>(std::cout, ","));
        std::cout << std::endl;
        return 0;
      }
      $ nvcc t635.cu -o t635
      $ ./t635
      0,6,7,0,3,0,6,0,9,0,0,0,0,9,3,6,0,6,0,6,
      0,2,3,0,1,0,2,0,4,0,0,0,0,4,1,2,0,2,0,2,
      $
      

答案 1 :(得分:0)

我的答案类似于@RobertCrovella给出的答案,但我认为使用thrust::lower_bound而不是自定义二进制搜索会更简单。 (既然是纯推力,后端就可以互换了)

  1. 复制输入数据
  2. 排序复制的数据
  3. 根据排序后的数据创建唯一列表
  4. 在唯一列表中找到每个输入的下限

我在下面提供了一个完整的示例。有趣的是,可以通过在排序步骤之前添加thrust::unique来加快处理速度。根据输入数据,这可以显着减少排序中的元素数量,这是这里的瓶颈。

#include <iostream>
#include <stdlib.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <thrust/transform.h>
#include <thrust/generate.h>
#include <thrust/sort.h>
#include <thrust/unique.h>
#include <thrust/binary_search.h>
#include <thrust/copy.h>

int main()
{
  const int ndata = 20;
  // Generate host input data
  thrust::host_vector<int> h_data(ndata);
  thrust::generate(h_data.begin(), h_data.end(), rand);
  for (int i = 0; i < ndata; i++)
  {
    if ((rand() % 100) < 40)
      h_data[i] = 0;
    else
      h_data[i] %= 10;
  }

  // Copy data to the device
  thrust::device_vector<int> d_data = h_data;
  // Make a second copy of the data
  thrust::device_vector<int> d_result = d_data;
  // Sort the data copy
  thrust::sort(d_result.begin(), d_result.end());
  // Allocate an array to store unique values
  thrust::device_vector<int> d_unique = d_result;
  {
    // Compress all duplicates
    const auto end = thrust::unique(d_unique.begin(), d_unique.end());
    // Search for all original labels, in this compressed range, and write their
    // indices back as the result
    thrust::lower_bound(
      d_unique.begin(), end, d_data.begin(), d_data.end(), d_result.begin());
  }

  thrust::copy(
    d_data.begin(), d_data.end(), std::ostream_iterator<int>(std::cout, ","));
  std::cout << std::endl;
  thrust::copy(d_result.begin(),
               d_result.end(),
               std::ostream_iterator<int>(std::cout, ","));
  std::cout << std::endl;
  return 0;
}