首页/文章/ 详情

稀疏矩阵线性方程组如何基于gpu求解?稀疏矩阵Gpu求解之神-amgx的示例

1年前浏览1112
稀疏矩阵线性方程组求解一直是一个历史悠久,经久不衰的研究课题。在现实中,稀疏矩阵广泛用于各个领域。通过数值方法,如有限元法、有限体积法等将物理模型转化为稀疏矩阵方程组的求解是十分常见的求解方法。
对于稀疏矩阵的线性方程组求解,常见的方法可分为直接法和迭代法。
直接法以高斯消去,LU分解等为基础,通过消去+回代的方式获得最终解。这种方法的鲁棒性很强,通常是很“稳”的方法,其劣势是当规模较大如千万自由度之后,分解过程中需要耗费超大的内存,分解效率也很低,耗时很长,尽管通过重排后可以减少一部分时间,但对于大规模稀疏矩阵依然效率不高,内存需求太大。
另外,对于稀疏矩阵,直接法求解的编程也是比较复杂的,稀疏矩阵采用COO,CSR等格式存储时通常只存储非0元素的位置和值,而分解过程中会破坏直接的非0元分布的结构,在专业术语上分解的过程可能被称为 符号分解,消去树等过程,总体上都是很复杂的算法。
迭代法可以大致不太准确地分为两类,一类是以雅可比迭代,高斯赛德尔迭代,超松弛迭代为代表的“分裂迭代法”,这类方法本质上是把矩阵拆分为两个矩阵之和,其中一部分与解的乘积移至方程组右边,从而形成一个迭代列式。
 
 
 
 
这一类迭代方法目前实际上直接使用的情况并不多见,很多时候都是作为另一类迭代方法即Krylov迭代方法的预处理进行使用。Krylov迭代方法基于Krylov空间进行迭代,常见的方法包括共轭梯度法(CG),广义最小残差法(GMRES),稳定双共轭梯度法(Bicgstab)等。这些方法具体的推导比较复杂,但编程实现通常相对简单,通常仅需要完成一些矩阵向量乘,向量点乘之类的运算,即可编写完成整个算法。
 
 
随着稀疏矩阵求解规模的不断增大,使用Gpu实现高速求解稀疏矩阵线性方程组成为一个值得做的选择。Amgx,就是英伟达基于Gpu(CUDA)实现的多重网格预处理的Krylov迭代求解开源库。Amgx为分布式代数多重网格法和预处理迭代法提供即插即用的 GPU 加速,实现了基于经典方法和聚合方法的代数多重网格法,包含不同的选择器和插值策略,同时还提供了多种平滑器和预处理器,包括块雅可比(block-Jacobi)、高斯 - 赛德尔(Gauss-Seidel)和不完全 LU 分解(incomplete-LU factorization)。
Amgx库包含许多标准且灵活的预处理 Krylov 子空间迭代法,这些方法可与任何可用的多重网格法或更简单的预处理器相结合。聚合方案中的并行性利用了并行图匹配技术,而平滑器和预处理器则通常依赖于并行图着色算法。
Amgx的常见应用是CFD和油藏模拟领域,知名的商业CFD软件Ansys fluent就使用amgx用于稀疏矩阵的Gpu求解。
AmgX 库中实现的代数多重网格算法在单个 GPU 上的运行速度,是性能相当的 CPU 实现的 2-5 倍。正如数值实验部分将要展示的,其设置阶段和求解阶段在多个节点上都能很好地扩展,并保持这种性能优势。其提供了多种预处理及迭代求解方法,具体包括以下:
 
 
基于该开源库,我们仅需要调用amgx库,即可以完成稀疏矩阵线性方程组在Gpu上的求解,而基本不需要或者仅需要少量地编写CUDA代码。
本文提供一个amgx的基础示例,表明如何使用amgx完成稀疏矩阵线性方程组的求解。关于该库的安装,可参考文章“Windows下编译安装AMGX 2.4.0”。











































































































































































































































































#define _CRT_SECURE_NO_WARNINGS#define MAX_MSG_LEN 4096#include <stdio.h>#include <stdlib.h>#include <string.h>//#include "cuda_runtime.h"/* CUDA error macro */#define CUDA_SAFE_CALL(call) do {                                 \  cudaError_t err = call;                                         \  if(cudaSuccess != err) {                                        \    fprintf(stderr, "Cuda error in file '%s' in line %i : %s.\n", \            __FILE__, __LINE__, cudaGetErrorString( err) );       \    exit(EXIT_FAILURE);                                           \  } } while (0)/* standard or dynamically load library */#ifdef AMGX_DYNAMIC_LOADING#include "amgx_capi.h"#else#include "amgx_c.h"#endif/* print error message and exit */void errAndExit(const char *err){    printf("%s\n", err);    fflush(stdout);    exit(1);}/* print callback (could be customized) */void print_callback(const char *msg, int length){    printf("%s", msg);}/* print usage and exit */void printUsageAndExit(){    printf("%s""     -mode:   select the solver mode\n");    printf("%s""     -m file: read matrix stored in the file\n");    printf("%s""     -c:      set the amg solver options from the config file\n");    printf("%s""     -amg:    set the amg solver options from the command line\n");    exit(0);}/* parse parameters */int findParamIndex(const char **argv, int argc, const char *parm){    int count = 0;    int index = -1;    for (int i = 0; i < argc; i++)    {        if (strncmp(argv[i], parm, 100) == 0)        {            index = i;            count++;        }    }    if (count == 0 || count == 1)    {        return index;    }    else    {        printf("Error, parameter %s has been specified more than once, exiting\n", parm);        exit(1);    }    return -1;}int main(int argc, const char **argv){    //parameter parsing    int pidx = 0;    int pidy = 0;    //versions    int major, minor;    char *ver, *date, *time;    //input geometry    double *gx = NULL;    double *gy = NULL;    double *gz = NULL;    //input coloring    int dim = 0;    int numrows = 0;    int num_colors = 0;    int colored_rows = 0;    int *row_coloring = NULL;    //input matrix and rhs/solution    int n = 0;    int bsize_x = 0;    int bsize_y = 0;    int sol_size = 0;    int sol_bsize = 0;    //library handles    AMGX_Mode mode;    AMGX_config_handle cfg;    AMGX_resources_handle rsrc;    AMGX_matrix_handle A;    AMGX_vector_handle b, x;    AMGX_solver_handle solver;    //status handling    AMGX_SOLVE_STATUS status;    /* check arguments */    if (argc == 1)    {        printUsageAndExit();    }    /* load the library (if it was dynamically loaded) */#ifdef AMGX_DYNAMIC_LOADING    void *lib_handle = NULL;    //open the library#ifdef _WIN32    lib_handle = amgx_libopen("amgxsh.dll");#else    lib_handle = amgx_libopen("libamgxsh.so");#endif    if (lib_handle == NULL)    {        errAndExit("ERROR: can not load the library");    }    //load all the routines    if (amgx_liblink_all(lib_handle) == 0)    {        amgx_libclose(lib_handle);        errAndExit("ERROR: corrupted library loaded\n");    }#endif    /* init */    AMGX_SAFE_CALL(AMGX_initialize());    /* system */    AMGX_SAFE_CALL(AMGX_register_print_callback(&print_callback));    AMGX_SAFE_CALL(AMGX_install_signal_handler());    /* get api and build info */    if ((pidx = findParamIndex(argv, argc, "--version")) != -1)    {        AMGX_get_api_version(&major, &minor);        printf("amgx api version: %d.%d\n", major, minor);        AMGX_get_build_info_strings(&ver, &date, &time);        printf("amgx build version: %s\nBuild date and time: %s %s\n",            ver, date, time);        AMGX_SAFE_CALL(AMGX_finalize());        /* close the library (if it was dynamically loaded) */#ifdef AMGX_DYNAMIC_LOADING        amgx_libclose(lib_handle);#endif        exit(0);    }    /* get mode */    if ((pidx = findParamIndex(argv, argc, "-mode")) != -1)    {        if (strncmp(argv[pidx + 1], "hDDI"100) == 0)        {            mode = AMGX_mode_hDDI;        }        else if (strncmp(argv[pidx + 1], "hDFI"100) == 0)        {            mode = AMGX_mode_hDFI;        }        else if (strncmp(argv[pidx + 1], "hFFI"100) == 0)        {            mode = AMGX_mode_hFFI;        }        else if (strncmp(argv[pidx + 1], "dDDI"100) == 0)        {            mode = AMGX_mode_dDDI;        }        else if (strncmp(argv[pidx + 1], "dDFI"100) == 0)        {            mode = AMGX_mode_dDFI;        }        else if (strncmp(argv[pidx + 1], "dFFI"100) == 0)        {            mode = AMGX_mode_dFFI;        }        else if (strncmp(argv[pidx + 1], "hCCI"100) == 0)        {            mode = AMGX_mode_hZZI;        }        else if (strncmp(argv[pidx + 1], "hZCI"100) == 0)        {            mode = AMGX_mode_hZCI;        }        else if (strncmp(argv[pidx + 1], "hZZI"100) == 0)        {            mode = AMGX_mode_hZZI;        }        else if (strncmp(argv[pidx + 1], "dCCI"100) == 0)        {            mode = AMGX_mode_dCCI;        }        else if (strncmp(argv[pidx + 1], "dZCI"100) == 0)        {            mode = AMGX_mode_dZCI;        }        else if (strncmp(argv[pidx + 1], "dZZI"100) == 0)        {            mode = AMGX_mode_dZZI;        }        else        {            errAndExit("ERROR: invalid mode");        }    }    else    {        printf("Warning: No mode specified, using dDDI by default.\n");        mode = AMGX_mode_dDDI;    }    /* create config */    pidx = findParamIndex(argv, argc, "-amg");    pidy = findParamIndex(argv, argc, "-c");    if ((pidx != -1) && (pidy != -1))    {        printf("%s\n", argv[pidx + 1]);        AMGX_SAFE_CALL        (AMGX_config_create_from_file_and_string(&cfg, argv[pidy + 1], argv[pidx + 1]));    }    else if (pidy != -1)    {        AMGX_SAFE_CALL(AMGX_config_create_from_file(&cfg, argv[pidy + 1]));    }    else if (pidx != -1)    {        printf("%s\n", argv[pidx + 1]);        AMGX_SAFE_CALL(AMGX_config_create(&cfg, argv[pidx + 1]));    }    else    {        errAndExit("ERROR: no config was specified");    }    AMGX_resources_create_simple(&rsrc, cfg);    AMGX_matrix_create(&A, rsrc, mode);    AMGX_vector_create(&x, rsrc, mode);    AMGX_vector_create(&b, rsrc, mode);    AMGX_solver_create(&solver, rsrc, mode, cfg);    int ia[]{ 0,3,5,7 };    int ja[]{ 0,1,2,1,2,0,2 };    double value[]{ 3.0,5.0,2.0,2.0,3.0,1.0,3.0 };    int nrow = 3;    AMGX_matrix_upload_all(A, 3711, ia, ja, value,NULL);    AMGX_matrix_get_size(A, &nrow, &bsize_x, &bsize_y);    double xvalue1[]{ 0,0,0 };    AMGX_vector_upload(x, nrow, 1, xvalue1);    double rhs[3]{ 1.0,1.0,1.0 };    AMGX_vector_upload(b, nrow, 1, rhs);      /* solver setup */    AMGX_solver_setup(solver, A);    /* solver solve */    AMGX_solver_solve(solver, b, x);    AMGX_solver_get_status(solver, &status);    double xvalue[3];    AMGX_vector_download(x,xvalue);    for (int i = 0; i < 3; i++)    {        printf("x:% le\n ", xvalue[i]);    }    AMGX_solver_destroy(solver);    AMGX_vector_destroy(x);    AMGX_vector_destroy(b);    AMGX_matrix_destroy(A);    AMGX_resources_destroy(rsrc);    /* destroy config (need to use AMGX_SAFE_CALL after this point) */    AMGX_SAFE_CALL(AMGX_config_destroy(cfg));    /* shutdown and exit */    AMGX_SAFE_CALL(AMGX_finalize());    /* close the library (if it was dynamically loaded) */#ifdef AMGX_DYNAMIC_LOADING    amgx_libclose(lib_handle);#endif    return status;}

以上,即是Amgx的基础示例,代码的具体内容,主要包括提供一个CSR格式的稀疏矩阵,定义好对应的右端项和对应的Amgx库中的各种参数如config创建,求解模式选择等。通过该示例,实际上可以快速地移植到任意待求解的稀疏矩阵线性方程组中,要做的,仅仅是将矩阵输入数据和右端项进行替换。

这里提供的稀疏矩阵如下:
右端项的值均为1,最终运行结果如下:
 
 
当然,尽管Amgx提供了多种迭代方法和多重网格预处理器,同时每个求解方法中还包含多种参数的设置,比如其中一个config的json文件内容如下:
 
 
参数众多,要设置合适的参数以适应自己求解的问题,并不是件容易的事情。实际上,Amgx一开始主要就是用于CFD计算流体动力学和油气储层的模拟。对于固体力学有限元问题,想要求解高效率,可能还得不断尝试参数调整。
以上,即是本文的全部内容,感谢阅读!
【完】
 
来源:有限元术
ACTFluentSystem油气UMANSYS
著作权归作者所有,欢迎分享,未经许可,不得转载
首次发布时间:2025-08-23
最近编辑:1年前
寒江雪_123
硕士 | cae工程师 签名征集中
获赞 54粉丝 120文章 92课程 9
点赞
收藏
作者推荐

¥30 5.0
未登录
还没有评论
课程
培训
服务
行家
VIP会员 学习计划 福利任务
下载APP
联系我们
帮助与反馈