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 功能范围。
接口名称 |
示例用法 |
适用数据类型 |
KML优化接口或方式 |
|---|---|---|---|
矩阵加法 |
|
float double std::complex<float> std::complex<double> |
?omatadd ?imatadd |
矩阵减法 |
|
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加速。
- 获取优化后Eigen库。下载地址:kunpeng-eigen。
- 进入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动态库。
- 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优化实现,以提升计算效率。
- 获取优化后Eigen库。下载地址:kunpeng-eigen。
- 进入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