开发者
我要评分
获取效率
正确性
完整性
易理解
在线提单
论坛求助

Eigen库使用KML

Eigen库介绍

Eigen库是一个用于线性代数运算的C++模板库。它提供了高性能的矩阵运算和线性代数操作,被广泛应用于科学计算、机器学习和计算机图形学等领域。详细介绍可参考Eigen官网。Eigen库官方提供了使能开源BLAS以及LAPACK的方式,同样可以使用到KML中的BLAS与LAPACK库,使用说明详见 Using BLAS/LAPACK from Eigen ,使用外部BLAS为Eigen提供了更高性能的矩阵运算接口。除了标准的BLAS、LAPACK接口,鲲鹏数学库提供了额外的BLAS接口供Eigen使用,详细接口信息请见表1

Eigen库KML优化适配

KML库在 EIGEN_USE_BLAS 机制基础上,进一步扩展并优化了多类Eigen接口,以提升应用场景下的整体性能。优化后的Eigen版本获取地址:kunpeng-eigen

对于矩阵加法等基础算子,其使用方式与原生 Eigen 接口保持一致,仅需在编译阶段将宏定义 -DEIGEN_USE_BLAS 替换为 -DKUNPENG_USE 即可启用相应优化路径。针对其他已优化接口的具体使用方法以及验证流程,将在后续章节中进行详细说明。

下表给出了当前支持优化的 Eigen 功能范围。

表1 Eigen库KML优化接口说明

接口名称

示例用法

适用数据类型

KML优化接口或方式

矩阵加法

  1. C = A + B
  2. C += A

float

double

std::complex<float>

std::complex<double>

?omatadd

?imatadd

矩阵减法

  1. C = A - B
  2. C -= A

float

double

std::complex<float>

std::complex<double>

?omatsub

?imatsub

逐元素乘

C = A.cwiseProduct(B)

float

double

std::complex<float>

std::complex<double>

?omatmul

按行/列求和

v = A.rowwise().sum()

v = A.colwise().sum()

float

double

std::complex<float>

std::complex<double>

?gemv

连续子块拷贝

B = A.block(100, 200, 200, 300);

float

double

std::complex<float>

std::complex<double>

?copy

逐元素拷贝

B = A.array()

float

double

std::complex<float>

std::complex<double>

?copy

激活函数

B = A.tanh()

float

SVE intrinsic kernel

张量收缩

C=A.contract(B,dims)

2d float

sgemm

Eigen库tensor.contract使能KML

针对Eigen中的TensorContraction模块,在数据类型为float的二维张量收缩计算时,保持Eigen的多线程调度框架(ThreadPool),替换底层算子,使能KML加速。

  1. 获取优化后Eigen库。下载地址:kunpeng-eigen
  2. 进入eigen/eigen_blas目录下,此处为Eigen对接KML适配层,独立编译,以二进制方式连接到Eigen,编译命令:
    clang -shared -fPIC -o libeigen_blas.so eigen_blas.c -L${PATH_TO_KBLAS} -lkblas

    其中,PATH_TO_KBLAS需替换成对应的KML动态库路径,编译完成后生成libeigen_blas.so动态库。

  3. Eigen根目录下,设置环境变量OMP_NUM_THREADS=1,KUNPENG_OPT_CONTRACT=1,KUNPENG_CONTRACT_NUM_THREADS=目标线程数,再执行编译命令。

    示例代码matmul_test.cpp:

    #include <iostream>
    #include <chrono>
    #include <Eigen/Core>
    #include <unsupported/Eigen/CXX11/Tensor>
    #include <unsupported/Eigen/CXX11/ThreadPool>
    using namespace Eigen;
    int main() {
        int M = 5000, N = 1024, K = 1024;
        Tensor<float, 2, RowMajor> t_a(M, K);
        Tensor<float, 2, RowMajor> t_b(K, N);
        Tensor<float, 2, RowMajor> t_c(M, N);
        t_a.setRandom();
        t_b.setRandom();
        Eigen::array<Eigen::IndexPair<int>, 1> product_dims = { Eigen::IndexPair<int>(1, 0) };
        int num_threads = 4;
        Eigen::ThreadPool pool(num_threads);
        Eigen::ThreadPoolDevice device(&pool, num_threads);
        unsetenv("KUNPENG_OPT_CONTRACT"); 
      
        t_c.device(device) = t_a.contract(t_b, product_dims);
        auto start_ori = std::chrono::high_resolution_clock::now();
        t_c.device(device) = t_a.contract(t_b, product_dims);
        auto end_ori = std::chrono::high_resolution_clock::now();
        auto dur_ori = std::chrono::duration_cast<std::chrono::microseconds>(end_ori - start_ori).count();
        setenv("KUNPENG_OPT_CONTRACT", "1", 1);
        setenv("KUNPENG_CONTRACT_NUM_THREADS", "4", 1);
        t_c.device(device) = t_a.contract(t_b, product_dims);
        auto start_opt = std::chrono::high_resolution_clock::now();
        t_c.device(device) = t_a.contract(t_b, product_dims);
        auto end_opt = std::chrono::high_resolution_clock::now();
        
        auto dur_opt = std::chrono::duration_cast<std::chrono::microseconds>(end_opt - start_opt).count();
        std::cout << "--- MatMul (Contraction) KML Optimization Test ---" << std::endl;
        std::cout << "Scale          : " << M << "x" << N << "x" << K << std::endl;
        std::cout << "Original Time  : " << dur_ori << " us" << std::endl;
        std::cout << "Optimized Time : " << dur_opt << " us" << std::endl;
        std::cout << "Speedup        : " << (float)dur_ori / dur_opt << "x" << std::endl;
        return 0;
    }

    编译命令:

    clang++ matmul_test.cpp -I ./eigen -I ./eigen/eigen_blas -o matmul_test -march=armv9-a+sve -DEIGEN_USE_THREADS -L ./eigen/eigen_blas -leigen_blas

    成功输出如下所示:

    --- MatMul (Contraction) KML Optimization Test ---
    Scale          : 1024x1024x1024
    Original Time  : 2131122 us
    Optimized Time : 5400 us
    Speedup        : 394.652x

Eigen库tanh优化

在ARM架构上,Eigen tanh函数默认使用NEON指令集进行向量化处理。相比之下,SVE(可伸缩矢量扩展)支持128位到2048位的可变长度向量,可以一次性处理更多的tanh计算来提升性能。因此,在tanh的计算过程中,针对float数据类型,增加SVE intrinsic优化实现,以提升计算效率。

  1. 获取优化后Eigen库。下载地址:kunpeng-eigen
  2. 进入eigen根目录下,编译以下示例测试代码:
    tanh_test.cpp
    #include <iostream>
    #include <chrono>
    #include <vector>
    #include <Eigen/Core>
    #include <unsupported/Eigen/CXX11/Tensor>
    #include <unsupported/Eigen/CXX11/ThreadPool>
    using namespace Eigen;
    int main() {
        int M = 500, N = 500;
        int size = M * N;
        std::vector<float> input_raw(size);
        std::vector<float> output_raw(size);
        for(int i = 0; i < size; ++i) input_raw[i] = static_cast<float>(rand());
        TensorMap<Tensor<float, 2, RowMajor>> input_map(input_raw.data(), M, N);
        TensorMap<Tensor<float, 2, RowMajor>> output_map(output_raw.data(), M, N);
        Eigen::ThreadPool pool(1);
        Eigen::ThreadPoolDevice device(&pool, 1);
        auto start_ori = std::chrono::high_resolution_clock::now();
        output_map.device(device) = (1.0f * input_map).tanh();
        auto end_ori = std::chrono::high_resolution_clock::now();
        auto dur_ori = std::chrono::duration_cast<std::chrono::microseconds>(end_ori - start_ori).count();
        auto start_opt = std::chrono::high_resolution_clock::now();
        output_map.device(device) = input_map.tanh();
        auto end_opt = std::chrono::high_resolution_clock::now();
        auto dur_opt = std::chrono::duration_cast<std::chrono::microseconds>(end_opt - start_opt).count();
        std::cout << "--- Tanh SVE Optimization ---" << std::endl;
        std::cout << "Original Time  : " << dur_ori << " us" << std::endl;
        std::cout << "Optimized Time : " << dur_opt << " us" << std::endl;
        std::cout << "Speedup        : " << (float)dur_ori / dur_opt << "x" << std::endl;
        return 0;
    }
    编译命令:
    clang++ test_tanh.cpp -I ./eigen -o test_tanh -march=armv9-a+sve -I ./eigen/eigen_blas -DEIGEN_USE_THREADS

    执行后输出如下所示:

    --- Tanh SVE Optimization ---
    Original Time  : 43847 us
    Optimized Time : 3987 us
    Speedup        : 10.9975x