通过 cuda 实现快速并行矩阵求逆算法但有些东西不起作用

Posted

技术标签:

【中文标题】通过 cuda 实现快速并行矩阵求逆算法但有些东西不起作用【英文标题】:Implementing a fast parallel Matrix Inversion algorithm by cuda but something is not working 【发布时间】:2022-01-19 02:52:21 【问题描述】:

目前,我正在实施

用于 cuda 的下三角矩阵。我现在只使用一个线程在矩阵的左下角进行矩阵乘法。但是,我在代码中使用的临时矩阵似乎存在一些问题。我怀疑第 24 行存在一些问题,即临时矩阵无法存储矩阵乘法后的值。这是result。有谁知道如何解决这个问题?

void print_mat(int size_small,int size_large, float*m)
    for (int row = 0; row < size_small; row++)
        for(int col = 0; col<size_small;col++)
            printf("%.2f ",m[ col + row*size_large]);

            if (col % size_small == size_small-1)
                printf("\n");

        
    



__device__ void SMM(float *A1inv, float *A2, float *A3inv, float*temp, float * O,int small_size, int large_size)

    for (int i = 0; i  < small_size; i++)
        for (int j = 0; j  < small_size; j++)
            temp[small_size*i + j] = 0;
            for (int k = 0; k < small_size; k++)
                temp[small_size*i + j] += A3inv[i*large_size+k]*A2[k*small_size+j];
            
            temp[small_size*i+j] = -temp[small_size*i+j];
        
    

    for (int i = 0; i  < small_size; i++)
        for (int j = 0; j  < small_size; j++)
            O[large_size*i + j] = 0;
            for (int k = 0; k < small_size; k++)
                O[large_size*i + j] += temp[i*small_size+k]*A1inv[k*large_size+j];
            
        
    




__global__ void mat_inv(float*A,float*O,float* temp,int n)

    int ix = blockDim.x * blockIdx.x + threadIdx.x; // global thread ix

    if(ix < n* n)

        // Step 1: copy the element on diagonal
        for (int i = 0; i<n; i++)
            O[ix*(n+1)] = 1/A[ix*(n+1)];
        __syncthreads();
        // Generate n-series
        int k = n;

        int n_series = 0;

        while (k>0)

            k = k /2;

            n_series++;

        


        for(int i = 0;i<(n_series-1);i++)
            int m_size = 1<<(i);//size_subm[i];
            int x_start = 0;
            int y_start = m_size;
            int jump = 2*m_size;

            // for each matrix
            int j = ix;

            float *A21 = &A[(x_start + j*jump) +  (y_start + j*jump)*n ];
            float *O21 = &O[(x_start + j*jump) +  (y_start + j*jump)*n];
            float *O11 = O21 - m_size*n;
            float *O22 = O21 + m_size;
            //      if(i ==1)
            //              print_mat_d(n,n,ix,A);

            SMM(O11,A21,O22,temp,O21,m_size,n);

            __syncthreads();


        


    







int main()

    int n = 8;

    // Use only 1 block

    int num_blocks = 1;

    // Create a matrix

    float* hA = (float*) malloc(n*n*sizeof(float));
    float* hO = (float*) malloc(n*n*sizeof(float));
    float* dA;
    float* dO;
    float* temp;

    cudaMalloc(&dA,n*n*sizeof(float));

    cudaMalloc(&dO,n*n*sizeof(float));

    cudaMalloc(&temp,n*n*sizeof(float));




    for(int i = 0; i<n*n;i++)

        if(i%n>i/n)
            hA[i] = 0;
        else
            hA[i] = 1.1;

    
    print_mat(n,n,hA);

    cudaMemcpy(dA,hA,n*n*sizeof(float),cudaMemcpyHostToDevice);

    int num_thread_per_block = n;

    mat_inv<<<num_blocks,num_thread_per_block>>>(dA,dO,temp,n);

    cudaMemcpy(hO,dO,n*n*sizeof(float),cudaMemcpyDeviceToHost);
    printf("\n");
    print_mat(n,n,hO);



    return 0;


【问题讨论】:

我看不出您编写的代码在数学上与您引用的图像相同。我在该代码的任何地方都看不到任何子矩阵反转。你认为O[ix*(n+1)] = 1/A[ix*(n+1)]; 在做什么? 这是反转 1x1 矩阵以开始 但这在数学上是不可能的。如果这些块矩阵中的任何一个是 1 x 1,那么根据定义,其他块矩阵对于分解为 1x1 块的 2x2 矩阵以外的任何东西都不是正方形且不可逆转。 【参考方案1】:

一些初步的评论:

如果您对“快速、并行”的逆矩阵感兴趣,您应该只使用library。 我将假设矩阵的边维是 2 的幂。 我只是尝试解决您的代码中的一些缺陷,而不是尝试创建“最佳”的东西。我认为与其完善这一点,我更愿意使用库。 我不会总是assume 认为矩阵求逆是必要的。

对于矩阵侧维度的 2 次幂,您勾勒出的一般方法对我来说似乎是合理的:

用倒数逐个元素替换主对角线元素。 沿“连续的次对角线方向”工作,使用增加大小的矩阵乘法运算来填充下三角结果。

作为一般性声明,我不清楚您是否真正了解 CUDA 开发的线程并行性质。我在这里看到的一个例子是:

    for (int i = 0; i<n; i++)
        O[ix*(n+1)] = 1/A[ix*(n+1)];

让我们研究一下。首先,i 上的循环让我感到困惑,因为i 在循环主体中没有使用(并且输出与输入不相交)。循环的目的是什么?主体功能是循环不变的。其次,让我们考虑一下您的索引。如果我们信任前面的if 语句,您的线程的ix 变量取值范围为0 到n*n-1。这不可能。当然,您只是在这里启动n 线程,所以它可以工作。但循环是不必要的。

还要注意__syncthreads() 在条件代码中是非法的,除非所有线程都可以访问它。因此,只要我们不启动超过 n 线程(并且我们修改了初始 if 语句),我们就可以了。

因为您已选择在每种情况下都由单个线程执行矩阵-矩阵乘法,所以从来没有需要所有n 线程执行矩阵-矩阵乘法的情况,因为我们通过“subdiagonal”(我们需要的最大数字是n/2,在第一个“subdiagonal”处)。因此,您必须进行过多的矩阵乘法运算,因为当您通过次对角线进行工作时,您在任何地方都没有使代码执行越来越少的矩阵乘法运算。因此,您必须进行非法索引,如果您使用cuda-memcheckcompute-sanitizer 运行代码,这一点很明显,因为我总是建议您在遇到 CUDA 代码问题时。换句话说,在第二个i 循环的每次迭代中需要进行的矩阵乘法不是n。但这就是您的代码试图做的事情。

我注意到的另一件事是您没有正确处理temp。所有线程都使用相同的 temp 数组,有效开始为 0。temp 未正确偏移,每个线程,因此每个线程使用单独的区域。

可能还有其他问题。由于上述问题中的至少 2 个,因此需要进行重大改写。因此,我提供了自己的代码,并没有尝试完全匹配您的代码:

$ cat t12.cu
#include <cstdio>
// matrix-matrix multiply, with optional negation, allowing output to overwrite input
template <typename T>
__host__ __device__
void mm(T *A, T *B, T *D,  int stride, int n, bool negate)

  T *C = new T[n*n];  // avoids temp indexing madness
  for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++) 
      C[i*n+j] = 0;
      for (int k = 0; k < n; k++)  C[i*n+j] += A[i*stride+k]*B[k*stride+j];
  for (int i = 0; i < n; i++)
    for (int j = 0; j < n; j++) D[i*stride+j] = (negate)?(-C[i*n+j]):(C[i*n+j]);
  delete[] C;


// assumes one block only, n == threads per block, n power of 2
// lower triangular matrix invert
template <typename T>
__global__
void ltmi(T *A, int n)
  int i = threadIdx.x;
    // "invert" main diagonal
  A[i*(n+1)] = 1/A[i*(n+1)];

// process "subdiagonals" in order
  int mask = 1;
  for (int s = 1; s < n; s *= 2)
    __syncthreads();
    if (!(i & mask)) // select needed threads at each "subdiagonal"
      T *mA = A + i*(n+1); // pointer to thread's A sub matrix
      mm(mA+s*(n+1), mA+s*n, mA+s*n, n, s, true); // -A3i*A2
      mm(mA+s*n, mA, mA+s*n, n, s, false);       // A2*A1i
      mask = 2*mask+1;
    



typedef float mt;
const int n = 8;  // must be power-of-2, 1024 or less
int main()
// setup matrix
  mt *A;
  cudaMallocManaged(&A, sizeof(*A)*n*n);
  memset(A, 0, sizeof(*A)*n*n);
  for (int i = 0; i < n; i++)
    for (int j = 0; j < i+1; j++)
      A[i*n+j] = 1.1f;

  ltmi<<<1,n>>>(A, n);
  cudaDeviceSynchronize();

  for (int i = 0; i < n; i++) 
    for (int j = 0; j < n; j++)
      printf("%.6f ",  A[i*n+j]);
    printf("\n");

$ nvcc -o t12 t12.cu -lineinfo
$ compute-sanitizer ./t12
========= COMPUTE-SANITIZER
0.909091 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
-0.909091 0.909091 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000
0.000000 -0.909091 0.909091 0.000000 0.000000 0.000000 0.000000 0.000000
0.000000 0.000000 -0.909091 0.909091 0.000000 0.000000 0.000000 0.000000
-0.000000 -0.000000 0.000000 -0.909091 0.909091 0.000000 0.000000 0.000000
0.000000 0.000000 0.000000 0.000000 -0.909091 0.909091 0.000000 0.000000
0.000000 0.000000 0.000000 0.000000 0.000000 -0.909091 0.909091 0.000000
0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 -0.909091 0.909091
========= ERROR SUMMARY: 0 errors
$

我还没有彻底测试过。我真的不推荐这种方法。

【讨论】:

我是 Cuda 的新手...感谢您的评论! 你的评论和代码对我学习cuda肯定很有帮助。真的让我大开眼界。再次感谢!

以上是关于通过 cuda 实现快速并行矩阵求逆算法但有些东西不起作用的主要内容,如果未能解决你的问题,请参考以下文章

由正交矩阵构建的仿射变换矩阵求逆的快速算法

Cuda内核中的大型for循环不适用于大型数组[关闭]

基于FPGA的7x7矩阵求逆verilog开发

论如何求矩阵的逆?先看看基础芝士!

CUDA 中 Smith-Waterman 算法的矩阵填充

这是 CUDA 线程同步问题还是其他问题?