开源鸿蒙PC移植LAPACKE 0.3.30复盘
欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper/
摘要:本文复盘 LAPACKE 0.3.30 移植到开源鸿蒙 PC(HarmonyOS,aarch64)的全过程。LAPACKE 没有独立上游发布,版本跟随 OpenBLAS 0.3.30(内嵌 LAPACK 3.12.0),交付它等于把 OpenBLAS 的 Fortran 构建体系整体搬上真机。途中遇到三类问题:精简 flang 工具链链接 Fortran 可执行文件缺目标级链接件、OpenBLAS 构建系统在鸿蒙上误记 CROSS=1 导致全部上游测试被静默跳过、affinity 接口适配被代码审查打回后重写为 POSIX 能力级。最终上游测试四件套(utest/blat/ctest/lapack-test)真实执行:合计 5,197,358 项判定,通过率 99.989%,576 项 DGEEV 特征值族阈值未达有上游三处引证归因。配方与补丁已交付社区仓库 OpenHarmonyPCDeveloper/build_in_harmonyos(AtomGit)的 archives/l/liblapacke/0.3.30。
1 背景
LAPACKE 是 netlib 给 LAPACK 做的标准 C 接口(官方页面),高层函数自动管理工作区,同时支持行主序和列主序——C 代码中调用 LAPACKE_dgesv 求解线性方程组,使用的就是这一层接口。numpy/scipy 一类科学计算栈的底层依赖里都有它的位置;科学计算软件要在鸿蒙 PC 上落地,这个库是必要的基础组件。
从"C 接口库"的定位出发,这项任务最初被评估为一次常规适配。核对源码分布后,评估需要修正:LAPACKE 没有独立上游发布,源码位于 OpenBLAS 源码树的 lapack-netlib/ 子目录,版本号与 OpenBLAS 保持一致——0.3.30 对应 OpenBLAS 0.3.30(2025-06-19 发布),内嵌 LAPACK 3.12.0。交付 liblapacke 0.3.30,实际是把 OpenBLAS 完整构建出来,lapacke.h 和 LAPACKE_* 符号是其中一层。
最终结果:真机交付 lapacke.h 等全套头文件加内嵌 LAPACKE 符号的 libopenblas,消费者直接 -lopenblas;上游测试四件套全部真实执行,合计 5,197,358 项判定、通过率 99.989%;CI 共 5 轮,前两轮分别败于补丁偏移和测试脚本自身缺陷,第 3 轮起全绿。全程使用的环境:
| 项 | 值 |
|---|---|
| 设备 | HUAWEI MateBook Pro(开源鸿蒙 PC,HarmonyOS,aarch64) |
| 上游 | OpenBLAS 0.3.30(含 LAPACKE + LAPACK 3.12.0),BSD-3-Clause |
| Fortran 工具链 | flang-ohos 20.1.0(LLVM Flang 系) |
| C 工具链 | OHOS SDK clang 15.0.4 |
| 构建 | Conan 2.x,设备原生构建 |
| 源码校验 | tar.gz SHA256 27342cff…63c95d(GitHub release 校验值) |
2 上游假设与鸿蒙 PC 环境的差异
OpenBLAS 的构建系统是 GotoBLAS 传下来的 perl 脚本加 Makefile 体系,隐含假设比一般 CMake 项目多。在真机上启动构建后,实际遇到的差异集中在四处:
| 差异点 | 上游假设 | 鸿蒙 PC 实际情况 |
|---|---|---|
| Fortran 工具链 | 系统有可用的 Fortran 编译器 | 无 gfortran,仅有精简打包的 flang-ohos |
| 测试可执行链接 | 驱动自带 crt 等目标级链接件 | flang 包资源目录缺件,链接期报错 |
| 平台判定 | uname -s 与编译器探针结论一致 | HarmonyOS vs OS_LINUX,被误判为交叉编译 |
| affinity 接口 | glibc 风格 pthread_*_np 可用 | 设备动态库无法解析,_np 本就是非移植扩展 |
其中两处的影响超出构建失败本身:它们会让结果呈现正常、实际不正常。这也是本次移植耗时最多的两处,见第 5、6 节。
3 路线:纯 C 编译为什么不是答案
OpenBLAS 有 NOFORTRAN=1 开关,字面含义是不使用 Fortran 编译,表面上适用于无 Fortran 工具链的平台。翻构建系统确认它的实际行为:NOFORTRAN=1 置 C_LAPACK=1(Makefile.system :382),LAPACK 的编译整体切换到源码树自带的 f2c 转换 C 源、用 CC 进行(SRC/Makefile 的 C_LAPACK 分支),测试矩阵生成器 MATGEN 也有对应的 C 转换——库本体的编译存在纯 C 路径。另一条联动 NO_LAPACK=1 强制 NO_LAPACKE=1(:1458)服务于"只要 BLAS"的场景,由 ONLY_CBLAS 等开关触发,与 NOFORTRAN 无关。两条联动不是一条链,混读会得出"纯 C 路线产不出 LAPACKE"的错误结论。
纯 C 路线真正过不去的是验证层:上游 LAPACK 测试的主体 EIG 与 LIN 两个目录共 1,132 个源文件(358 + 774)全部是 Fortran 实现,没有任何 C 转换路径。没有 Fortran 编译器,lapack-test 无从构建——库编得出来,上游口径的验证无从谈起。本任务的验收口径是上游测试全量真实执行,纯 C 路线在这个口径下不成立,Fortran 工具链问题必须正面解决。
设备上可用的 Fortran 编译器只有仓库自带的 flang-ohos 20.1.0,选择它有依据:仓库内 blas、lapack、cblas 等十几个配方已用它完成静态库构建,路线有先例。不过"静态库可以编译"与"测试可执行可以链接、可以运行"之间还差两个环节,这是后两节的内容。
另外两条评估后未采用的路线,记录如下。路线 B 是把 LAPACKE 的 C 源码单独抽出编译成独立库、依赖现成的 openblas 包:LAPACKE 0.3.30 与仓库内其他版本的 OpenBLAS 引擎混搭,版本口径不一致,且消费者闭包中会出现两个 BLAS 引擎。路线 C 是走仓内 lapack/3.12.1 配方的 CMake 路线:该先例自身就因 FortranCInterface 链接验证失败,产不出 LAPACKE 共享库。综合版本口径、依赖闭包与验证可行性,全量构建是唯一同时满足的路线。
4 flang 链接缺件:构建目录内的 overlay 资源目录
构建打通后,最先暴露问题的是测试可执行的链接步骤。OpenBLAS 的上游测试(blat、lapack-test)全部是 Fortran 可执行程序,ld.lld 连续报:
ld.lld: error: cannot open Scrt1.o: No such file or directory
ld.lld: error: cannot open crti.o: No such file or directory
ld.lld: error: cannot open crtbeginS.o: No such file or directory
ld.lld: error: unable to find library -lm
ld.lld: error: cannot open …/lib/clang/20/lib/aarch64-linux-ohos/libclang_rt.builtins.a: …
ld.lld: error: unable to find library -l:libunwind.a
用 flang -v 查看驱动实际执行的链接行,失败机理明确:flang 把 crt、内置库、unwind 这些目标级链接件全部从自己的资源目录(<包>/lib/clang/20)取用,而该目录在精简包中不存在——包内只有 FortranRuntime 和 FortranDecimal 两个运行库。另外,OHOS flang 编译的 program 对象自带 main 与 _QQmain 双符号(llvm-nm 可验证),入口不依赖 FortranMain。鸿蒙 SDK 中有全部实体,但那是 clang 15.0.4 的目录布局,文件名和位置都与 flang 20 期望的不一致。再试 --sysroot,该驱动不接受此选项;-isysroot 也无法注入链接搜索路径。
一种直观的修复方向是修改 flang 包或 SDK 实体,通过改名、复制补齐缺失文件。这条路线不能采用:工具链实体是设备上的共享环境,任何改动都会影响后续所有构建。实际采用的方案是在构建目录内构造 overlay 资源目录:10 个软链接,把 SDK 中同物异名的实体按 flang 期望的名字集中(crtbeginS.o 指向 SDK 的 clang_rt.crtbegin.o,以此类推),每一项都做存在性检查,缺件直接报错而不是静默跳过,然后链接期注入 LDFLAGS="-resource-dir <overlay>"。上游源码一行未动,flang 包和 SDK 一个文件未改。
为了确认该方案针对的是真实缺口、而非既有做法的重复包装,做了一个负对照实验:用仓库现有配方(blas、lapack 系)的 -resource-dir <SDK> 直连写法链接同一可执行文件,结果失败,报错与上面一致。这说明配方树内同族的 16 个以上 Fortran 先例全部止步于静态库构建(静态库不涉及 crt),可执行链接此前没有先例。验证用的五步探针在新 shell 中可以完整复现:
flang-20 -c hello.f90 # 编译成功——问题只在链接
flang-20 -o hello_neg hello.o # 负对照:无 overlay,预期失败
# …构造 overlay(10 项 symlink)…
flang-20 -o hello_pos hello.o -resource-dir rd
./hello_pos # 输出 AUDITOR_HELLO_OK 1.4142135
这一步的负对照不能省略——缺少它,无法区分"修复了真实缺口"与"重复包装既有做法"两种情况。
5 测试静默跳过:CROSS=1 造成的假 PASS
链接打通后执行 make lapack-test,数秒内正常退出。没有报错,没有告警,日志中没有任何测试输出。重复执行,现象相同。
检查构建树生成的 Makefile.conf,其中记录为 CROSS=1——构建系统判定这是一次交叉编译,而构建机即目标机,本次是设备原生构建,判定有误。定位到 c_check 脚本,判定逻辑如下:它用 uname -s 取主机系统名,鸿蒙设备上返回 HarmonyOS;再用编译器预定义宏探测目标系统,clang 报告 OS_LINUX。两个结论不一致,脚本判定为交叉编译,写入 CROSS=1。而上游 Makefile 中所有就地测试执行都挂在 ifneq ($(CROSS), 1) 门下——CROSS=1 时测试段整体不展开,make 照常退出,零告警。
验证这个判断只需要两行,输出 0 和 1:
make -n lapack-test CROSS=1 | grep -c lapack_testing.py # 0,执行段被跳过
make -n lapack-test CROSS=0 | grep -c lapack_testing.py # 1,执行段恢复展开
解法是在测试步骤命令行显式加 CROSS=0 覆盖。只覆盖测试执行判定这一个变量,上游的探测逻辑和生成的配置文件都不动。真交叉构建的场景下这个覆盖是错误的(交叉产物本就不应就地执行),因此该动作的适用边界明确:构建机等于目标机时才成立。
这个问题的风险在于它不产生任何错误信号:构建失败会迫使排查,静默跳过则呈现为正常结束,测试结论在未被察觉的情况下失真。“先检查 grep ^CROSS Makefile.conf、再采信测试结果”,应当作为 GotoBLAS 系构建的固定检查步骤。
6 affinity 返工:从平台宏到 POSIX
musl 环境下还有一个问题:上游代码使用的 pthread_setaffinity_np 系列在设备动态库中无法解析。_np 后缀本身就是 glibc 的非移植扩展(man page 明确标注 STANDARDS: GNU),更换 libc 实现后遇到此问题在预期之内。
第一版补丁用 #if defined(OS_LINUX) && !defined(__OHOS__) 将这段代码在鸿蒙上条件编译排除。功能上可以运行,但被代码审查打回:项目规则禁止在补丁中引入平台条件编译宏,要求按 POSIX 能力级适配。这个判定成立——平台宏方案存在一层隐患:它依赖编译器恰好定义了该宏,工具链变更后守卫会静默失效,原始缺陷无告警复现。
重写后的补丁改用 sched_setaffinity/sched_getaffinity,POSIX 标准接口,musl 和 glibc 都提供。跨线程寻址的问题用 syscall(SYS_gettid) 解决:工作线程启动时记录自己的内核 TID,按 TID 调用;openblas_setaffinity/openblas_getaffinity 两个对外 API 原样保留。返修后全量重跑 CI(第 5 轮),并在产物级做了核对:.so 和 .a 中 pthread_*_np 引用归零,sched_* 正常导入,对外符号集不变。
这次返工的对比值得记录:平台宏的适配只对当前编译环境成立,能力探测的适配对接口语义成立;前者的维护成本会在工具链变更时再次出现。
7 全量测试:5,197,358 项判定和 576 项"未达阈值"
测试四件套的规模比预想大:
| 套件 | 规模 | 结果 |
|---|---|---|
| OpenBLAS utest + utest_ext | 127 + 1522 项断言 | 全过(已折入 CI 默认链) |
| BLAS blat(s/d/c/z × blat1/2/3 × 2 线程模式) | 24 次程序执行 | 92 个 PASS 块,0 fail |
| CBLAS ctest | 12 个程序(含 error-exits) | 全过 |
| LAPACK 官方 lapack-test(EIG + LIN) | 5,195,617 项判定 | 576 项阈值未达,其他错误 0 |
lapack-test 的官方汇总(lapack_testing.py):REAL 1,568,556 / DOUBLE 1,569,378 / COMPLEX 1,028,308 / COMPLEX16 1,029,375。合计 5,197,358 项判定(含 utest 和 blat),通过 5,196,782 项,通过率 99.989%。
这 576 项需要交代清楚——含糊处理会直接影响测试结论的可信度。逐项分析其分布:全部落在非对称特征值驱动族(sed/ded/ced/zed 四个输入文件的 DGEEV 相关子节),四个精度各 144 项,失败结构完全一致,残差比值全部顶在 1/ulp 的钳制值上,命中的只有极端量级的病态矩阵类型。同一个输入文件中 DGEES、DGEEVX、DGEESX 子节和 nep.in 全部通过。这个形态与上游的已知现象一致:Reference-LAPACK issue #732 中维护者原话是"激进编译优化和/或高度优化的 BLAS,是 LAPACK 严格测试失败的已知来源";netlib LAPACK FAQ 将轻微超阈归因于数学库实现差异、判定为可忽略;OpenBLAS issue #4187 在 ARM 平台报告过同族失败(53/1092)。判定口径因此写作"通过,附记录例外"——0.011% 的阈值未达有出处、有归因,不是回避。
耗时数据均为真机实测:CI 默认路径(全量构建 + utest + 消费者测试)约 25 分钟,在官方默认 3600 秒构建超时内自足;全量上游测试约 2.6 小时(23:02 首个输出,01:38 收官),其中最重的 LIN 族 c/z 各 677,060 项判定——构建负载并行时单族约 2.1 小时,空载时同规模约 9 分钟。因此全量套件做成配方里的 RUN_UPSTREAM_TESTS=1 可选目标、留档真实执行日志;CI 常规门禁只跑轻量回归,以单轮约 25 分钟换取每轮都可回归。
CI 共 5 轮,逐轮记录:第 1 轮补丁按 0.3.34 源码编写,hunk 在 0.3.30 对应位置偏移,应用失败——更换目标版本不是修改版本号,补丁必须对着目标树重新生成;第 2 轮 test_package 的行主序断言传错了 ldb——n×1 的右端项 b 在行主序下 ldb 应为 nrhs 而非 n,dge_trans 按 in[j*ldin+i] 寻址,ldb 传 n 会越界读——测试脚本自身也可能有缺陷,负向验证因此不能省略;第 3 轮起进入正轨;第 4 轮为补齐 Fortran 测试环境后的终树;第 5 轮为 affinity 返工后的全量重跑,即交付树。
8 真机运行与复现
交付后做了端到端复现,做成一个分六段的演示脚本:环境、链接探针、CROSS 静默门、产物自省、消费者基准、回归测试,每段结束暂停确认,全程真机执行、逐段截图。
第一段确认设备与工具链:HarmonyOS 内核、aarch64、20 逻辑核,flang 20.1.0 就位。

第二段是第 4 节链接缺件的最小复现。先做无 overlay 负对照链接,ld.lld 依次报缺 Scrt1.o、crti.o、crtbeginS.o、libclang_rt.builtins.a、libunwind.a;随后建 symlink overlay 资源目录、链接行注入 -resource-dir,同一个 hello.f90 链接成功并运行;llvm-nm 现场验证 flang 对象自带 main 与 _QQmain 双符号——这是第 4 节"不需要 FortranMain"的实证。

第三段到真实构建树解剖第 5 节的静默门:grep ^CROSS Makefile.conf 现场输出 CROSS=1;make -n 对照显示 CROSS=1 时 lapack_testing.py 的执行行为 0、CROSS=0 时为 1——0 与 1 之差,就是假 PASS 与真实执行的分界。

第四段自省交付物:8 个 lapacke 头文件、libopenblas 的 SONAME、2605 个 LAPACKE_* 导出符号;再 grep 证实 affinity 返工后的工件状态——sched_* 能力接口在位、glibc 风格的非移植扩展 pthread_*_np 引用为 0。

第五段是消费者视角的性能与正确性实测:写一个 C 程序 #include <lapacke.h>,链接 libopenblas,跑 dgemm/dpotrf/dgesv 三类基准,并求解一个 3×3 线性方程组逐项核对数值——

clang -O2 -o consumer consumer.c -I<包>/include -L<包>/lib -lopenblas
LD_LIBRARY_PATH=<包>/lib ./consumer
openblas : OpenBLAS 0.3.30 NO_AFFINITY ARMV8 MAX_THREADS=20
core : ARMV8
threads : 20
cblas_dgemm N=1500 0.068 s 98.56 GFLOPS
cblas_dgemm N=2500 0.245 s 127.43 GFLOPS
LAPACKE_dpotrf N=2500 0.222 s 23.50 GFLOPS info=0
LAPACKE_dgesv N=2000 0.240 s 22.21 GFLOPS info=0 rel.residual=1.46e-15
correctness: LAPACKE_DGESV_CONSUMER_OK x = [6 15 -23]
20 线程下 dgemm 两档测得 98.56 与 127.43 GFLOPS(同机另一次运行为 104.19/121.62,波动来自后台负载);dgesv 解 2000×2000 方程组的相对残差 1.46e-15,量级上就是机器精度——这是可信赖的双精度解。
第六段现场执行轻量回归全套——utest 127 项、utest_ext 1522 项断言全部通过,随后展示全量 lapack-test 的官方汇总表:合计 5,195,617 项判定、576 项阈值未达、其他错误 0,与第 7 节的记录一致。

全量 lapack-test 约 2.6 小时,演示中不重放,官方汇总表与逐精度明细见留档日志。配方、两个补丁、test_package 的完整源码都在社区仓库的 archives/l/liblapacke/0.3.30,文中每个问题都能对应到具体 diff。
9 小结
回顾整个过程,可复用的方法有三条:构建系统的限制先读机理再定方案,NOFORTRAN 的联动关系查清之前不动手;测试结论先确认测试真实执行再评估通过,make -n 对照可以直接暴露静默跳过;平台差异用能力探测表达,不依赖平台宏——affinity 的返工是一次现成的对照。链接探针与 CROSS 对照两条命令自包含,同场景的移植可以直接复用。
最后把这次用到的入口留在这里,想动手的读者可以直接取:
- 开源鸿蒙PC社区——适配方向与积分赛动态;
- 社区项目平台——新库在这里申请;
- liblapacke 0.3.30 配方与补丁(fork 分支 feat/liblapacke-0.3.30,评审中)——本篇交付的配方、补丁与 test_package。
10 常见问题(FAQ)
Q1:鸿蒙 PC 上能运行 Fortran 程序吗?
能。flang-ohos(LLVM Flang 系)全量编译没有问题;链接可执行文件需要 overlay 补齐目标级链接件(第 4 节),运行时依赖 FortranRuntime/FortranDecimal。
Q2:LAPACKE 0.3.30 是官方版本号吗?
LAPACKE 没有独立上游发布,版本跟随 OpenBLAS 0.3.30(2025-06-19),内嵌 LAPACK 3.12.0,见第 1 节。
Q3:576 项测试未达阈值,这个库还能用吗?
能。576/5,195,617 = 0.011%,全部集中在 DGEEV 驱动族的极端病态矩阵,上游三处引证一致归因为优化 BLAS 的已知阈值敏感;非法输入和其他错误为 0,逐项归因见第 7 节。
Q4:为什么全量测试不放进 CI?
全量约 2.6 小时,纳入后会阻塞常规门禁。CI 默认链保留 utest 和消费者测试(约 25 分钟一轮),全量做成 RUN_UPSTREAM_TESTS=1 可选目标,交付时全量执行留档,见第 7 节。
Q5:make lapack-test 几秒就结束且没有任何测试输出,是怎么回事?
GotoBLAS 系构建在鸿蒙上被 c_check 误记 CROSS=1,测试段整体静默跳过。先执行 grep ^CROSS Makefile.conf,结果为 1 就在测试步骤加 CROSS=0 覆盖,见第 5 节。
Q6:LAPACKE_dgesv 行主序调用崩溃或解出错误值,怎么排查?
先查 ldb。行主序下 n×nrhs 的右端项 b,ldb 是行长度 nrhs,不是 n——传成 n 时 dge_trans 按 in[j*ldin+i] 寻址会越界读,小尺寸表现为解出错误值,大尺寸越界直接段错误(本例实测 N≥350 起)。A 的 lda 传 n 是对的,b 的 ldb 要传 nrhs。这个坑在本次 CI 第 2 轮和演示脚本编写时各踩过一次,轮次记录见第 7 节。
参考文献
[1] netlib LAPACKE 官方页面. https://www.netlib.org/lapack/lapacke.html
[2] OpenBLAS v0.3.30 Release. https://github.com/OpenMathLib/OpenBLAS/releases/tag/v0.3.30
[3] LAPACK 3.12.0 Release. https://github.com/Reference-LAPACK/lapack/releases/tag/v3.12.0
[4] LLVM Flang 项目主页. https://flang.llvm.org/
[5] Reference-LAPACK issue #732(优化 BLAS 与严格 LAPACK 测试失败). https://github.com/Reference-LAPACK/lapack/issues/732
[6] netlib LAPACK FAQ:How do I interpret LAPACK testing failures? https://www.netlib.org/lapack/faq.html
[7] OpenBLAS issue #4187(ARM 平台同族阈值未达). https://github.com/OpenMathLib/OpenBLAS/issues/4187
[8] pthread_setaffinity_np(3) Linux man page(STANDARDS: GNU). https://man7.org/linux/man-pages/man3/pthread_setaffinity_np.3.html
欢迎加入开源鸿蒙PC社区: https://harmonypc.csdn.net/
欢迎在PC社区平台申请新建项目:https://atomgit.com/OpenHarmonyPCDeveloper/
更多推荐



所有评论(0)