ONNX Runtime 算子内核支持矩阵详解:OperatorKernels.md 的生成机制、执行提供程序差异与源码级解析
2026/9/13 3:47:15 网站建设 项目流程

ONNX Runtime 算子内核支持矩阵详解:OperatorKernels.md 的生成机制、执行提供程序差异与源码级解析

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

OperatorKernels.md 是 ONNX Runtime 仓库中一张"算子 × 执行提供程序(Execution Provider,EP)× OpSet 版本 × 数据类型"的支持矩阵总表,直接决定了"某个模型在某条硬件路径上能不能跑、用哪种精度跑"。本文以该文档为主体,逐节解读其结构(版本记号法、EP 章节、算子域分组),结合 gen_opkernel_doc.py 的生成逻辑与 build.py 的 CI 流程,还原这张表是如何从内核注册表中自动产出的;并以 Clip、Cast、Gemm 等真实算子为线索,对照 CPU 实现、CUDA 与 DML 的注册代码,说明三个 EP 在类型覆盖上的关键差异(如 CUDA 的 fp16/bf16 优先策略、DML 的 float/float16 限定与com.microsoft.dml专属融合算子域),帮助你在选型 EP、排查"算子不支持/类型不支持"错误时快速定位依据。

一、文档主体:三个执行提供程序的算子支持总览

OperatorKernels.md 的目录部分只列出三个 EP:

  • CPUExecutionProvider(CPU,第 23 行起,锚点#cpuexecutionprovider
  • CUDAExecutionProvider(CUDA,第 661 行起,锚点#cudaexecutionprovider
  • DmlExecutionProvider(DirectML,第 1194 行起,锚点#dmlexecutionprovider

这不是因为 ONNX Runtime 只有这三个 EP,而是文档生成时被刻意限制了一个子集:build.py 中generate_documentation(第 2366~2396 行)在构建后调用:

python gen_opkernel_doc.py --output_path <source>/docs/OperatorKernels.md \ --providers CPU CUDA DML

源码注释写得很直白:"we currently limit the documentation created by a build to a subset of EP's. Run get_opkernel_doc.py directly if you need/want documentation from other EPs that are enabled in the build."(第 2382~2383 行)。也就是说,XNNPACK、TensorRT、ROCM、OpenVINO、CoreML 等 EP 的支持表不会出现在这份 checked-in 文档里;若你的构建里启用了这些 EP,可以自行完整运行生成脚本(不带--providers过滤)得到全量表格。

每个 EP 章节内部按"算子域(domain)"分块,用一行加粗的分隔行标注,例如|**Operator Domain:** *ai.onnx*|||。三个 EP 的域覆盖各有侧重:

执行提供程序文档中出现的算子域特点
CPUExecutionProviderai.onnxai.onnx.mlcom.microsoft基线实现最全,com.microsoft域含 MoE、BeamSearch、MatMulNBits 等 70 余个贡献算子
CUDAExecutionProviderai.onnxai.onnx.mlcom.microsoftcom.ms.internal.nhwc额外覆盖 NHWC 内部域(Conv/ConvTranspose/BatchNormalization等 NHWC 变体,第 1153~1188 行)
DmlExecutionProviderai.onnxcom.microsoftcom.microsoft.dml独有com.microsoft.dml域(第 1618 行起):DmlFusedAddDmlFusedConvDmlFusedGemmDmlFusedMatMul等 9 个融合算子,均为tensor(float), tensor(float16)

注意ai.onnx.ml(传统机器学习域:TreeEnsemble、LinearClassifier、SVM 等)只有 CPU EP 提供了完整实现,CUDA 域里仅列了LabelEncoder(第 1066~1068 行)——这也印证了 CUDA EP 聚焦深度学习算子的定位。

二、版本记号法(Version Notation)如何读

文档开头(第 5~12 行)定义了 OpSet Version 列的三种记号,这与生成脚本 gen_opkernel_doc.py 的format_version_range(第 14~21 行)一一对应:

记号含义生成逻辑
N(如13仅注册到 opset N 这一个版本v[0] == v[1]时输出单值
[N, M](如[6, 12]注册到 opset N 到 M(含两端)区间且终点为有限值
N+(如16+从 opset N 起注册,并延续到后续版本,直到被更新的注册"接管"终点>= 2147483647(即 INT_MAX)时输出N+

N+的"被更新注册接管"这一点在真实表格里随处可见。例如 CPU EP 的Acos(第 30~31 行)同时存在22+tensor(float))与[7, 21]两行:opset 22 的算子 Schema 变化后,内核注册拆成了新的版本段。而 CUDA 的Acos只有7+一行(文档中 CUDA 章节按类型列出),DML 则显示7+——同一算子在不同 EP 的版本切分粒度不同,直接反映各 EP 注册内核时选择的start~end版本区间写法。

三、表格的生成机制:从注册表到 Markdown

OperatorKernels.md 是自动生成的,文件头声明"Do not modify directly",对应生成器 gen_opkernel_doc.py。其核心数据流是:

  1. 拉取算子 Schemartpy.get_all_operator_schema()(第 114 行)遍历所有 ONNX 算子定义,把每个算子的输入/输出名与类型串拼成 Parameters 列文本(*in* X:**T**形式,第 122~139 行),按domain.name存进paramdict。同一算子在不同 opset 下 Schema 可能不同(如Clipinput/min/max三输入形态与单输入形态),脚本用集合去重后以<br><br>or<br><br>分隔并列展示——这就是表格里"or"多形态的来源。
  2. 拉取内核注册表rtpy.get_all_opkernel_def()(第 149 行)返回所有已注册内核,按provider → domain → op_name三级索引(第 148~153 行)。
  3. 合并类型约束:对每个算子,把各版本区间(op.version_range)下的类型约束(op.type_constraints)聚合成version_type_index[opset 区间][T] = {类型集合}(第 181~185 行),再按版本从新到旧输出(reverse=True,第 188 行)——所以表格中同一算子的多行总是"最新版本段在最上"。
  4. 插件 EP 扩展--plugin-ep NAME:PATH(第 61~93、219~226 行)会通过ort.register_execution_provider_library加载插件 EP 动态库(例如CUDAExecutionProvider:/path/to/libonnxruntime_providers_cuda.so),再经get_registered_ep_kernel_defs把其内核并入索引。这是为"插件化 EP"(以独立动态库形式分发的 EP)补充支持表的官方途径。

CI 的防漂移校验:build.py 第 2398~2422 行在文档生成后会git diff比对 docs/OperatorKernels.md 与docs/ContribOperators.md,若有差异即抛出BuildError("Generated documents have diffs")。也就是说,任何改动了内核注册的 PR(新增算子、增删类型约束、调整版本区间)都必须重新生成该文档,否则 CI 直接失败——这也是文档与实现永不失同步的保证。

四、注册链路:一张表背后的源码结构

表格里的每一条记录,源头都是各 EP 在编译期通过KernelDefBuilder注册的内核。以 CPU EP 的Clip为例(clip.cc 第 12~52 行):

ONNX_CPU_OPERATOR_VERSIONED_KERNEL( Clip, 6, 10, KernelDefBuilder().MayInplace(0, 0) .TypeConstraint("T", DataTypeImpl::GetTensorType<float>()), Clip_6<float>); // opset 11 允许 float;opset 12 起扩展为 double/MLFloat16/int8/... ORT_SPECIFY_OP_KERNEL_ARG_DEFAULT_TYPES( kCpuExecutionProvider, kOnnxDomain, Clip, 12, Input, 0, float, MLFloat16, double, int8_t, uint8_t, int32_t, uint32_t, int64_t, uint64_t);

要点:

  • 宏的第一个版本段Clip, 6, 10恰好对应文档 CPU 表格中Clip[6, 10]行(**T** = tensor(float),OperatorKernels.md 第 85 行);
  • 下方op_kernel_type_control命名空间的ORT_SPECIFY_OP_KERNEL_ARG_DEFAULT_TYPES声明了"启用类型列表",再由BuildKernelDefConstraintsFromTypeList<...>()展开为TypeConstraint——文档第 82~85 行Clip13+(含tensor(int32)...tensor(uint8)十种类型)、1211(仅tensor(float))、[6, 10]四行,正是不同版本段启用类型列表的并集呈现;
  • EP 名称常量统一维护在 constants.h(如第 32~34 行的kCpuExecutionProvider = "CPUExecutionProvider"),这正是gen_opkernel_doc.py --providers参数注释中"Matches provider names from constants.h"所指的来源;
  • 内核定义(KernelDef,含version_rangetype_constraints,即文档生成脚本直接消费的两个字段)集中在 kernel_registry.h / kernel_info.h,会话装配时由 kernel_registry_manager.h 管理查找。从源码结构看,文档表格就是"注册表字段 → Markdown 行"的直译。

运行时语义:模型中的算子节点要能在某个 EP 上执行,需同时满足三条——算子在 EP 的内核注册表中存在、模型的算子 opset 落在该内核的version_range内、实际张量类型属于该版本段的类型约束集合。三者任一不满足,算子就会"落回"其它 EP(通常是 CPU)。

五、逐 EP 解读支持矩阵

5.1 CPUExecutionProvider:类型覆盖最广的基线

CPU 章节(第 23~657 行)覆盖ai.onnx全量主干算子 + 大量扩展:

  • 量化/反量化QuantizeLinear25+段支持float8e4m3fnfloat8e5m2int2int4uint2uint4等低位宽与 FP8 目标类型(第 329 行),DequantizeLinear对应段支持int2/int4源类型(第 121 行)——CPU EP 是低比特量化的参考实现;
  • LLM 基础设施Attention24+,Q/K 为float, float16,第 54 行)、TensorScatter(KV cache 写入,第 510 行)、RMSNormalizationRotaryEmbedding(ai.onnx 域23+),以及com.microsoft域的GroupQueryAttentionMoEQMoEBeamSearchGreedySearchSamplingPagedAttention之外的多种 attention 变体(第 570~643 行);
  • 控制流与序列If/Loop/ScanV类型约束覆盖了optional(...)seq(...)组合类型(第 210、246、418 行),Sequence*全家族齐备;
  • 字符串StringConcatStringNormalizerStringSplitRegexFullMatch,以及Casttensor(string)的支持(第 72 行)。

5.2 CUDAExecutionProvider:fp16/bf16 优先

CUDA 章节(第 661~1189 行)的类型列表呈现明显的"半精度优先"策略:

  • Gemm/MatMul13+段为bfloat16, double, float, float16(第 777、846 行);
  • 对比 CPU 的Gemmdouble, float两种,第 177 行)——CUDA 多出的float16/bfloat16是 Tensor Core 路径的前提;
  • Cast25+段额外出现tensor(float4e2m1)(第 690 行),这是 CPU EP 没有的 FP4 类型,对应 CUDA 硬件新特性;
  • 独有com.ms.internal.nhwc域(第 1153 行起):ConvConvTransposeBatchNormalizationMaxPool等的 NHWC 布局变体(float, float16),从源码结构看是 CUDA EP 内部用于布局优化重写(NCHW→NHWC)的中间层算子,不对外暴露;
  • ai.onnx.ml域仅LabelEncoder一项(第 1067 行)。

5.3 DmlExecutionProvider:float/float16 限定与 DML 融合域

DML 章节(第 1194 行起):

  • 类型基本限定在tensor(float), tensor(float16),个别算子扩展到 int 系列(如Add14+段覆盖 10 种数值类型,第 1203 行);无 bfloat16、无 FP8 主线路径(com.microsoft.dml域算子亦仅为float, float16);
  • 独有com.microsoft.dml域(第 1618 行起)的 9 个DmlFused*算子(DmlFusedConvDmlFusedGemmDmlFusedMatMulDmlFusedAddDmlFusedBatchNormalizationDmlFusedInstanceNormalizationDmlFusedMeanVarianceNormalizationDmlFusedSum等),是把"计算 + 激活/归一化"融合的 DirectML 专属算子,配合com.microsoft域的FusedMatMulActivationNhwcConv等共同构成 DML 路径的融合算子族;
  • 版本记号更"粗":很多算子直接写N+(如Reshape21+/19+/14+/13+/5+),说明 DML 的内核注册习惯上以更宽的版本段承接后续 opset。

六、实战:查询、再生成与插件 EP 支持表

6.1 查表决策流

判断"模型算子能否走 EP X":

  1. 在 docs/OperatorKernels.md 对应 EP 章节找到算子(注意域:ai.onnx之外需核对com.microsoft等);
  2. 确认模型 opset 落在某个版本段(N[N, M]N+)内;
  3. 核对实际张量类型是否在该行Types Supported中。例如:Clip想走 DML 用int32,其13+段包含tensor(int32)(第 1244 行)可以;若用double则不在列表内,会被重划到 CPU。

6.2 本地再生成文档

前提:已构建出包含 Python 绑定的 ONNX Runtime,并在构建目录下import onnxruntime可用。

# 全量(构建中启用的所有 EP) python tools/python/gen_opkernel_doc.py --output_path /tmp/OperatorKernels.md # 只看 CPU 与 CUDA(--providers 大小写不敏感,自动补全 "ExecutionProvider" 后缀) python tools/python/gen_opkernel_doc.py --providers cpu cuda --output_path /tmp/opk_cpu_cuda.md # 纳入以插件形式分发的 EP 动态库 python tools/python/gen_opkernel_doc.py --output_path /tmp/opk_plugin.md \ --plugin-ep "CUDAExecutionProvider:/path/to/libonnxruntime_providers_cuda.so"

参数细节来自 gen_opkernel_doc.py 第 210~233 行的 argparse 定义:--providers(可选,过滤)、--plugin-epNAME:PATH形式,可多个)、--output_path(必填)。CI 中使用的正是"CPU CUDA DML"这一固定组合(见第三节),若本地构建启用了更多 EP,不带--providers即可得到更全的表格——但请注意此时产物与仓库 checked-in 版本不一致是预期行为。

6.3 常见"缺失"排查

  • 某个 EP 的章节整体不存在:因为生成时被--providers过滤掉了,按 6.2 自行生成;
  • 算子在某 EP 缺席:该 EP 未注册对应内核(如 CUDA 无RegexFullMatch、DML 无MoE/BeamSearch),运行时该算子会落回 CPU;
  • 类型不在列表:需要 EP 侧补充类型约束注册(参考 clip.cc 中ORT_SPECIFY_OP_KERNEL_ARG_DEFAULT_TYPES的写法),并同步重新生成 OperatorKernels.md 以通过 build.py 的git diff校验。

七、小结

docs/OperatorKernels.md 是 ONNX Runtime 算子内核注册表的"单一事实来源"文档:N/[N, M]/N+三种版本记号精确刻画每个内核的版本适用域;三张 EP 表格分别呈现了 CPU(全类型基线)、CUDA(fp16/bf16 + NHWC 内部域)、DML(float/fp16 + DmlFused 融合域)三条硬件路径的能力边界;而 gen_opkernel_doc.py 与 build.py 的生成 + diff 校验闭环保证了它与源码注册(KernelDefBuilder/ORT_SPECIFY_OP_KERNEL_ARG_DEFAULT_TYPES等)严格一致。对使用者,它是 EP 选型与精度决策的速查表;对贡献者,它是每次内核注册变更都必须同步更新的交付物。

【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询