一、Plugin EP:为什么"不用重编 ONNX Runtime"很重要
ONNX Runtime 通过 Execution Provider(EP)机制支持不同硬件后端:CPU EP、CUDA EP、TensorRT EP、QNN EP 等等。传统上,新增一个硬件后端意味着把代码合进 ONNX Runtime 主仓、然后整体重新编译一份带该后端的 ORT。这条路的代价对使用方极其不友好:你不能再用 pip install onnxruntime 装官方包,必须使用厂商定制版本;ORT 每次升级你都要等厂商跟进重编;两个厂商的定制版还没法共存。
摩尔线程这次走的是out-of-tree Plugin EP 路线:遵循 onnxruntime-qnn 的产品骨架,基于 ORT 的 kernel-registry Plugin EP C API,最终产物是一个独立的动态库加一个 Python wheel,可以注册进社区官方的 stock pip install onnxruntime,无需重新编译 ONNX Runtime。
这个架构选择的价值可以用三个场景说明。场景一:升级解耦——ORT 发布新版本时,只要 Plugin EP C API 保持稳定,你可以先升 ORT、后升插件,而不必等厂商出整包。场景二:多后端共存——同一套环境里可以同时装 MUSA 插件与其他插件,按模型选择 EP。场景三:交付简化——给客户交付时不再需要分发几百 MB 的定制 ORT,只需一个插件包,客户原有的 ONNX Runtime 依赖声明完全不用改。
二、依赖与版本兼容矩阵(注意 MUSA 版本与其他课程不同)
MUSA_HOME 指向的版本,并做好构建失败的心理准备——这是跨课程操作时最容易被忽略的一处版本错配。| 依赖项 | 要求 | 说明 |
|---|---|---|
| MUSA Toolkit | 5.1.0 | 默认路径 /usr/local/musa,可用 -DMUSA_HOME= 覆盖 |
| 链接库 | MUSA runtime / muBLAS / muDNN | 三者均需可用 |
| C++ 标准 | C++20 | 需要 std::span,项目已弃用 GSL |
| 编译器 | GCC 11+ 或 Clang 14+ | 低于此版本无法编译 |
| ORT 源码 | submodule 固定于 tag v1.26.0 | commit 8c546c37,稀疏检出仅 include 目录 |
| Python(运行时) | >= 3.11 | 注意与 Mooncake 的 cp310 要求不同 |
| onnxruntime | 1.26.0 | 社区官方包即可 |
| onnx | 1.21.0 | |
| 构建系统 | CMake | 入口脚本 ./build.sh |
值得注意的是 submodule 的处理方式:项目并不需要完整的 ORT 源码树,只需要头文件来对接 Plugin EP C API,因此使用稀疏检出(sparse checkout)只拉取 include 目录。这既大幅缩短了首次拉取时间,也从工程上强化了"我们只依赖公开 API、不碰内部实现"这一边界约定。
三、Step-by-Step 构建流程
Step 1:工具链检查
# C++20 与编译器版本(必须 GCC 11+ 或 Clang 14+)
g++ --version
echo '#include
int main(){ int a[3]={1,2,3}; std::span s(a); return s.size()==3?0:1; }' > /tmp/t.cpp
g++ -std=c++20 /tmp/t.cpp -o /tmp/t && /tmp/t && echo "C++20 std::span ok"
# CMake
cmake --version
# MUSA Toolkit:确认版本为 5.1.0
ls -d /usr/local/musa
cat /usr/local/musa/version* 2>/dev/null || mcc --version
Step 2:拉取仓库与初始化 submodule
git clone https://github.com/MooreThreads/onnxruntime-musa.git
cd onnxruntime-musa
# 稀疏检出 ORT v1.26.0(commit 8c546c37)的 include 目录
./scripts/init_onnxruntime_submodule.sh
# 核对仓库结构
ls musa/ep/src # C++ Plugin EP 实现
ls musa/ep/python # Python wheel 打包
Step 3:构建
# 默认构建(MUSA_HOME 取 /usr/local/musa)
./build.sh
# MUSA 安装在非默认路径时显式指定
./build.sh -DMUSA_HOME=/opt/musa-5.1.0
# 需要干净重建时
./build.sh --clean
Step 4:安装并注册到 stock ONNX Runtime
# 运行时依赖:注意 Python >= 3.11
python -V
pip install onnxruntime==1.26.0 onnx==1.21.0
# 安装构建产物 wheel(文件名以实际构建输出为准)
pip install dist/*.whl
# 确认 stock onnxruntime 未被替换
python -c "import onnxruntime as ort; print(ort.__version__, ort.__file__)"
Step 5:验证 EP 已注册并可用
python - <<'PY'
import numpy as np
import onnxruntime as ort
print("ORT version:", ort.__version__)
print("available EPs:", ort.get_available_providers())
# 用一个最小模型做端到端冒烟:构造 -> 推理 -> 与 CPU EP 比对
import onnx
from onnx import helper, TensorProto
X = helper.make_tensor_value_info("X", TensorProto.FLOAT, [4, 8])
W = helper.make_tensor_value_info("W", TensorProto.FLOAT, [8, 8])
Y = helper.make_tensor_value_info("Y", TensorProto.FLOAT, [4, 8])
node = helper.make_node("MatMul", ["X", "W"], ["Y"])
graph = helper.make_graph([node], "mm", [X, W], [Y])
model = helper.make_model(graph, opset_imports=[helper.make_opsetid("", 17)])
onnx.save(model, "/tmp/mm.onnx")
x = np.random.rand(4, 8).astype(np.float32)
w = np.random.rand(8, 8).astype(np.float32)
cpu = ort.InferenceSession("/tmp/mm.onnx", providers=["CPUExecutionProvider"])
ref = cpu.run(None, {"X": x, "W": w})[0]
# EP 名称以 get_available_providers() 实际返回值为准
musa_eps = [p for p in ort.get_available_providers() if "MUSA" in p.upper()]
if not musa_eps:
print("MUSA EP not registered — check wheel installation")
else:
sess = ort.InferenceSession("/tmp/mm.onnx", providers=[musa_eps[0]])
out = sess.run(None, {"X": x, "W": w})[0]
print("max abs diff:", float(np.abs(out - ref).max()))
PY
这个验证脚本刻意做成"先跑 CPU EP 拿参考值,再跑 MUSA EP 比对"的形式。对任何新硬件后端,能跑通不等于算对,数值比对必须是验证流程的一部分。MatMul 是最好的第一个测试算子,因为它同时覆盖 muBLAS 链路与内存搬运链路。
四、窗口内已实现的图优化与融合
根据仓库提交记录,2026-07-27 至 07-31 期间的 38 次提交主要集中在图优化与算子实现两个方向:
- MHA fusion:把多头注意力的散装算子融合成单个 kernel,减少中间张量与访存往返。
- ms_mha 算子:对应 ORT 的 Microsoft 域多头注意力算子实现。
- fp32 flash attention:FP32 精度下的 FlashAttention 实现。
- SDPA matcher 修复:修正 scaled-dot-product-attention 的图模式匹配,避免漏匹配导致回落到未融合路径。
- linear fusion 与 reshape-gemm-relu fusion:常见的线性层组合融合。
- strided view fusion:消除跨步视图带来的额外拷贝。
- split 快路径:针对 split 算子的专用优化路径。
- stream-ordered allocator 修复(MR115):流序内存分配器修复,与多流并发执行的正确性直接相关。
- 构建加速:缩短编译时间。
从这份清单可以判断项目的成熟阶段:算子覆盖与图融合正在密集补齐,基础设施(分配器、构建)仍在修 bug。这意味着它适合用于评估验证与预研,把它直接放进生产关键链路需要充分的自测——尤其要覆盖数值比对与长稳测试。
五、排障与回滚
| 现象 | 根因 | 处理 |
|---|---|---|
编译报 std::span 未定义 | 未启用 C++20 或编译器过旧 | 升级到 GCC 11+/Clang 14+ |
| CMake 找不到 MUSA | MUSA_HOME 未设置或指向 5.2.0 | -DMUSA_HOME= 显式指向 5.1.0 安装目录 |
| submodule 为空 | 未执行初始化脚本 | ./scripts/init_onnxruntime_submodule.sh |
| 链接报缺 muBLAS/muDNN | Toolkit 安装不完整 | 补装组件,检查 LD_LIBRARY_PATH |
get_available_providers() 无 MUSA | wheel 未安装或 ORT 版本不是 1.26.0 | 核对 onnxruntime==1.26.0 并重装插件 |
| Python import 失败 | Python 低于 3.11 | 切换到 3.11+ 环境 |
| 数值与 CPU EP 不一致 | 命中在修的融合路径 | 逐个关闭图优化定位,向仓库提 issue |
回滚极其简单,这正是 out-of-tree Plugin EP 架构的最大红利:只需 pip uninstall 掉插件 wheel,你的 onnxruntime==1.26.0 环境立刻回到纯 CPU EP 状态,业务代码一行不用改(前提是你的推理代码用 providers= 参数做了优雅降级,而不是硬编码 MUSA EP)。建议在生产代码里始终写成"优先 MUSA EP、失败则回落 CPU EP"的形式:
import onnxruntime as ort
def make_session(model_path):
avail = ort.get_available_providers()
prefer = [p for p in avail if "MUSA" in p.upper()]
providers = prefer + ["CPUExecutionProvider"]
sess = ort.InferenceSession(model_path, providers=providers)
print("running on:", sess.get_providers()[0])
return sess
onnxruntime-qnn 骨架、基于 ORT kernel-registry Plugin EP C API,产出单一动态库加 Python wheel,注册进社区 stock pip install onnxruntime,无需重编 ORT。构建侧四个硬约束务必记牢:MUSA Toolkit 5.1.0(与本站其他课程的 5.2.0 基线不同)、C++20 与 GCC 11+/Clang 14+(需 std::span,已弃用 GSL)、ORT submodule 固定在 v1.26.0(8c546c37,稀疏检出仅 include)、运行时 Python ≥ 3.11 + onnxruntime 1.26.0 + onnx 1.21.0。验证必须包含与 CPU EP 的数值比对,生产代码应写成"优先 MUSA EP、失败回落 CPU EP",这样回滚就只是一次 pip uninstall。来源:onnxruntime-musa README、提交记录。待合并案例:一次推理成功还不够
截至 2026-10-15,PR #6 仍为开放状态,作者报告在 S5000 / MUSA 4.3.8 上验证 40 个公开 ONNX 模型,每个模型重复三轮,并检查数值相似度与 NaN/Inf。这里是 PR 作者自报结果,不代表主线已经提供相同覆盖,也不替换上文 ORT 1.26 的构建基线。ONNX Runtime MUSA PR #6
该方案涉及 MemcpyFromHost / MemcpyToHost、CPU 侧 Slice 元数据以及 Resize 的 CPU 回退等边界;说明中提到重复执行时的 memory-pattern 问题,因此课程验收应包括第二次及后续运行。ONNX Runtime MUSA PR #6
设计 CPU / GPU 分区与重复执行实验
- 前置条件:能运行原课程 CPU EP 数值对照。先记录模型哈希、opset、输入 shape / dtype、ORT 与插件 commit、CPU/GPU/OS/驱动/SDK 完整清单;本 PR 不足以补齐所有版本。
- 保留原来的可运行镜像,候选 PR 只进入隔离环境。未合并代码不得覆盖正式服务。
- 对固定输入运行 CPU 基线,再运行候选 EP;至少连续三次,并分别测试固定形状和模型实际支持的动态形状。
- 记录每轮输出误差、NaN/Inf、设备分配、CPU 回退节点及拷贝路径;相似度门槛应按模型任务预先定义,不把其他模型的阈值照搬。
- 量化 CPU 回退与数据搬运的端到端影响,不能用“GPU 注册成功”证明所有算子都在 GPU 上。
- 若第二轮报错、shape 变化失败或精度超限,保存最小复现并回滚整套环境。待修复后重跑全部轮次。
这是受 PR 问题启发的验证方法,而非本站已运行的 40 模型测试报告。提交记录时将“作者自报”“本地复现”“主线合并”“正式发行”分为不同栏位。ONNX Runtime MUSA PR #6
状态更新:PR #6 已合并并进入 v0.2.0
上一轮记录时 PR #6 仍开放。本轮核对:该 PR 已于 2026-10-26 合入 master(merge commit 932eb0c9058811da004f42b1e4bae0769082023d),并列入 2026-11-10 发布的 v0.2.0。同一 Release 还包括 grouped / depthwise 卷积支持(PR #3)和 CI 工作流(PR #9)。ONNX Runtime MUSA PR #6 ONNX Runtime MUSA v0.2.0 Release
PR #6 标题中的“40 public models pass on S5000”仍是作者报告的覆盖范围(作者测试条件为 MUSA 4.3.8),Release 本身没有给出逐模型清单;不要据此推断其他 GPU、SDK 或模型均已支持。ONNX Runtime MUSA PR #6
CI 只能按具体 run 说明
PR #9 新增 musa-ci.yml 与 musa-weekly-model-zoo.yml,作者列出单元测试、model zoo 与 CPU EP 对比、周度端到端性能。ONNX Runtime MUSA PR #9 2026-11-09 的周度运行在 self-hosted / musa / s5000 标签的 runner 上成功,对应 commit 7e12a22bec,即 v0.2.0 标签 commit。这只说明该 commit 在该 runner 上通过,不代表每个后续 commit 或其他平台都通过。Weekly Model Zoo run 42545489186
升级到 v0.2.0 的验收
- 保留原课程的构建产物与上一轮的 CPU 基线结果,作为回滚与对照。
- 按 v0.2.0 标签重新构建插件,记录 commit、ORT 版本与 MUSA SDK 版本;SDK 版本以仓库当前文档为准,与其他课程可能不同。
- 复用上一节设计的连续三轮实验,重点复查 Slice、Resize 回退和第二轮执行;新增卷积模型时加入 grouped / depthwise 用例。
- 记录栏位分为“Release 说明”“作者自报”“CI run”“本地复现”。本站尚未运行本地复现。