起因

公司内部需要实现一个量子算法(量子真机),需要使用对称矩阵的矩阵特征值与特征向量的分解算法,我尝试了go语言原生包:gonum,实测边长为1000的矩阵需要的时间为10分钟以上,于是开始寻求c语言或者c++编写的并行算法,首先尝试了OpenBlas,对接用了差不多一星期(因为我不熟悉cgo,太菜),实测边长为10000的矩阵求解时间是两个半小时,只测了一次我就知道不行,因为团队里的另一位工程师用matlab只需要一分钟左右就可以求解相同的矩阵,于是转变思路开始集成cuda的算法,尝试使用gpu进行求解。

算子

cuda算子在windows系统上比较恶心,它对于cuda版本有严格的要求(我用的版本是cuda12.8版本,为了编译时能够兼容更多的版本,所以只能用最新版cuda),不过幸好还有visual studio可以救一手,下面直接贴cuda代码:

//Kernel.cu
#include "Kernel.cuh"
#include <stdio.h>

extern "C" __declspec(dllexport) int computeEigen(float* h_matrix, float* h_eigenvalues, float* h_eigenvectors, int n) {
    cusolverDnHandle_t handle;
    cusolverStatus_t status = CUSOLVER_STATUS_SUCCESS;
    cudaError_t cudaStat;

    // 创建cuSOLVER句柄
    status = cusolverDnCreate(&handle);
    if (status != CUSOLVER_STATUS_SUCCESS) return -1;

    // 分配设备内存
    float* d_matrix, * d_eigenvalues, * d_work;
    int* d_info, lwork;
    cudaStat = cudaMalloc((void**)&d_matrix, n * n * sizeof(float));
    cudaStat = cudaMalloc((void**)&d_eigenvalues, n * sizeof(float));
    cudaStat = cudaMalloc((void**)&d_info, sizeof(int));
    if (cudaStat != cudaSuccess) { cusolverDnDestroy(handle); return -2; }

    // 数据拷贝到设备
    cudaMemcpy(d_matrix, h_matrix, n * n * sizeof(float), cudaMemcpyHostToDevice);

    // 计算工作空间大小
    status = cusolverDnSsyevd_bufferSize(handle, CUSOLVER_EIG_MODE_VECTOR, CUBLAS_FILL_MODE_LOWER,
        n, d_matrix, n, d_eigenvalues, &lwork);
    if (status != CUSOLVER_STATUS_SUCCESS) { cudaFree(d_matrix); cusolverDnDestroy(handle); return -3; }

    // 分配工作空间
    cudaStat = cudaMalloc((void**)&d_work, lwork * sizeof(float));
    if (cudaStat != cudaSuccess) { cudaFree(d_matrix); cusolverDnDestroy(handle); return -4; }

    // 执行特征分解
    status = cusolverDnSsyevd(handle, CUSOLVER_EIG_MODE_VECTOR, CUBLAS_FILL_MODE_LOWER,
        n, d_matrix, n, d_eigenvalues, d_work, lwork, d_info);

    // 同步设备并检查结果
    int info;
    cudaMemcpy(&info, d_info, sizeof(int), cudaMemcpyDeviceToHost);
    if (info != 0 || status != CUSOLVER_STATUS_SUCCESS) {
        cudaFree(d_work); cudaFree(d_matrix); cusolverDnDestroy(handle);
        return (info < 0) ? -5 : -6; // 参数错误或收敛失败
    }

    // 拷贝结果回主机
    cudaMemcpy(h_eigenvalues, d_eigenvalues, n * sizeof(float), cudaMemcpyDeviceToHost);
    cudaMemcpy(h_eigenvectors, d_matrix, n * n * sizeof(float), cudaMemcpyDeviceToHost);

    // 释放资源
    cudaFree(d_matrix);
    cudaFree(d_eigenvalues);
    cudaFree(d_work);
    cudaFree(d_info);
    cusolverDnDestroy(handle);

    return 0;
}

cuh头文件:

//Kernel.cuh
#pragma once
#include "cuda_runtime.h"
#include "cusolverDn.h"

extern "C" __declspec(dllexport) int computeEigen(float* h_matrix, float* h_eigenvalues, float* h_eigenvectors, int n);

编译

  1. 在vs中创建一个cuda项目
    image
  2. 将刚才的Kernel.cu代码直接粘贴到项目生成的Kernel.cu文件中,直接全部覆盖,如果有需要集成其他cuda算法的请找deepseek,让它给你生成个调用函数(乐)
    image
  3. 创建一个.cuh文件,然后把你引用到的包引用上,千万不要用尖括号的方式引用,容易出问题(也可能是我太菜~)。
    image
    image
  4. 这一步非常重要,cuda自带的模板只会给你引入个runtime.lib库,编译的时候直接识别不到,除非你编译的是exe,exe是可以直接引用环境变量中的runtime.dll的,所以不会出异常,但我的最终目的是让其他语言引用,所以不可能使用exe的。所以首先,右键点击你项目的名字->单击属性->单击常规->配置类型选择dll
    image
    image
    然后单击链接器->常规->附加库目录,添加自己的cuda toolkit的lib\x64或者lib\W32路径(其实我估计这一步不搞也行,模版里应该是自带了,只是你自己设置了之后原先的默认值会消失)
    image
    再然后,单击链接器下的输入->附加依赖项->编辑,把要引用的库加上,不知道自己引用哪个库的就看看引用的头文件,文件后半段有可能不一样,需要自己在cuda tookit的lib文件夹下面多找找,实在不行把.lib文件全加上肯定不出错(玩的就是个暴力)。
    image
  5. 设置完一定要点击应用再点击确定,要不然vs一关它再给你回退设置你就笑了。
  6. 接下来就简单了,右键解决方案'xxxxxx',点击生成解决方案,然后到项目下面有个x64文件夹(看你配置,上面工具栏有运行图标、Release/DeBug、编译平台等设置,默认应该是Debug和x64),往里面翻一翻就能看到dll。
  7. 然后就简单了,把你dll所在的那个文件夹扔到你的go语言项目目录下面,然后该怎么引用就怎么引用就完事了。

go语言编写

只有在函数前加上extern "C" __declspec(dllexport)才能在dll中导出函数,我第一次成功导出dll的时候试了半天报错0x0000005,用dumpbin /exports XXX.dll才知道是导出有问题(C++也没学明白~)。
接下来是go语言代码,不使用cgo,因为cuh文件无法被nvcc编译成.h文件,所以无法使用cgo进行引用。

//main.go
package main
import (
	"fmt"
	"runtime"
	"syscall"
	"unsafe"
)

func main() {
	runtime.LockOSThread()
	dllPath := "./libs/Cuda2.dll"
	handle, err := syscall.LoadDLL(dllPath)
	if err != nil {
		fmt.Println(err.Error())
	}
	proc, err := handle.FindProc("computeEigen")
	if err != nil {
		fmt.Println(err.Error())
	}
	callByReference := proc
	const size = 10240
	blockSize := size / 2
	matrix := make([]int, size*size) // 一维切片(列优先存储)

	// 生成分块对角矩阵
	for col := 0; col < size; col++ {
		for row := 0; row < size; row++ {
			// 判断是否属于左上或右下的 5x5 全1块
			if (row < blockSize && col < blockSize) || (row >= blockSize && col >= blockSize) {
				matrix[col*size+row] = 1 // 列优先索引公式
			} else {
				matrix[col*size+row] = 0
			}
		}
	}
	r1 := make([]float32, size*size)
	r2 := make([]float32, size*size)

	i1 := uintptr(unsafe.Pointer(&matrix[0]))
	r11 := uintptr(unsafe.Pointer(&r1[0]))
	r22 := uintptr(unsafe.Pointer(&r2[0]))
	_, _, err = callByReference.Call(
		i1,
		r11,
		r22,
		size)
	if err != nil {
		fmt.Println(err.Error())
	}
	fmt.Println(r1[0])
	fmt.Println(r2[0])
}

至此,go语言集成cuda完毕。

 posted on 2025-04-12 01:50  vctrey  阅读(169)  评论(0)    收藏  举报