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
304 lines
11 KiB
JavaScript
304 lines
11 KiB
JavaScript
|
|
// Experimental
|
|
|
|
const qnn = {};
|
|
|
|
qnn.ModelFactory = class {
|
|
|
|
async match(context) {
|
|
const obj = await context.peek('json');
|
|
if (obj && obj['model.cpp'] !== undefined && obj.graph) {
|
|
return context.set('qnn.json', obj);
|
|
}
|
|
const entries = await context.peek('tar');
|
|
if (entries && entries.size > 0 && Array.from(entries).every(([name]) => name.endsWith('.raw'))) {
|
|
return context.set('qnn.weights', entries);
|
|
}
|
|
const identifier = context.identifier.toLowerCase();
|
|
if (identifier.endsWith('.bin') || identifier.endsWith('.serialized')) {
|
|
const stream = context.stream;
|
|
const signatures = [
|
|
[0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00],
|
|
[0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00],
|
|
[0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
|
|
[0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01],
|
|
];
|
|
if (stream.length >= 16 && signatures.some((signature) => stream.peek(signature.length).every((value, index) => value === signature[index]))) {
|
|
return context.set('qnn.serialized');
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async open(context) {
|
|
const metadata = await context.metadata('qnn-metadata.json');
|
|
switch (context.type) {
|
|
case 'qnn.json': {
|
|
const obj = context.value;
|
|
let weights = new Map();
|
|
try {
|
|
if (obj['model.bin']) {
|
|
const name = obj['model.bin'].split('/').pop();
|
|
const content = await context.fetch(name);
|
|
const entries = await content.read('tar');
|
|
if (entries) {
|
|
weights = entries;
|
|
}
|
|
}
|
|
} catch {
|
|
// continue regardless of error
|
|
}
|
|
return new qnn.Model(metadata, obj, weights);
|
|
}
|
|
case 'qnn.weights': {
|
|
const weights = context.value;
|
|
const identifier = context.identifier;
|
|
const parts = identifier.split('.');
|
|
parts.pop();
|
|
const base = parts.join('.');
|
|
const content = await context.fetch(`${base}_net.json`);
|
|
const obj = await content.read('json');
|
|
return new qnn.Model(metadata, obj, weights);
|
|
}
|
|
case 'qnn.serialized': {
|
|
throw new qnn.Error("File contains undocumented QNN serialized context.");
|
|
}
|
|
default: {
|
|
throw new qnn.Error(`Unsupported QNN format '${context.type}'.`);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
qnn.Model = class {
|
|
|
|
constructor(metadata, obj, weights) {
|
|
this.format = 'QNN';
|
|
if (obj.converter_command) {
|
|
this.producer = obj.converter_command.split(' ').shift();
|
|
}
|
|
this.metadata = [];
|
|
if (obj.copyright_str) {
|
|
this.metadata.push(new qnn.Argument('License', obj.copyright_str));
|
|
}
|
|
this.modules = [new qnn.Graph(metadata, obj.graph, weights)];
|
|
}
|
|
};
|
|
|
|
qnn.Graph = class {
|
|
|
|
constructor(metadata, obj, weights) {
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.nodes = [];
|
|
const values = new Map();
|
|
values.map = (name, type, tensor, quantization) => {
|
|
type = type || null;
|
|
tensor = tensor || null;
|
|
if (!values.has(name)) {
|
|
const value = new qnn.Value(name, type, tensor, quantization);
|
|
values.set(name, value);
|
|
} else if ((type && !type.equals(values.get(name).type)) || tensor) {
|
|
throw new qnn.Error(`Duplicate value '${name}'.`);
|
|
}
|
|
return values.get(name);
|
|
};
|
|
const tensors = Object.entries(obj.tensors);
|
|
for (const [name, obj] of tensors) {
|
|
const type = new qnn.TensorType(obj);
|
|
switch (obj.type) {
|
|
case 0: {
|
|
const value = values.map(name, type, null, obj.quant_params);
|
|
const argument = new qnn.Argument(name, [value]);
|
|
this.inputs.push(argument);
|
|
break;
|
|
}
|
|
case 1: {
|
|
const value = values.map(name, type, null, obj.quant_params);
|
|
const argument = new qnn.Argument(name, [value]);
|
|
this.outputs.push(argument);
|
|
break;
|
|
}
|
|
case 3: {
|
|
values.map(name, type);
|
|
break;
|
|
}
|
|
case 4: {
|
|
const reader = weights.get(`${name}.raw`);
|
|
const tensor = new qnn.Tensor(name, type, obj, reader);
|
|
values.map(name, type, tensor, obj.quant_params);
|
|
break;
|
|
}
|
|
default: {
|
|
throw new qnn.Error(`Unsupported tensor type '${obj.type}'.`);
|
|
}
|
|
}
|
|
}
|
|
const nodes = Object.entries(obj.nodes);
|
|
for (const [name, obj] of nodes) {
|
|
const node = new qnn.Node(metadata, name, obj, values, weights);
|
|
this.nodes.push(node);
|
|
}
|
|
}
|
|
};
|
|
|
|
qnn.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
qnn.Value = class {
|
|
|
|
constructor(name, type, initializer, quantization) {
|
|
if (typeof name !== 'string') {
|
|
throw new qnn.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
this.type = type;
|
|
this.initializer = initializer;
|
|
if (quantization && quantization.definition === 1 && quantization.scale_offset) {
|
|
this.quantization = {
|
|
type: 'linear',
|
|
scale: [quantization.scale_offset.scale],
|
|
offset: [quantization.scale_offset.offset]
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
qnn.Node = class {
|
|
|
|
constructor(metadata, name, obj, values) {
|
|
this.name = name;
|
|
this.type = { name: obj.type, ...metadata.type(obj.type) };
|
|
this.type.module = obj.package;
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.attributes = [];
|
|
const inputs = Array.isArray(obj.input_names) ? Array.from(obj.input_names).map((name) => values.map(name)) : [];
|
|
if (Array.isArray(this.type.inputs) && inputs.length === this.type.inputs.length) {
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
const argument = new qnn.Argument(this.type.inputs[i].name, [inputs[i]]);
|
|
this.inputs.push(argument);
|
|
}
|
|
} else if (inputs.length > 0) {
|
|
const argument = new qnn.Argument(inputs.length === 1 ? 'input' : 'inputs', inputs);
|
|
this.inputs.push(argument);
|
|
}
|
|
const outputs = Array.isArray(obj.output_names) ? Array.from(obj.output_names).map((name) => values.map(name)) : [];
|
|
if (Array.isArray(this.type.outputs) && outputs.length === this.type.outputs.length) {
|
|
for (let i = 0; i < outputs.length; i++) {
|
|
const argument = new qnn.Argument(this.type.outputs[i].name, [outputs[i]]);
|
|
this.outputs.push(argument);
|
|
}
|
|
} else if (outputs.length > 0) {
|
|
const argument = new qnn.Argument(outputs.length === 1 ? 'output' : 'outputs', outputs);
|
|
this.outputs.push(argument);
|
|
}
|
|
for (const [name, value] of Object.entries(obj.scalar_params || {})) {
|
|
const entries = Object.entries(value);
|
|
if (entries.length === 1 && name !== 'packageName') {
|
|
const dataType = qnn.Utility.dataType(parseInt(entries[0][0], 10));
|
|
const argument = new qnn.Argument(name, entries[0][1], dataType);
|
|
this.attributes.push(argument);
|
|
}
|
|
}
|
|
for (const [name, value] of Object.entries(obj.tensor_params || {})) {
|
|
const entries = Object.entries(value);
|
|
if (entries.length === 1 && name !== 'packageName') {
|
|
const tensor = new qnn.Tensor(name, null, entries[0][1]);
|
|
const argument = new qnn.Argument(name, tensor, 'tensor');
|
|
this.attributes.push(argument);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
qnn.Tensor = class {
|
|
|
|
constructor(name, type, obj, data) {
|
|
this.type = type || new qnn.TensorType(obj);
|
|
this.data = obj.data ? obj.data.flat() : data;
|
|
this.encoding = Array.isArray(this.data) ? '|' : '<';
|
|
}
|
|
|
|
get values() {
|
|
if (this.data && this.data.peek) {
|
|
return this.data.peek();
|
|
}
|
|
return this.data;
|
|
}
|
|
};
|
|
|
|
qnn.TensorType = class {
|
|
|
|
constructor(obj) {
|
|
this.dataType = qnn.Utility.dataType(obj.data_type);
|
|
this.shape = new qnn.TensorShape(obj.dims);
|
|
this.denotation = obj.axis_format && obj.axis_format !== 'ANY' ? obj.axis_format : '';
|
|
}
|
|
|
|
toString() {
|
|
return this.dataType + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
qnn.TensorShape = class {
|
|
|
|
constructor(dimensions) {
|
|
this.dimensions = dimensions;
|
|
}
|
|
|
|
toString() {
|
|
if (Array.isArray(this.dimensions) && this.dimensions.length > 0) {
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
return '';
|
|
}
|
|
};
|
|
|
|
qnn.Utility = class {
|
|
|
|
static dataType(value) {
|
|
switch (value) {
|
|
case 0x0008: return 'int8';
|
|
case 0x0016: return 'int16';
|
|
case 0x0032: return 'int32';
|
|
case 0x0064: return 'int64';
|
|
case 0x0108: return 'uint8';
|
|
case 0x0116: return 'uint16';
|
|
case 0x0132: return 'uint32';
|
|
case 0x0164: return 'uint64';
|
|
case 0x0216: return 'float16';
|
|
case 0x0232: return 'float32';
|
|
case 0x0304: return 'qint4';
|
|
case 0x0308: return 'qint8';
|
|
case 0x0316: return 'qint16';
|
|
case 0x0332: return 'qint32';
|
|
case 0x0404: return 'quint4';
|
|
case 0x0408: return 'quint8';
|
|
case 0x0416: return 'quint16';
|
|
case 0x0432: return 'quint32';
|
|
case 0x0508: return 'boolean';
|
|
case 0x0608: return 'string';
|
|
case 0x7fffffff: return 'undefined';
|
|
default: throw new qnn.Error(`Unsupported data type '${JSON.stringify(value)}'.`);
|
|
}
|
|
}
|
|
};
|
|
|
|
qnn.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading QNN model.';
|
|
}
|
|
};
|
|
|
|
export const ModelFactory = qnn.ModelFactory;
|