Eigen 的 riscv64 向量化后端(RVV10)技术调研

由 eigen-riscv64-research.md 生成

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-03v1.0初版。存在错判:据"MR 未合并 + release notes 沉默"误判无 RVV 后端,已废弃Qoder Agent
2026-09-03v2.0修正 v1.0:clone 官方仓库核实,RVV10 存在,重写主体结论Qoder Agent
2026-09-04v3.0按 document skill 规范重写;源码更新至 17d8ddb;补时间线、作者矩阵、GEMM 覆盖、门控链Qoder Agent
2026-09-04v3.1升级内容深度:新增机制深挖三章(gebp 微核 / generic 超越函数 / VLEN 自适应归约,含代码级证据与 file:line);mermaid 全部替换为 matplotlib + graphviz 房屋风格图;文字改为新闻体(倒金字塔 + 5W 导语)Qoder Agent

术语说明

术语含义出处
RVV10Eigen 后端目录名 arch/RVV10,指 RVV 1.0(V 扩展 ratified 版)后端Eigen 源码目录
LMULRVV 向量寄存器分组系数(1/2/4/8)RVV 1.0 规范
VLEN / zvl硬件向量寄存器位宽;zvl128b 等声明最小 VLENRVV 1.0 规范
固定 VLEN编译期定死 VLEN(-mrvv-vector-bits=zvl*n*b),与运行时 VLA 相对GCC/Clang 选项
VLAvector-length agnostic,同一二进制跨 VLEN 档运行RVV 编程模型
gebpEigen GEMM 分块微核(general block panel kernel),性能关键路径Core/products
PacketNXT包类型命名:N=LMUL,T=类型(f=float32,d=float64 等),如 Packet4Xf=LMUL4 float32arch/RVV10/PacketMathDecl.h
Eigen 5.0EIGEN_WORLD_VERSION 恒为 3,"5.0" 即 MAJOR=5;当前 master 为 5.0.1-devEigen/Version

可靠性标注约定

标注含义
C一手来源在线核实(本报告 = eigen-src master 17d8ddb 源码 / 官方页面)
C*规范级稳定常识,标注核对出处但未在线复核
S单一来源,未交叉验证
P预测 / 估算,非既成事实
?未能核实——留空,禁止估计值填充

1. 核心判断(TL;DR)

  1. RVV10 后端真实存在且活跃:2025-11-20 首次合入 master,截至 2026-08-26 累计 25 个专项提交,最近一次提交在 5 周前。C
  2. 覆盖广度超出"基础后端":GEMM 微核覆盖 float / double / half / bfloat16 / complex 全组合(含实×复混合);包类型按 LMUL 1/2/4 全档展开。C
  3. 尚未进入任何发布版:最新发布 Eigen 5.0.0(2025-09-30)早于 RVV10 落地(2025-11-20)约 8 周;使用必须跟 master(5.0.1-dev)。C
  4. 启用有门槛且不自动启用:必须显式 -DEIGEN_RISCV64_USE_RVV10 + 固定 VLEN(-mrvv-vector-bits=zvl*n*b),否则编译期 #error;默认 LMUL=1。C
  5. 最大缺口是超越函数:exp/log/sin/cos 等经 EIGEN_INSTANTIATE_GENERIC_MATH_FUNCS_* 宏逐 lane 调用标量 libm,无任何向量化(机制见第 6 章)。C
  6. 公开性能基准缺失:未找到任何 Eigen-on-RVV 公开 benchmark;收益必须自测,测量规格见第 10 章。?

2. 支持矩阵速览

一张图交代全景(绿=完整支持,黄=部分,灰=generic 标量路径):

图 1:RVV10 覆盖矩阵——线性代数核心全绿,超越函数全 generic,int8 仅 m1 无 GEMM
图 1:RVV10 覆盖矩阵——线性代数核心全绿,超越函数全 generic,int8 仅 m1 无 GEMM

精确值补充(矩阵中"部分"两档的含义):

数据类型包体系GEMM 微核(gebp)超越函数补充
float32m1/m2/m4 C有 Cgeneric C算子最全的主力类型
float64m1/m2/m4 C有 Cgeneric C
complex\<float\>m1/m2/m4 C有(含实×复混合)C——2026-05-05 加入
complex\<double\>gebp 特化覆盖 C有 C——包声明弱于 complex\<f\>
half(FP16)Packet1Xh/2Xh C有 Ccast 到 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 边界修复 + 包声明重构),此后进入维护期但未停止。

图 2:RVV10 提交节奏——2026-05 峰值 8 个提交,2026-02~04 空窗,08 月仍有 4 个
图 2:RVV10 提交节奏——2026-05 峰值 8 个提交,2026-02~04 空窗,08 月仍有 4 个

关键节点:C

日期提交内容
2025-11-2049623d0c初始 RVV1.0 支持(Chip Kerchner)
2025-12-05~163 个提交predux_half 修复、FP16 GCC 编译修复、BF16 基础包运算
2026-01-075d9beb81GEBP(GEMM 微核)激活
2026-05-01~268 个提交complex 向量 API(4b325d6b)、VLEN=128/1024 编译修复、包声明样板简化
2026-06~0810 个提交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。

图 3:RVV10 编译门控链——不定义宏走 Generic 标量路径,定义宏但不固定 VLEN 直接 #error
图 3:RVV10 编译门控链——不定义宏走 Generic 标量路径,定义宏但不固定 VLEN 直接 #error

典型编译组合(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 意味着一档硬件一个二进制,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。

图 4:gebp 微核 madd 机制——B 标量广播 + vfmadd_vf 向量-标量融合乘加
图 4:gebp 微核 madd 机制——B 标量广播 + vfmadd_vf 向量-标量融合乘加

这个设计的两个工程要点:C

  1. 走 vf(向量-标量)而非 vv(向量-向量):RHS 用广播标量避免了 B 矩阵的向量装载,寄存器压力只来自 A 块与累加器。代价是 B 元素复用率受限于广播宽度——这是与 AVX 微核(同样用 broadcast)一致的经典取舍。
  2. 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=1vfmadd.vf(4 lane)4×
RVV10 LMUL=4vfmadd.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

  1. 立即可做(零新硬件):GEMM 密集且 VLEN 统一的场景,锁 master commit 试用(编译组合见第 4 章)。
  2. 低成本参数扫描:DEFAULT_LMUL=1/2/4 三档 × 代表性 GEMM 尺寸矩阵。
  3. 需决策:VLEN 不统一时按档出包,或维持标量路径——部署成本见第 4 章工程含义。
  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 核实)

官方发布信息

规范级(C*)


附:方法说明

检索与核实: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 后端",被源码直接推翻。本版起存在性结论一律以源码树为一手证据。