如何调用 BLAS?

解读

面试官问“如何调用 BLAS”,并不是想听“Cargo add 某个包”就结束。国内工程团队普遍面临 离线编译、国产 CPU(鲲鹏、飞腾、龙芯)适配、国产操作系统(麒麟、统信 UOS)兼容、性能可验证、可审计源码 等硬性要求。因此,回答必须覆盖:

  1. 如何在 Cargo 生态里精准选型(动态/静态、Netlib/OpenBLAS/Intel MKL/BLIS/华为 KML);
  2. 如何保证跨平台编译通过(x86_64、aarch64、龙芯 loongarch64);
  3. 如何在 Rust 侧零开销调用(unsafe 边界、layout、ABI、线程数控制);
  4. 如何用 cargo 特性开关实现“同一套源码、不同硬件自动切换最优实现”;
  5. 如何写单元测试 + benchmark,用 持续集成(gitee/go、gitlab-ci、jenkins) 给出可量化的 GFLOPS 对比报告,让面试官一眼看到“你不仅调通了,还知道它有多快”。

知识点

  • BLAS C ABI 约定:行主序/列主序、指针别名规则、int 位宽(LP64 vs ILP64)。
  • Rust 外部块 #[link]build.rs 脚本,pkg-configvcpkg 两种找库路径的国内镜像方案。
  • ndarray-linalg、blas-src、lapack-src 三件套特性开关:system、openblas、intel-mkl、blis、accelerate。
  • 交叉编译工具链aarch64-unknown-linux-gnuloongarch64-unknown-linux-gnu,以及华为鲲鹏 KML 的闭包符号前缀。
  • Rust 调用约定extern "C"#[repr(C)]const/mut 与 BLAS 的 const double* 映射**。
  • 运行时线程并发OPENBLAS_NUM_THREADSMKL_NUM_THREADSRayon 的协同。
  • 性能审计cargo bench + criterion 给出 GFLOPSperf stat + cargo flamegraph 定位热点。

答案

“我在上一个国产超算前置项目里,用 Rust 调用 BLAS 分五步落地,可直接迁移到贵司的麒麟服务器环境。

第一步,Cargo.toml 选型

[dependencies]
ndarray = { version = "0.15", features = ["blas"] }
blas-src = { version = "0.10", features = ["openblas"] }
openblas-src = { version = "0.10", features = ["cblas", "static"] }

特性开关留好后门:features = ["intel-mkl"] 可在 x86 服务器一键切换。

第二步,build.rs 保证离线可编译
国内机房无外网,我在 build.rs 里写死 OPENBLAS_ROOT=/opt/openblas-aarch64,用 println!("cargo:rustc-link-search=native={}/lib"); 注入路径,避免 pkg-config 失败导致 CI 红灯

第三步,Rust 侧零开销封装

use libc::{c_int, c_double};
#[link(name = "openblas", kind = "static")]
extern "C" {
    fn cblas_dgemm(
        layout: c_int, transa: c_int, transb: c_int,
        m: c_int, n: c_int, k: c_int,
        alpha: c_double, a: *const c_double, lda: c_int,
        b: *const c_double, ldb: c_int,
        beta: c_double, c: *mut c_double, ldc: c_int,
    );
}
pub fn rust_dgemm(...) {
    unsafe { cblas_dgemm(...) }  // 编译器自动内联,无额外开销
}

注意#[repr(C)] 结构体与 ndarray::ArrayView2<'a, f64>.as_ptr() 保证内存连续,防止 Rust 侧行主序与 BLAS 列主序错位

第四步,国产 CPU 适配
鲲鹏 920 使用 KML(Kunpeng Math Library),符号前缀 cblas_ 相同但库名改为 libkml_blas.so。我在 .cargo/config.toml 写:

[target.aarch64-unknown-linux-gnu]
runner = "qemu-aarch64 -L /opt/kml"
rustflags = ["-C", "link-arg=-Wl,-rpath,/opt/kml/lib"]

一条 cargo build --target aarch64-unknown-linux-gnu 即可通过

第五步,性能验收
CI 脚本里跑 cargo bench --bench gemm,用 criterion 输出 > 85% 理论峰值 GFLOPS;同时 perf stat -e cache-misses,cyclesGFlops = 2MNK / time / 1e9 对比,确保多线程未放大缓存抖动

总结:以上方案在 麒麟 V10 + 鲲鹏 920 双路服务器单精度 SGEMM 实测 380 GFLOPS,达到 OpenBLAS 官方 95% 水平,且 源码可审计、离线可重现,满足国内信创验收要求。”

拓展思考

  1. 如果客户强制静态链接且二进制体积 < 5 MB,如何裁剪 OpenBLAS 并关闭 LAPACK、LAPACKE,只保留 level-3 接口?
  2. 龙芯 loongarch64 上尚无 OpenBLAS 官方支持,如何用 CBLAS 接口桥接自研 BLAS 并通过 build.rs 自动探测 __loongarch__ 宏?
  3. 当 Rust 侧已用 Rayon 并行迭代,如何设置 OPENBLAS_NUM_THREADS=1 避免 两层并行导致 CPU 颠簸?请给出 运行时检测线程池大小 的代码片段。