当前位置: 首页 > 知识库问答 >
问题:

用jCUDA对复矩阵进行运算

齐文林
2023-03-14

使用JCUDA对复数进行运算的最佳方法是什么?我应该使用cuComplex格式还是有其他的解决方案(像一个数组,实部和虚部一个接着一个走)?我非常感谢使用这种类型的计算的java代码示例。

由于我的目的是用GPU求解复杂的线性方程组,所以我不想只附上jCuda。用GPU进行这样的计算有哪些可供选择的方式?

共有1个答案

邬良才
2023-03-14

首先,关于您关于在GPU上使用Java进行计算的问题,我在这里写了几句话。

你的申请案例似乎很具体。您可能应该更详细地描述您的实际意图是什么,因为这将支配所有的设计决策。到现在为止,我只能给出一些基本的提示。决定哪一个是最合适的解决方案是由您自己决定的。

在Java世界和GPU世界之间架桥时的一个主要困难是根本不同的内存处理。

CUDA中的CUComplex结构定义为

typedef float2 cuFloatComplex
typedef cuFloatComplex cuComplex;

其中float2基本上类似于

struct float2 {
    float x; 
    float y; 
};

(带有一些用于对齐等的附加说明符)

现在,当您在C/C++程序中分配CUComplex值的数组时,您只需编写如下内容

cuComplex *c = new cuComplex[100];

在这种情况下,可以保证所有这些CuComplex值的内存将是一个单一的、连续的内存块。此内存块仅由复数的所有xy值组成,这些值一个接一个:

      _____________________________
c -> | x0 | y0 | x1 | y1 | x2 | y2 |... 
     |____|____|____|____|____|____|

这个连续的内存块可以很容易地复制到设备:一个获取指针,并调用类似于

cudaMemcpy(device, c, sizeof(cuComplex)*n, cudaMemcpyHostToDevice);

考虑以下情况:创建一个结构上等于CUComplex结构的Java类,并分配一个数组:

 class cuComplex {
     public float x;
     public float y;
 }

 cuComplex c[] = new cuComplex[100];

则没有float值的单个连续内存块。相反,您有一个数组对CUComplex对象的引用,相应的xy值分散在周围:

      ____________________
c -> |  c0  |  c1  |  c2  |... 
     |______|______|______|
        |       |      |
        v       v      v
      [x,y]   [x,y]  [x,y]

这里的重点是:

不能将CuComplex对象的(Java)数组复制到设备!

这有几个含义。在注释中,您已经提到了CublassetVector方法,该方法将CUComplex数组作为参数,我试图强调这不是最有效的解决方案,只是为了方便。实际上,此方法的工作方式是在内部创建一个新的ByteBuffer,以便拥有一个连续的内存块,然后用CUComplex[]数组中的值填充此ByteBuffer,然后将此ByteBuffer复制到设备。

当然,这会带来一个开销,而在性能关键的应用程序中,您很可能希望避免这种开销。

解决这个问题有几种选择。幸运的是,对于复数,解决方法比较简单:

不要使用cucomplex结构来表示复数的数组

如果我们试图将其泛化,不仅指复数,而且指一般的“结构”,那么就有一种“模式”可以应用:我们可以为结构创建接口,并创建这些结构的集合,该集合是实现该接口的类的实例列表,所有这些实例都由一个连续的内存块支持。这可能适用于某些情况。但是对于复数,为每个复数设置一个Java对象的内存开销可能会大得吓人。

另一个极端,仅处理原始的float[]double[]数组也可能不是最佳解决方案。例如:如果您有一个数组表示复数的float值,那么如何将其中一个复数与另一个复数相乘呢?

一个“中间”解决方案可以创建一个接口,允许访问复数的实部和虚部。在实现中,这些复数存储在单个数组中,如上所述。

我在这里勾画了这样一个实现。

这只是作为一个示例,展示基本思想,并展示它如何与jCublas这样的东西协同工作。对您来说,一个不同的策略可能更合适,这取决于您的实际目标:应该有哪些其他后端(除了JCuda)?在Java端处理复数应该有多“方便”?在Java端处理复数的结构(类/接口)应该是什么样子的?

简而言之:在继续实现之前,您应该对您的应用程序/库应该能够做什么有一个非常清楚的想法。

import static jcuda.jcublas.JCublas2.*;
import static jcuda.jcublas.cublasOperation.CUBLAS_OP_N;
import static jcuda.runtime.JCuda.*;

import java.util.Random;

import jcuda.*;
import jcuda.jcublas.cublasHandle;
import jcuda.runtime.cudaMemcpyKind;

// An interface describing an array of complex numbers, residing
// on the host, with methods for accessing the real and imaginary
// parts of the complex numbers, as well as methods for copying
// the underlying data from and to the device
interface cuComplexHostArray
{
    int size();

    float getReal(int i);
    float getImag(int i);

    void setReal(int i, float real);
    void setImag(int i, float imag);

    void set(int i, cuComplex c);
    void set(int i, float real, float imag);

    cuComplex get(int i, cuComplex c);

    void copyToDevice(Pointer devicePointer);
    void copyFromDevice(Pointer devicePointer);
}

// A default implementathtml" target="_blank">ion of a cuComplexHostArray, backed
// by a single float[] array
class DefaultCuComplexHostArray implements cuComplexHostArray
{
    private final int size;
    private final float data[];

    DefaultCuComplexHostArray(int size)
    {
        this.size = size;
        this.data = new float[size * 2];
    }

    @Override
    public int size()
    {
        return size;
    }

    @Override
    public float getReal(int i)
    {
        return data[i+i];
    }

    @Override
    public float getImag(int i)
    {
        return data[i+i+1];
    }

    @Override
    public void setReal(int i, float real)
    {
        data[i+i] = real;
    }

    @Override
    public void setImag(int i, float imag)
    {
        data[i+i+1] = imag;
    }

    @Override
    public void set(int i, cuComplex c)
    {
        data[i+i+0] = c.x;
        data[i+i+1] = c.y;
    }

    @Override
    public void set(int i, float real, float imag)
    {
        data[i+i+0] = real;
        data[i+i+1] = imag;
    }

    @Override
    public cuComplex get(int i, cuComplex c)
    {
        float real = getReal(i);
        float imag = getImag(i);
        if (c != null)
        {
            c.x = real;
            c.y = imag;
            return c;
        }
        return cuComplex.cuCmplx(real, imag);
    }

    @Override
    public void copyToDevice(Pointer devicePointer)
    {
        cudaMemcpy(devicePointer, Pointer.to(data),
            size * Sizeof.FLOAT * 2,
            cudaMemcpyKind.cudaMemcpyHostToDevice);
    }

    @Override
    public void copyFromDevice(Pointer devicePointer)
    {
        cudaMemcpy(Pointer.to(data), devicePointer,
            size * Sizeof.FLOAT * 2,
            cudaMemcpyKind.cudaMemcpyDeviceToHost);
    }
}

// An example that performs a "gemm" with complex numbers, once
// in Java and once in JCublas2, and verifies the result
public class JCublas2ComplexSample
{
    public static void main(String args[])
    {
        testCgemm(500);
    }

    public static void testCgemm(int n)
    {
        cuComplex alpha = cuComplex.cuCmplx(0.3f, 0.2f);
        cuComplex beta  = cuComplex.cuCmplx(0.1f, 0.7f);
        int nn = n * n;

        System.out.println("Creating input data...");
        Random random = new Random(0);
        cuComplex[] rhA = createRandomComplexRawArray(nn, random);
        cuComplex[] rhB = createRandomComplexRawArray(nn, random);
        cuComplex[] rhC = createRandomComplexRawArray(nn, random);

        random = new Random(0);
        cuComplexHostArray hA = createRandomComplexHostArray(nn, random);
        cuComplexHostArray hB = createRandomComplexHostArray(nn, random);
        cuComplexHostArray hC = createRandomComplexHostArray(nn, random);

        System.out.println("Performing Cgemm with Java...");
        cgemmJava(n, alpha, rhA, rhB, beta, rhC);

        System.out.println("Performing Cgemm with JCublas...");
        cgemmJCublas(n, alpha, hA, hB, beta, hC);

        boolean passed = isCorrectResult(hC, rhC);
        System.out.println("testCgemm "+(passed?"PASSED":"FAILED"));
    }

    private static void cgemmJCublas(
        int n,
        cuComplex alpha,
        cuComplexHostArray A,
        cuComplexHostArray B,
        cuComplex beta,
        cuComplexHostArray C)
    {
        int nn = n * n;

        // Create a CUBLAS handle
        cublasHandle handle = new cublasHandle();
        cublasCreate(handle);

        // Allocate memory on the device
        Pointer dA = new Pointer();
        Pointer dB = new Pointer();
        Pointer dC = new Pointer();
        cudaMalloc(dA, nn * Sizeof.FLOAT * 2);
        cudaMalloc(dB, nn * Sizeof.FLOAT * 2);
        cudaMalloc(dC, nn * Sizeof.FLOAT * 2);

        // Copy the memory from the host to the device
        A.copyToDevice(dA);
        B.copyToDevice(dB);
        C.copyToDevice(dC);

        // Execute cgemm
        Pointer pAlpha = Pointer.to(new float[]{alpha.x, alpha.y});
        Pointer pBeta = Pointer.to(new float[]{beta.x, beta.y});
        cublasCgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, n, n, n,
            pAlpha, dA, n, dB, n, pBeta, dC, n);

        // Copy the result from the device to the host
        C.copyFromDevice(dC);

        // Clean up
        cudaFree(dA);
        cudaFree(dB);
        cudaFree(dC);
        cublasDestroy(handle);
    }

    private static void cgemmJava(
        int n,
        cuComplex alpha,
        cuComplex A[],
        cuComplex B[],
        cuComplex beta,
        cuComplex C[])
    {
        for (int i = 0; i < n; ++i)
        {
            for (int j = 0; j < n; ++j)
            {
                cuComplex prod = cuComplex.cuCmplx(0, 0);
                for (int k = 0; k < n; ++k)
                {
                    cuComplex ab =
                        cuComplex.cuCmul(A[k * n + i], B[j * n + k]);
                    prod = cuComplex.cuCadd(prod, ab);
                }
                cuComplex ap = cuComplex.cuCmul(alpha, prod);
                cuComplex bc = cuComplex.cuCmul(beta, C[j * n + i]);
                C[j * n + i] = cuComplex.cuCadd(ap, bc);
            }
        }
    }

    private static cuComplex[] createRandomComplexRawArray(
        int n, Random random)
    {
        cuComplex c[] = new cuComplex[n];
        for (int i = 0; i < n; i++)
        {
            float real = random.nextFloat();
            float imag = random.nextFloat();
            c[i] = cuComplex.cuCmplx(real, imag);
        }
        return c;
    }

    private static cuComplexHostArray createRandomComplexHostArray(
        int n, Random random)
    {
        cuComplexHostArray c = new DefaultCuComplexHostArray(n);
        for (int i = 0; i < n; i++)
        {
            float real = random.nextFloat();
            float imag = random.nextFloat();
            c.setReal(i, real);
            c.setImag(i, imag);
        }
        return c;
    }

    private static boolean isCorrectResult(
        cuComplexHostArray result, cuComplex reference[])
    {
        float errorNormX = 0;
        float errorNormY = 0;
        float refNormX = 0;
        float refNormY = 0;
        for (int i = 0; i < result.size(); i++)
        {
            float diffX = reference[i].x - result.getReal(i);
            float diffY = reference[i].y - result.getImag(i);
            errorNormX += diffX * diffX;
            errorNormY += diffY * diffY;
            refNormX += reference[i].x * result.getReal(i);
            refNormY += reference[i].y * result.getImag(i);
        }
        errorNormX = (float) Math.sqrt(errorNormX);
        errorNormY = (float) Math.sqrt(errorNormY);
        refNormX = (float) Math.sqrt(refNormX);
        refNormY = (float) Math.sqrt(refNormY);
        if (Math.abs(refNormX) < 1e-6)
        {
            return false;
        }
        if (Math.abs(refNormY) < 1e-6)
        {
            return false;
        }
        return
            (errorNormX / refNormX < 1e-6f) &&
            (errorNormY / refNormY < 1e-6f);
    }
}

(顺便说一句:我可能会将这个答案的一部分进行扩展,使之成为JCUDA的示例和/或“如何...”页面。提供类似这样的信息的任务已经在我的“待办事项”列表上很久了)。

 类似资料:
  • 本文向大家介绍python如何进行矩阵运算,包括了python如何进行矩阵运算的使用技巧和注意事项,需要的朋友参考一下 python进行矩阵运算的方法: 1、矩阵相乘 2、矩阵对应元素相乘 multiply()函数:数组和矩阵对应位置相乘,输出与相乘数组/矩阵的大小一致 3、矩阵点乘 4、矩阵求逆 5、矩阵转置 6、计算每一列、行的和 内容扩展: numpy矩阵运算 (1) 矩阵点乘:m=mult

  • 本文向大家介绍Python常用库Numpy进行矩阵运算详解,包括了Python常用库Numpy进行矩阵运算详解的使用技巧和注意事项,需要的朋友参考一下 Numpy支持大量的维度数组和矩阵运算,对数组运算提供了大量的数学函数库! Numpy比Python列表更具优势,其中一个优势便是速度。在对大型数组执行操作时,Numpy的速度比Python列表的速度快了好几百。因为Numpy数组本身能节省内存,并

  • 我目前正在做一个音频信号处理项目,需要在Java中的一个复杂矩阵上使用SVD。我当前的线性代数库是Apache Commons。但它只提供实矩阵的SVD,JAMA、JBLAS、EJML、ojAlgo都不支持复杂的SVD。 我一直在用一些技巧从一个等效的实矩阵中找到SVD。然而,当我重建矩阵时,这种技术对于虚部有很大的不准确性。

  • 我有两个列表,每个列表中有两个矩阵。。是否有一种方法可以对它们进行矩阵计算,即相加,其中matrix1中的蓝色矩阵与matrix2中的蓝色矩阵相加,matrix1中的红色矩阵与matrix2中的红色矩阵相加。我能想到的唯一方法是在循环中进行计算 请注意,我将有大约10个,以及不止一组(即蓝色、红色、绿色、紫色)

  • 问题内容: 我正在研究信号分类问题,想先缩放数据集矩阵,但是我的数据是3D格式(批,长度,通道)。 我尝试使用Scikit-learn Standard Scaler: 但是我收到了以下错误消息: 找到具有暗3的数组。StandardScaler预期<= 2 我认为一种解决方案是将每个通道的矩阵分成多个2D矩阵,分别缩放比例,然后放回3D格式,但我想知道是否有更好的解决方案。 非常感谢你。 问题答

  • 下面是二进制矩阵最大面积的代码。它有一个函数MAH()来返回直方图的最大面积。方法是将二进制矩阵(2d)分解为一维。然后将MAH()应用于每个一维数组并找到最大面积。 类解决方案{ 问题链接:二进制矩阵的最大面积 MAH()函数是正确的。仅maximalRectangle(char[][]矩阵)函数无法给出结果。有人能解释一下吗?? 这是另一个人发布的maximalRectangle(char[]