| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- // Experimental
- const qnn = {};
- qnn.ModelFactory = class {
- async match(context) {
- const obj = await context.peek('json');
- if (obj && obj['model.cpp'] && 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.graphs = [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, visible) {
- this.name = name;
- this.value = value;
- this.type = type;
- this.visible = visible !== false;
- }
- };
- 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;
|