const tvm = {}; tvm.ModelFactory = class { async match(context) { const identifier = context.identifier; const extension = identifier.lastIndexOf('.') > 0 ? identifier.split('.').pop().toLowerCase() : ''; if (extension === 'json') { const obj = await context.peek('json'); if (obj && Array.isArray(obj.nodes) && Array.isArray(obj.arg_nodes) && Array.isArray(obj.heads) && obj.nodes.every((node) => node && (node.op === 'null' || node.op === 'tvm_op'))) { return context.set('tvm.json', obj); } } const stream = context.stream; const signature = [0xB7, 0x9C, 0x04, 0x05, 0x4F, 0x8D, 0xE5, 0xF7]; if (stream && signature.length <= stream.length && stream.peek(signature.length).every((value, index) => value === signature[index])) { return context.set('tvm.params'); } return null; } filter(context, match) { return context.type !== 'tvm.json' || match.type !== 'tvm.params'; } async open(context) { const metadata = await context.metadata('tvm-metadata.json'); let obj = null; let params = null; switch (context.type) { case 'tvm.json': { obj = context.value; const identifier = context.identifier.replace(/\.json$/, '.params'); try { const content = await context.fetch(identifier); const reader = await content.read('binary'); params = tvm.NDArray.loadParams(reader); } catch { // continue regardless of error } break; } case 'tvm.params': { const identifier = context.identifier.replace(/\.params$/, '.json'); try { const content = await context.fetch(identifier); obj = await content.read('json'); } catch { // continue regardless of error } const reader = await context.read('binary'); params = tvm.NDArray.loadParams(reader); break; } default: throw new tvm.Error(`Unsupported TVN format '${context.type}'.`); } return new tvm.Model(metadata, obj, params); } }; tvm.Model = class { constructor(metadata, obj, params) { this.format = 'TVM'; this.modules = [new tvm.Graph(metadata, obj, params)]; } }; tvm.Graph = class { constructor(metadata, obj, params) { this.nodes = []; this.inputs = []; this.outputs = []; const tensors = new Map(); if (params) { for (const [name, value] of params) { const shape = new tvm.TensorShape(value.shape); const type = new tvm.TensorType(value.dtype, shape); const tensor = new tvm.Tensor(name, type, value.data); tensors.set(name, tensor); } } const values = new Map(); values.map = (name, type, tensor) => { if (!values.has(name)) { values.set(name, new tvm.Value(name, type || null, tensor || null)); } else if (type || (tensor && tensor !== values.get(name).initializer)) { throw new tvm.Error(`Duplicate value '${name}'.`); } return values.get(name); }; const updateOutput = (nodes, input) => { const [nodeIndex, outputIndex] = input; const node = nodes[nodeIndex]; if (node) { while (outputIndex >= node.outputs.length) { node.outputs.push([nodeIndex, node.outputs.length]); } } return [nodeIndex, outputIndex]; }; if (obj) { const nodes = obj.nodes; const inputs = {}; const outputs = {}; for (const node of nodes) { node.outputs = []; } for (const node of nodes) { node.inputs = node.inputs || []; node.inputs = node.inputs.map((input) => updateOutput(nodes, input)); } const arg_nodes = new Map(obj.arg_nodes.map((index) => [index, index < nodes.length ? nodes[index] : null])); for (let i = 0; i < obj.heads.length; i++) { const head = obj.heads[i]; const identifier = updateOutput(nodes, head); const name = `output${(i === 0) ? '' : (i + 1)}`; const signature = outputs[name]; const type = signature && signature.data_shape ? new tvm.TensorType(-1, new tvm.TensorShape(signature.data_shape)) : null; const value = values.map(`[${identifier.join(',')}]`, type); const argument = new tvm.Argument(name, [value]); this.outputs.push(argument); } const filtered = nodes.filter((node, index) => !arg_nodes.has(index)); const initializers = new Map(); for (const node of filtered) { for (const input of node.inputs) { const identifier = `[${input.join(',')}]`; if (!initializers.has(identifier)) { const [index] = input; const arg_node = arg_nodes.get(index); if (arg_node && arg_node.name && (!arg_node.inputs || arg_node.inputs.length === 0) && (arg_node.outputs && arg_node.outputs.length === 1)) { if (tensors.has(arg_node.name)) { initializers.set(identifier, tensors.get(arg_node.name)); arg_nodes.delete(index); } } } } if (node.params) { for (const param of node.params) { values.map(param.id, null, tensors.get(param.id)); } } } for (const [, arg_node] of arg_nodes) { if (arg_node && (!arg_node.inputs || arg_node.inputs.length === 0) && (arg_node.outputs && arg_node.outputs.length === 1)) { const identifier = `[${arg_node.outputs[0].join(',')}]`; const name = arg_node.name; const signature = inputs[name]; const type = signature && signature.data_shape ? new tvm.TensorType(-1, new tvm.TensorShape(signature.data_shape)) : null; const value = values.map(identifier, type, tensors.get(identifier)); const argument = new tvm.Argument(name, [value]); this.inputs.push(argument); } } for (const node of filtered) { this.nodes.push(new tvm.Node(metadata, node, initializers, values)); } } else if (params) { const blocks = new Map(); const separator = Array.from(params.keys()).every((key) => key.indexOf('_') !== -1) ? '_' : ''; for (const [key] of params) { const parts = separator ? key.split(separator) : [key]; let argumentName = parts.pop(); if (key.endsWith('moving_mean') || key.endsWith('moving_var')) { argumentName = [parts.pop(), argumentName].join(separator); } const nodeName = parts.join(separator); if (!blocks.has(nodeName)) { blocks.set(nodeName, { name: nodeName, op: 'Weights', params: [] }); } blocks.get(nodeName).params.push({ name: argumentName, id: key }); values.map(key, null, tensors.get(key)); } for (const block of blocks.values()) { const node = new tvm.Node(metadata, block, new Map(), values); this.nodes.push(node); } } } }; tvm.Argument = class { constructor(name, value, type = null, visible = true) { this.name = name; this.value = value; this.type = type; this.visible = visible; } }; tvm.Value = class { constructor(name, type, initializer = null) { if (typeof name !== 'string') { throw new tvm.Error(`Invalid value identifier '${JSON.stringify(name)}'.`); } this.name = !name && initializer && initializer.name ? initializer.name : name; this.type = !type && initializer && initializer.type ? initializer.type : type; this.initializer = initializer; } }; tvm.Node = class { constructor(metadata, node, initializers, values) { this.type = { name: node.op }; this.name = node.name; this.attributes = Object.entries(node.attrs || {}).map(([name, value]) => new tvm.Argument(name, value)); this.inputs = (node.inputs || []).map((input, index) => { const name = index.toString(); const identifier = `[${input.join(',')}]`; const value = values.map(identifier, null, initializers.get(identifier)); return new tvm.Argument(name, [value]); }); this.outputs = (node.outputs || []).map((output, index) => { const name = index.toString(); const value = values.map(`[${output.join(',')}]`); return new tvm.Argument(name, [value]); }); for (const param of node.params || []) { const value = values.map(param.id); const argument = new tvm.Argument(param.name, [value]); this.inputs.push(argument); } } }; tvm.Tensor = class { constructor(name, type, data) { this.name = name; this.type = type; this.values = data; this.encoding = '<'; } }; tvm.TensorType = class { constructor(dtype, shape) { let type = ''; switch (dtype.code) { // TVMArgTypeCode case 0: type = 'int'; break; case 1: type = 'uint'; break; case 2: type = 'float'; break; default: throw new tvm.Error(`Unsupported data type code '${dtype.code}'.`); } if (dtype.lanes !== 1) { throw new tvm.Error(`Unsupported data type lanes '${dtype.lanes}'.`); } this.dataType = `${type}${dtype.bits}`; this.shape = shape; } toString() { return this.dataType + this.shape.toString(); } }; tvm.TensorShape = class { constructor(dimensions) { this.dimensions = Array.isArray(dimensions) ? dimensions.map((dim) => typeof dim === 'bigint' ? dim.toNumber() : dim) : dimensions; } toString() { if (this.dimensions) { if (this.dimensions.length === 0) { return ''; } return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`; } return ''; } }; tvm.NDArray = class { static loadParams(reader) { // https://github.com/apache/tvm/blob/main/src/runtime/file_utils.cc reader = new tvm.BinaryReader(reader); const header = reader.read(8); const signature = [0xB7, 0x9C, 0x04, 0x05, 0x4F, 0x8D, 0xE5, 0xF7]; if (!header.every((value, index) => value === signature[index])) { throw new tvm.Error('Invalid signature.'); } reader.skip(8); // reserved const names = reader.strings(); const values = new Array(reader.uint64().toNumber()); if (names.length !== values.length) { throw new tvm.Error('Invalid parameters.'); } const params = new Map(); for (let i = 0; i < values.length; i++) { const value = new tvm.NDArray(reader); params.set(names[i], value); } return params; } constructor(reader) { // https://github.com/apache/tvm/blob/main/include/tvm/runtime/ndarray.h const header = reader.read(8); const signature = [0x3F, 0xA1, 0xB4, 0x96, 0xF0, 0x40, 0x5E, 0xDD]; if (!header.every((value, index) => value === signature[index])) { throw new tvm.Error('Invalid signature.'); } reader.skip(8); // reserved this.device = { deviceType: reader.uint32(), deviceId: reader.uint32() }; this.shape = new Array(reader.uint32()); this.dtype = { code: reader.uint8(), bits: reader.uint8(), lanes: reader.uint16(), }; for (let i = 0; i < this.shape.length; i++) { this.shape[i] = reader.uint64(); } const size = reader.uint64().toNumber(); this.data = reader.read(size); } }; tvm.BinaryReader = class { constructor(reader) { this._reader = reader; } skip(offset) { this._reader.skip(offset); } read(length) { return this._reader.read(length); } uint8() { return this._reader.byte(); } uint16() { return this._reader.uint16(); } uint32() { return this._reader.uint32(); } uint64() { return this._reader.uint64(); } string() { const length = this.uint64().toNumber(); const buffer = this._reader.read(length); return String.fromCharCode.apply(null, new Uint8Array(buffer)); } strings() { const list = new Array(this.uint64().toNumber()); for (let i = 0; i < list.length; i++) { list[i] = this.string(); } return list; } }; tvm.Error = class extends Error { constructor(message) { super(message); this.name = 'Error loading TVM model.'; } }; export const ModelFactory = tvm.ModelFactory;