尽可能快地计算矩阵的零空间

时间:2010-02-02 01:49:35

标签: algorithm math matrix cuda

我需要并行计算几千个小矩阵(8x9,而不是我之前写的4x3)的零空间(CUDA)。所有引用都指向SVD,但数值配方中的算法看起来非常昂贵,并且除了我不需要的零空间之外,它给了我很多东西。高斯消除真的不是一种选择吗?还有其他常用的方法吗?

7 个答案:

答案 0 :(得分:11)

直接回答你的问题......是的! QR分解!

设A是具有秩n的m×n矩阵。 QR分解找到正交m-by-m矩阵Q和上三角m-by-n矩阵R,使得A = QR。如果我们定义Q = [Q1 Q2],其中Q1是m-by-n,Q2是m-by-(m-n),那么Q2的列形成A ^ T的零空间。

QR分解由Gram-Schmidt,Givens旋转或Householder反射计算。它们具有不同的稳定性和操作次数。

你是对的:SVD很贵!我不能代表最先进的东西使用什么,但当我听到“计算零空间”(编辑:以一种对我来说很容易理解的方式)时,我认为QR。

答案 1 :(得分:3)

对于4x3矩阵,高斯消除速度非常快。 IIRC我用Java做了大约500万次,没有并行性。有了这么小的问题,最好的办法就是自己编写例程(行减少等);否则你将浪费大部分时间将数据放入外部例程的正确格式。

答案 2 :(得分:3)

我不认为上面提出的方法总是给出整个零空间。概括:“A = QR,其中Q = [Q1 Q2],Q1是m-by-n,Q2是m-by-(mn)。然后Q2的列形成A ^ T的零空间。”

实际上,这可能只给出零空间的子空间。简单的反例是当A = 0时,在这种情况下,A ^ T的零空间是整个R ^ m。

因此,有必要检查R.根据我对Matlab的经验,如果一行R是直0,那么Q中的相应列也应该是A ^ T的零空间的基础。显然,这种观察是启发式的,取决于用于QR分解的特定算法。

答案 3 :(得分:1)

“看起来很贵” - 你有什么数据支持这个?

也许Block Lanczos是您寻求的答案。

或者this

JAMA和Apache Commons Math都使用Java进行SVD​​实现。为什么不拿那些并试试呢?为您的案例获取一些真实数据而不是展示次数。它不会花费太多,因为代码已经编写和测试。

答案 4 :(得分:1)

我认为CUDA最重要的是找到一种不依赖于条件分支的算法(在图形硬件上非常慢)。简单if如果可以优化为条件赋值的语句要好得多(或者你可以使用?:运算符)。

如有必要,您应该能够使用条件分配进行某种形式的旋转。实际上可能更难确定如何存储结果:如果您的矩阵缺乏排名,那么您希望CUDA计划对此做些什么?

如果你假设你的4x3矩阵实际上没有秩缺陷,你可以找到你的(单个)零空间向量而没有任何条件:矩阵足够小,你可以有效地使用Cramer规则。

实际上,由于您实际上并不关心空向量的比例,因此您不必除以行列式 - 您可以只考虑未成年人的决定因素:

    x1 x2 x3
M = y1 y2 y3
    z1 z2 z3
    w1 w2 w3

         |y1 y2 y3|        |x1 x2 x3|       |x1 x2 x3|        |x1 x2 x3|
->  x0 = |z1 z2 z3|  y0 = -|z1 z2 z3|  z0 = |y1 y2 y3|  w0 = -|y1 y2 y3|
         |w1 w2 w3|        |w1 w2 w3|       |w1 w2 w3|        |z1 z2 z3|

请注意,这些3x3决定因素只是三重产品;您可以通过重复使用交叉产品来节省计算。

答案 5 :(得分:1)

我想知道矩阵是否相关而不仅仅是随机的,因此您正在寻找的零空间可以被认为是与N空间中的曲线的一维切线(N = 9)。如果是这样,你可以通过使用牛顿方法从前一个零空间向量开始求解二次方程Ax = 0,| x | ^ 2 = 1的系统的连续实例来加快速度。牛顿方法使用一阶导数收敛到一个解,因此将使用高斯消元来求解9x9系统。使用这种技术需要您通过改变参数来从矩阵到矩阵进行小步骤。

所以我们的想法是你在第一个矩阵上使用SVD进行初始化,但之后你从矩阵步进到矩阵,使用一个零空间向量作为下一个迭代的起始点。您需要一到两次迭代才能获得收敛。如果没有进行对流,则使用SVD重新启动。如果你有这种情况,那么比在每个矩阵上重新开始要快得多。

我很久以前用它来绘制与电力系统行为相关的50 x 50二次方程组解的轮廓图。

答案 6 :(得分:1)

在上面的讨论中,已经指出了如何使用QR或SVD方法计算矩阵的零空间。当需要精确度时,应首选SVD,另请参阅Null-space of a rectangular dense matrix

截至2015年2月,CUDA 7(现已发布候选版本)通过其新的cuSOLVER库提供SVD。下面我举例说明如何使用cuSOLVER的SVD来计算矩阵的零空间。

请注意,您关注的问题涉及几个小矩阵的计算,因此您应该通过使用流来适应您的情况来调整我在下面提供的示例。要将流关联到每个任务,您可以使用

cudaStreamCreate()

cusolverDnSetStream()

<强> kernel.cu

#include "cuda_runtime.h"
#include "device_launch_paraMeters.h"

#include<iostream>
#include<iomanip>
#include<stdlib.h>
#include<stdio.h>
#include<assert.h>
#include<math.h>

#include <cusolverDn.h>
#include <cuda_runtime_api.h>

#include "Utilities.cuh"

/********/
/* MAIN */
/********/
int main(){

    // --- gesvd only supports Nrows >= Ncols
    // --- column major memory ordering

    const int Nrows = 7;
    const int Ncols = 5;

    // --- cuSOLVE input/output parameters/arrays
    int work_size = 0;
    int *devInfo;           gpuErrchk(cudaMalloc(&devInfo,          sizeof(int)));

    // --- CUDA solver initialization
    cusolverDnHandle_t solver_handle;
    cusolverDnCreate(&solver_handle);

    // --- Singular values threshold
    double threshold = 1e-12;

    // --- Setting the host, Nrows x Ncols matrix
    double *h_A = (double *)malloc(Nrows * Ncols * sizeof(double));
    for(int j = 0; j < Nrows; j++)
        for(int i = 0; i < Ncols; i++)
            h_A[j + i*Nrows] = (i + j*j) * sqrt((double)(i + j));

    // --- Setting the device matrix and moving the host matrix to the device
    double *d_A;            gpuErrchk(cudaMalloc(&d_A,      Nrows * Ncols * sizeof(double)));
    gpuErrchk(cudaMemcpy(d_A, h_A, Nrows * Ncols * sizeof(double), cudaMemcpyHostToDevice));

    // --- host side SVD results space
    double *h_U = (double *)malloc(Nrows * Nrows     * sizeof(double));
    double *h_V = (double *)malloc(Ncols * Ncols     * sizeof(double));
    double *h_S = (double *)malloc(min(Nrows, Ncols) * sizeof(double));

    // --- device side SVD workspace and matrices
    double *d_U;            gpuErrchk(cudaMalloc(&d_U,  Nrows * Nrows     * sizeof(double)));
    double *d_V;            gpuErrchk(cudaMalloc(&d_V,  Ncols * Ncols     * sizeof(double)));
    double *d_S;            gpuErrchk(cudaMalloc(&d_S,  min(Nrows, Ncols) * sizeof(double)));

    // --- CUDA SVD initialization
    cusolveSafeCall(cusolverDnDgesvd_bufferSize(solver_handle, Nrows, Ncols, &work_size));
    double *work;   gpuErrchk(cudaMalloc(&work, work_size * sizeof(double)));

    // --- CUDA SVD execution
    cusolveSafeCall(cusolverDnDgesvd(solver_handle, 'A', 'A', Nrows, Ncols, d_A, Nrows, d_S, d_U, Nrows, d_V, Ncols, work, work_size, NULL, devInfo));
    int devInfo_h = 0;  gpuErrchk(cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost));
    if (devInfo_h != 0) std::cout   << "Unsuccessful SVD execution\n\n";

    // --- Moving the results from device to host
    gpuErrchk(cudaMemcpy(h_S, d_S, min(Nrows, Ncols) * sizeof(double), cudaMemcpyDeviceToHost));
    gpuErrchk(cudaMemcpy(h_U, d_U, Nrows * Nrows     * sizeof(double), cudaMemcpyDeviceToHost));
    gpuErrchk(cudaMemcpy(h_V, d_V, Ncols * Ncols     * sizeof(double), cudaMemcpyDeviceToHost));

    for(int i = 0; i < min(Nrows, Ncols); i++) 
        std::cout << "d_S["<<i<<"] = " << std::setprecision(15) << h_S[i] << std::endl;

    printf("\n\n");

    int count = 0;
    bool flag = 0;
    while (!flag) {
        if (h_S[count] < threshold) flag = 1;
        if (count == min(Nrows, Ncols)) flag = 1;
        count++;
    }
    count--;
    printf("The null space of A has dimension %i\n\n", min(Ncols, Nrows) - count);

    for(int j = count; j < Ncols; j++) {
        printf("Basis vector nr. %i\n", j - count);
        for(int i = 0; i < Ncols; i++)
                std::cout << "d_V["<<i<<"] = " << std::setprecision(15) << h_U[j*Ncols + i] << std::endl;
        printf("\n");
    }

    cusolverDnDestroy(solver_handle);

    return 0;

}

<强> Utilities.cuh

#ifndef UTILITIES_CUH
#define UTILITIES_CUH

extern "C" int iDivUp(int, int);
extern "C" void gpuErrchk(cudaError_t);
extern "C" void cusolveSafeCall(cusolverStatus_t);

#endif

<强> Utilities.cu

#include <stdio.h>
#include <assert.h>

#include "cuda_runtime.h"
#include <cuda.h>

#include <cusolverDn.h>

/*******************/
/* iDivUp FUNCTION */
/*******************/
extern "C" int iDivUp(int a, int b){ return ((a % b) != 0) ? (a / b + 1) : (a / b); }

/********************/
/* CUDA ERROR CHECK */
/********************/
// --- Credit to http://stackoverflow.com/questions/14038589/what-is-the-canonical-way-to-check-for-errors-using-the-cuda-runtime-api
void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
   if (code != cudaSuccess)
   {
      fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
      if (abort) { exit(code); }
   }
}

extern "C" void gpuErrchk(cudaError_t ans) { gpuAssert((ans), __FILE__, __LINE__); }

/**************************/
/* CUSOLVE ERROR CHECKING */
/**************************/
static const char *_cudaGetErrorEnum(cusolverStatus_t error)
{
    switch (error)
    {
        case CUSOLVER_STATUS_SUCCESS:
            return "CUSOLVER_SUCCESS";

        case CUSOLVER_STATUS_NOT_INITIALIZED:
            return "CUSOLVER_STATUS_NOT_INITIALIZED";

        case CUSOLVER_STATUS_ALLOC_FAILED:
            return "CUSOLVER_STATUS_ALLOC_FAILED";

        case CUSOLVER_STATUS_INVALID_VALUE:
            return "CUSOLVER_STATUS_INVALID_VALUE";

        case CUSOLVER_STATUS_ARCH_MISMATCH:
            return "CUSOLVER_STATUS_ARCH_MISMATCH";

        case CUSOLVER_STATUS_EXECUTION_FAILED:
            return "CUSOLVER_STATUS_EXECUTION_FAILED";

        case CUSOLVER_STATUS_INTERNAL_ERROR:
            return "CUSOLVER_STATUS_INTERNAL_ERROR";

        case CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED:
            return "CUSOLVER_STATUS_MATRIX_TYPE_NOT_SUPPORTED";

    }

    return "<unknown>";
}

inline void __cusolveSafeCall(cusolverStatus_t err, const char *file, const int line)
{
    if(CUSOLVER_STATUS_SUCCESS != err) {
        fprintf(stderr, "CUSOLVE error in file '%s', line %d\n %s\nerror %d: %s\nterminating!\n",__FILE__, __LINE__,err, \
                                _cudaGetErrorEnum(err)); \
        cudaDeviceReset(); assert(0); \
    }
}

extern "C" void cusolveSafeCall(cusolverStatus_t err) { __cusolveSafeCall(err, __FILE__, __LINE__); }