ONNX Runtime Node.js 示例详解:从 Tensor 构造到 InferenceSession 创建的完整实战指南
【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime
本文基于 ONNX Runtime 仓库samples/nodejs目录下的官方 Node.js 示例,逐一走读「基础推理」「Tensor 创建」「InferenceSession 创建」三个完整可运行的示例项目。读完后,你将掌握在 Node.js 环境中调用onnxruntime-node加载 ONNX 模型、以多种合法方式构造输入 Tensor(含各类型与维度的约束规则)、以及通过文件路径 / Buffer / ArrayBuffer 三种方式创建推理会话的完整流程,并能对照源码理解每个 API 背后的参数校验逻辑。
示例总览与运行方式
Node.js 示例位于仓库的 samples/nodejs,每个示例都是一个独立的 npm 子项目。根据该目录 README 的说明,在任一示例的实现子目录中执行以下两条命令即可运行:
npm install node ./三个示例的定位如下(引自 samples/nodejs/README.md):
- Basic Usage — ONNX Runtime Node.js 绑定的基础用法演示;
- Create Tensor — 以不同方式创建 Tensor 的基础用法演示;
- Create InferenceSession — 演示以不同方式创建
InferenceSession。
三个示例的依赖完全一致,均以onnxruntime-node为核心依赖,版本要求^1.20.1,见 01_basic-usage/package.json:
{ "name": "01_basic-usage", "version": "1.0.0", "main": "index.js", "dependencies": { "onnxruntime-node": "^1.20.1" } }其中main字段指向index.js,这也正是node ./之所以能直接运行示例的原因——Node.js 会按main入口加载并执行index.js。另外可以注意到目录编号从02直接跳到04,仓库中当前只保留了这三个示例子目录。
示例一:基础推理流程(01_basic-usage)
01_basic-usage 展示了最典型的「加载模型 → 构造输入 → 运行推理 → 读取输出」四步流程。其 index.js 完整代码如下:
'use strict'; const ort = require('onnxruntime-node'); // use an async context to call onnxruntime functions. async function main() { try { // create a new session and load the specific model. // // the model in this example contains a single MatMul node // it has 2 inputs: 'a'(float32, 3x4) and 'b'(float32, 4x3) // it has 1 output: 'c'(float32, 3x3) const session = await ort.InferenceSession.create('./model.onnx'); // prepare inputs. a tensor need its corresponding TypedArray as data const dataA = Float32Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]); const dataB = Float32Array.from([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]); const tensorA = new ort.Tensor('float32', dataA, [3, 4]); const tensorB = new ort.Tensor('float32', dataB, [4, 3]); // prepare feeds. use model input names as keys. const feeds = { a: tensorA, b: tensorB }; // feed inputs and run const results = await session.run(feeds); // read from results const dataC = results.c.data; console.log(`data of result tensor 'c': ${dataC}`); } catch (e) { console.error(`failed to inference ONNX model: ${e}.`); } } main();模型结构与张量布局的对应关系
示例自带的 model.onnx 是一个仅含单个 MatMul 节点的极简模型,代码注释中明确给出了它的规格:
| 张量 | 角色 | 类型 | 形状 |
|---|---|---|---|
a | 输入 | float32 | 3×4(12 个元素) |
b | 输入 | float32 | 4×3(12 个元素) |
c | 输出 | float32 | 3×3(9 个元素) |
代码中dataA与dataB各自提供了 12 个 float32 数值,恰好等于对应形状的元素总数,这与 02_create-tensor 中「维度乘积必须等于数据长度」的校验规则相互印证。
三个关键 API 调用点
ort.InferenceSession.create('./model.onnx'):传入模型文件路径创建会话,返回 Promise,必须await。这也是为什么整个推理逻辑都放在async function main()中执行。new ort.Tensor('float32', dataA, [3, 4]):Tensor 构造的第一个参数是数据类型,第二个参数是与之匹配的 TypedArray(float32 对应Float32Array),第三个参数是维度数组。注释特别强调「a tensor need its corresponding TypedArray as data」——数据类型必须与 TypedArray 类型一一对应。await session.run(feeds):feeds是一个以「模型输入名」为键、Tensor 为值的对象({ a: tensorA, b: tensorB }),返回结果同样以「模型输出名」为键组织,因此可以直接用results.c.data取出输出张量的底层数据。
整个流程用try/catch包裹,推理失败时打印failed to inference ONNX model: ${e}.,这是示例中给出的标准错误处理模式。
示例二:Tensor 的合法创建方式与常见错误(02_create-tensor)
02_create-tensor 系统演示了 Tensor 的各种创建姿势以及哪些写法会抛错,是理解 Tensor 构造约束的最佳参照。其 index.js 覆盖了五类合法用法和四类典型错误。
五类合法创建方式
1. 多维 float32 张量(显式指定类型)
const buffer01 = new Float32Array(24); buffer01[0] = 0.1; // fill buffer data const tensor01 = new Tensor('float32', buffer01, [2, 3, 4]);2. 省略类型、由数据推断类型
const tensor01_B = new Tensor(buffer01, [2, 3, 4]);注释说明type 'float32' can be omitted and the type is inferred from data——当第一个参数直接是 TypedArray 时,构造器会按数据推断类型。这与 tensor-impl.ts 中的构造函数重载签名一致:既有constructor(type, data, dims?)形式,也有constructor(data, dims?)的形式。
3. bool 张量(类型不可省略)
const buffer02 = new Uint8Array(2); buffer02[0] = 1; // true buffer02[1] = 0; // false const tensor02 = new Tensor('bool', buffer02, [1, 2]);这里有个容易踩的坑:bool和uint8都使用Uint8Array作为底层数据,仅凭数据无法区分,因此创建 bool 张量时必须显式写明'bool',否则会被推断为uint8。
4. 标量张量与一维张量(维度可省略)
// 标量 float64 张量 const tensor03 = new Tensor(new Float64Array(1), []); tensor03.data[0] = 1.0; // setting data after tensor is created is allowed // 一维张量 const tensor04 = new Tensor(new Float32Array(100), [100]); const tensor04_B = new Tensor(new Float32Array(100)); // dims can be omitted if it is a 1-D tensor.两点值得注意:空维度数组[]表示标量;数据长度本身就能推出维度时(一维情形),dims参数可整体省略。此外,Tensor 创建之后仍然允许直接写入.data(示例中tensor03.data[0] = 1.0),这在「先创建、后填充」的场景下很有用。
5. string 张量(字符串数组数据)
const tensor05 = new Tensor('string', ['a', 'b'], [1, 2]); const tensor05_B = new Tensor(['a', 'b'], [1, 2]); // type 'string' can be omitted字符串张量的数据是readonly string[],'string'类型同样可以省略由数据推断。
四类会抛错的错误用法
示例的后半部分专门给出了四个反例(均包裹在try/catch中吞掉异常),这些约束可以直接作为代码评审清单:
// 1. 数据类型与 TypedArray 不匹配 const tensor = new Tensor('float64', new Float32Array(100)); // 'float64' must use with Float64Array as data. // 2. 维度含负数 const tensor = new Tensor(new Float32Array(100), [1, 2, -1]); // negative dims is not allowed. // 3. 标量张量的数据长度必须为 1 const tensor = new Tensor(new Float32Array(0), []); // 4. 维度乘积与数据长度不一致 const tensor = new Tensor(new Float32Array(40), [5, 6]); // 5 * 6 != 40这四类校验在底层 Tensor 实现中均有对应逻辑:tensor-impl-type-mapping.ts 维护了数值类型与 TypedArray 的双向映射(NUMERIC_TENSOR_TYPE_TO_TYPEDARRAY_MAP/NUMERIC_TENSOR_TYPEDARRAY_TO_TYPE_MAP),构造时checkTypedArray()负责校验数据类型与 TypedArray 是否匹配;而「维度乘积等于数据长度」的检查则由 tensor-utils-impl.ts 中的calculateSize完成——tensor-impl.ts 的构造函数正是 import 了它来做尺寸校验。
示例三:InferenceSession 的三种创建方式(04_create-inference-session)
04_create-inference-session 专注于InferenceSession.create()的调用形态差异。其 index.js 完整展示了从文件路径、Node.js Buffer、ArrayBuffer 三种来源加载模型的方式:
'use strict'; const fs = require('fs'); const util = require('util'); const InferenceSession = require('onnxruntime-node').InferenceSession; async function main() { try { // session options const options = { intraOpNumThreads: 1 }; // // 方式一:从 ONNX 模型文件路径创建 // const session01 = await InferenceSession.create('./model.onnx'); const session01_B = await InferenceSession.create('./model.onnx', options); // 指定 options // // 方式二:从 Node.js Buffer(Uint8Array)创建 // const buffer02 = await util.promisify(fs.readFile)('./model.onnx'); // buffer is Uint8Array const session02 = await InferenceSession.create(buffer02); const session02_B = await InferenceSession.create(buffer02, options); // 指定 options // // 方式三:从 ArrayBuffer 创建(需指定 byteOffset / byteLength) // const arrayBuffer03 = buffer02.buffer; const offset03 = buffer02.byteOffset; const length03 = buffer02.byteLength; const session03 = await InferenceSession.create(arrayBuffer03, offset03, length03); const session03_B = await InferenceSession.create(arrayBuffer03, offset03, length03); } catch (e) { console.error(`failed to inference ONNX model: ${e}.`); } } main();三种创建方式与参数签名
从 inference-session-impl.ts 的静态方法重载声明可以精确对上示例中的每种调用形态:
static create(path: string, options?: SessionOptions): Promise<InferenceSessionInterface>; static create(buffer: ArrayBufferLike, options?: SessionOptions): Promise<InferenceSessionInterface>; static create( buffer: ArrayBufferLike, byteOffset: number, byteLength?: number, options?: SessionOptions ): Promise<InferenceSessionInterface>; static create(buffer: Uint8Array, options?: SessionOptions): Promise<InferenceSessionInterface>;| 方式 | 适用场景 | 关键参数说明 |
|---|---|---|
文件路径string | 模型落盘为本地文件时 | 第二参数是options,必须是对象,否则抛TypeError |
Uint8Array/ Node.js Buffer | 从网络下载、从内存中读取的模型二进制 | Node.js 的Buffer是Uint8Array的子类,可直接传入 |
ArrayBuffer/SharedArrayBuffer | 模型数据嵌在更大的内存块中 | 需要byteOffset(必填时从 0 起)与byteLength定位模型所在区间 |
从实现代码 inference-session-impl.ts 可以看出几处严格的参数校验逻辑,值得在使用时留意:
- 当第一个参数是字符串或
Uint8Array时,第二个参数若给出,必须是对象('options' must be an object.),传其他类型会抛TypeError; - 当第一个参数是
ArrayBuffer时,第二个参数有两种解释:若是number则视为byteOffset,必须是安全整数且落在[0, buffer.byteLength)区间内,否则抛RangeError; - 第三个参数
byteLength若给出,必须满足byteLength > 0 && byteOffset + byteLength <= buffer.byteLength,第三个参数之后才允许再传options。
示例中使用buffer02.byteOffset/buffer02.byteLength来取值,正是为了正确处理 Buffer 视图相对于底层 ArrayBuffer 的偏移——在 Node.js 中fs.readFile返回的 Buffer 底层可能复用一个更大的 ArrayBuffer,直接拿buffer.buffer而不传偏移量会导致加载到错误的数据区间。这正是示例三比示例二多出的实用细节。
示例中的options = { intraOpNumThreads: 1 }表示限制算子内部并行线程数为 1。SessionOptions的完整字段说明在本示例中被有意省略(README 注明 "For more information aboutSessionOptions, please refer to other examples"),可结合onnxruntime-node的公开 API 文档进一步查阅;本仓库 js/common/lib 目录下的session-options.ts等文件定义了这些选项的接口形态。
会话的释放
创建会话后,示例代码未显式释放,但实现中提供了release()方法(见 inference-session-impl.ts),在长时间运行的 Node.js 服务中反复创建/销毁会话时,调用await session.release()释放原生资源是推荐做法。
小结与延伸阅读
三个示例构成了一条完整的 Node.js 使用路径:
- 01_basic-usage 给出「建会话 → 造 Tensor → run → 读结果」的最小完整闭环,模型为单 MatMul 节点(
a: 3×4、b: 4×3、c: 3×3); - 02_create-tensor 穷举了 float32/float64/bool/string/标量/一维等张量创建姿势,并给出了「类型-数据类型匹配、维度非负、乘积等于长度」等四条硬性校验规则;
- 04_create-inference-session 覆盖文件路径、Buffer、ArrayBuffer 三种模型加载来源及其参数校验细节。
底层实现可进一步对照 js/common/lib/inference-session-impl.ts 与 js/common/lib/tensor-impl.ts 阅读;若你的目标平台是浏览器而非 Node.js,仓库中的 js/web 提供了对应的 Web 端绑定与测试。所有示例均可通过npm install && node ./在各自子目录中直接复现,配合 model.onnx 这类随附的小模型即可完成端到端验证。
【免费下载链接】onnxruntimeONNX Runtime: cross-platform, high performance ML inferencing and training accelerator项目地址: https://gitcode.com/GitHub_Trending/on/onnxruntime
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考