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
354 lines
14 KiB
JavaScript
354 lines
14 KiB
JavaScript
// Experimental
|
|
|
|
const hailo = {};
|
|
|
|
hailo.ModelFactory = class {
|
|
|
|
async match(context) {
|
|
const container = await hailo.Container.open(context);
|
|
if (container) {
|
|
return context.set(container.type, container);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
filter(context, match) {
|
|
if (context.type === 'hailo.metadata' && (match.type === 'hailo.configuration' || match.type === 'npz' || match.type === 'onnx.proto')) {
|
|
return false;
|
|
}
|
|
if (context.type === 'hailo.configuration' && match.type === 'npz') {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async open(context) {
|
|
const metadata = await context.metadata('hailo-metadata.json');
|
|
const target = context.value;
|
|
await target.read();
|
|
return new hailo.Model(metadata, target);
|
|
}
|
|
};
|
|
|
|
hailo.Model = class {
|
|
|
|
constructor(metadata, container) {
|
|
const configuration = container.configuration;
|
|
this.modules = [new hailo.Graph(metadata, configuration, container.weights)];
|
|
this.name = configuration && configuration.name || "";
|
|
this.format = container.format + (container.metadata && container.metadata.sdk_version ? ` v${container.metadata.sdk_version}` : '');
|
|
this.metadata = [];
|
|
if (container.metadata && container.metadata.state) {
|
|
this.metadata.push(new hailo.Argument('state', container.metadata.state));
|
|
}
|
|
}
|
|
};
|
|
|
|
hailo.Graph = class {
|
|
|
|
constructor(metadata, configuration, weights) {
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
const values = new Map();
|
|
values.map = (name, type, tensor) => {
|
|
if (name.length === 0 && tensor) {
|
|
return new hailo.Value(name, type || null, tensor);
|
|
}
|
|
if (!values.has(name)) {
|
|
values.set(name, new hailo.Value(name, type || null, tensor || null));
|
|
} else if (tensor) {
|
|
throw new hailo.Error(`Duplicate value '${name}'.`);
|
|
} else if (type && !type.equals(values.get(name).type)) {
|
|
throw new hailo.Error(`Duplicate value '${name}'.`);
|
|
}
|
|
return values.get(name);
|
|
};
|
|
const layers = Object.entries(configuration.layers || {}).map(([name, value]) => {
|
|
value.name = name;
|
|
return value;
|
|
});
|
|
const inputs = new Set();
|
|
for (const layer of layers) {
|
|
switch (layer.type) {
|
|
case 'input_layer': {
|
|
for (let i = 0; i < layer.output.length; i++) {
|
|
const shape = Array.isArray(layer.output_shapes) && layer.output_shapes.length > 0 ? layer.output_shapes[0] : null;
|
|
const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
|
|
const output = layer.output[i];
|
|
if (!inputs.has(output)) {
|
|
const name = `${layer.name}\n${output}`;
|
|
const argument = new hailo.Argument('input', [values.map(name, type)]);
|
|
this.inputs.push(argument);
|
|
inputs.add(output);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case 'output_layer': {
|
|
for (let i = 0; i < layer.input.length; i++) {
|
|
const shape = Array.isArray(layer.input_shapes) && layer.input_shapes.length > 0 ? layer.input_shapes[i] : null;
|
|
const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
|
|
const input = layer.input[i];
|
|
const name = `${input}\n${layer.name}`;
|
|
const argument = new hailo.Argument('output', [values.map(name, type)]);
|
|
this.outputs.push(argument);
|
|
}
|
|
break;
|
|
}
|
|
default: {
|
|
const node = new hailo.Node(metadata, layer, values, weights.get(layer.name));
|
|
this.nodes.push(node);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
hailo.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
hailo.Value = class {
|
|
|
|
constructor(name, type, initializer) {
|
|
if (typeof name !== 'string') {
|
|
throw new hailo.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = initializer ? initializer.type : type;
|
|
this.initializer = initializer;
|
|
}
|
|
};
|
|
|
|
hailo.Node = class {
|
|
|
|
constructor(metadata, layer, values, weights) {
|
|
weights = weights || new Map();
|
|
this.name = layer.name || '';
|
|
this.type = metadata.type(layer.type);
|
|
if (layer.type === 'activation') {
|
|
const name = layer.params.activation || layer.name || '';
|
|
this.type = { ...this.type, name };
|
|
}
|
|
this.inputs = layer.input.map((name, index) => {
|
|
const shape = layer.input_shapes ? layer.input_shapes[index] : null;
|
|
const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
|
|
name = `${name}\n${layer.name}`;
|
|
return new hailo.Argument("input", [values.map(name, type, null)]);
|
|
});
|
|
const layer_params = layer.params ? Object.entries(layer.params) : [];
|
|
const params_list = layer_params.reduce((acc, [name, value]) => {
|
|
const schema = metadata.attribute(layer.type, name) || {};
|
|
if (schema.visible) {
|
|
const label = schema.label ? schema.label : name;
|
|
if (!weights.has(label)) {
|
|
const array = weights.get(label);
|
|
const tensor = new hailo.Tensor(array, value);
|
|
acc.push(new hailo.Argument(label, [values.map('', tensor.type, tensor)]));
|
|
}
|
|
}
|
|
return acc;
|
|
}, []);
|
|
const params_from_npz = Array.from(weights).filter(([, value]) => value).map(([name, value]) => {
|
|
const tensor = new hailo.Tensor(value);
|
|
return new hailo.Argument(name, [values.map('', tensor.type, tensor)]);
|
|
});
|
|
this.inputs = this.inputs.concat(params_list).concat(params_from_npz);
|
|
this.outputs = (layer.output || []).map((name, index) => {
|
|
const shape = layer.output_shapes ? layer.output_shapes[index] : null;
|
|
const type = shape ? new hailo.TensorType('?', new hailo.TensorShape(shape)) : null;
|
|
name = `${layer.name}\n${name}`;
|
|
return new hailo.Argument("output", [values.map(name, type, null)]);
|
|
});
|
|
this.attributes = [];
|
|
const attrs = Object.assign(layer.params || {}, { original_names: layer.original_names || [] });
|
|
for (const [name, value] of Object.entries(attrs)) {
|
|
const schema = metadata.attribute(layer.type, name);
|
|
const type = schema && schema.type ? schema.type : '';
|
|
const visible = name === 'original_names' || (schema && schema.visible === false) ? false : true;
|
|
const attribute = new hailo.Argument(name, value, type, visible);
|
|
this.attributes.push(attribute);
|
|
}
|
|
this.chain = [];
|
|
if (layer && layer.params && layer.params.activation && layer.params.activation !== 'linear' && layer.type !== 'activation') {
|
|
const activation = {
|
|
type: layer.params.activation,
|
|
name: layer.params.activation,
|
|
input: [],
|
|
output: []
|
|
};
|
|
const node = new hailo.Node(metadata, activation, values.map);
|
|
this.chain.push(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
hailo.Tensor = class {
|
|
|
|
constructor(array, shape) {
|
|
const dataType = array && array.dtype ? array.dtype.__name__ : '?';
|
|
shape = array && array.shape ? array.shape : shape;
|
|
this.type = new hailo.TensorType(dataType, new hailo.TensorShape(shape));
|
|
if (array) {
|
|
this.stride = array.strides.map((stride) => stride / array.itemsize);
|
|
this.layout = this.type.dataType === 'string' || this.type.dataType === 'object' ? '|' : array.dtype.byteorder;
|
|
this.values = this.type.dataType === 'string' || this.type.dataType === 'object' || this.type.dataType === 'void' ? array.tolist() : array.tobytes();
|
|
}
|
|
}
|
|
};
|
|
|
|
hailo.TensorType = class {
|
|
|
|
constructor(dataType, shape) {
|
|
this.dataType = dataType;
|
|
this.shape = shape;
|
|
}
|
|
|
|
equals(obj) {
|
|
return obj && this.dataType === obj.dataType && this.shape && this.shape.equals(obj.shape);
|
|
}
|
|
|
|
toString() {
|
|
return (this.dataType || '?') + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
hailo.TensorShape = class {
|
|
|
|
constructor(dimensions) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
equals(obj) {
|
|
if (obj && Array.isArray(obj.dimensions) && Array.isArray(this.dimensions)) {
|
|
if (this.dimensions.length === obj.dimensions.length) {
|
|
return obj.dimensions.every((value, index) => this.dimensions[index] === value);
|
|
}
|
|
const a = this.dimensions.filter((value, index) => index === 0 || index === this.dimensions.length - 1 || value !== 1);
|
|
const b = obj.dimensions.filter((value, index) => index === 0 || index === obj.dimensions.length - 1 || value !== 1);
|
|
if (a.length === b.length) {
|
|
return a.every((value, index) => b[index] === value);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
toString() {
|
|
if (this.dimensions && this.dimensions.length > 0) {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
return '';
|
|
}
|
|
};
|
|
|
|
hailo.Container = class {
|
|
|
|
static async open(context) {
|
|
const identifier = context.identifier;
|
|
const basename = identifier.split('.');
|
|
basename.pop();
|
|
if (identifier.toLowerCase().endsWith('.hn')) {
|
|
if (basename.length > 1 && (basename[basename.length - 1] === 'native' || basename[basename.length - 1] === 'fp')) {
|
|
basename.pop();
|
|
}
|
|
const configuration = await context.peek('json');
|
|
if (configuration && configuration.name && configuration.net_params && configuration.layers) {
|
|
return new hailo.Container(context, 'hailo.configuration', basename.join('.'), configuration, null);
|
|
}
|
|
} else if (identifier.toLowerCase().endsWith('.metadata.json')) {
|
|
basename.pop();
|
|
const metadata = await context.peek('json');
|
|
if (metadata && metadata.state && metadata.hn) {
|
|
return new hailo.Container(context, 'hailo.metadata', basename.join('.'), null, metadata);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
constructor(context, type, basename, configuration, metadata) {
|
|
this.type = type;
|
|
this.context = context;
|
|
this.basename = basename;
|
|
this.configuration = configuration;
|
|
this.metadata = metadata;
|
|
}
|
|
|
|
async _request(name, type) {
|
|
try {
|
|
const content = await this.context.fetch(name);
|
|
if (content) {
|
|
return await content.read(type);
|
|
}
|
|
} catch {
|
|
// continue regardless of error
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async read() {
|
|
this.format = 'Hailo NN';
|
|
this.weights = new Map();
|
|
if (!this.metadata) {
|
|
this.metadata = await this._request(`${this.basename}.metadata.json`, 'json');
|
|
}
|
|
if (this.metadata) {
|
|
this.format = 'Hailo Archive';
|
|
this.configuration = await this._request(this.metadata.hn, 'json');
|
|
if (!this.configuration) {
|
|
throw new hailo.Error("Archive does not contain '.nn' configuration.");
|
|
}
|
|
let extension = '';
|
|
switch (this.metadata.state) {
|
|
case 'fp_optimized_model': extension = '.fpo.npz'; break;
|
|
case 'quantized_model': extension = '.q.npz'; break;
|
|
case 'compiled_model': extension = '.q.npz'; break;
|
|
default: extension = '.npz'; break;
|
|
}
|
|
const entries = await this._request(this.basename + extension, 'npz');
|
|
if (entries && entries.size > 0) {
|
|
const inputs = new Set([
|
|
'kernel', 'bias',
|
|
'input_activation_bits', 'output_activation_bits',
|
|
'weight_bits', 'bias_decomposition'
|
|
]);
|
|
for (const [name, value] of entries) {
|
|
const key = name.split('.').slice(0, -1).join('.');
|
|
const match = key.match(/.*?(?=:[0-9])/);
|
|
if (match) {
|
|
const path = match[0].split('/');
|
|
if (inputs.has(path[2])) {
|
|
const layer = `${path[0]}/${path[1]}`;
|
|
if (!this.weights.has(layer)) {
|
|
this.weights.set(layer, new Map());
|
|
}
|
|
const weights = this.weights.get(layer);
|
|
weights.set(path[2], value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
delete this.context;
|
|
delete this.basename;
|
|
}
|
|
};
|
|
|
|
hailo.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading Hailo model.';
|
|
}
|
|
};
|
|
|
|
export const ModelFactory = hailo.ModelFactory;
|
|
|