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
367 lines
13 KiB
JavaScript
367 lines
13 KiB
JavaScript
|
|
const xmodel = {};
|
|
|
|
xmodel.ModelFactory = class {
|
|
|
|
async match(context) {
|
|
const tags = await context.tags('pb');
|
|
if (tags.get(5) === 2) {
|
|
return context.set('xmodel.pb');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
async open(context) {
|
|
xmodel.proto = await context.require('./xmodel-proto');
|
|
xmodel.proto = xmodel.proto.serial_v2;
|
|
let graph = null;
|
|
try {
|
|
const reader = await context.read('protobuf.binary');
|
|
graph = xmodel.proto.Graph.decode(reader);
|
|
} catch (error) {
|
|
const message = error && error.message ? error.message : error.toString();
|
|
throw new xmodel.Error(`File format is not serial_v2.Graph (${message.replace(/\.$/, '')}).`);
|
|
}
|
|
return new xmodel.Model(graph);
|
|
}
|
|
};
|
|
|
|
xmodel.Model = class {
|
|
|
|
constructor(graph) {
|
|
this.name = graph.graph_name || '';
|
|
this.format = 'xmodel';
|
|
this.producer = graph && graph.graph_attr && graph.graph_attr.origin && graph.graph_attr.origin.string_value ? graph.graph_attr.origin.string_value : '';
|
|
this.modules = [new xmodel.Graph(graph)];
|
|
}
|
|
};
|
|
|
|
xmodel.Graph = class {
|
|
|
|
constructor(graph) {
|
|
const metadata = new xmodel.Metadata(graph.op_defs);
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
const counts = new Map();
|
|
for (const op_node of graph.op_node) {
|
|
for (const arg of op_node.args) {
|
|
for (const arg_op of arg.arg_ops) {
|
|
counts.set(arg_op, counts.has(arg_op) ? counts.get(arg_op) + 1 : 1);
|
|
}
|
|
}
|
|
}
|
|
const values = new Map();
|
|
values.map = (name, node, initializer) => {
|
|
if (!values.has(name)) {
|
|
values.set(name, new xmodel.Value(name, node, initializer));
|
|
}
|
|
return values.get(name);
|
|
};
|
|
const nodes = [];
|
|
for (const node of graph.op_node) {
|
|
if (node.args.length === 0) {
|
|
if (node.op_type === 'data' || node.op_type === 'data-fix') {
|
|
const value = values.map(node.op_name, node);
|
|
this.inputs.push(new xmodel.Argument(node.op_name, [value]));
|
|
continue;
|
|
}
|
|
}
|
|
if (node.args.length === 0 && counts.get(node.op_name) === 1) {
|
|
if (node.op_type === 'const' || node.op_type === 'const-fix') {
|
|
values.map(node.op_name, node, true);
|
|
continue;
|
|
}
|
|
}
|
|
values.map(node.op_name, node);
|
|
nodes.push(node);
|
|
}
|
|
this.nodes = nodes.map((node) => new xmodel.Node(metadata, node, values));
|
|
}
|
|
};
|
|
|
|
xmodel.Argument = class {
|
|
|
|
constructor(name, value, type = null, visible = true) {
|
|
this.name = name;
|
|
this.value = value;
|
|
this.type = type;
|
|
this.visible = visible;
|
|
}
|
|
};
|
|
|
|
xmodel.Value = class {
|
|
|
|
constructor(name, node, initializer) {
|
|
if (typeof name !== 'string') {
|
|
throw new xmodel.Error(`Invalid value identifier '${JSON.stringify(name)}'.`);
|
|
}
|
|
this.name = name;
|
|
if (node) {
|
|
const tensor = node.output_tensor;
|
|
if (tensor && tensor.tensor_attr && tensor.data_type) {
|
|
if (initializer) {
|
|
this.initializer = new xmodel.Tensor(node);
|
|
this.type = this.initializer.type;
|
|
} else {
|
|
this.type = new xmodel.TensorType(tensor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
xmodel.Node = class {
|
|
|
|
constructor(metadata, op_node, values) {
|
|
this.name = op_node.op_name || '';
|
|
this.type = metadata.type(op_node.op_type);
|
|
this.inputs = [];
|
|
this.outputs = [];
|
|
this.attributes = [];
|
|
this.chain = [];
|
|
if (op_node.op_attr) {
|
|
for (const [name, obj] of Object.entries(op_node.op_attr)) {
|
|
if (name === 'device') {
|
|
this.device = obj.string_value;
|
|
} else if (name !== 'workload' && !name.startsWith('quant_in_') && !name.startsWith('quant_out_')) {
|
|
const attr = xmodel.Utility.attribute(obj);
|
|
if (name === 'nonlinear' && attr.value && attr.value !== 'NONE' && attr.value !== 0) {
|
|
let activation = attr.value;
|
|
if (typeof activation === 'string') {
|
|
activation = activation.toLowerCase();
|
|
} else if (Number.isInteger(activation) && activation < 5) {
|
|
activation = ['none', 'relu', 'prelu', 'leakyrelu', 'relu6'][activation];
|
|
} else {
|
|
activation = JSON.stringify(activation);
|
|
}
|
|
const node = new xmodel.Node(metadata, { op_type: activation }, values);
|
|
this.chain.push(node);
|
|
} else {
|
|
const schema = metadata.attribute(this.type.name, name);
|
|
const visible = (schema && schema.default !== undefined && schema.default === attr.value) ||
|
|
(schema && Array.isArray(schema.default) && Array.isArray(this.value) && schema.default.length === attr.value.length && schema.default.every((value, index) => value === attr.value[index])) ? false : true;
|
|
const attribute = new xmodel.Argument(name, attr.value, attr.type, visible);
|
|
this.attributes.push(attribute);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (op_node.args) {
|
|
for (const input of op_node.args) {
|
|
const argument = new xmodel.Argument(input.arg_name, input.arg_ops.map((arg_op) => values.map(arg_op)));
|
|
this.inputs.push(argument);
|
|
}
|
|
}
|
|
if (op_node.op_name) {
|
|
const argument = new xmodel.Argument('output', [values.map(op_node.op_name)]);
|
|
this.outputs.push(argument);
|
|
}
|
|
}
|
|
};
|
|
|
|
xmodel.TensorType = class {
|
|
|
|
constructor(tensor) {
|
|
let type = '';
|
|
switch (tensor.data_type) {
|
|
case 0: type = 'int'; break;
|
|
case 1: type = 'uint'; break;
|
|
case 2: type = 'xint'; break;
|
|
case 3: type = 'xuint'; break;
|
|
case 4: type = 'float'; break;
|
|
case 5: type = 'bfloat'; break;
|
|
default: throw new xmodel.Error(`Unsupported data type '${tensor.data_type}'.`);
|
|
}
|
|
this.dataType = type + tensor.tensor_bit_width.toString();
|
|
this.shape = new xmodel.TensorShape(tensor.tensor_dim);
|
|
if (tensor.tensor_attr) {
|
|
const attr = {};
|
|
for (const [key, obj] of Object.entries(tensor.tensor_attr)) {
|
|
const value = obj[obj.value];
|
|
if (key.startsWith('quant_')) {
|
|
continue;
|
|
}
|
|
attr[key] = value;
|
|
const denotation = [];
|
|
if (attr.fix_point !== undefined) {
|
|
denotation.push(`${attr.fix_point}.`);
|
|
}
|
|
if (attr.round_mode !== undefined) {
|
|
denotation.push(attr.round_mode.toString());
|
|
}
|
|
if (denotation.length > 0) {
|
|
this.denotation = denotation.join(' ');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
toString() {
|
|
return (this.dataType || '?') + this.shape.toString();
|
|
}
|
|
};
|
|
|
|
xmodel.TensorShape = class {
|
|
|
|
constructor(dimensions) {
|
|
this.dimensions = Array.from(dimensions);
|
|
}
|
|
|
|
toString() {
|
|
if (!this.dimensions || this.dimensions.length === 0) {
|
|
return '';
|
|
}
|
|
return `[${this.dimensions.map((dimension) => dimension.toString()).join(',')}]`;
|
|
}
|
|
};
|
|
|
|
xmodel.Tensor = class {
|
|
|
|
constructor(node) {
|
|
this.type = new xmodel.TensorType(node.output_tensor);
|
|
this.category = node.op_type;
|
|
if (node.op_attr && node.op_attr.data) {
|
|
const data = node.op_attr.data;
|
|
if (data.bytes_value && data.bytes_value.value) {
|
|
this.encoding = '<';
|
|
this.values = data.bytes_value.value;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
xmodel.Utility = class {
|
|
|
|
static attribute(attr_value) {
|
|
const key = attr_value.value;
|
|
const type = key.replace(/_value$/, '');
|
|
const value = attr_value[attr_value.value];
|
|
switch (type) {
|
|
case 'bool': return { type: 'boolean', value };
|
|
case 'int32': return { type: 'int32', value };
|
|
case 'int32_vec': return { type: 'int32[]', value: value.value };
|
|
case 'uint32': return { type: 'uint32', value };
|
|
case 'uint32_vec': return { type: 'uint32[]', value: value.value };
|
|
case 'int64': return { type: 'int64', value };
|
|
case 'uint64': return { type: 'uint64', value };
|
|
case 'float': return { type: 'float32', value };
|
|
case 'float_vec': return { type: 'float32[]', value: value.value };
|
|
case 'double': return { type: 'float64', value };
|
|
case 'double_vec': return { type: 'float64[]', value };
|
|
case 'string': return { type: 'string', value };
|
|
case 'string_vec': return { type: 'string[]', value: value.value };
|
|
case 'bytes': return { type: 'byte[]', value: value.value };
|
|
case 'map_string_2_int32': return { type: 'map<string,int32>', value: value.value };
|
|
default: throw new xmodel.Error(`Unsupported attribute type '${type}'.`);
|
|
}
|
|
}
|
|
};
|
|
|
|
xmodel.Metadata = class {
|
|
|
|
constructor(op_defs) {
|
|
this._types = new Map();
|
|
this._attributes = new Map();
|
|
const categories = [
|
|
['avgpool2d', 'Pool'],
|
|
['batchnorm', 'Normalization'],
|
|
['celu', 'Activation'],
|
|
['concat-fix', 'Tensor'],
|
|
['concat', 'Tensor'],
|
|
['conv2d-fix', 'Layer'],
|
|
['conv2d', 'Layer'],
|
|
['depthwise-conv2d-fix', 'Layer'],
|
|
['depthwise-conv2d', 'Layer'],
|
|
['elu', 'Activation'],
|
|
['fix', 'Quantization'],
|
|
['fix2float', 'Quantization'],
|
|
['flatten', 'Shape'],
|
|
['float2fix', 'Quantization'],
|
|
['gelu', 'Activation'],
|
|
['hard-sigmoid', 'Activation'],
|
|
['hard-sigmoid-fix', 'Activation'],
|
|
['hard-swish', 'Activation'],
|
|
['hard-tanh', 'Activation'],
|
|
['identity', 'Control'],
|
|
['inner-product', 'Layer'],
|
|
['l2_normalize', 'Normalization'],
|
|
['leaky-relu', 'Activation'],
|
|
['leakyrelu', 'Activation'],
|
|
['maxpool2d', 'Pool'],
|
|
['pool-fix', 'Pool'],
|
|
['relu', 'Activation'],
|
|
['relu6', 'Activation'],
|
|
['reshape-fix', 'Shape'],
|
|
['reshape', 'Shape'],
|
|
['scale', 'Layer'],
|
|
['selu', 'Activation'],
|
|
['shape', 'Shape'],
|
|
['sigmoid', 'Activation'],
|
|
['softmax', 'Activation'],
|
|
['squeeze', 'Transform'],
|
|
['stack', 'Tensor'],
|
|
['strided_slice', 'Tensor'],
|
|
['swish', 'Activation'],
|
|
['tanh', 'Activation'],
|
|
['threshold', 'Quantization'],
|
|
['transpose', 'Tensor'],
|
|
['transposed-conv2d', 'Layer'],
|
|
['transposed-conv2d-fix', 'Layer'],
|
|
['transposed-depthwise-conv2d', 'Layer'],
|
|
['transposed-depthwise-conv2d-fix', 'Layer'],
|
|
['upsample-fix', 'Data'],
|
|
];
|
|
this._types = new Map(categories.map(([name, category]) => [name, { name, category }]));
|
|
for (const op_def of op_defs) {
|
|
const type = this._types.get(op_def.name) || { name: op_def.name };
|
|
if (op_def.annotation) {
|
|
type.description = op_def.annotation;
|
|
}
|
|
type.inputs = op_def.input_args.map((input_arg) => {
|
|
const input = {};
|
|
input.name = input_arg.name;
|
|
if (input_arg.annotation) {
|
|
input.description = input_arg.annotation;
|
|
}
|
|
return input;
|
|
});
|
|
type.attributes = op_def.attrs.map((attr) => {
|
|
const attribute = {};
|
|
attribute.name = attr.name;
|
|
attribute.default = xmodel.Utility.attribute(attr.default_value).value;
|
|
if (attr.annotation) {
|
|
attribute.description = attr.annotation;
|
|
}
|
|
return attribute;
|
|
});
|
|
for (const attribute of type.attributes) {
|
|
this._attributes.set(`${type.name}:${attribute.name}`, attribute);
|
|
}
|
|
this._types.set(type.name, type);
|
|
}
|
|
}
|
|
|
|
type(name) {
|
|
if (!this._types.has(name)) {
|
|
this._types.set(name, { name });
|
|
}
|
|
return this._types.get(name);
|
|
}
|
|
|
|
attribute(type, name) {
|
|
const key = `${type}:${name}`;
|
|
return this._attributes.get(key);
|
|
}
|
|
};
|
|
|
|
xmodel.Error = class extends Error {
|
|
|
|
constructor(message) {
|
|
super(message);
|
|
this.name = 'Error loading xmodel.';
|
|
}
|
|
};
|
|
|
|
export const ModelFactory = xmodel.ModelFactory;
|
|
|