7254f7b4d1
Build / Build (macos-latest) (push) Has been cancelled
Build / Build (ubuntu-latest) (push) Has been cancelled
Build / Build (windows-latest) (push) Has been cancelled
Build / Analyze (javascript) (push) Has been cancelled
Build / Analyze (python) (push) Has been cancelled
1295 lines
47 KiB
JavaScript
1295 lines
47 KiB
JavaScript
|
|
// Experimental
|
|
|
|
const executorch = {};
|
|
const coreml = {};
|
|
const vulkan = {};
|
|
const xnnpack = {};
|
|
const qnn = {};
|
|
const ethosu = {};
|
|
const openvino = {};
|
|
const rockchip = {};
|
|
|
|
import * as base from './base.js';
|
|
import * as python from './python.js';
|
|
import * as pytorch from './pytorch.js';
|
|
|
|
executorch.ModelFactory = class {
|
|
|
|
async match(context) {
|
|
const reader = await executorch.Reader.open(context);
|
|
if (reader) {
|
|
return context.set('executorch', reader);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async open(context) {
|
|
executorch.schema = await context.require('./executorch-schema');
|
|
const target = context.value;
|
|
await target.read();
|
|
return new executorch.Model(target);
|
|
}
|
|
};
|
|
|
|
executorch.Model = class {
|
|
|
|
constructor(target) {
|
|
this.format = `ExecuTorch v${target.program.version}`;
|
|
this.modules = [];
|
|
for (const plan of target.program.execution_plan) {
|
|
for (const chain of plan.chains) {
|
|
const graph = new executorch.Graph(target, plan, chain);
|
|
this.modules.push(graph);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
executorch.Graph = class {
|
|
|
|
constructor(target, plan, chain) {
|
|
this.name = plan.name || '';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
const values = new Map();
|
|
values.tensors = (index, items) => {
|
|
const list = [];
|
|
for (let i = 0; i < items.length; i++) {
|
|
const item = items[i];
|
|
const type = item ? new executorch.TensorType(item) : null;
|
|
let initializer = null;
|
|
if (item && item.data_buffer_idx > 0) {
|
|
initializer = new executorch.Tensor(item, target);
|
|
}
|
|
const identifier = items.length > 1 ? `${index}.${i}` : index.toString();
|
|
const value = new executorch.Value(identifier, type, initializer);
|
|
list.push(value);
|
|
}
|
|
return list;
|
|
};
|
|
values.map = (index, output) => {
|
|
if (output && values.has(index) && !Array.isArray(values.get(index).value)) {
|
|
const value = [new executorch.Value(index.toString(), null, null)];
|
|
values.set(index, { type: null, value });
|
|
}
|
|
if (!values.has(index)) {
|
|
const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
|
|
const val = plan.values[index].val;
|
|
const tensor = val instanceof executorch_flatbuffer.Tensor || val instanceof executorch_flatbuffer.TensorList || val instanceof executorch_flatbuffer.OptionalTensorList;
|
|
if (output && !tensor) {
|
|
const value = [new executorch.Value(index.toString(), null, null)];
|
|
values.set(index, { type: null, value });
|
|
} else if (val instanceof executorch_flatbuffer.Null) {
|
|
values.set(index, { type: 'attribute', value: null });
|
|
} else if (val instanceof executorch_flatbuffer.Int) {
|
|
values.set(index, { type: 'int64', value: val.int_val });
|
|
} else if (val instanceof executorch_flatbuffer.Bool) {
|
|
values.set(index, { type: 'int64', value: val.bool_val });
|
|
} else if (val instanceof executorch_flatbuffer.Double) {
|
|
values.set(index, { type: 'float64', value: val.double_val });
|
|
} else if (val instanceof executorch_flatbuffer.Tensor) {
|
|
const items = [val];
|
|
values.set(index, { type: null, value: values.tensors(index, items) });
|
|
} else if (val instanceof executorch_flatbuffer.String) {
|
|
values.set(index, { type: 'string', value: val.string_val });
|
|
} else if (val instanceof executorch_flatbuffer.IntList) {
|
|
const list = val.items.map((index) => plan.values[index].val.int_val);
|
|
values.set(index, { type: 'int64[]', value: list });
|
|
} else if (val instanceof executorch_flatbuffer.DoubleList) {
|
|
values.set(index, { type: 'float64[]', value: Array.from(val.items) });
|
|
} else if (val instanceof executorch_flatbuffer.BoolList) {
|
|
throw new executorch.Error('executorch_flatbuffer.BoolList not implemented.');
|
|
} else if (val instanceof executorch_flatbuffer.TensorList) {
|
|
const items = Array.from(val.items).map((arg) => arg === -1 ? null : plan.values[arg].val);
|
|
values.set(index, { type: null, value: values.tensors(index, items) });
|
|
} else if (val instanceof executorch_flatbuffer.OptionalTensorList) {
|
|
const items = Array.from(val.items).map((arg) => arg === -1 ? null : plan.values[arg].val);
|
|
values.set(index, { type: null, value: values.tensors(index, items) });
|
|
} else {
|
|
throw new Error(`Value type '${val.constructor.name}' not implemented.`);
|
|
}
|
|
}
|
|
return values.get(index);
|
|
};
|
|
for (let i = 0; i < plan.inputs.length; i++) {
|
|
const input = plan.inputs[i];
|
|
const value = values.map(input);
|
|
const name = plan.inputs.length === 1 ? 'input' : `input.${i}`;
|
|
const argument = new executorch.Argument(name, value.value, value.type);
|
|
this.inputs.push(argument);
|
|
}
|
|
for (let i = 0; i < plan.outputs.length; i++) {
|
|
const output = plan.outputs[i];
|
|
const value = values.map(output);
|
|
const name = plan.outputs.length === 1 ? 'output' : `output.${i}`;
|
|
const argument = new executorch.Argument(name, value.value, value.type);
|
|
this.outputs.push(argument);
|
|
}
|
|
const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
|
|
for (const instruction of chain.instructions) {
|
|
const instr_args = instruction.instr_args;
|
|
if (instr_args instanceof executorch_flatbuffer.JumpFalseCall ||
|
|
instr_args instanceof executorch_flatbuffer.MoveCall ||
|
|
instr_args instanceof executorch_flatbuffer.FreeCall) {
|
|
continue;
|
|
}
|
|
const node = new executorch.Node(target, plan, chain, instruction, values);
|
|
this.nodes.push(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
executorch.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
executorch.Value = class Value {
|
|
|
|
constructor(name, type, initializer = null) {
|
|
if (typeof name !== 'string') {
|
|
throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = initializer && initializer.type ? initializer.type : type || null;
|
|
this.initializer = initializer;
|
|
}
|
|
};
|
|
|
|
executorch.Node = class {
|
|
|
|
constructor(target, plan, chain, instruction, values) {
|
|
this.name = '';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.attributes = [];
|
|
const instr_args = instruction.instr_args;
|
|
const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
|
|
if (instr_args instanceof executorch_flatbuffer.KernelCall) {
|
|
const op = plan.operators[instr_args.op_index];
|
|
const name = op.name.split('::').pop();
|
|
const identifier = op.overload ? `${op.name}.${op.overload}` : op.name;
|
|
const torch = target.execution.__import__('torch');
|
|
const schemas = torch._C._jit_get_schemas_for_operator(op.name);
|
|
const schema = schemas.find((schema) => schema.name === op.name && schema.overload_name === op.overload);
|
|
if (!schema) {
|
|
throw new executorch.Error(`Operator schema for '${identifier}' not found.`);
|
|
}
|
|
const category = schema && schema.category ? schema.category : '';
|
|
const alias = (arg) => arg && arg.alias_info && arg.alias_info.before_set.length === 1 ? arg.alias_info.before_set[0] : null;
|
|
const outputs = new Set(schema && Array.isArray(schema.returns) ? schema.returns.map((arg) => alias(arg)).filter((alias) => alias !== null) : []);
|
|
const inputs = new Map();
|
|
this.type = { name, identifier, category };
|
|
let i = 0;
|
|
const args = instr_args.args;
|
|
for (; i < schema.arguments.length; i++) {
|
|
const index = args[i];
|
|
const arg = schema && i < schema.arguments.length ? schema.arguments[i] : null;
|
|
const output = arg ? alias(schema.arguments[i]) : null;
|
|
if (output && outputs.has(output)) {
|
|
inputs.set(output, index);
|
|
continue;
|
|
}
|
|
const name = arg ? arg.name : i.toString();
|
|
const value = values.map(index);
|
|
const argument = new executorch.Argument(name, value.value, value.type);
|
|
this.inputs.push(argument);
|
|
}
|
|
for (let j = 0; j < schema.returns.length; j++) {
|
|
const ret = schema.returns[j];
|
|
const output = alias(ret);
|
|
let index = args[i++];
|
|
index = output && inputs.has(output) ? inputs.get(output) : index;
|
|
const name = ret.name;
|
|
const value = values.map(index, true);
|
|
const argument = new executorch.Argument(name || '', value.value, value.type);
|
|
this.outputs.push(argument);
|
|
}
|
|
} else if (instr_args instanceof executorch_flatbuffer.DelegateCall) {
|
|
const delegate = plan.delegates[instr_args.delegate_index];
|
|
const args = instr_args.args;
|
|
if (!delegate.backend || !delegate.backend.type) {
|
|
throw new executorch.Error(`ExecuTorch delegate '${delegate.id}' not implemented.`);
|
|
}
|
|
this.type = delegate.backend.type;
|
|
const inputs = args.slice(0, this.type.inputs.length);
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
const input = inputs[i];
|
|
const value = values.map(input);
|
|
const name = inputs.length === 1 ? 'input' : `input.${i}`;
|
|
const argument = new executorch.Argument(name, value.value, value.type);
|
|
this.inputs.push(argument);
|
|
}
|
|
const outputs = args.slice(this.type.inputs.length, this.type.inputs.length + this.type.outputs.length);
|
|
for (let i = 0; i < outputs.length; i++) {
|
|
const output = outputs[i];
|
|
const value = values.map(output);
|
|
const name = outputs.length === 1 ? 'output' : `output.${i}`;
|
|
const argument = new executorch.Argument(name, value.value, value.type);
|
|
this.outputs.push(argument);
|
|
}
|
|
for (const spec of delegate.compile_specs) {
|
|
const value = spec.value instanceof Uint8Array ? new TextDecoder('utf-8').decode(spec.value) : spec.value;
|
|
const attribute = new executorch.Argument(spec.key, value, 'attribute');
|
|
this.attributes.push(attribute);
|
|
}
|
|
} else {
|
|
throw new Error(`Instruction type '${instr_args.constructor.name}' not implemented.`);
|
|
}
|
|
}
|
|
};
|
|
|
|
executorch.TensorType = class {
|
|
|
|
constructor(tensor) {
|
|
const ScalarType = executorch.schema.executorch_flatbuffer.ScalarType;
|
|
switch (tensor.scalar_type) {
|
|
case ScalarType.BYTE: this.dataType = 'uint8'; break;
|
|
case ScalarType.CHAR: this.dataType = 'int8'; break;
|
|
case ScalarType.SHORT: this.dataType = 'int16'; break;
|
|
case ScalarType.INT: this.dataType = 'int32'; break;
|
|
case ScalarType.LONG: this.dataType = 'int64'; break;
|
|
case ScalarType.HALF: this.dataType = 'float16'; break;
|
|
case ScalarType.FLOAT: this.dataType = 'float32'; break;
|
|
case ScalarType.DOUBLE: this.dataType = 'float64'; break;
|
|
case ScalarType.BFLOAT16: this.dataType = 'bfloat16'; break;
|
|
case 8: this.dataType = 'complex<float16>'; break;
|
|
case 9: this.dataType = 'complex<float32>'; break;
|
|
case 10: this.dataType = 'complex<float64>'; break;
|
|
case ScalarType.BOOL: this.dataType = 'boolean'; break;
|
|
case ScalarType.QINT8: this.dataType = 'qint8'; break;
|
|
case ScalarType.QUINT8: this.dataType = 'quint8'; break;
|
|
case ScalarType.QINT32: this.dataType = 'qint32'; break;
|
|
case 15: this.dataType = 'bfloat16'; break;
|
|
case ScalarType.QUINT4X2: this.dataType = 'quint4x2'; break;
|
|
case ScalarType.QUINT2X4: this.dataType = 'quint2x4'; break;
|
|
case 18: this.dataType = 'bits1x8'; break;
|
|
case 19: this.dataType = 'bits2x4'; break;
|
|
case 20: this.dataType = 'bits4x2'; break;
|
|
case 21: this.dataType = 'bits8'; break;
|
|
case ScalarType.BITS16: this.dataType = 'bits16'; break;
|
|
case ScalarType.FLOAT8E5M2: this.dataType = 'float8e5m2'; break;
|
|
case ScalarType.FLOAT8E4M3FN: this.dataType = 'float8e4m3fn'; break;
|
|
case ScalarType.FLOAT8E5M2FNUZ: this.dataType = 'float8e5m2fnuz'; break;
|
|
case ScalarType.FLOAT8E4M3FNUZ: this.dataType = 'float8e4m3fnuz'; break;
|
|
case ScalarType.UINT16: this.dataType = 'uint16'; break;
|
|
case ScalarType.UINT32: this.dataType = 'uint32'; break;
|
|
case ScalarType.UINT64: this.dataType = 'uint64'; break;
|
|
default: throw new executorch.Error(`Unknown tensor data type '${tensor.scalar_type}'.`);
|
|
}
|
|
this.shape = new executorch.TensorShape(Array.from(tensor.sizes));
|
|
}
|
|
|
|
toString() {
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
executorch.TensorShape = class {
|
|
|
|
constructor(dimensions = []) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
toString() {
|
|
if (this.dimensions && this.dimensions.length > 0) {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
return '';
|
|
}
|
|
};
|
|
|
|
executorch.Tensor = class {
|
|
|
|
constructor(tensor, target) {
|
|
this.type = new executorch.TensorType(tensor);
|
|
const data_buffer_idx = tensor.data_buffer_idx;
|
|
const program = target.program;
|
|
if (tensor.extra_tensor_info) {
|
|
throw new executorch.Error('Extra tensor info not implemented.');
|
|
} else if (Array.isArray(program.constant_buffer) && program.constant_buffer.length > 0) {
|
|
if (data_buffer_idx >= program.constant_buffer.length) {
|
|
throw new executorch.Error(`Constant buffer index out of range.`);
|
|
}
|
|
const buffer = program.constant_buffer[data_buffer_idx];
|
|
this.values = buffer.storage;
|
|
this.encoding = '<';
|
|
} else if (tensor.allocation_info === null) {
|
|
const constant_segment = program.constant_segment;
|
|
const data_segment = program.segments[constant_segment.segment_index];
|
|
const offset = constant_segment.offsets[data_buffer_idx];
|
|
let next = data_segment.size;
|
|
if (data_buffer_idx + 1 < constant_segment.offsets.length) {
|
|
next = constant_segment.offsets[data_buffer_idx + 1];
|
|
}
|
|
const size = next - offset;
|
|
const position = data_segment.offset + offset;
|
|
this.values = target.blob(position.toNumber(), size.toNumber());
|
|
this.encoding = '<';
|
|
} else {
|
|
throw new executorch.Error('Tensor allocation info not implemented.');
|
|
}
|
|
}
|
|
};
|
|
|
|
executorch.Reader = class {
|
|
|
|
static async open(context) {
|
|
const reader = await context.peek('flatbuffers.binary');
|
|
if (reader && reader.identifier === 'ET12') {
|
|
return new executorch.Reader(context, reader);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(context, reader) {
|
|
this.context = context;
|
|
this.reader = reader;
|
|
}
|
|
|
|
async read() {
|
|
const context = this.context;
|
|
this.metadata = await pytorch.Metadata.open(context);
|
|
this.execution = new python.Execution();
|
|
this.metadata.register(this.execution);
|
|
const executorch_flatbuffer = executorch.schema.executorch_flatbuffer;
|
|
this.program = executorch_flatbuffer.Program.create(this.reader);
|
|
this.named_data = new Map();
|
|
if (this.program.named_data) {
|
|
this.named_data = new Map(this.program.named_data.map((entry) => [entry.key, entry.segment_index]));
|
|
}
|
|
this.reader = await context.read('binary');
|
|
if (this.reader.length >= 32) {
|
|
this.reader.seek(8);
|
|
const magic = String.fromCharCode(...this.reader.read(4));
|
|
if (magic === 'eh00') {
|
|
this.extended_file_header = {
|
|
length: this.reader.uint32(),
|
|
program_size: this.reader.uint64(),
|
|
segment_base_offset: this.reader.uint64(),
|
|
};
|
|
}
|
|
this.reader.seek(0);
|
|
}
|
|
for (const plan of this.program.execution_plan) {
|
|
for (const chain of plan.chains) {
|
|
for (const instruction of chain.instructions) {
|
|
const instr_args = instruction.instr_args;
|
|
if (instr_args instanceof executorch_flatbuffer.DelegateCall) {
|
|
const delegate = plan.delegates[instr_args.delegate_index];
|
|
if (delegate.backend) {
|
|
continue;
|
|
}
|
|
let data = null;
|
|
switch (delegate.processed.location) {
|
|
case executorch_flatbuffer.DataLocation.INLINE: {
|
|
data = this.program.backend_delegate_data[delegate.processed.index].data;
|
|
break;
|
|
}
|
|
case executorch_flatbuffer.DataLocation.SEGMENT: {
|
|
const segment = this.program.segments[delegate.processed.index];
|
|
const offset = segment.offset;
|
|
const size = segment.size;
|
|
data = this.blob(offset.toNumber(), size.toNumber());
|
|
break;
|
|
}
|
|
default: {
|
|
throw new executorch.Error(`Delegate data location '${delegate.processed.location}' not implemented.`);
|
|
}
|
|
}
|
|
switch (delegate.id) {
|
|
case 'XnnpackBackend':
|
|
delegate.backend = xnnpack.Reader.open(data, this);
|
|
break;
|
|
case 'CoreMLBackend':
|
|
delegate.backend = coreml.Reader.open(data, this);
|
|
break;
|
|
case 'VulkanBackend':
|
|
delegate.backend = vulkan.Reader.open(data, this);
|
|
break;
|
|
case 'QnnBackend':
|
|
delegate.backend = qnn.Reader.open(data, this);
|
|
break;
|
|
case 'EthosUBackend':
|
|
delegate.backend = ethosu.Reader.open(data, this);
|
|
break;
|
|
case 'OpenvinoBackend':
|
|
delegate.backend = openvino.Reader.open(data, this);
|
|
break;
|
|
case 'RockchipBackend':
|
|
delegate.backend = rockchip.Reader.open(data, this);
|
|
break;
|
|
default:
|
|
throw new executorch.Error(`ExecuTorch delegate '${delegate.id}' not implemented.`);
|
|
}
|
|
// eslint-disable-next-line no-await-in-loop
|
|
await delegate.backend.read();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
blob(offset, size) {
|
|
if (this.extended_file_header) {
|
|
const segment_base_offset = this.extended_file_header.segment_base_offset;
|
|
this.reader.seek(segment_base_offset.toNumber() + offset);
|
|
const data = this.reader.read(size);
|
|
this.reader.seek(0);
|
|
return data;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
segment(key) {
|
|
if (this.named_data.has(key)) {
|
|
const segment_index = this.named_data.get(key);
|
|
if (segment_index >= 0 && segment_index < this.program.segments.length) {
|
|
const segment = this.program.segments[segment_index];
|
|
const offset = segment.offset;
|
|
const size = segment.size;
|
|
return this.blob(offset.toNumber(), size.toNumber());
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
executorch.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading ExecuTorch model.';
|
|
}
|
|
};
|
|
|
|
xnnpack.Reader = class {
|
|
|
|
static open(data, target) {
|
|
if (data.length >= 30) {
|
|
const reader = base.BinaryReader.open(data);
|
|
reader.skip(4);
|
|
const magic = String.fromCharCode(...reader.read(4));
|
|
if (magic === 'XH00') {
|
|
return new xnnpack.Reader(reader, target);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(reader, target) {
|
|
this.reader = reader;
|
|
this.target = target;
|
|
reader.skip(2);
|
|
this.flatbuffer = {
|
|
offset: reader.uint32(),
|
|
size: reader.uint32(),
|
|
};
|
|
this.constants = {
|
|
offset: reader.uint32(),
|
|
size: reader.uint32(),
|
|
};
|
|
}
|
|
|
|
async read() {
|
|
this.reader.seek(this.flatbuffer.offset);
|
|
const flatbuffers = await import('./flatbuffers.js');
|
|
const data = this.reader.read(this.flatbuffer.size);
|
|
const reader = flatbuffers.BinaryReader.open(data);
|
|
if (!executorch.schema.fb_xnnpack.XNNGraph.identifier(reader)) {
|
|
throw new xnnpack.Error('Invalid XNNPACK data.');
|
|
}
|
|
this.graph = executorch.schema.fb_xnnpack.XNNGraph.create(reader);
|
|
this.reader.seek(0);
|
|
const metadata = new xnnpack.Metadata();
|
|
this.type = new xnnpack.Graph(metadata, this.graph, this);
|
|
}
|
|
|
|
constant(idx) {
|
|
const constant_data = this.graph.constant_data[idx];
|
|
const named_key = constant_data.named_key;
|
|
if (named_key) {
|
|
return this.target.segment(named_key);
|
|
}
|
|
const offset = constant_data.offset;
|
|
const size = constant_data.size;
|
|
this.reader.seek(this.constants.offset + offset.toNumber());
|
|
const data = this.reader.read(size.toNumber());
|
|
this.reader.seek(0);
|
|
return data;
|
|
}
|
|
};
|
|
|
|
xnnpack.Graph = class {
|
|
|
|
constructor(metadata, graph, reader) {
|
|
this.name = 'XnnpackBackend';
|
|
this.type = 'graph';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
const values = new Map();
|
|
values.map = (id) => {
|
|
if (!values.has(id)) {
|
|
const fb_xnnpack = executorch.schema.fb_xnnpack;
|
|
const name = id.toString();
|
|
const xvalue = graph.xvalues[id].xvalue_union;
|
|
if (xvalue instanceof fb_xnnpack.XNNTensorValue) {
|
|
const type = new xnnpack.TensorType(xvalue);
|
|
const initializer = xvalue.constant_buffer_idx === 0 ? null : new xnnpack.Tensor(xvalue, reader);
|
|
const value = new xnnpack.Value(name, type, initializer);
|
|
values.set(id, value);
|
|
} else if (xvalue instanceof fb_xnnpack.XNNQuantizedTensorValue) {
|
|
const value = new xnnpack.Value(name, null, null);
|
|
values.set(id, value);
|
|
} else {
|
|
throw new xnnpack.Error(`Value type '${xvalue.constructor.name}' not implemented.`);
|
|
}
|
|
}
|
|
return values.get(id);
|
|
};
|
|
for (let i = 0; i < graph.input_ids.length; i++) {
|
|
const id = graph.input_ids[i];
|
|
const value = values.map(id);
|
|
const name = graph.input_ids.length === 1 ? 'input' : `input.${i}`;
|
|
const argument = new xnnpack.Argument(name, [value]);
|
|
this.inputs.push(argument);
|
|
}
|
|
for (let i = 0; i < graph.output_ids.length; i++) {
|
|
const id = graph.output_ids[i];
|
|
const value = values.map(id);
|
|
const name = graph.output_ids.length === 1 ? 'output' : `output.${i}`;
|
|
const argument = new xnnpack.Argument(name, [value]);
|
|
this.outputs.push(argument);
|
|
}
|
|
for (const xnode of graph.xnodes) {
|
|
const node = new xnnpack.Node(metadata, xnode, values);
|
|
this.nodes.push(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
xnnpack.Node = class {
|
|
|
|
constructor(metadata, xnode, values) {
|
|
const node = xnode.xnode_union;
|
|
this.type = metadata.type(node.constructor.name) || { name: node.constructor.name };
|
|
this.name = '';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
for (const [name, obj] of Object.entries(node)) {
|
|
let value = ArrayBuffer.isView(obj) ? Array.from(obj) : obj;
|
|
let type = 'attribute';
|
|
if (name.endsWith('_id')) {
|
|
value = obj === -1 || obj === 0xFFFFFFFF ? [] : [values.map(obj)];
|
|
type = null;
|
|
}
|
|
const argument = new xnnpack.Argument(name, value, type);
|
|
if (name === 'output_id') {
|
|
this.outputs.push(argument);
|
|
} else {
|
|
this.inputs.push(argument);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
xnnpack.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
xnnpack.Value = class Value {
|
|
|
|
constructor(name, type, initializer = null) {
|
|
if (typeof name !== 'string') {
|
|
throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = initializer && initializer.type ? initializer.type : type || null;
|
|
this.initializer = initializer;
|
|
}
|
|
};
|
|
|
|
xnnpack.Metadata = class {
|
|
|
|
constructor() {
|
|
this._types = new Map();
|
|
this.register('_XNNCat', 'Tensor');
|
|
this.register('_XNNNodeConv', 'Layer');
|
|
this.register('XNNArgMaxPooling2d', 'Pool');
|
|
this.register('XNNAvgPooling2d', 'Pool');
|
|
this.register('XNNCeiling', 'Activation');
|
|
this.register('XNNConcatenate2', 'Tensor');
|
|
this.register('XNNConcatenate3', 'Tensor');
|
|
this.register('XNNConcatenate4', 'Tensor');
|
|
this.register('XNNConcatenate5', 'Tensor');
|
|
this.register('XNNConv2d', 'Layer');
|
|
this.register('XNNConvTranspose2d', 'Layer');
|
|
this.register('XNNDepthwiseConv2d', 'Layer');
|
|
this.register('XNNELU', 'Activation');
|
|
this.register('XNNFullyConnected', 'Layer');
|
|
this.register('XNNGelu', 'Activation');
|
|
this.register('XNNGlobalAvgPooling2d', 'Pool');
|
|
this.register('XNNGlobalAvgPooling2d', 'Pool');
|
|
this.register('XNNHardswish', 'Activation');
|
|
this.register('XNNLeakyReLU', 'Activation');
|
|
this.register('XNNMaxPooling2d', 'Pool');
|
|
this.register('XNNPReLU', 'Activation');
|
|
this.register('XNNSigmoid', 'Activation');
|
|
this.register('XNNSoftmax', 'Activation');
|
|
this.register('XNNTanh', 'Activation');
|
|
this.register('XNNStaticTranspose', 'Transform');
|
|
}
|
|
|
|
register(name, category) {
|
|
this._types.set(name, { name, category });
|
|
}
|
|
|
|
type(name) {
|
|
return this._types.get(name);
|
|
}
|
|
};
|
|
|
|
xnnpack.TensorType = class {
|
|
|
|
constructor(tensor) {
|
|
xnnpack.TensorType._types = executorch.TensorType._types || [
|
|
'invalid', 'float32', 'float16',
|
|
'qint8', 'quint8', 'qint32',
|
|
'qcint8', 'qcint32', 'qcint4',
|
|
'qdint8', 'qbint4', 'qpint8',
|
|
'int32', 'pfp32', 'bfloat16'
|
|
];
|
|
if (tensor.datatype >= xnnpack.TensorType._types.length) {
|
|
throw new xnnpack.Error(`Unknown tensor data type '${tensor.datatype}'.`);
|
|
}
|
|
this.dataType = xnnpack.TensorType._types[tensor.datatype];
|
|
this.shape = new xnnpack.TensorShape(Array.from(tensor.dims));
|
|
}
|
|
|
|
toString() {
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
xnnpack.TensorShape = class {
|
|
|
|
constructor(dimensions = []) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
toString() {
|
|
if (this.dimensions && this.dimensions.length > 0) {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
return '';
|
|
}
|
|
};
|
|
|
|
xnnpack.Tensor = class {
|
|
|
|
constructor(tensor, reader) {
|
|
this.type = new xnnpack.TensorType(tensor);
|
|
this.values = reader.constant(tensor.constant_buffer_idx);
|
|
this.encoding = '<';
|
|
}
|
|
};
|
|
|
|
xnnpack.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading XNNPACK model.';
|
|
}
|
|
};
|
|
|
|
vulkan.Reader = class {
|
|
|
|
static open(data, target) {
|
|
if (data.length >= 30) {
|
|
const reader = base.BinaryReader.open(data);
|
|
reader.skip(4);
|
|
const magic = String.fromCharCode(...reader.read(4));
|
|
if (magic === 'VH00') {
|
|
return new vulkan.Reader(reader, target);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(reader, target) {
|
|
this.reader = reader;
|
|
this.target = target;
|
|
reader.skip(2);
|
|
this.flatbuffer = {
|
|
offset: reader.uint32(),
|
|
size: reader.uint32(),
|
|
};
|
|
this.constants = {
|
|
offset: reader.uint32(),
|
|
size: reader.uint32(),
|
|
};
|
|
}
|
|
|
|
async read() {
|
|
this.reader.seek(this.flatbuffer.offset);
|
|
const metadata = new vulkan.Metadata(this.target.execution);
|
|
const flatbuffers = await import('./flatbuffers.js');
|
|
const data = this.reader.read(this.flatbuffer.size);
|
|
const reader = flatbuffers.BinaryReader.open(data);
|
|
if (!executorch.schema.vkgraph.VkGraph.identifier(reader)) {
|
|
throw new xnnpack.Error('Invalid Vuklan data.');
|
|
}
|
|
this.graph = executorch.schema.vkgraph.VkGraph.create(reader);
|
|
this.reader.seek(0);
|
|
this.type = new vulkan.Graph(metadata, this.graph, this);
|
|
}
|
|
|
|
constant(id) {
|
|
const constant = this.graph.constants[id];
|
|
if (constant.named_key && constant.offset === 0xffffffffffffffffn) {
|
|
return this.target.segment(constant.named_key);
|
|
}
|
|
const offset = constant.offset;
|
|
const length = constant.length;
|
|
this.reader.seek(this.constants.offset + offset.toNumber());
|
|
const data = this.reader.read(length.toNumber());
|
|
this.reader.seek(0);
|
|
return data;
|
|
}
|
|
};
|
|
|
|
vulkan.Graph = class {
|
|
|
|
constructor(metadata, graph, reader) {
|
|
this.name = 'VulkanBackend';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
const values = new Map();
|
|
values.map = (id) => {
|
|
if (!values.has(id)) {
|
|
const vkgraph = executorch.schema.vkgraph;
|
|
const arg = graph.values[id].value;
|
|
if (arg instanceof vkgraph.VkTensor) {
|
|
const type = new vulkan.TensorType(arg);
|
|
const initializer = arg.constant_id === -1 ? null : new vulkan.Tensor(arg, reader);
|
|
const value = new vulkan.Value(id.toString(), type, initializer);
|
|
values.set(id, { type: null, value: [value] });
|
|
} else if (arg instanceof vkgraph.Int) {
|
|
values.set(id, { type: 'int64', value: arg.int_val });
|
|
} else if (arg instanceof vkgraph.IntList) {
|
|
values.set(id, { type: 'int64[]', value: Array.from(arg.items) });
|
|
} else if (arg instanceof vkgraph.Double) {
|
|
values.set(id, { type: 'float64', value: arg.double_val });
|
|
} else if (arg instanceof vkgraph.Bool) {
|
|
values.set(id, { type: 'boolean', value: arg.bool_val });
|
|
} else if (arg instanceof vkgraph.Null) {
|
|
values.set(id, { type: 'attribute', value: null });
|
|
} else {
|
|
throw new Error(`Value type '${arg.constructor.name}' not implemented.`);
|
|
}
|
|
}
|
|
return values.get(id);
|
|
};
|
|
for (let i = 0; i < graph.input_ids.length; i++) {
|
|
const id = graph.input_ids[i];
|
|
const value = values.map(id);
|
|
const name = graph.input_ids.length === 1 ? 'input' : `input.${i}`;
|
|
const argument = new vulkan.Argument(name, value.value, value.type);
|
|
this.inputs.push(argument);
|
|
}
|
|
for (let i = 0; i < graph.output_ids.length; i++) {
|
|
const id = graph.output_ids[i];
|
|
const value = values.map(id);
|
|
const name = graph.output_ids.length === 1 ? 'output' : `output.${i}`;
|
|
const argument = new vulkan.Argument(name, value.value, value.type);
|
|
this.outputs.push(argument);
|
|
}
|
|
for (const op of graph.chain) {
|
|
const node = new vulkan.Node(metadata, op, values);
|
|
this.nodes.push(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
vulkan.Node = class {
|
|
|
|
constructor(metadata, op, values) {
|
|
const schema = metadata.type(op.name);
|
|
if (!schema) {
|
|
throw new vulkan.Error(`Operator schema for '${op.name}' not found.`);
|
|
}
|
|
this.type = {
|
|
name: op.name.split(/\.([^.]*)$/)[0],
|
|
identifier: op.name,
|
|
category: schema.category || ''
|
|
};
|
|
this.name = op.node_id.toString();
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.attributes = [];
|
|
for (let i = 0; i < op.args.length; i++) {
|
|
const arg = op.args[i];
|
|
const input = schema && i < schema.arguments.length;
|
|
const def = input ? schema.arguments[i] : schema.returns[i - schema.arguments.length];
|
|
const value = values.map(arg);
|
|
const argument = new vulkan.Argument(def.name || '', value.value, value.type);
|
|
if (input) {
|
|
this.inputs.push(argument);
|
|
} else {
|
|
this.outputs.push(argument);
|
|
}
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
vulkan.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
vulkan.Value = class Value {
|
|
|
|
constructor(name, type, initializer = null) {
|
|
if (typeof name !== 'string') {
|
|
throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = initializer && initializer.type ? initializer.type : type || null;
|
|
this.initializer = initializer;
|
|
}
|
|
};
|
|
|
|
vulkan.TensorType = class {
|
|
|
|
constructor(tensor) {
|
|
const types = ['bool', 'uint8', 'int8', 'int32', 'float16', 'float32', 'float64', 'int64'];
|
|
if (tensor.datatype >= types.length) {
|
|
throw new vulkan.Error(`Unknown tensor data type '${tensor.datatype}'.`);
|
|
}
|
|
this.dataType = types[tensor.datatype];
|
|
this.shape = new vulkan.TensorShape(Array.from(tensor.dims));
|
|
const vkgraph = executorch.schema.vkgraph;
|
|
if (tensor.memory_layout !== vkgraph.VkMemoryLayout.DEFAULT_LAYOUT) {
|
|
this.denotation = vkgraph.VkMemoryLayout[tensor.memory_layout];
|
|
if (!this.denotation) {
|
|
throw new vulkan.Error(`Unsupported memory layout '${tensor.memory_layout}'.`);
|
|
}
|
|
}
|
|
if (tensor.storage_type !== vkgraph.VkStorageType.DEFAULT_STORAGE) {
|
|
this.layout = vkgraph.VkStorageType[tensor.storage_type];
|
|
if (!this.layout) {
|
|
throw new vulkan.Error(`Unsupported storage type '${tensor.storage_type}'.`);
|
|
}
|
|
}
|
|
}
|
|
|
|
toString() {
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
vulkan.TensorShape = class {
|
|
|
|
constructor(dimensions = []) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
toString() {
|
|
if (this.dimensions && this.dimensions.length > 0) {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
return '';
|
|
}
|
|
};
|
|
|
|
vulkan.Tensor = class {
|
|
|
|
constructor(tensor, reader) {
|
|
this.type = new vulkan.TensorType(tensor);
|
|
this.values = reader.constant(tensor.constant_id);
|
|
this.encoding = '<';
|
|
}
|
|
};
|
|
|
|
vulkan.Metadata = class {
|
|
|
|
constructor(execution) {
|
|
this.execution = execution;
|
|
}
|
|
|
|
register(signature) {
|
|
const torch = this.execution.__import__('torch');
|
|
const registry = torch._C.getRegistry();
|
|
const schema = torch.FunctionSchema.parse(signature);
|
|
const op = new torch._C.Operator(schema);
|
|
registry.registerOperator(op);
|
|
}
|
|
|
|
type(identifier) {
|
|
identifier = identifier.split(/\.([^.]*)$/);
|
|
let name = identifier[0].replace('.', '::');
|
|
if (name.indexOf('::') === -1) {
|
|
name = `et_vk::${name}`;
|
|
}
|
|
const overload = identifier[1] === 'default' ? '' : identifier[1];
|
|
const torch = this.execution.__import__('torch');
|
|
const schemas = torch._C._jit_get_schemas_for_operator(name);
|
|
const schema = schemas.find((schema) => schema.name === name && schema.overload_name === overload);
|
|
return schema;
|
|
}
|
|
};
|
|
|
|
vulkan.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading Vulkan model.';
|
|
}
|
|
};
|
|
|
|
coreml.Reader = class {
|
|
|
|
static open(data, target) {
|
|
const reader = base.BinaryReader.open(data);
|
|
return new coreml.Reader(reader, target);
|
|
}
|
|
|
|
constructor(reader, target) {
|
|
this.reader = reader;
|
|
this.target = target;
|
|
}
|
|
|
|
async factory() {
|
|
const coreml = await import('./coreml.js');
|
|
return new coreml.ModelFactory();
|
|
}
|
|
|
|
async read() {
|
|
const entries = this.entries(this.reader);
|
|
const factory = await this.factory();
|
|
const streams = new Map();
|
|
for (const [path, location] of entries) {
|
|
streams.set(path, this.stream(location.offset, location.size));
|
|
}
|
|
const context = this.target.context;
|
|
for (const [key] of streams) {
|
|
const content = context.context(key, streams.get(key), streams);
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const type = await factory.match(content);
|
|
if (type === 'coreml.manifest') {
|
|
// eslint-disable-next-line no-await-in-loop
|
|
const model = await factory.open(content);
|
|
[this.type] = model.modules;
|
|
this.type.name = 'CoreMLBackend';
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
stream(offset, size) {
|
|
this.reader.seek(offset);
|
|
const stream = this.reader.stream(size);
|
|
this.reader.seek(0);
|
|
return stream;
|
|
}
|
|
|
|
entries(reader) {
|
|
const files = new Map();
|
|
reader.seek(reader.length - 1);
|
|
const str = [];
|
|
let depth = 0;
|
|
do {
|
|
const c = String.fromCharCode(reader.byte());
|
|
reader.skip(-2);
|
|
if (c === '{') {
|
|
depth++;
|
|
} else if (c === '}') {
|
|
depth--;
|
|
}
|
|
str.push(c);
|
|
} while (depth > 0);
|
|
const metadata = JSON.parse(str.join(''));
|
|
const nodes = metadata.nodes;
|
|
const roots = Array.from(nodes);
|
|
for (const root of roots) {
|
|
if (root !== null) {
|
|
for (const index of Object.values(root.children)) {
|
|
roots[index] = null;
|
|
}
|
|
}
|
|
}
|
|
const process = (path, node) => {
|
|
path = path ? `${path}/${node.name}` : node.name;
|
|
if (node.kind === 0) {
|
|
files.set(path, node.dataRegion);
|
|
} else if (node.kind === 1) {
|
|
for (const index of Object.values(node.children)) {
|
|
process(path, nodes[index]);
|
|
}
|
|
} else {
|
|
throw new Error(`Node kind '${node.kind}' not implemented.`);
|
|
}
|
|
};
|
|
for (const root of roots.filter((node) => node !== null)) {
|
|
process('', root);
|
|
}
|
|
return files;
|
|
}
|
|
};
|
|
|
|
qnn.Reader = class {
|
|
|
|
static open(data, target) {
|
|
if (data.length >= 20) {
|
|
const reader = base.BinaryReader.open(data);
|
|
const magic = reader.uint32();
|
|
if (magic === 0x5678ABCD) {
|
|
return new qnn.Reader(reader, target);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(reader, target) {
|
|
this.reader = reader;
|
|
this.target = target;
|
|
this.signature = reader.uint64();
|
|
this.size = reader.uint64();
|
|
}
|
|
|
|
async read() {
|
|
// https://github.com/pytorch/executorch/blob/main/backends/qualcomm/runtime/backends/QnnCustomProtocol.h
|
|
throw new executorch.Error('Undocumented QNN backend not implemented.');
|
|
}
|
|
};
|
|
|
|
qnn.Graph = class {
|
|
|
|
constructor() {
|
|
this.name = 'QnnBackend';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
}
|
|
};
|
|
|
|
ethosu.Reader = class {
|
|
|
|
static open(data /* , target */) {
|
|
if (data.length >= 32) {
|
|
const reader = base.BinaryReader.open(data);
|
|
const magicBuffer = reader.read(16);
|
|
const magic = String.fromCharCode(...magicBuffer).replace(/\0/g, '');
|
|
if (magic === 'vela_bin_stream') {
|
|
return new ethosu.Reader(reader, data.length);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(reader, size) {
|
|
this.reader = reader;
|
|
this.size = size;
|
|
}
|
|
|
|
async read() {
|
|
this.reader.seek(0);
|
|
const blocks = new Map();
|
|
while (this.reader.position < this.size) {
|
|
const nameBuffer = this.reader.read(16);
|
|
const name = String.fromCharCode(...nameBuffer).replace(/\0/g, '');
|
|
const size = this.reader.uint32();
|
|
this.reader.skip(12);
|
|
const data = this.reader.read(size);
|
|
blocks.set(name, data);
|
|
const padding = (16 - (size % 16)) % 16;
|
|
this.reader.skip(padding);
|
|
if (name === 'vela_end_stream') {
|
|
break;
|
|
}
|
|
}
|
|
const args = (data) => {
|
|
if (data && data.length >= 4) {
|
|
const reader = base.BinaryReader.open(data);
|
|
const count = reader.int32();
|
|
const arg = [];
|
|
for (let i = 0; i < count; i++) {
|
|
const shape = [];
|
|
for (let j = 0; j < 6; j++) {
|
|
shape.push(reader.int32());
|
|
}
|
|
const elem_size = reader.int32();
|
|
const offset = reader.int32();
|
|
const region = reader.int32();
|
|
arg.push({ shape, elem_size, offset, region });
|
|
}
|
|
return arg;
|
|
}
|
|
return [];
|
|
};
|
|
const inputs = args(blocks.get('inputs'));
|
|
const outputs = args(blocks.get('outputs'));
|
|
this.type = new ethosu.Graph(inputs, outputs);
|
|
}
|
|
};
|
|
|
|
ethosu.Graph = class {
|
|
|
|
constructor(inputs, outputs) {
|
|
this.name = 'EthosUBackend';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
const input = inputs[i];
|
|
const type = new ethosu.TensorType(input);
|
|
const value = new ethosu.Value(i.toString(), type, null);
|
|
const name = inputs.length === 1 ? 'input' : `input.${i}`;
|
|
const argument = new ethosu.Argument(name, [value]);
|
|
this.inputs.push(argument);
|
|
}
|
|
for (let i = 0; i < outputs.length; i++) {
|
|
const output = outputs[i];
|
|
const type = new ethosu.TensorType(output);
|
|
const value = new ethosu.Value((inputs.length + i).toString(), type, null);
|
|
const name = outputs.length === 1 ? 'output' : `output.${i}`;
|
|
const argument = new ethosu.Argument(name, [value]);
|
|
this.outputs.push(argument);
|
|
}
|
|
}
|
|
};
|
|
|
|
ethosu.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
ethosu.Value = class Value {
|
|
|
|
constructor(name, type, initializer = null) {
|
|
if (typeof name !== 'string') {
|
|
throw new executorch.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = initializer && initializer.type ? initializer.type : type || null;
|
|
this.initializer = initializer;
|
|
}
|
|
};
|
|
|
|
ethosu.TensorType = class {
|
|
|
|
constructor(io) {
|
|
switch (io.elem_size) {
|
|
case 1: this.dataType = 'int8'; break;
|
|
case 2: this.dataType = 'int16'; break;
|
|
case 4: this.dataType = 'int32'; break;
|
|
default: this.dataType = `?${io.elem_size}`; break;
|
|
}
|
|
const shape = io.shape.filter((dim, index) => dim !== 1 || index === io.shape.length - 1 || io.shape.slice(index).some((d) => d !== 1));
|
|
this.shape = new ethosu.TensorShape(shape.length > 0 ? shape : [1]);
|
|
}
|
|
|
|
toString() {
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
ethosu.TensorShape = class {
|
|
|
|
constructor(dimensions = []) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
toString() {
|
|
if (this.dimensions && this.dimensions.length > 0) {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
return '';
|
|
}
|
|
};
|
|
|
|
ethosu.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading Ethos-U model.';
|
|
}
|
|
};
|
|
|
|
openvino.Reader = class {
|
|
|
|
static open(data /* , target */) {
|
|
return new openvino.Reader(data);
|
|
}
|
|
|
|
constructor(data) {
|
|
this.data = data;
|
|
}
|
|
|
|
async read() {
|
|
throw new executorch.Error('OpenVINO backend not implemented.');
|
|
}
|
|
};
|
|
|
|
openvino.Graph = class {
|
|
|
|
constructor() {
|
|
this.name = 'OpenvinoBackend';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
}
|
|
};
|
|
|
|
rockchip.Reader = class {
|
|
|
|
static open(data /* , target */) {
|
|
return new rockchip.Reader(data);
|
|
}
|
|
|
|
constructor(data) {
|
|
this.data = data;
|
|
}
|
|
|
|
async read() {
|
|
throw new executorch.Error('Rockchip backend not implemented.');
|
|
}
|
|
};
|
|
|
|
rockchip.Graph = class {
|
|
|
|
constructor() {
|
|
this.name = 'RockchipBackend';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
}
|
|
};
|
|
|
|
export const ModelFactory = executorch.ModelFactory; |