1. 介绍

Eigen是一个用C++实现的开源线性代数库。它速度快、适用性强,可以胜任从大规模的数值计算到简单的向量运算等任务。

1.1. 安装

在安装 MXMACA® 工具包时,会将Eigen的头文件复制到系统上的标准MXMACA包含目录中。MXMACA工具包的安装,参见 《曦云系列通用计算GPU快速上手指南》。安装后,确保设置了环境变量 MACA_PATH

export MACA_PATH=/opt/maca

这里的 /opt/maca 路径为MXMACA工具包的默认安装路径。如果安装时选择了其它路径,则将 MACA_PATH 设置为该路径。 以下是与mcEigen相关的头文件所在目录

#header location
${MACA_PATH}/include/eigen3/Eigen
${MACA_PATH}/include/eigen3/unsupported/Eigen

Eigen使用纯头文件库,也就是说所有源码都包含在头文件中。没有单独用于函数和类定义的 .cpp 文件——所有内容都在 .h 文件中。这就让使用Eigen变得非常简单。只需要简单的在自己代码的开头 #include 这些头文件就可以使用Eigen。

1.2. Eigen应用

要使用Eigen,您只需要在 Eigen 子目录和 unsupported/Eigen 子目录中包含头文件。如果您想启用GPU功能,需要在包含Eigen头文件之前定义一个宏EIGEN_USE_GPU。 如果目标平台是MXMACA,则在使用 mxcc 编译时还需要定义一个宏 EIGEN_USE_MACA。

以下是一个使用Eigen的简单示例:

// myEigenApp.cpp
#define EIGEN_USE_GPU
#define EIGEN_USE_MACA

#include <Eigen/Core>
#include <unsupported/Eigen/CXX11/Tensor>
#include <unsupported/Eigen/CXX11/src/Tensor/TensorGpuHipCudaDefines.h>

int main()
{
  constexpr int m_size = 128;
  constexpr int k_size = 128;
  constexpr int n_size = 128;
  std::cout << "Testing for (" << m_size << "," << k_size << "," << n_size << ")" << std::endl;
  Eigen::Tensor<float, 3, Eigen::ColMajor> t_input(m_size, k_size, n_size);
  Eigen::Tensor<float, 3, Eigen::ColMajor> t_result(m_size, k_size, n_size);
  Eigen::Tensor<float, 3, Eigen::ColMajor> t_result_gpu(m_size, k_size, n_size);

  t_input.setRandom();

  std::size_t t_input_bytes = t_input.size()  * sizeof(float);
  std::size_t t_result_bytes = t_result.size() * sizeof(float);

  float* d_t_input;
  float* d_t_result;

  gpuMalloc((void**)(&d_t_input), t_input_bytes);
  gpuMalloc((void**)(&d_t_result), t_result_bytes);

  gpuMemcpy(d_t_input, t_input.data(), t_input_bytes, gpuMemcpyHostToDevice);

  Eigen::GpuStreamDevice stream;
  Eigen::GpuDevice gpu_device(&stream);

  Eigen::TensorMap<Eigen::Tensor<float, 3, Eigen::ColMajor> >
      gpu_t_input(d_t_input, Eigen::array<int, 3>(m_size, k_size, n_size));
  Eigen::TensorMap<Eigen::Tensor<float, 3, Eigen::ColMajor> >
      gpu_t_result(d_t_result, Eigen::array<int, 3>(m_size, k_size, n_size));

  gpu_t_result.device(gpu_device) = gpu_t_input.cumsum(1);
  t_result = t_input.cumsum(1);

  bool ret = true;
  gpuMemcpy(t_result_gpu.data(), d_t_result, t_result_bytes, gpuMemcpyDeviceToHost);
  for (Eigen::DenseIndex i = 0; i < t_result.size(); i++) {
    if (fabs(t_result(i) - t_result_gpu(i)) < 1e-4f) {
      continue;
    }
    if (Eigen::internal::isApprox(t_result(i), t_result_gpu(i), 1e-4f)) {
      continue;
    }
    ret = false;
    std::cout << "mismatch detected at index " << i << ": " << t_result(i)
              << " vs " <<  t_result_gpu(i) << std::endl;
  }
  if (ret) {
    std::cout << "the result is correct" << std::endl;
  } else {
    std::cout << "the result is mismatched" << std::endl;
  }
  gpuFree((void*)d_t_input);
  gpuFree((void*)d_t_result);

  return 0;
}

假设上面的示例包含在名为myEigenApp.cpp的文件中,您可以在Linux上使用以下命令进行编译:

export MACA_PATH=/opt/maca
export MACA_CLANG_PATH=${MACA_PATH}/mxgpu_llvm/bin
export LD_LIBRARY_PATH=${MACA_PATH}/lib:${LD_LIBRARY_PATH}

${MACA_CLANG_PATH}/mxcc -x maca myEigenApp.cpp -o myEigenApp -I ${MACA_PATH}/include/eigen3

这里的 /opt/maca 路径为MXMACA工具包的默认安装路径。如果安装时选择了其它路径,则将 MACA_PATH 设置为该路径。

1.3. 工程项目中使用mcEigen

以下是一个CMakeLists.txt的简单示例,用于编译上面的myEigenApp.cpp。

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(example)

# maca toolkits path
set(MACA_PATH $ENV{MACA_PATH})
set(CMAKE_C_COMPILER ${MACA_PATH}/mxgpu_llvm/bin/mxcc)
set(CMAKE_CXX_COMPILER ${MACA_PATH}/mxgpu_llvm/bin/mxcc)

# eigen path in maca toolkits
set(eigen_src_dir ${MACA_PATH}/include/eigen3)

set(example_target myEigenApp)
set(example_src myEigenApp.cpp)

add_compile_options( -x maca -fPIC
                    -DEIGEN_USE_GPU
                    -DEIGEN_USE_MACA
                    --maca-device-lib-path=${MACA_PATH}/lib
                    --maca-device-lib=maca_mathlib.bc
                    --maca-device-lib=maca_kernellib.bc
                    --maca-host-lib-path=${MACA_PATH}/lib
                    --maca-host-lib=maca_mathlib_host.bc
)

add_executable(${example_target} "${example_src}")
target_include_directories(${example_target} PRIVATE ${eigen_src_dir})

把myEigenApp.cpp和CMakeLists.txt放在同一目录下,然后执行命令:

export MACA_PATH=/opt/maca

mkdir build
cmake -B build .
cmake --build build -j

这里的 /opt/maca 路径为MXMACA工具包的默认安装路径。如果安装时选择了其它路径,则将 MACA_PATH 设置为该路径。 编译结束后,二进制文件myEigenApp就会出现在build文件夹中。

2. 使用Eigen API

2.1. 模块以及头文件

Eigen库可以分为一个Core模块和几个其他模块。每个模块都有一个相对应的头文件,只有包含对应的头文件才能使用对应的模块。为方便同时访问多个模块,我们提供了Dense和Eigen头文件。 Eigen头文件的内容如下:

  • Core: 包含矩阵和数组类,基本线性代数(包括三角函数和自伴乘积相关),以及相关数组操作

  • Geometry:包括变换,平移、缩放、二维旋转、三维旋转(四元数,角轴相关)

  • LU: 有关求逆、求行列式、LU分解解算器(FullPivLU, PartialPivLU)

  • Cholesky: 包含LLT和LDLT的Cholesky因式分解法

  • Householder: Householder变换;这个模块供几何线性代数模块使用

  • SVD: SVD奇异值分解,最小二乘解算器解决奇异值分解(JacobiSVD, BDCSVD)

  • QR: QR分解解算器(HouseholderQR, ColPivHouseholderQR, FullPivHouseholderQR)

  • Eigenvalues: 特征值和特征向量分解(EigenSolver, SelfAdjpintEigenSolver, ComplexEigenSolver)

  • Sparse: 稀疏矩阵存储以及相关线性代数(SparseMatrix, SparseVector)

  • Dense: 包含 Core, Geometry, LU, Cholesky, SVD, QR和Eigenvalues模块的头文件

  • Eigen: 包含 Dense和Sparse的头文件(上述所有模块)