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