Eigen 的 riscv64 向量化后端(RVV10)技术调研
Eigen 的 riscv64 向量化后端(RVV10)技术调研
本文档由 Qoder Agent(AI)生成;全部存在性论断与代码引用均在一手源码核实,证据截至 2026-09-04。
| 项目 | 内容 |
|---|---|
| 密级 | 内部技术调研 |
| 目标读者 | RISC-V CPU 软件性能优化工程师 |
| 审核状态 | 待评审 |
| 调研目的 | 进展评估 + 任务准备:判断 Eigen 在 riscv64 上向量化的真实覆盖面、工作机制、缺口与启用成本 |
| 覆盖范围 | Eigen master(commit 17d8ddb,2026-09-03)的 RVV10 后端;不含发布版差异与 BLAS 后端对比 |
版本历史
| 日期 | 版本号 | 描述 | 作者 |
|---|---|---|---|
| 2026-09-03 | v1.0 | 初版。存在错判:据"MR 未合并 + release notes 沉默"误判无 RVV 后端,已废弃 | Qoder Agent |
| 2026-09-03 | v2.0 | 修正 v1.0:clone 官方仓库核实,RVV10 存在,重写主体结论 | Qoder Agent |
| 2026-09-04 | v3.0 | 按 document skill 规范重写;源码更新至 17d8ddb;补时间线、作者矩阵、GEMM 覆盖、门控链 | Qoder Agent |
| 2026-09-04 | v3.1 | 升级内容深度:新增机制深挖三章(gebp 微核 / generic 超越函数 / VLEN 自适应归约,含代码级证据与 file:line);mermaid 全部替换为 matplotlib + graphviz 房屋风格图;文字改为新闻体(倒金字塔 + 5W 导语) | Qoder Agent |
术语说明
| 术语 | 含义 | 出处 |
|---|---|---|
| RVV10 | Eigen 后端目录名 arch/RVV10,指 RVV 1.0(V 扩展 ratified 版)后端 | Eigen 源码目录 |
| LMUL | RVV 向量寄存器分组系数(1/2/4/8) | RVV 1.0 规范 |
| VLEN / zvl | 硬件向量寄存器位宽;zvl128b 等声明最小 VLEN | RVV 1.0 规范 |
| 固定 VLEN | 编译期定死 VLEN(-mrvv-vector-bits=zvl*n*b),与运行时 VLA 相对 | GCC/Clang 选项 |
| VLA | vector-length agnostic,同一二进制跨 VLEN 档运行 | RVV 编程模型 |
| gebp | Eigen GEMM 分块微核(general block panel kernel),性能关键路径 | Core/products |
| PacketNXT | 包类型命名:N=LMUL,T=类型(f=float32,d=float64 等),如 Packet4Xf=LMUL4 float32 | arch/RVV10/PacketMathDecl.h |
| Eigen 5.0 | EIGEN_WORLD_VERSION 恒为 3,"5.0" 即 MAJOR=5;当前 master 为 5.0.1-dev | Eigen/Version |
可靠性标注约定
| 标注 | 含义 |
|---|---|
| C | 一手来源在线核实(本报告 = eigen-src master 17d8ddb 源码 / 官方页面) |
| C* | 规范级稳定常识,标注核对出处但未在线复核 |
| S | 单一来源,未交叉验证 |
| P | 预测 / 估算,非既成事实 |
| ? | 未能核实——留空,禁止估计值填充 |
1. 核心判断(TL;DR)
- RVV10 后端真实存在且活跃:2025-11-20 首次合入 master,截至 2026-08-26 累计 25 个专项提交,最近一次提交在 5 周前。C
- 覆盖广度超出"基础后端":GEMM 微核覆盖 float / double / half / bfloat16 / complex 全组合(含实×复混合);包类型按 LMUL 1/2/4 全档展开。C
- 尚未进入任何发布版:最新发布 Eigen 5.0.0(2025-09-30)早于 RVV10 落地(2025-11-20)约 8 周;使用必须跟 master(
5.0.1-dev)。C - 启用有门槛且不自动启用:必须显式
-DEIGEN_RISCV64_USE_RVV10+ 固定 VLEN(-mrvv-vector-bits=zvl*n*b),否则编译期#error;默认 LMUL=1。C - 最大缺口是超越函数:exp/log/sin/cos 等经
EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_*宏逐 lane 调用标量 libm,无任何向量化(机制见第 6 章)。C - 公开性能基准缺失:未找到任何 Eigen-on-RVV 公开 benchmark;收益必须自测,测量规格见第 10 章。?
2. 支持矩阵速览
一张图交代全景(绿=完整支持,黄=部分,灰=generic 标量路径):
精确值补充(矩阵中"部分"两档的含义):
| 数据类型 | 包体系 | GEMM 微核(gebp) | 超越函数 | 补充 |
|---|---|---|---|---|
| float32 | m1/m2/m4 C | 有 C | generic C | 算子最全的主力类型 |
| float64 | m1/m2/m4 C | 有 C | generic C | |
| complex\<float\> | m1/m2/m4 C | 有(含实×复混合)C | —— | 2026-05-05 加入 |
| complex\<double\> | gebp 特化覆盖 C | 有 C | —— | 包声明弱于 complex\<f\> |
| half(FP16) | Packet1Xh/2Xh C | 有 C | cast 到 float 路径 C | 需 zfh+zvfh C |
| bfloat16 | 有 C | 有 C | —— | 需 zvfbfwma C |
| int8/uint8 | 仅 m1 C | 无 C | —— | |
| int16/32/64 及无符号 | m1/m2/m4 C | 无 C | —— | pdiv/predux/移位齐全 |
工程含义:覆盖矩阵的形状直接预测收益形状——GEMM 密集负载吃满向量化;逐元素/归约表达式全类型受益;超越函数热点(ML 前处理、核方法)零受益。评估收益前先把自己的负载按这三类拆开。
3. 时间线与驱动者
RVV10 的开发节奏如何?如图 2 所示:2025-11 单点落地后,经历三个阶段——12 月补基础(FP16/BF16 包运算),2026 年 1 月激活 GEMM 微核,5 月为峰值(complex API + VLEN 边界修复 + 包声明重构),此后进入维护期但未停止。
关键节点:C
| 日期 | 提交 | 内容 |
|---|---|---|
| 2025-11-20 | 49623d0c | 初始 RVV1.0 支持(Chip Kerchner) |
| 2025-12-05~16 | 3 个提交 | predux_half 修复、FP16 GCC 编译修复、BF16 基础包运算 |
| 2026-01-07 | 5d9beb81 | GEBP(GEMM 微核)激活 |
| 2026-05-01~26 | 8 个提交 | complex 向量 API(4b325d6b)、VLEN=128/1024 编译修复、包声明样板简化 |
| 2026-06~08 | 10 个提交 | fast-math 复数修复、NaN 常量治理、min/max 归约 NaN 种子修复、维护性清理 |
作者分布(25 提交):Chip Kerchner(Tenstorrent)9、Rasmus Munk Larsen 8、Florian Maurin 5、Charles Schlosser 2、onalante-ebay 1;版权头另见 Syntacore(Kseniya Zaytseva,MathFunctions 与 Complex 初始版本)。C RML/Maurin/Schlosser 为 Eigen 维护者团队成员(机构归属未逐项核实)C*。
工程含义:这不是"捐赠后弃维护"的代码——2026-08 仍在修归约正确性,且 CI 在真实硬件上跑(第 9 章);跟 master 的主要风险是 API 演进,不是死代码。
4. 编译启用与门控链
怎么把 RVV10 编译出来?三个条件缺一不可:riscv64 目标、显式宏、固定 VLEN;两个可选扩展按需解锁。完整门控链如图 3。
典型编译组合(VLEN=128 档,CI 用 gcc-15):C*
riscv64-unknown-linux-gnu-g++ \
-march=rv64gcv_zvl128b \
-mrvv-vector-bits=zvl128b \
-DEIGEN_RISCV64_USE_RVV10 \
-O3 -I<path-to-eigen-master> ...
四个细节与代价:C
- 固定 VLEN 是硬性要求:
ConfigureVectorization.h:542直接#error "Eigen requires a fixed RVV vector length but -mrvv-vector-bits=zvl is not set."。对比:Eigen 的 SVE 后端走 scalable 模式(-msve-vector-bits未设置),RVV10 选择 fixed 是编译期定死包宽度换微核自由调度。 - 栈分配上限自动上调:VLEN > 512 时 Eigen 把栈上限从默认提到 384KB(
ConfigureVectorization.h),VLEN ≥ 256 时向 GCC 声明不干预栈对齐——固定 VLEN 下包对象按值传参会放大栈压力,这是配套动作。 - 默认 LMUL=1:
EIGEN_RISCV64_DEFAULT_LMUL默认 1(ConfigureVectorization.h:658),一行宏可切 2/4。 - FP16 双扩展门控:仅 zvfh(无 zfh 标量半精度)时发
#warning,Half 向量化不生效——工具链要同时带两个扩展。
工程含义:固定 VLEN 意味着一档硬件一个二进制,VLA 的"同一包跨档部署"优势被放弃;产品线 VLEN 不统一时按档出包的部署成本要计入评估。
5. 机制深挖 I:GEMM 微核怎么跑起来
GEMM 的向量化收益从哪来?来自每条微核指令同时干三件事——vfmadd_vf 把"乘一个广播标量 + 加进累加器"合成一条指令。这是 RVV10 后端最核心的机制。
gebp 微核的单步计算(float32 为例,GeneralBlockPanelKernel.h:16-24):C
EIGEN_STRONG_INLINE void madd(const LhsPacket& a, const RhsPacket& b,
AccPacket& c, RhsPacket& /*tmp*/,
const FixedInt<0>&) const {
#if EIGEN_RISCV64_DEFAULT_LMUL == 1
c = __riscv_vfmadd_vf_f32m1(a, b, c, unpacket_traits<AccPacket>::size);
#elif EIGEN_RISCV64_DEFAULT_LMUL == 2
c = __riscv_vfmadd_vf_f32m2(a, b, c, unpacket_traits<AccPacket>::size);
#elif EIGEN_RISCV64_DEFAULT_LMUL == 4
c = __riscv_vfmadd_vf_f32m4(a, b, c, unpacket_traits<AccPacket>::size);
#endif
}
数据通路如图 4:A 矩阵的列块进向量寄存器,B 矩阵的元素经 pset1 广播成标量,vfmadd_vf(注意后缀 vf = 向量-标量变体)一次完成 c += a × b。
这个设计的两个工程要点:C
- 走
vf(向量-标量)而非vv(向量-向量):RHS 用广播标量避免了 B 矩阵的向量装载,寄存器压力只来自 A 块与累加器。代价是 B 元素复用率受限于广播宽度——这是与 AVX 微核(同样用 broadcast)一致的经典取舍。 - LMUL 档位编译期三选一:同一份
madd按DEFAULT_LMUL展开为 m1/m2/m4 三种寄存器组宽度;LMUL≥2 时额外提供窄包重载(Packet1Xf/Packet2Xf版本,GeneralBlockPanelKernel.h:27-38),让小尺寸矩阵用窄通路省寄存器。
对比框架——三种 GEMM 路径的差异:C
| 路径 | 每步指令 | 理论吞吐(VLEN=128, f32) |
|---|---|---|
| Generic 标量 | fmadd.s(1 lane) | 1 FLOP×2/周期量级 P |
| RVV10 LMUL=1 | vfmadd.vf(4 lane) | 4× |
| RVV10 LMUL=4 | vfmadd.vf(16 lane) | 16× |
注:理论倍数不含访存与寄存器压力;实际收益必须实测(规格见第 10 章)。P
工程含义:GEMM 密集负载直接受益于微核,且 LMUL 档位是一行宏的事——实测时 LMUL 1/2/4 三档必扫。
6. 机制深挖 II:超越函数为什么是最大缺口
超越函数慢在哪?不在"没实现",在"实现的机制是逐 lane 标量调用"。MathFunctions.h 全文仅 31 行,核心是两行宏调用:C
// Eigen/src/Core/arch/RVV10/MathFunctions.h:33-39
EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_FLOAT(Packet1Xf)
EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_FLOAT(Packet2Xf)
EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_FLOAT(Packet4Xf)
EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_DOUBLE(Packet1Xd)
// ... 共 6 个包类型
宏展开链(三层,全部在 arch/Default/):C
// GenericPacketMathFunctionsFwd.h:269 —— 列出 sin/cos/tan/asin/acos/
// sinh/cosh/tanh/asinh/acosh/atanh/log ... 全部函数
#define EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_FLOAT(PACKET) \
EIGEN_FLOAT_PACKET_FUNCTION(sin, PACKET) \
EIGEN_FLOAT_PACKET_FUNCTION(cos, PACKET) \
// ...
// GenericPacketMathFunctionsFwd.h:253-257 —— 单个函数的展开
#define EIGEN_PACKET_FUNCTION(METHOD, SCALAR, PACKET) \
template <> EIGEN_STRONG_INLINE PACKET p##METHOD<PACKET>(const PACKET& _x) { \
return p##METHOD##_##SCALAR(_x); /* -> psin_float(Packet1Xf) */ \
}
psin_float 的 generic 实现(GenericPacketMathFunctions.h)逐 lane 循环调 std::sin——即 RVV10 包类型上的 exp,每 lane 一次标量 libm 调用,向量寄存器只在进出函数时用了一下。C
对比其他后端的处理:AVX2 有 pexp/plog 的多项式向量化实现(Sleef 风格算法内联),SVE 走部分向量化——RVV10 目前一行专属实现都没有,是同类后端中覆盖最薄的。C*
FP16 的 ptanh 同理:PacketMathFP16.h:836 经 F16_PACKET_FUNCTION 宏 cast 到 float 包再走同一 generic 路径。C
工程含义:含大量 exp/log 的负载(ML 前处理、核方法、概率计算)不要等 Eigen 补——上游没有任何进行中的向量化提交(时间线第 3 章可证),热点在超越函数时需自行评估手写 RVV intrinsics 或换库。
7. 机制深挖 III:归约树的 VLEN 自适应与已知代价
归约(reduction)是 Eigen 表达式引擎的高频操作,RVV10 的实现有两个值得注意的机制。
机制一:乘法归约的对折树按 VLEN 编译期自适应(PacketMath.h:728-746):C
EIGEN_STRONG_INLINE float predux_mul<Packet1Xf>(const Packet1Xf& a) {
Packet1Xf prod = __riscv_vfmul_vv_f32m1(preverse(a), a, ...); // 自乘逆序
EIGEN_IF_CONSTEXPR (EIGEN_RISCV64_RVV_VL >= 1024) { // 逐步对折
half_prod = __riscv_vslidedown_vx_f32m1(prod, 8, ...);
prod = __riscv_vfmul_vv_f32m1(prod, half_prod, ...);
}
// ... VL>=512 对折、VL>=256 对折、最后 1 元素对折
return pfirst(prod);
}
乘法不满足结合交换的归约器友好性(且 vfredusum 无乘法对应物),所以用 slidedown 对折:VLEN 越大对折层数越多,编译期 IF_CONSTEXPR 裁剪——每档 VLEN 生成精确的层数,不冗余。
机制二:min/max 归约用自身首元素做种子(PacketMath.h:650-656,2026-08-21 修复):C
// 注释原文:Reusing the first lane is exact for an idempotent reduction
// and avoids a NaN seed that becomes poison under finite fast-math.
EIGEN_STRONG_INLINE float predux_min<Packet1Xf>(const Packet1Xf& a) {
return __riscv_vfmv_f(__riscv_vfredmin_vs_f32m1_f32m1(a, a, ...));
// ^ 种子是 a 自身
}
用 ±inf 做种子会在 fast-math 下被编译器当作 poison 优化掉(2026-08 的 8763a058 修复,commit message: "Avoid NaN seeds in RVV min/max reductions")。这个坑值得记住:fast-math 下任何"哨兵值种子"模式都危险。
已知代价:ptranspose 走内存往返(PacketMath.h:658-670):C
EIGEN_DEVICE_FUNC inline void ptranspose(PacketBlock<Packet1Xf, N>& kernel) {
float buffer[unpacket_traits<Packet1Xf>::size * N]; // 标量缓冲区
// ... 写入 buffer 再按转置序读回
}
对比 AVX2 用 _MM_TRANSPOSE4_PS(寄存器内 shuffle)——RVV10 的转置经过内存,是寄存器内向量化未覆盖的角落。C
工程含义:归约密集负载(norm、dot、sum)吃满向量化且质量在持续打磨;但含 transpose 的路径(部分求解器、ptranspose 显式调用的表达式)有已知内存往返开销。
8. 包类型体系与算子覆盖
包体系的组织方式是什么?PacketMathDecl.h(339 行)集中声明,每个宽度档按 LMUL 1/2/4 展开三份:float32/float64 各三档(Packet1Xf/2Xf/4Xf、Packet1Xd/2Xd/4Xd);int16/32/64 及无符号各三档;int8 仅 m1;complex\<float\> 三档(Packet1Xcf/2Xcf/4Xcf,经 complex_packet_wrapper 包装);FP16 两档(Packet1Xh/2Xh)。C 别名机制:PacketXf 等无前缀名由 EIGEN_RISCV64_DEFAULT_LMUL 决定指向哪一档(PacketMathDecl.h:241-259)。C
三档实现各自完整:PacketMath.h(1716 行,LMUL=1)/ PacketMath2.h(1579 行)/ PacketMath4.h(1581 行),每份约 240 个内联算子。C 已向量化:padd/psub/pmul/pdiv(含整数)、psqrt(f32/f64)、predux 全家(sum/mul/min/max)、ptranspose、移位、比较掩码、类型转换(f32↔i32、f64↔i64)。C
工程含义:默认 LMUL=1 在 VLEN=128 下 float32 仅 4 lane,GEMM 微核寄存器利用率偏低;切换是一行宏,实测必扫三档。
9. CI 与质量保障
质量信号可信吗?可信——RVV10 是 Eigen 少数在真实硬件上跑 CI 的后端。.agents/ci.md:151 明确:arch/RVV10 由 rvv-tests 标签覆盖,riscv64 gcc-15,native runner(对比:SVE 走 qemu 模拟,SME 仅编译不跑)。C
2026 年的提交流与 CI 相互印证:VLEN=128 与 VLEN=1024 边界修复(05 月)、fast-math 下复数运算修复(06-01)、NaN 常量治理(07-10)、归约 NaN 种子修复(08-21)——这类"实机才会暴露"的问题被持续修,说明测试真的在跑。C
工程含义:跟 master 质量风险可控,但锁定 commit hash 而非追 HEAD;升级时重跑自己的回归。
10. 落地建议与测量规格
按"证据强度 × 收益 × 成本"排序:C
- 立即可做(零新硬件):GEMM 密集且 VLEN 统一的场景,锁 master commit 试用(编译组合见第 4 章)。
- 低成本参数扫描:
DEFAULT_LMUL=1/2/4三档 × 代表性 GEMM 尺寸矩阵。 - 需决策:VLEN 不统一时按档出包,或维持标量路径——部署成本见第 4 章工程含义。
- 需自查:超越函数热点负载(机制见第 6 章),上游短期不会补。
公开 benchmark 缺失,自测规格如下(把 ? 转成可执行的下一步):
| 测什么 | 怎么测 | 什么算好 |
|---|---|---|
| GEMM 吞吐收益 | 固定 VLEN 档,对比 Generic 标量 vs RVV10(LMUL 1/2/4),矩阵尺寸 64³~2048³,取稳态 GFLOPS | 大矩阵 ≥4×(LMUL=1, VLEN=128 理论上限附近)P |
| 归约/逐元素收益 | .sum()/.norm()/.array().exp() 微基准,长度 10³~10⁶ | 归约 ≥2×;exp 无收益(第 6 章)P |
| 编译/部署成本 | 三档 VLEN 全量编译耗时与产物大小对比 | 记录基线,供产品线决策 |
11. 不确定性清单
| 项 | 级别 | 说明 |
|---|---|---|
| Eigen-on-RVV 公开性能数据 | ? | 未找到(检索 2026-09-04);收益数字必须自测(规格见第 10 章) |
| Tensor 模块向量化深度 | ? | 无 RVV 专属文件确认;经通用包算子的间接受益程度未逐模块验证 |
| RML / Maurin / Schlosser 机构归属 | C* | 维护者团队背景,未逐一核实当前雇主 |
| RVV10 进入发布版时间 | P | 无官方时间表;按 8 月仍活跃推断后续 5.0.x/5.1 系列纳入 |
| gcc-15 之外工具链可用性 | ? | CI 仅 gcc-15;源码无版本门控,但未在 LLVM/低版本 GCC 实测 |
受限来源:无(GitLab 源码与官方发布页均直接可访问)。
12. 参考来源
一手来源(C 级证据出处,eigen master 17d8ddb,本地 shallow clone 核实)
Eigen/src/Core/arch/RVV10/:11 文件 10010 行Eigen/src/Core/arch/RVV10/GeneralBlockPanelKernel.h:16-38(gebp madd 微核)Eigen/src/Core/arch/RVV10/PacketMath.h:614-670(predux / predux_mul / predux_min/max / ptranspose)Eigen/src/Core/arch/Default/GenericPacketMathFunctionsFwd.h:253-281(generic 实例化宏链)Eigen/src/Core/util/ConfigureVectorization.h:520-560, 658(门控链 / DEFAULT_LMUL)Eigen/Version(5.0.1-dev+master)、.agents/ci.md:151(rvv-tests CI)- git log(
--shallow-since=2025-10-01,RVV10 目录 25 提交全量)
官方发布信息
- Eigen releases(libeigen.gitlab.io):最新发布 5.0(2025-09-30),无 RISC-V/RVV 发布说明
- MR !1687: RISC-V RVV1.0 support(libeigen GitLab)
规范级(C*)
- RVV 1.0 / Zvl / Zvfh / Zvfbfwma 扩展语义(RISC-V ratified 规范)
- GCC
-mrvv-vector-bits=zvl*n*b需-march声明对应 Zvl(编译器文档级稳定行为) - AVX2/SVE 后端的超越函数与 transpose 处理方式(Eigen 源码级常识,未逐行复核)
附:方法说明
检索与核实:git clone --depth 1 + git fetch --shallow-since=2025-10-01 获取 master(17d8ddb,2026-09-03);全部存在性结论来自源码树内定位与 git log;发布状态经官方发布页与 Eigen/Version 双向核对;性能数据检索未命中,转为测量规格(第 10 章)。
图表生成:图 1/2 由 matplotlib 3.7.5(房屋风格 figstyle.py,字体 Alibaba PuHuiTi 2.0),图 3/4 由 graphviz 2.43 生成;脚本存档于 .figs/eigen-figs.py 与 .figs/eigen-dot-figs.py,可复现。
标注口径:C = 本地源码/官方页面逐条核实;C* = 规范/文档级稳定常识;S = 单一来源;P = 推断或理论估算;? = 未核实缺口。
v1.0 教训:初版以"MR 未合并 + release notes 沉默"推断"无 RVV 后端",被源码直接推翻。本版起存在性结论一律以源码树为一手证据。