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
868 lines
40 KiB
JavaScript
868 lines
40 KiB
JavaScript
|
|
const openvino = {};
|
|
|
|
openvino.ModelFactory = class {
|
|
|
|
async match(context) {
|
|
const identifier = context.identifier.toLowerCase();
|
|
const extension = identifier.split('.').pop();
|
|
if (/^.*\.ncnn\.bin$/.test(identifier) ||
|
|
/^.*\.pnnx\.bin$/.test(identifier) ||
|
|
/^.*pytorch_model.*\.bin$/.test(identifier) ||
|
|
/^.*group.+-shard.+of.+\.bin$/.test(identifier) ||
|
|
/^.*param\.bin$/.test(identifier)) {
|
|
return null;
|
|
}
|
|
if (extension === 'bin') {
|
|
const stream = context.stream;
|
|
const length = Math.min(0x10000, stream.length);
|
|
if (length >= 4) {
|
|
let buffer = stream.peek(length);
|
|
const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.length);
|
|
const signature = view.getUint32(0, true);
|
|
for (let i = 0; i < buffer.length - 4; i++) {
|
|
const signature = view.getUint32(i, true);
|
|
if (signature === 0xdeadbeef || // Core ML
|
|
signature === 0x01306b47 || signature === 0x000d4b38 || signature === 0x0002c056) { // ncnn
|
|
return null;
|
|
}
|
|
}
|
|
const match = (pattern, identifier, buffer) => {
|
|
if (pattern.identifier && typeof pattern.identifier === 'string' && identifier !== pattern.identifier) {
|
|
return false;
|
|
} else if (pattern.identifier && pattern.identifier instanceof RegExp && !pattern.identifier.test(identifier)) {
|
|
return false;
|
|
} else if (pattern.signature && !pattern.signature.every((value, index) => value === buffer[index])) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
const include = [
|
|
{ identifier: 'googlenet-v1.bin', signature: [0x80, 0xD6, 0x50, 0xD7, 0xB0, 0xD7, 0xA5, 0x2D, 0xCA, 0x28, 0x49, 0x2A, 0x35, 0x31, 0x0A, 0x31] },
|
|
{ identifier: 'text-recognition-0012.bin', signature: [0x0B, 0x21, 0xC6, 0xBC, 0xD0, 0xBB, 0xC1, 0x3B] },
|
|
];
|
|
if (include.some((pattern) => match(pattern, identifier, buffer))) {
|
|
return context.set('openvino.bin');
|
|
}
|
|
const exclude = [
|
|
{ identifier: '__model__.bin' },
|
|
{ identifier: 'config.bin' },
|
|
{ identifier: 'model.bin' },
|
|
{ identifier: 'ncnn.bin' },
|
|
{ identifier: 'programs.bin' },
|
|
{ identifier: 'weights.bin' },
|
|
{ identifier: /stories\d+(m|k)\.bin$/ },
|
|
{ signature: [0x21, 0xA8, 0xEF, 0xBE, 0xAD, 0xDE] }
|
|
];
|
|
if (exclude.some((pattern) => match(pattern, identifier, buffer))) {
|
|
return null;
|
|
}
|
|
if (signature === 0x00000001) {
|
|
return null;
|
|
}
|
|
const size = Math.min(buffer.length & 0xfffffffc, 128);
|
|
buffer = buffer.subarray(0, size);
|
|
if (Array.from(buffer).every((value) => value === 0)) {
|
|
return context.set('openvino.bin');
|
|
}
|
|
const f32 = new Array(buffer.length >> 2);
|
|
for (let i = 0; i < f32.length; i++) {
|
|
f32[i] = view.getFloat32(i << 2, true);
|
|
}
|
|
const f16 = new Array(buffer.length >> 1);
|
|
for (let i = 0; i < f16.length; i++) {
|
|
f16[i] = view.getFloat16(i << 1, true);
|
|
}
|
|
const i32 = new Array(buffer.length >> 2);
|
|
for (let i = 0; i < f32.length; i++) {
|
|
i32[i] = view.getInt32(i << 2, true);
|
|
}
|
|
const validateFloat = (array) => array[0] !== 0 && array.every((x) => !Number.isNaN(x) && Number.isFinite(x)) &&
|
|
(array.every((x) => x > -20.0 && x < 20.0 && (x >= 0 || x < -0.0000001) && (x <= 0 || x > 0.0000001)) ||
|
|
array.every((x) => x > -100.0 && x < 100.0 && (x * 10) % 1 === 0));
|
|
const validateInt = (array) => array.length > 32 &&
|
|
array.slice(0, 32).every((x) => x === 0 || x === 1 || x === 2 || x === 0x7fffffff);
|
|
if (validateFloat(f32) || validateFloat(f16) || validateInt(i32)) {
|
|
return context.set('openvino.bin');
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
const tags = await context.tags('xml');
|
|
if (tags.has('net')) {
|
|
return context.set('openvino.xml');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
filter(context, match) {
|
|
return context.type !== 'openvino.xml' || match.type !== 'openvino.bin';
|
|
}
|
|
|
|
async open(context) {
|
|
const identifier = context.identifier;
|
|
const base = identifier.substring(0, identifier.length - 4);
|
|
let bin = null;
|
|
switch (context.type) {
|
|
case 'openvino.xml': {
|
|
try {
|
|
const file = `${base}.bin`;
|
|
const content = await context.fetch(file);
|
|
bin = content.stream.peek();
|
|
} catch {
|
|
// continue regardless of error
|
|
}
|
|
break;
|
|
}
|
|
case 'openvino.bin': {
|
|
try {
|
|
const file = `${base}.xml`;
|
|
bin = context.stream.peek();
|
|
context = await context.fetch(file, null);
|
|
} catch (error) {
|
|
const message = error && error.message ? error.message : error.toString();
|
|
throw new openvino.Error(`Required OpenVINO model definition not found (${message.replace(/\.$/, '')}).`);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
throw new openvino.Error(`Unsupported OpenVINO format '${context.type}'.`);
|
|
}
|
|
}
|
|
let document = null;
|
|
try {
|
|
document = await context.read('xml');
|
|
} catch (error) {
|
|
const message = error && error.message ? error.message : error.toString();
|
|
throw new openvino.Error(`File format is not OpenVINO XML (${message.replace(/\.$/, '')}).`);
|
|
}
|
|
if (!document.documentElement || document.documentElement.localName !== 'net') {
|
|
throw new openvino.Error('File format is not OpenVINO IR.');
|
|
}
|
|
const element = document.documentElement;
|
|
const metadata = await context.metadata('openvino-metadata.json');
|
|
const object = (element) => {
|
|
const obj = {};
|
|
for (const attribute of element.attributes) {
|
|
obj[attribute.localName] = attribute.value;
|
|
}
|
|
return obj;
|
|
};
|
|
const child = (parent, name) => {
|
|
const elements = parent.getElementsByTagName(name);
|
|
if (elements.length > 1) {
|
|
throw new openvino.Error(`Element '${parent.localName}' has multiple '${name}' elements.`);
|
|
}
|
|
return elements.length > 0 ? elements[0] : null;
|
|
};
|
|
const children = (parent, name, element) => {
|
|
const list = child(parent, name);
|
|
return list ? list.getElementsByTagName(element) : [];
|
|
};
|
|
const edges = (parent, name) => {
|
|
const map = {};
|
|
for (const element of children(parent, name || 'edges', 'edge')) {
|
|
const fromLayer = element.getAttribute('from-layer');
|
|
const fromPort = element.getAttribute('from-port');
|
|
const toLayer = element.getAttribute('to-layer');
|
|
const toPort = element.getAttribute('to-port');
|
|
map[`${toLayer}:${toPort}`] = `${fromLayer}:${fromPort}`;
|
|
}
|
|
return map;
|
|
};
|
|
const layers = (parent) => {
|
|
const ports = (parent, name) => {
|
|
return children(parent, name, 'port').map((element) => {
|
|
const port = object(element);
|
|
port.dims = element.getElementsByTagName('dim').map((dim) => parseInt(dim.textContent.trim(), 10));
|
|
return port;
|
|
});
|
|
};
|
|
return children(parent, 'layers', 'layer').map((element) => {
|
|
const layer = object(element);
|
|
layer.input = ports(element, 'input');
|
|
layer.output = ports(element, 'output');
|
|
const data = child(element, 'data');
|
|
const blobs = child(element, 'blobs');
|
|
layer.data = data ? object(data) : {};
|
|
layer.blobs = blobs ? blobs.getElementsByTagName('*').map((blob) => {
|
|
const obj = object(blob);
|
|
obj.name = blob.localName;
|
|
obj.offset = parseInt(obj.offset, 10);
|
|
obj.size = parseInt(obj.size, 10);
|
|
return obj;
|
|
}) : [];
|
|
if (layer.type === 'TensorIterator') {
|
|
layer.back_edges = edges(element, 'back_edges');
|
|
const body = child(element, 'body');
|
|
if (body) {
|
|
layer.body = {
|
|
layers: layers(body),
|
|
edges: edges(body)
|
|
};
|
|
}
|
|
const port_map = child(element, 'port_map');
|
|
if (port_map) {
|
|
layer.port_map = { input: [], output: [] };
|
|
for (const port of port_map.getElementsByTagName('*')) {
|
|
const item = object(port);
|
|
switch (port.localName) {
|
|
case 'input': layer.port_map.input.push(item); break;
|
|
case 'output': layer.port_map.output.push(item); break;
|
|
default: throw new openvino.Error(`Unsupported port local name '${port.localName}'.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return layer;
|
|
});
|
|
};
|
|
const net = object(element);
|
|
net.body = {
|
|
layers: layers(element),
|
|
edges: edges(element)
|
|
};
|
|
return new openvino.Model(metadata, net, bin);
|
|
}
|
|
};
|
|
|
|
openvino.Model = class {
|
|
|
|
constructor(metadata, net, bin) {
|
|
this.name = net.name || '';
|
|
this.modules = [new openvino.Graph(metadata, net, bin)];
|
|
this.format = 'OpenVINO IR';
|
|
}
|
|
};
|
|
|
|
openvino.Graph = class {
|
|
|
|
constructor(metadata, net, bin) {
|
|
this.name = net.name || '';
|
|
this.nodes = [];
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
const tensors = new Map();
|
|
const values = new Map();
|
|
values.map = (layer, precision, port, map) => {
|
|
const id = `${layer}:${port.id}`;
|
|
const name = map && map[id] ? map[id] : id;
|
|
if (name === '') {
|
|
throw new openvino.Error('Empty value name.');
|
|
}
|
|
const shape = port.dims.length === 0 ? null : new openvino.TensorShape(port.dims);
|
|
if (!precision && values.has(name)) {
|
|
const value = values.get(name);
|
|
if (value.type && value.type.shape && value.type.shape.equals(shape)) {
|
|
return value;
|
|
}
|
|
}
|
|
const type = new openvino.TensorType(precision, shape);
|
|
let tensor = null;
|
|
if (tensors.has(id)) {
|
|
const blob = tensors.get(id);
|
|
const offset = blob.offset;
|
|
const size = blob.size;
|
|
const shape = new openvino.TensorShape(blob.shape);
|
|
const type = new openvino.TensorType(blob.precision || precision, shape);
|
|
const data = (bin && (offset + size) <= bin.length) ? bin.slice(offset, offset + size) : null;
|
|
tensor = new openvino.Tensor(type, data, 'Const');
|
|
}
|
|
if (!values.has(name)) {
|
|
values.set(name, new openvino.Value(name, type, tensor));
|
|
} else if (name === id && type && !type.equals(values.get(name).type)) {
|
|
throw new openvino.Error(`Duplicate value '${name}'.`);
|
|
}
|
|
return values.get(name);
|
|
};
|
|
const nodes = new Map();
|
|
const constant = (layers, edges, back_edges) => {
|
|
back_edges = back_edges || {};
|
|
for (const layer of layers) {
|
|
if (layer.type === 'Const' &&
|
|
layer.input.length === 0 && layer.output.length === 1 && layer.blobs.length === 0 &&
|
|
layer.data && layer.data.element_type !== undefined && layer.data.offset !== undefined && layer.data.size !== undefined) {
|
|
let precision = null;
|
|
switch (layer.data.element_type) {
|
|
case 'f16': precision = 'FP16'; break;
|
|
case 'f32': precision = 'FP32'; break;
|
|
case 'f64': precision = 'FP64'; break;
|
|
default: precision = layer.data.element_type.toUpperCase();
|
|
}
|
|
const shape = layer.data.shape;
|
|
layer.blobs.push({
|
|
name: 'value',
|
|
precision,
|
|
offset: parseInt(layer.data.offset, 10),
|
|
size: parseInt(layer.data.size, 10),
|
|
shape: shape ? shape.split(',').map((dim) => parseInt(dim.trim(), 10)) : null
|
|
});
|
|
layer.data = {};
|
|
}
|
|
if (layer.type === 'Const' && layer.blobs.length === 1 && !layer.blobs[0].shape &&
|
|
layer.input.length === 0 && layer.output.length === 1 && layer.output[0].dims) {
|
|
layer.blobs[0].shape = layer.output[0].dims;
|
|
}
|
|
}
|
|
const constants = new Map();
|
|
for (const layer of layers) {
|
|
if (layer.type === 'Const' && layer.input.length === 0 && layer.output.length === 1) {
|
|
const from = `${layer.id}:${layer.output[0].id}`;
|
|
constants.set(from, { layer, counter: 0 });
|
|
}
|
|
}
|
|
for (const from of Object.values(edges)) {
|
|
if (constants.has(from)) {
|
|
constants.get(from).counter++;
|
|
}
|
|
}
|
|
if (back_edges) {
|
|
for (const from of Object.values(back_edges)) {
|
|
if (constants.has(from)) {
|
|
constants.get(from).counter++;
|
|
}
|
|
}
|
|
}
|
|
for (const [name, value] of constants) {
|
|
if (value.counter !== 1) {
|
|
constants.delete(name);
|
|
}
|
|
}
|
|
for (const layer of layers) {
|
|
if (layer.blobs.length === 0) {
|
|
for (let i = layer.input.length - 1; i >= 0; i--) {
|
|
const input = layer.input[i];
|
|
const to = `${layer.id}:${input.id}`;
|
|
const from = edges[to] || back_edges[to];
|
|
if (!constants.has(from)) {
|
|
break;
|
|
}
|
|
const constLayer = constants.get(from).layer;
|
|
if (constLayer && Array.isArray(constLayer.blobs) && constLayer.blobs.length > 0) {
|
|
const [blob] = constLayer.blobs;
|
|
if (blob) {
|
|
blob.id = constLayer.name || constLayer.id;
|
|
layer.input[i].blob = blob;
|
|
constants.get(from).layer = null;
|
|
constants.get(from).delete = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return layers.filter((layer) => {
|
|
if (layer.type === 'Const' && layer.input.length === 0 && layer.output.length === 1) {
|
|
const from = `${layer.id}:${layer.output[0].id}`;
|
|
if (constants.has(from) && constants.get(from).delete) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
};
|
|
const body = net.body;
|
|
const body_layers = body && Array.isArray(body.layers) ? body.layers : [];
|
|
const body_edges = body && body.edges ? body.edges : {};
|
|
const layers = new Map(body_layers.map((entry) => [entry.id, entry]));
|
|
const ports = new Map();
|
|
if (Array.isArray(net.input)) {
|
|
for (const input of net.input) {
|
|
const value = values.map('', input.precision || null, input);
|
|
const argument = new openvino.Argument(input.id, [value]);
|
|
this.inputs.push(argument);
|
|
ports.set(input.id, value);
|
|
}
|
|
}
|
|
if (Array.isArray(net.output)) {
|
|
for (const output of net.output) {
|
|
const value = values.map('', output.precision || null, output);
|
|
const argument = new openvino.Argument(output.id, [value]);
|
|
this.outputs.push(argument);
|
|
ports.set(output.id, value);
|
|
}
|
|
}
|
|
for (const layer of body_layers) {
|
|
for (const output of layer.output) {
|
|
if (!output.precision) {
|
|
output.precision = layer.precision;
|
|
}
|
|
}
|
|
}
|
|
if (net.port_map) {
|
|
for (const input of net.port_map.input) {
|
|
const external_port = net.input.find((v) => v.id === input.external_port_id);
|
|
const layer = layers.get(input.internal_layer_id);
|
|
if (input.internal_port_id === undefined) {
|
|
input.internal_port_id = '';
|
|
layer.input.push({
|
|
id: input.internal_port_id,
|
|
precision: layer.data.element_type,
|
|
dims: layer.data.shape.split(',')
|
|
});
|
|
}
|
|
const internal_port = layer.input.find((v) => v.id === input.internal_port_id);
|
|
internal_port.precision = external_port.precision;
|
|
}
|
|
for (const output of net.port_map.output) {
|
|
const external_port = net.output.find((v) => v.id === output.external_port_id);
|
|
const layer = layers.get(output.internal_layer_id);
|
|
if (output.internal_port_id === undefined) {
|
|
output.internal_port_id = '';
|
|
layer.output.push({
|
|
id: output.internal_port_id,
|
|
precision: external_port.precision,
|
|
dims: external_port.dims
|
|
});
|
|
}
|
|
}
|
|
}
|
|
const layer_list = constant(body_layers, body_edges);
|
|
for (const layer of layer_list) {
|
|
for (const input of layer.input) {
|
|
if (input.blob) {
|
|
tensors.set(`${layer.id}:${input.id}`, input.blob);
|
|
}
|
|
}
|
|
}
|
|
for (const layer of layer_list) {
|
|
for (const output of layer.output) {
|
|
values.map(layer.id, output.precision, output, null);
|
|
}
|
|
}
|
|
for (const layer of layer_list) {
|
|
const inputs = layer.input.map((input) => {
|
|
const to = `${layer.id}:${input.id}`;
|
|
if (body.edges[to]) {
|
|
const output = body.edges[to] ? body.edges[to].split(':') : [];
|
|
const [outputLayerId, outputId] = output;
|
|
const outputLayer = layers.get(outputLayerId);
|
|
if (outputLayer && outputId) {
|
|
const output = outputLayer.output.find((output) => output.id === outputId);
|
|
if (input && output) {
|
|
input.precision = output.precision;
|
|
}
|
|
}
|
|
}
|
|
return values.map(layer.id, input.precision || layer.precision || null, input, body.edges);
|
|
});
|
|
const outputs = layer.output.map((output) => {
|
|
let precision = null;
|
|
if (output && output.precision) {
|
|
precision = output.precision;
|
|
} else if (layer && layer.precision) {
|
|
precision = layer.precision;
|
|
}
|
|
return values.map(layer.id, precision, output, null);
|
|
});
|
|
const subgraph = Array.isArray(net.input) || Array.isArray(net.output);
|
|
if (!subgraph && (layer.type === 'Input' || layer.type === 'Parameter')) {
|
|
const name = layer.name || '';
|
|
// precision is a part of OpenVINO IR layers of IR v6 and earlier
|
|
// in IR v7 and newer the port is no longer an attribute of the layer but of each output port
|
|
// IR input is not just a placeholder, it is conceptually the legitimate layer
|
|
// in order not to break compatibility with the overall approach
|
|
// with openvino.Argument for inputs and openvino.Node for outputs
|
|
// input openvino.Node would be stored as an optional attribute of openvino.Parameter
|
|
this.inputs.push(new openvino.Argument(name, outputs));
|
|
} else {
|
|
const node = new openvino.Node(metadata, layer, inputs, outputs, bin);
|
|
nodes.set(layer.id, node);
|
|
}
|
|
}
|
|
this.nodes = Array.from(nodes.values());
|
|
if (net.port_map) {
|
|
const createMapLayer = (obj) => {
|
|
const data = {};
|
|
for (const [name, value] of Object.entries(obj)) {
|
|
if (name !== 'external_port_id' && name !== 'internal_layer_id' && name !== 'internal_port_id') {
|
|
data[name] = value;
|
|
}
|
|
}
|
|
return { type: '-', data };
|
|
};
|
|
for (const input of net.port_map.input) {
|
|
const internal_port = layers.get(input.internal_layer_id).input.find((v) => v.id === input.internal_port_id);
|
|
const inputs = [ports.get(input.external_port_id)];
|
|
const outputs = [values.map(input.internal_layer_id, internal_port.precision, internal_port)];
|
|
const layer = createMapLayer(input);
|
|
this.nodes.push(new openvino.Node(metadata, layer, inputs, outputs));
|
|
}
|
|
for (const output of net.port_map.output) {
|
|
const internal_port = layers.get(output.internal_layer_id).output.find((v) => v.id === output.internal_port_id);
|
|
const inputs = [values.map(output.internal_layer_id, internal_port.precision, internal_port)];
|
|
const outputs = [ports.get(output.external_port_id)];
|
|
const layer = createMapLayer(output);
|
|
this.nodes.push(new openvino.Node(metadata, layer, inputs, outputs));
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
openvino.Node = class {
|
|
|
|
constructor(metadata, layer, inputs, outputs, bin) {
|
|
this.name = layer.name || '';
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.attributes = [];
|
|
const type = layer.type;
|
|
this.type = metadata.type(type) || { name: type };
|
|
for (let i = 0; i < inputs.length;) {
|
|
let input = null;
|
|
if (this.type && Array.isArray(this.type.inputs) && i < this.type.inputs.length) {
|
|
input = this.type.inputs[i];
|
|
} else if (inputs.length === 1) {
|
|
input = { name: 'input' };
|
|
} else {
|
|
input = { name: i.toString() };
|
|
}
|
|
const count = input.type === 'Tensor[]' ? inputs.length - i : 1;
|
|
const values = inputs.slice(i, i + count);
|
|
const argument = new openvino.Argument(input.name, values);
|
|
this.inputs.push(argument);
|
|
i += count;
|
|
}
|
|
for (let i = 0; i < outputs.length;) {
|
|
let output = null;
|
|
if (this.type && Array.isArray(this.type.outputs) && i < this.type.outputs.length) {
|
|
output = this.type.outputs[i];
|
|
} else if (outputs.length === 1) {
|
|
output = { name: 'output' };
|
|
} else {
|
|
output = { name: i.toString() };
|
|
}
|
|
const count = output.type === 'Tensor[]' ? outputs.length - i : 1;
|
|
const values = outputs.slice(i, i + count);
|
|
const argument = new openvino.Argument(output.name, values);
|
|
this.outputs.push(argument);
|
|
i += count;
|
|
}
|
|
const op = type;
|
|
for (const [name, obj] of Object.entries(layer.data)) {
|
|
const schema = metadata.attribute(op, name);
|
|
let value = obj;
|
|
let type = null;
|
|
let visible = true;
|
|
if (schema && schema.type !== undefined) {
|
|
type = schema.type;
|
|
switch (schema.type) {
|
|
case '':
|
|
case 'graph':
|
|
case 'string':
|
|
break;
|
|
case 'boolean':
|
|
if (obj === '1' || obj === 'true' || obj === 'True') {
|
|
value = true;
|
|
} else if (obj === '0' || obj === 'false' || obj === 'False') {
|
|
value = false;
|
|
} else {
|
|
throw new openvino.Error(`Unsupported attribute boolean value '${obj}'.`);
|
|
}
|
|
break;
|
|
case 'int32':
|
|
case 'int64': {
|
|
const intValue = Number.parseInt(obj, 10);
|
|
value = Number.isNaN(obj - intValue) ? obj : intValue;
|
|
break;
|
|
}
|
|
case 'float32':
|
|
case 'float64': {
|
|
const floatValue = Number.parseFloat(obj);
|
|
value = Number.isNaN(obj - floatValue) ? obj : floatValue;
|
|
break;
|
|
}
|
|
case 'int32[]':
|
|
if (obj.length > 2) {
|
|
let ints = [];
|
|
for (const entry of obj.split(',')) {
|
|
const item = entry.trim();
|
|
const intValue = Number.parseInt(item, 10);
|
|
if (Number.isNaN(item - intValue)) {
|
|
ints = null;
|
|
} else if (ints !== null) {
|
|
ints.push(intValue);
|
|
}
|
|
}
|
|
if (ints !== null) {
|
|
value = ints;
|
|
}
|
|
}
|
|
break;
|
|
case 'float32[]':
|
|
if (obj.length > 2) {
|
|
let floats = [];
|
|
for (const entry of obj.split(',')) {
|
|
const item = entry.trim();
|
|
const floatValue = Number.parseFloat(item);
|
|
if (Number.isNaN(item - floatValue)) {
|
|
floats = null;
|
|
} else if (floats !== null) {
|
|
floats.push(floatValue);
|
|
}
|
|
}
|
|
if (floats !== null) {
|
|
value = floats;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
throw new openvino.Error(`Unsupported attribute type '${schema.type}'.`);
|
|
}
|
|
}
|
|
if (schema && schema.visible === false) {
|
|
visible = false;
|
|
} else if (schema && schema.default !== undefined) {
|
|
const defaultValue = schema.default;
|
|
if (value === defaultValue) {
|
|
visible = false;
|
|
} else if (Array.isArray(value) && Array.isArray(defaultValue)) {
|
|
const repeat = defaultValue.length > 1 && defaultValue[defaultValue.length - 1] === null;
|
|
if (value.every((item, index) => item === (repeat && index >= defaultValue.length - 1 ? defaultValue[defaultValue.length - 2] : defaultValue[index]))) {
|
|
visible = false;
|
|
}
|
|
}
|
|
}
|
|
const attribute = new openvino.Argument(name, value, type, visible);
|
|
this.attributes.push(attribute);
|
|
}
|
|
if (layer.type === 'TensorIterator') {
|
|
const graph = new openvino.Graph(metadata, layer, null);
|
|
const attribute = new openvino.Argument('body', graph, 'graph');
|
|
this.attributes.push(attribute);
|
|
}
|
|
for (const blob of layer.blobs || []) {
|
|
const name = blob.name;
|
|
const offset = blob.offset;
|
|
let data = (bin && (offset + blob.size) <= bin.length) ? bin.slice(offset, offset + blob.size) : null;
|
|
let dimensions = blob.shape || null;
|
|
const category = blob.kind || 'Blob';
|
|
const id = blob.id || '';
|
|
const precision = blob.precision || layer.precision || null;
|
|
let itemSize = -1;
|
|
switch (precision) {
|
|
case 'BOOL': case 'BOOLEAN': itemSize = 1; break;
|
|
case 'I1': case 'U1': itemSize = 0.125; break;
|
|
case 'I2': case 'U2': itemSize = 0.25; break;
|
|
case 'I4': case 'U4': itemSize = 0.5; break;
|
|
case 'I8': case 'U8': itemSize = 1; break;
|
|
case 'I16': case 'U16': case 'FP16': itemSize = 2; break;
|
|
case 'I32': case 'U32': case 'FP32': itemSize = 4; break;
|
|
case 'I64': case 'U64': case 'FP64': itemSize = 8; break;
|
|
case 'F8E4M3': itemSize = 1; break;
|
|
case 'BF16': itemSize = 2; break;
|
|
case 'DYNAMIC': itemSize = 0; break;
|
|
default: throw new openvino.Error(`Unsupported data type size '${precision}'.`);
|
|
}
|
|
const weight = (name, precision, dimensions, data) => {
|
|
const shape = dimensions ? new openvino.TensorShape(dimensions) : null;
|
|
const type = new openvino.TensorType(precision, shape);
|
|
const tensor = new openvino.Tensor(type, data, category);
|
|
const value = new openvino.Value(id, null, tensor);
|
|
this.inputs.push(new openvino.Argument(name, [value]));
|
|
const size = Math.ceil(dimensions.reduce((a, b) => a * b, 1) * itemSize);
|
|
if (data && data.length !== size) {
|
|
return data.slice(size, data.length);
|
|
}
|
|
return null;
|
|
};
|
|
if (itemSize !== -1) {
|
|
switch (`${type}:${name}`) {
|
|
case 'FullyConnected:weights': {
|
|
const outSize = parseInt(layer.data['out-size'], 10);
|
|
dimensions = [layer.input[0].dims[1], outSize];
|
|
break;
|
|
}
|
|
case 'FullyConnected:biases': {
|
|
dimensions = [parseInt(layer.data['out-size'], 10)];
|
|
break;
|
|
}
|
|
case 'Convolution:weights':
|
|
case 'Deconvolution:weights': {
|
|
const c = this.inputs[0].value[0].type.shape.dimensions[1];
|
|
const group = parseInt(layer.data.group || '1', 10);
|
|
const kernel = layer.data['kernel-x'] !== undefined && layer.data['kernel-y'] !== undefined ?
|
|
[parseInt(layer.data['kernel-x'], 10), parseInt(layer.data['kernel-y'], 10)] :
|
|
layer.data.kernel.split(',').map((v) => parseInt(v.trim(), 10));
|
|
const n = parseInt(layer.data.output, 10);
|
|
dimensions = [Math.floor(c / group), n].concat(kernel);
|
|
break;
|
|
}
|
|
case 'LSTMCell:weights': {
|
|
const input_size = inputs[0].type.shape.dimensions[1];
|
|
const hidden_size = parseInt(layer.data.hidden_size, 10);
|
|
data = weight('W', precision, [4 * hidden_size, input_size], data);
|
|
data = weight('R', precision, [4 * hidden_size, hidden_size], data);
|
|
break;
|
|
}
|
|
case 'LSTMCell:biases': {
|
|
const hidden_size = parseInt(layer.data.hidden_size, 10);
|
|
data = weight('B', precision, [4 * hidden_size], data);
|
|
break;
|
|
}
|
|
case 'GRUCell:weights': {
|
|
const input_size = inputs[0].type.shape.dimensions[1];
|
|
const hidden_size = parseInt(layer.data.hidden_size, 10);
|
|
data = weight('W', precision, [3 * hidden_size, input_size], data);
|
|
data = weight('R', precision, [3 * hidden_size, hidden_size], data);
|
|
break;
|
|
}
|
|
case 'GRUCell:biases': {
|
|
const linear_before_reset = parseInt(layer.data.linear_before_reset, 10);
|
|
const hidden_size = parseInt(layer.data.hidden_size, 10);
|
|
dimensions = linear_before_reset ? [4 * hidden_size] : [3 * hidden_size];
|
|
data = weight('B', precision, dimensions, data);
|
|
break;
|
|
}
|
|
case 'Convolution:biases': {
|
|
dimensions = [parseInt(layer.data.output, 10)];
|
|
break;
|
|
}
|
|
case 'ScaleShift:weights':
|
|
case 'ScaleShift:biases':
|
|
case 'Normalize:weights': {
|
|
dimensions = [layer.input[0].dims[1]];
|
|
break;
|
|
}
|
|
case 'PReLU:weights': {
|
|
dimensions = layer.data.channel_shared === '1' ? [1] : [layer.input[0].dims[1]];
|
|
break;
|
|
}
|
|
case 'Const:custom': {
|
|
if (this.outputs.length > 0 &&
|
|
this.outputs[0].value.length > 0 &&
|
|
this.outputs[0].value[0].type &&
|
|
this.outputs[0].value[0].type.shape &&
|
|
this.outputs[0].value[0].type.shape.dimensions) {
|
|
dimensions = this.outputs[0].value[0].type.shape.dimensions;
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (data) {
|
|
weight(name, precision, dimensions, data);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
openvino.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
openvino.Value = class {
|
|
|
|
constructor(name, type, initializer = null) {
|
|
if (typeof name !== 'string') {
|
|
throw new openvino.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = initializer ? initializer.type : type;
|
|
this.initializer = initializer;
|
|
}
|
|
};
|
|
|
|
openvino.Tensor = class {
|
|
|
|
constructor(type, data, category) {
|
|
this.type = type;
|
|
this.values = data;
|
|
this.category = category;
|
|
}
|
|
};
|
|
|
|
openvino.TensorType = class {
|
|
|
|
constructor(precision, shape) {
|
|
precision = precision ? precision.toLowerCase() : precision;
|
|
switch (precision) {
|
|
case 'f4e2m1': this.dataType = 'float4e2m1'; break;
|
|
case 'f8e4m3': this.dataType = 'float8e4m3'; break;
|
|
case 'f8e5m2': this.dataType = 'float8e5m2'; break;
|
|
case 'f8e8m0': this.dataType = 'float8e8m0'; break;
|
|
case 'f16': this.dataType = 'float16'; break;
|
|
case 'f32': this.dataType = 'float32'; break;
|
|
case 'f64': this.dataType = 'float64'; break;
|
|
case 'fp16': this.dataType = 'float16'; break;
|
|
case 'fp32': this.dataType = 'float32'; break;
|
|
case 'fp64': this.dataType = 'float64'; break;
|
|
case 'bf16': this.dataType = 'bfloat16'; break;
|
|
case 'nf4': this.dataType = 'nfloat4'; break;
|
|
case 'i2': this.dataType = 'int2'; break;
|
|
case 'i4': this.dataType = 'int4'; break;
|
|
case 'i8': this.dataType = 'int8'; break;
|
|
case 'i16': this.dataType = 'int16'; break;
|
|
case 'i32': this.dataType = 'int32'; break;
|
|
case 'i64': this.dataType = 'int64'; break;
|
|
case 'u1': this.dataType = 'boolean'; break;
|
|
case 'u2': this.dataType = 'uint2'; break;
|
|
case 'u4': this.dataType = 'uint4'; break;
|
|
case 'u8': this.dataType = 'uint8'; break;
|
|
case 'u16': this.dataType = 'uint16'; break;
|
|
case 'u32': this.dataType = 'uint32'; break;
|
|
case 'u64': this.dataType = 'uint64'; break;
|
|
case 'bool': this.dataType = 'boolean'; break;
|
|
case 'boolean': this.dataType = 'boolean'; break;
|
|
case 'bin': this.dataType = 'bit'; break;
|
|
case 'string': this.dataType = 'string'; break;
|
|
case 'dynamic': this.dataType = 'dynamic'; break;
|
|
case 'unspecified': this.dataType = 'unspecified'; break;
|
|
case '': this.dataType = '?'; break;
|
|
case null: this.dataType = '?'; break;
|
|
default: throw new openvino.Error(`Unsupported precision '${JSON.stringify(precision)}'.`);
|
|
}
|
|
this.shape = shape;
|
|
}
|
|
|
|
equals(obj) {
|
|
return obj && this.dataType === obj.dataType &&
|
|
((this.shape === null && obj.shape === null) || this.shape && this.shape.equals(obj.shape));
|
|
}
|
|
|
|
toString() {
|
|
if (this.shape === null) {
|
|
return `${this.dataType}[?]`;
|
|
}
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
openvino.TensorShape = class {
|
|
|
|
constructor(dimensions) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
equals(obj) {
|
|
return obj && Array.isArray(obj.dimensions) &&
|
|
Array.isArray(this.dimensions) && this.dimensions.length === obj.dimensions.length
|
|
&& obj.dimensions.every((value, index) => this.dimensions[index] === value);
|
|
}
|
|
|
|
toString() {
|
|
if (!this.dimensions || this.dimensions.length === 0) {
|
|
return '';
|
|
}
|
|
return `[${this.dimensions.join(',')}]`;
|
|
}
|
|
};
|
|
|
|
openvino.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading OpenVINO model.';
|
|
}
|
|
};
|
|
|
|
export const ModelFactory = openvino.ModelFactory;
|