情况如下:我有一个数字(1000s)的元素,由尺寸为4x2,9x3的小矩阵给出......你明白了。所有矩阵都具有相同的维度。
我想将这些矩阵中的每一个与预先计算的值的固定向量相乘。简而言之:
for(i = 1...n)
X[i] = M[i] . N;
使用Thrust并行执行此操作的最佳方法是什么?如何在内存中布置数据?
注意:可能有专门的,更合适的库在GPU上执行此操作。我对Thrust感兴趣,因为它允许我部署到不同的后端,而不仅仅是CUDA。
答案 0 :(得分:2)
一种可能的方法:
将数组(矩阵)展平为单个数据向量。无论如何,这是实现一般推力加工的有利步骤。
使用strided range机制获取缩放矢量并将其扩展到展平数据矢量的总长度
将thrust::transform与thrust::multiplies一起使用,将两个向量相乘。
如果您需要稍后使用展平数据向量(或结果向量)访问矩阵,则可以使用指针运算或fancy iterators的组合来执行此操作。
如果您需要重新使用扩展缩放矢量,您可能希望使用步骤2中概述的方法(即使用该方法创建实际矢量,长度= N矩阵,重复)。如果你只是这样做一次,你可以用计数迭代器实现相同的效果,然后是变换迭代器(以元素中矩阵的长度为模),然后是置换迭代器,索引到你的原始缩放矢量(长度) = 1矩阵)。
以下示例实现了上述内容,而不使用跨步范围迭代器方法:
#include <iostream>
#include <stdlib.h>
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <thrust/functional.h>
#include <thrust/iterator/permutation_iterator.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/iterator/transform_iterator.h>
#include <thrust/transform.h>
#define N_MAT 1000
#define H_MAT 4
#define W_MAT 3
#define RANGE 1024
struct my_modulo_functor : public thrust::unary_function<int, int>
{
__host__ __device__
int operator() (int idx) {
return idx%(H_MAT*W_MAT);}
};
int main(){
thrust::host_vector<int> data(N_MAT*H_MAT*W_MAT);
thrust::host_vector<int> scale(H_MAT*W_MAT);
// synthetic; instead flatten/copy matrices into data vector
for (int i = 0; i < N_MAT*H_MAT*W_MAT; i++) data[i] = rand()%RANGE;
for (int i = 0; i < H_MAT*W_MAT; i++) scale[i] = rand()%RANGE;
thrust::device_vector<int> d_data = data;
thrust::device_vector<int> d_scale = scale;
thrust::device_vector<int> d_result(N_MAT*H_MAT*W_MAT);
thrust::transform(d_data.begin(), d_data.end(), thrust::make_permutation_iterator(d_scale.begin(), thrust::make_transform_iterator(thrust::counting_iterator<int>(0), my_modulo_functor())) ,d_result.begin(), thrust::multiplies<int>());
thrust::host_vector<int> result = d_result;
for (int i = 0; i < N_MAT*H_MAT*W_MAT; i++)
if (result[i] != data[i] * scale[i%(H_MAT*W_MAT)]) {std::cout << "Mismatch at: " << i << " cpu result: " << (data[i] * scale[i%(H_MAT*W_MAT)]) << " gpu result: " << result[i] << std::endl; return 1;}
std::cout << "Success!" << std::endl;
return 0;
}
编辑:回答以下问题:
花式迭代器(即transform(numbers, iterator)
)的好处是,与汇编other number
(需要额外的步骤和数据移动)相比,它们通常允许消除额外的数据副本/数据移动。然后将其传递给transform(numbers, other numbers)
。如果你只打算使用other numbers
一次,那么花哨的迭代器通常会更好。如果您要再次使用other numbers
,那么您可能需要明确地组装它。 This preso具有指导意义,特别是“融合”。
对于other numbers
的一次性使用使用花式迭代器和仿函数动态组装它的开销通常低于显式创建新向量,然后将新向量传递给{{1}例行公事。
答案 1 :(得分:-1)
在寻找一个简洁的用于乘以小矩阵的软件库时,可以查看https://github.com/hfp/libxsmm。下面,代码根据典型的GEMM参数请求专门的矩阵内核(请注意,某些limitations适用)。
double alpha = 1, beta = 1;
const char transa = 'N', transb = 'N';
int flags = LIBXSMM_GEMM_FLAGS(transa, transb);
int prefetch = LIBXSMM_PREFETCH_AUTO;
libxsmm_blasint m = 23, n = 23, k = 23;
libxsmm_dmmfunction xmm = NULL;
xmm = libxsmm_dmmdispatch(m, n, k,
&m/*lda*/, &k/*ldb*/, &m/*ldc*/,
&alpha, &beta, &flags, &prefetch);
鉴于上述代码,可以继续并运行&#34; xmm&#34;对于没有特定数据结构的整个系列(小)矩阵(下面的代码也使用&#34;预取位置&#34;)。
if (0 < n) { /* check that n is at least 1 */
# pragma parallel omp private(i)
for (i = 0; i < (n - 1); ++i) {
const double *const ai = a + i * asize;
const double *const bi = b + i * bsize;
double *const ci = c + i * csize;
xmm(ai, bi, ci, ai + asize, bi + bsize, ci + csize);
}
xmm(a + (n - 1) * asize, b + (n - 1) * bsize, c + (n - 1) * csize,
/* pseudo prefetch for last element of batch (avoids page fault) */
a + (n - 1) * asize, b + (n - 1) * bsize, c + (n - 1) * csize);
}
除了如上所示的手动循环控制之外,还可以使用libxsmm_gemm_batch(或libxsmm_gemm_batch_omp)(参见ReadTheDocs)。如果存在描述一系列操作数(A,B和C矩阵)的数据结构,则后者非常有用。
这个库提供卓越性能的原因有两个:(1)使用内存中代码生成技术的动态代码专用化,以及(2)在计算当前产品时加载下一个矩阵操作数。
(鉴于有人正在寻找与C / C ++完美融合的东西,这个库支持它。但是,它并不针对CUDA / Thrust。)