ONNX Runtime Web 中的 ONNX Protobuf 生成代码:onnx.js / onnx.d.ts 的生成、依赖修复与工程实践
2026/9/13 2:35:59 网站建设 项目流程

ONNX Runtime Web 中的 ONNX Protobuf 生成代码:onnx.js / onnx.d.ts 的生成、依赖修复与工程实践

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

导读

在 ONNX Runtime 的前端 JavaScript 生态中,模型文件(.onnx)本质上是遵循 ONNX 规范定义的 protobuf 序列化数据。js/web/lib/onnxjs/ort-schema/protobuf/目录存放了 ONNX 协议定义的生成代码——onnx.jsonnx.d.ts,它们是 onnxjs(ONNX Runtime 的纯 JS 推理后端)解析标准 ONNX 模型的核心依赖。本文围绕该目录的 README 展开,说明这些生成文件的来源、所依赖的 protobufjs / long 版本,以及针对两个已知 bug 的修复方案,并结合 onnxjs 的模型加载实现 剖析生成代码在真实推理链路中的用法,帮助读者理解生成代码的来龙去脉,并掌握在自身 TypeScript 项目中规避同类坑位的具体做法。

一、目录里有什么:一份生成物清单

js/web/lib/onnxjs/ort-schema/protobuf/目录内容非常精简,共 3 个文件:

文件说明
README.md本文所依据的说明文档,交代生成来源与已知问题
onnx.jsprotobufjs 生成的 ONNX 协议序列化/反序列化运行时代码
onnx.d.tsonnx.js配套的 TypeScript 类型声明文件

按 README 的说法,这两个文件是从 onnx-proto 的一个 fork(update-v9 分支)生成的,即 ONNX 协议定义的 protobuf 描述(.proto)经 protobufjs 工具链编译后的产物,并非手写代码。因此在日常开发中不应直接修改这两个文件,而应回到 proto 源定义重新生成。

从生成的onnx.d.ts内容看,这份生成物覆盖了 ONNX 完整的数据结构体系:AttributeProtoTensorProtoGraphProtoTypeProtoSparseTensorProtoModelProto等接口与枚举一应俱全,并包含Version枚举(_START_VERSION = 0IR_VERSION = 9)。onnx.js则使用protobufjs/minimal实现了完整的 encode / decode / verify / fromObject / toObject 静态方法。也就是说,这一对文件足够支撑对任意标准 ONNX 模型的二进制解析与序列化。

二、生成代码的版本依赖:protobufjs@7.2.4 与 long@5.2.3

README 明确指出生成代码基于以下两个运行时依赖:

  • protobufjs@7.2.4:protobuf 编解码的运行时核心,生成的onnx.js顶部即require('protobufjs/minimal')
  • long@5.2.3:用于表示 64 位整数的库(ONNX 的int64字段在 JS 中会映射为Long类型)。

这两个版本号在仓库的包配置中可以得到印证:

  • js/web/package.json 的dependencies中声明了"protobufjs": "^7.2.4""long": "^5.2.3"
  • js/node/package.json 中同样声明了"protobufjs": "^7.2.4"

注意^前缀意味着安装时会解析到 7.x / 5.x 的最新兼容版本,因此这里记录的 7.2.4 / 5.2.3 是生成时的基准版本,也是 README 描述两个 bug 时的上下文版本。

为什么 64 位整数会成为问题焦点

在生成的onnx.d.ts中,凡是 ONNX 规范里定义为int64的字段(如AttributeProto.iAttributeProto.intsModelProto.irVersion等),其类型都被声明为number | Long

/** AttributeProto i */ i?: (number|Long|null);

在 onnxjs 的运行时逻辑中,这些Long值会被统一转换为普通 JS number 再参与计算。例如 js/web/lib/onnxjs/util.ts 中的LongUtil.longToNumber()就专门处理这一转换:

static longToNumber(n: Long | bigint | number) { if (Long.isLong(n)) { return n.toNumber(); } else if (typeof n === 'bigint') { return Number(n); } return n; }

该工具方法被用于转换irVersion、opset 版本、张量维度dims、节点属性等所有可能出现Long的场景(参见 js/web/lib/onnxjs/model.ts 对irVersionopsetImport的读取)。可见long库在生成代码与运行时中都是关键依赖,其类型声明一旦出错,整个编译链路都会受影响——这正是 README 记录第二个 bug 的背景。

三、bug 1:long@5.2.3 的 CommonJS 类型导出问题与 postinstall 修复

问题现象

README 记载 long@5.2.3 存在两个 bug,第一个是:

type export does not work with commonjs

即 long 包的类型导出在 CommonJS 模块体系下不生效,会导致 TypeScript 编译期报出类型缺失或解析失败的错误。这是 long.js 的已知问题(对应 dcodeIO/long.js 的 PR #124 所讨论的内容)。

修复方案

README 给出的做法是为 long 添加一个 "postinstall" 脚本,在依赖安装完成后自动执行修补。在 ONNX Runtime 的包配置中可以看到同源思路的具体实现,例如 js/web/package.json 中的:

"preprepare": "node -e \"require('node:fs').copyFileSync('./node_modules/long/index.d.ts', './node_modules/long/umd/index.d.ts')\""

该命令在 prepare 阶段把 long 包根目录的index.d.ts复制到umd/子目录,确保 CommonJS/UMD 入口能够解析到类型声明。虽然仓库内这条命令挂在preprepare上,其本质与 README 所述的 postinstall 修补策略一致:在安装/构建阶段以脚本方式对 node_modules 中的 long 进行类型文件修补,从而绕开上游类型导出缺陷

给开发者的启示

在自己的项目中若遇到long类型解析失败,可参考此模式:在package.json中挂一个postinstall(或preprepare)脚本,将正确的.d.ts拷贝到模块解析预期的位置;若不想维护脚本,也可在tsconfig.jsonpaths中为long显式指定类型声明路径。

四、bug 2:onnx.d.ts 中的 import 语句需要改写

问题现象

第二个 bug 出现在生成的 TypeScript 声明文件onnx.d.ts内部。protobufjs 生成器产出的声明文件包含如下语句:

import Long = require('long');

这种import ... = require(...)写法在部分 TypeScript 编译配置(尤其是使用 ESM /esModuleInterop语义的项目)下会引发类型导入错误。

修复方案

README 明确记载该行已被替换为 ES 模块风格的默认导入:

import Long from 'long';

这一替换已经完成,并已对onnx.d.ts应用了代码格式化。验证仓库中的 js/web/lib/onnxjs/ort-schema/protobuf/onnx.d.ts,文件开头正是:

import Long from 'long'; import * as $protobuf from 'protobufjs';

说明修复已实际落地。这也是为什么该文件虽然是"生成代码",却在版本库中被保留为已修补状态的原因——直接重新生成会覆盖这一手写修复,因此在升级 protobufjs 或重新生成时,需要再次应用同样的替换。

深层原因

import Long = require('long')是 TypeScript 针对 CommonJS 模块的专用导入语法,仅允许在 CommonJS 模块目标下使用;当项目以 ESM 方式编译或启用严格模块检查时,会报错。改为import Long from 'long'后,借助esModuleInterop的默认导入语义即可兼容两种模块体系。

五、生成代码在 onnxjs 中的实际调用链

理解生成代码的用途,最直观的方式是看它如何被 onnxjs 消费。onnxjs 的标准 ONNX 模型加载入口在 js/web/lib/onnxjs/model.ts:

import { onnx } from './ort-schema/protobuf/onnx'; // ... private loadFromOnnxFormat(buf: Uint8Array, graphInitializer?: Graph.Initializer): void { const modelProto = onnx.ModelProto.decode(buf); const irVersion = LongUtil.longToNumber(modelProto.irVersion); if (irVersion < 3) { throw new Error('only support ONNX model with IR_VERSION>=3'); } this._opsets = modelProto.opsetImport.map((i) => ({ domain: i.domain as string, version: LongUtil.longToNumber(i.version!), })); this._graph = Graph.from(modelProto.graph!, graphInitializer); }

调用链如下:

  1. onnx.ModelProto.decode(buf)将模型二进制(Uint8Array)反序列化为ModelProto对象——这一步由生成的onnx.js完成;
  2. 通过LongUtil.longToNumberirVersion与 opset 版本从Long转为 number,并校验 IR_VERSION ≥ 3;
  3. 取出modelProto.graph,交给Graph.from()构建内部图结构(js/web/lib/onnxjs/graph.ts 同目录依赖该类型)。

onnx.d.ts中的onnx命名空间类型(onnx.ModelProtoonnx.GraphProtoonnx.AttributeProto等)为整个 onnxjs 的图构建、属性解析、张量处理模块提供了类型约束,例如 js/web/lib/onnxjs/attribute.ts、js/web/lib/onnxjs/tensor.ts 均导入该命名空间类型。可以说,onnx.js+onnx.d.ts是 onnxjs 理解标准 ONNX 模型二进制格式的"翻译层"。

六、工程实践要点总结

综合 README 与仓库源码,围绕这份生成代码的工程实践可以归纳为以下几点:

  1. 区分生成物与手写代码onnx.js/onnx.d.ts由 onnx-proto fork(update-v9 分支)经 protobufjs 生成,改 proto 定义后需重新生成,而不是手改生成物;
  2. 锁定依赖版本上下文:生成代码依赖protobufjs@7.2.4long@5.2.3,升级这些库后应回归验证生成代码与新版本运行时是否兼容;
  3. 记住两处手写修补
    • long 的 CommonJS 类型导出问题,通过安装后脚本拷贝index.d.ts修复;
    • onnx.d.ts中的import Long = require('long')已被替换为import Long from 'long',重新生成后需再次应用;
  4. 验证手段:修改或升级后,可通过 onnxjs 的模型加载路径(Model.loadonnx.ModelProto.decode)跑通一个标准.onnx模型的加载来确认序列化层工作正常。

这些经验不仅适用于 ONNX Runtime Web 仓库本身,对于任何依赖 protobufjs 生成代码 + long 64 位整数的 TypeScript 项目都有直接参考价值。

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

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

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

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

立即咨询