Lutz Roeder 3 роки тому
батько
коміт
baf1f6523c
8 змінених файлів з 706 додано та 164 видалено
  1. 1 0
      Makefile
  2. 63 2
      source/rknn-metadata.json
  3. 119 0
      source/rknn-schema.js
  4. 390 160
      source/rknn.js
  5. 1 2
      source/view.js
  6. 35 0
      test/models.json
  7. 20 0
      tools/rknn
  8. 77 0
      tools/rknn.fbs

+ 1 - 0
Makefile

@@ -31,6 +31,7 @@ update: install
 	@./tools/nnabla sync schema metadata
 	@./tools/onnx sync install schema metadata
 	@./tools/om schema
+	@./tools/rknn schema
 	@./tools/paddle sync schema
 	@./tools/pytorch sync install schema metadata
 	@./tools/sklearn sync install metadata

+ 63 - 2
source/rknn-metadata.json

@@ -113,11 +113,11 @@
   },
   {
     "name": "VSI_NN_OP_LRN",
-    "category": "NORMALIZATION"
+    "category": "Normalization"
   },
   {
     "name": "VSI_NN_OP_BATCH_NORM",
-    "category": "NORMALIZATION",
+    "category": "Normalization",
     "inputs": [
       { "name": "input" },
       { "name": "gamma" },
@@ -125,5 +125,66 @@
       { "name": "mean" },
       { "name": "variance" }
     ]
+  },
+  {
+    "name": "VSI_NN_OP_LSTM",
+    "category": "Layer"
+  },
+  {
+    "name": "VSI_NN_OP_STRIDED_SLICE",
+    "category": "Tensor"
+  },
+  {
+    "name": "VSI_NN_OP_SWISH",
+    "category": "Activation"
+  },
+  {
+    "name": "VSI_NN_OP_RELUN",
+    "category": "Activation"
+  },
+  {
+    "name": "VSI_NN_OP_INSTANCE_NORM",
+    "category": "Normalization",
+    "inputs": [
+      { "name": "input" },
+      { "name": "scale" },
+      { "name": "bias" }
+    ]
+  },
+  {
+    "name": "Conv",
+    "category": "Layer"
+  },
+  {
+    "name": "Concat",
+    "category": "Layer"
+  },
+  {
+    "name": "BatchNormalization",
+    "category": "Normalization"
+  },
+  {
+    "name": "Relu",
+    "category": "Activation"
+  },
+  {
+    "name": "Softmax",
+    "category": "Activation"
+  },
+  {
+    "name": "MaxPool",
+    "category": "Pool"
+  },
+  {
+    "name": "AveragePool",
+    "category": "Pool"
+  },
+  {
+    "name": "Reshape",
+    "category": "Shape"
+  },
+  {
+    "name": "Transpose",
+    "category": "Transform"
   }
 ]

+ 119 - 0
source/rknn-schema.js

@@ -0,0 +1,119 @@
+var $root = flatbuffers.get('rknn');
+
+$root.rknn = $root.rknn || {};
+
+$root.rknn.Model = class Model {
+
+    static identifier(reader) {
+        return reader.identifier === 'RKNN';
+    }
+
+    static create(reader) {
+        return $root.rknn.Model.decode(reader, reader.root);
+    }
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Model();
+        $.var1 = reader.int32_(position, 4, 0);
+        $.format = reader.string_(position, 6, null);
+        $.graphs = reader.tableArray(position, 8, $root.rknn.Graph.decode);
+        $.generator = reader.string_(position, 10, null);
+        $.var2 = reader.tableArray(position, 12, $root.rknn.Type1.decode);
+        $.var3 = reader.int32_(position, 14, 0);
+        $.var4 = reader.int32_(position, 16, 0);
+        $.compiler = reader.string_(position, 18, null);
+        $.runtime = reader.string_(position, 20, null);
+        $.source = reader.string_(position, 22, null);
+        $.var5 = reader.bool_(position, 24, false);
+        $.var6 = reader.int32_(position, 26, 0);
+        $.input_json = reader.string_(position, 28, null);
+        $.output_json = reader.string_(position, 30, null);
+        return $;
+    }
+};
+
+$root.rknn.Graph = class Graph {
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Graph();
+        $.tensors = reader.tableArray(position, 4, $root.rknn.Tensor.decode);
+        $.nodes = reader.tableArray(position, 6, $root.rknn.Node.decode);
+        $.inputs = reader.typedArray(position, 8, Int32Array);
+        $.outputs = reader.typedArray(position, 10, Int32Array);
+        $.var1 = reader.tableArray(position, 12, $root.rknn.Type2.decode);
+        return $;
+    }
+};
+
+$root.rknn.Node = class Node {
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Node();
+        $.var1 = reader.int32_(position, 4, 0);
+        $.type = reader.string_(position, 6, null);
+        $.name = reader.string_(position, 8, null);
+        $.var2 = reader.int8_(position, 10, 0);
+        $.inputs = reader.typedArray(position, 12, Int32Array);
+        $.outputs = reader.typedArray(position, 14, Int32Array);
+        $.var3 = reader.tableArray(position, 16, $root.rknn.Type3.decode);
+        $.var4 = reader.int8_(position, 18, 0);
+        $.var5 = reader.int32_(position, 20, 0);
+        $.var6 = reader.int32_(position, 22, 0);
+        return $;
+    }
+};
+
+$root.rknn.Tensor = class Tensor {
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Tensor();
+        $.var01 = reader.int32_(position, 4, 0);
+        $.var02 = reader.int32_(position, 6, 0);
+        $.var03 = reader.int32_(position, 8, 0);
+        $.var04 = reader.int32_(position, 10, 0);
+        $.var05 = reader.int32_(position, 12, 0);
+        $.name = reader.string_(position, 14, null);
+        $.var06 = reader.int32_(position, 16, 0);
+        $.var07 = reader.int32_(position, 18, 0);
+        $.var08 = reader.int32_(position, 20, 0);
+        $.var09 = reader.int32_(position, 22, 0);
+        $.var10 = reader.int32_(position, 24, 0);
+        $.var11 = reader.int32_(position, 26, 0);
+        $.var12 = reader.int32_(position, 28, 0);
+        $.var13 = reader.int32_(position, 30, 0);
+        $.var14 = reader.int32_(position, 32, 0);
+        $.var15 = reader.int32_(position, 34, 0);
+        $.var16 = reader.int32_(position, 36, 0);
+        $.var17 = reader.int32_(position, 38, 0);
+        return $;
+    }
+};
+
+$root.rknn.Type1 = class Type1 {
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Type1();
+        $.var1 = reader.int32_(position, 4, 0);
+        return $;
+    }
+};
+
+$root.rknn.Type2 = class Type2 {
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Type2();
+        $.var1 = reader.typedArray(position, 4, Int32Array);
+        $.var2 = reader.typedArray(position, 6, Int32Array);
+        $.var3 = reader.typedArray(position, 8, Int32Array);
+        return $;
+    }
+};
+
+$root.rknn.Type3 = class Type3 {
+
+    static decode(reader, position) {
+        const $ = new $root.rknn.Type3();
+        $.var1 = reader.int32_(position, 4, 0);
+        return $;
+    }
+};

+ 390 - 160
source/rknn.js

@@ -1,32 +1,88 @@
 
 var rknn = rknn || {};
+var base = base || require('./base');
+var flatbuffers = flatbuffers || require('./flatbuffers');
 var json = json || require('./json');
 
 rknn.ModelFactory = class {
 
     match(context) {
-        return rknn.Reader.open(context);
+        return rknn.Container.open(context);
     }
 
     open(context, match) {
-        return context.metadata('rknn-metadata.json').then((metadata) => {
-            const reader = match;
-            return new rknn.Model(metadata, reader.model, reader.weights);
+        return context.require('./rknn-schema').then(() => {
+            rknn.schema = flatbuffers.get('rknn').rknn;
+            return context.metadata('rknn-metadata.json').then((metadata) => {
+                const container = match;
+                const type = container.type;
+                switch (type) {
+                    case 'json': {
+                        const buffer = container.value;
+                        const reader = json.TextReader.open(buffer);
+                        const model = reader.read();
+                        return new rknn.Model(metadata, type, model, container.next);
+                    }
+                    case 'flatbuffers': {
+                        const buffer = container.value;
+                        const reader = flatbuffers.BinaryReader.open(buffer);
+                        const model = rknn.schema.Model.create(reader);
+                        return new rknn.Model(metadata, type, model, null);
+                    }
+                    case 'openvx': {
+                        const buffer = container.value;
+                        const model = new openvx.Model(buffer);
+                        return new rknn.Model(metadata, type, model, null);
+                    }
+                    default: {
+                        throw new rknn.Error("Unsupported RKNN format '" + container.type + "'.");
+                    }
+                }
+            });
         });
     }
 };
 
 rknn.Model = class {
 
-    constructor(metadata, model, weights) {
-        this._version = model.version;
-        this._producer = model.ori_network_platform || model.network_platform || '';
-        this._runtime = model.target_platform ? model.target_platform.join(',') : '';
-        this._graphs = [ new rknn.Graph(metadata, model, weights) ];
+    constructor(metadata, type, model, next) {
+        switch (type) {
+            case 'json': {
+                this._format = 'RKNN v' + model.version.split('-').shift();
+                this._name = model.name || '';
+                this._producer = model.ori_network_platform || model.network_platform || '';
+                this._runtime = model.target_platform ? model.target_platform.join(',') : '';
+                this._graphs = [ new rknn.Graph(metadata, type, model.name || '', model, next) ];
+                break;
+            }
+            case 'flatbuffers': {
+                const version = model.compiler.split('-').shift();
+                this._format = 'RKNN Lite' + (version ? ' v' + version : '');
+                this._runtime = model.runtime;
+                this._name = model.name || '';
+                this._graphs = model.graphs.map((graph) => new rknn.Graph(metadata, type, '', graph, null));
+                this._metadata = [];
+                this._metadata.push({ name: 'source', value: model.source });
+                break;
+            }
+            case 'openvx': {
+                this._format = 'RKNN OpenVX';
+                this._name = model.name || '';
+                this._graphs = [ new rknn.Graph(metadata, type, '', model, next) ];
+                break;
+            }
+            default: {
+                throw new rknn.Error("Unsupported RKNN model type '" + type + "'.");
+            }
+        }
     }
 
     get format() {
-        return 'RKNN v' + this._version;
+        return this._format;
+    }
+
+    get name() {
+        return this._name;
     }
 
     get producer() {
@@ -37,6 +93,10 @@ rknn.Model = class {
         return this._runtime;
     }
 
+    get metadata() {
+        return this._metadata;
+    }
+
     get graphs() {
         return this._graphs;
     }
@@ -44,79 +104,101 @@ rknn.Model = class {
 
 rknn.Graph = class {
 
-    constructor(metadata, model, weights) {
-        this._name = model.name || '';
+    constructor(metadata, type, name, obj, next) {
+        this._name = name;
         this._inputs = [];
         this._outputs = [];
         this._nodes = [];
-
-        const args = new Map();
-        for (const const_tensor of model.const_tensor) {
-            const name = 'const_tensor:' + const_tensor.tensor_id.toString();
-            const shape = new rknn.TensorShape(const_tensor.size);
-            const type = new rknn.TensorType(const_tensor.dtype, shape);
-            const tensor = new rknn.Tensor(type, const_tensor.offset, weights);
-            const argument = new rknn.Argument(name, type, tensor);
-            args.set(name, argument);
-        }
-        for (const virtual_tensor of model.virtual_tensor) {
-            const name = virtual_tensor.node_id.toString() + ':' + virtual_tensor.output_port.toString();
-            const argument = new rknn.Argument(name, null, null);
-            args.set(name, argument);
-        }
-        for (const norm_tensor of model.norm_tensor) {
-            const name = 'norm_tensor:' + norm_tensor.tensor_id.toString();
-            const shape = new rknn.TensorShape(norm_tensor.size);
-            const type = new rknn.TensorType(norm_tensor.dtype, shape);
-            const argument = new rknn.Argument(name, type, null);
-            args.set(name, argument);
-        }
-        const arg = (name) => {
-            if (!args.has(name)) {
-                const argument = new rknn.Argument(name, null, null);
-                args.set(name, argument);
+        switch (type) {
+            case 'json': {
+                const model = obj;
+                const args = new Map();
+                for (const const_tensor of model.const_tensor) {
+                    const name = 'const_tensor:' + const_tensor.tensor_id.toString();
+                    const shape = new rknn.TensorShape(const_tensor.size);
+                    const type = new rknn.TensorType(const_tensor.dtype, shape);
+                    const tensor = new rknn.Tensor(type, const_tensor.offset, next.value);
+                    const argument = new rknn.Argument(name, type, tensor);
+                    args.set(name, argument);
+                }
+                for (const virtual_tensor of model.virtual_tensor) {
+                    const name = virtual_tensor.node_id.toString() + ':' + virtual_tensor.output_port.toString();
+                    const argument = new rknn.Argument(name, null, null);
+                    args.set(name, argument);
+                }
+                for (const norm_tensor of model.norm_tensor) {
+                    const name = 'norm_tensor:' + norm_tensor.tensor_id.toString();
+                    const shape = new rknn.TensorShape(norm_tensor.size);
+                    const type = new rknn.TensorType(norm_tensor.dtype, shape);
+                    const argument = new rknn.Argument(name, type, null);
+                    args.set(name, argument);
+                }
+                const arg = (name) => {
+                    if (!args.has(name)) {
+                        const argument = new rknn.Argument(name, null, null);
+                        args.set(name, argument);
+                    }
+                    return args.get(name);
+                };
+                for (const node of model.nodes) {
+                    node.input = [];
+                    node.output = [];
+                }
+                for (const connection of model.connection) {
+                    switch (connection.left) {
+                        case 'input':
+                            model.nodes[connection.node_id].input.push(connection);
+                            if (connection.right_node) {
+                                model.nodes[connection.right_node.node_id].output[connection.right_node.tensor_id] = connection;
+                            }
+                            break;
+                        case 'output':
+                            model.nodes[connection.node_id].output.push(connection);
+                            break;
+                        default:
+                            throw new rknn.Error("Unsupported left connection '" + connection.left + "'.");
+                    }
+                }
+                for (const graph of model.graph) {
+                    const key = graph.right + ':' + graph.right_tensor_id.toString();
+                    const argument = arg(key);
+                    const name = graph.left + (graph.left_tensor_id === 0 ? '' : graph.left_tensor_id.toString());
+                    const parameter = new rknn.Parameter(name, [ argument ]);
+                    switch (graph.left) {
+                        case 'input':
+                            this._inputs.push(parameter);
+                            break;
+                        case 'output':
+                            this._outputs.push(parameter);
+                            break;
+                        default:
+                            throw new rknn.Error("Unsupported left graph connection '" + graph.left + "'.");
+                    }
+                }
+                this._nodes = model.nodes.map((node) => new rknn.Node(metadata, type, node, arg, next));
+                break;
             }
-            return args.get(name);
-        };
-
-        for (const node of model.nodes) {
-            node.input = [];
-            node.output = [];
-        }
-        for (const connection of model.connection) {
-            switch (connection.left) {
-                case 'input':
-                    model.nodes[connection.node_id].input.push(connection);
-                    if (connection.right_node) {
-                        model.nodes[connection.right_node.node_id].output[connection.right_node.tensor_id] = connection;
+            case 'flatbuffers': {
+                const graph = obj;
+                const args = graph.tensors.map((tensor) => new rknn.Argument(tensor.name));
+                const arg = (index) => {
+                    if (index >= args.length) {
+                        throw new rknn.Error("Invalid tensor index '" + index.toString() + "'.");
                     }
-                    break;
-                case 'output':
-                    model.nodes[connection.node_id].output.push(connection);
-                    break;
-                default:
-                    throw new rknn.Error("Unsupported left connection '" + connection.left + "'.");
+                    return args[index];
+                };
+                this._nodes = graph.nodes.map((node) => new rknn.Node(metadata, type, node, arg, next));
+                break;
             }
-        }
-
-        for (const graph of model.graph) {
-            const key = graph.right + ':' + graph.right_tensor_id.toString();
-            const argument = arg(key);
-            const name = graph.left + (graph.left_tensor_id === 0 ? '' : graph.left_tensor_id.toString());
-            const parameter = new rknn.Parameter(name, [ argument ]);
-            switch (graph.left) {
-                case 'input':
-                    this._inputs.push(parameter);
-                    break;
-                case 'output':
-                    this._outputs.push(parameter);
-                    break;
-                default:
-                    throw new rknn.Error("Unsupported left graph connection '" + graph.left + "'.");
+            case 'openvx': {
+                const model = obj;
+                this._nodes = model.nodes.map((node) => new rknn.Node(metadata, type, node, null, next));
+                break;
+            }
+            default: {
+                throw new rknn.Error("Unsupported RKNN graph type '" + type + "'.");
             }
         }
-
-        this._nodes = model.nodes.map((node) => new rknn.Node(metadata, node, arg));
     }
 
     get name() {
@@ -182,55 +264,98 @@ rknn.Argument = class {
 
 rknn.Node = class {
 
-    constructor(metadata, node, arg) {
-        this._name = node.name || '';
-        this._type = Object.assign({}, metadata.type(node.op) || { name: node.op });
-        for (const prefix of [ 'VSI_NN_OP_', 'RKNN_OP_' ]) {
-            this._type.name = this._type.name.startsWith(prefix) ? this._type.name.substring(prefix.length) : this._type.name;
-        }
-        this._inputs = [];
-        this._outputs = [];
-        this._attributes = [];
-        node.input = node.input || [];
-        for (let i = 0; i < node.input.length; ) {
-            const input = this._type && this._type.inputs && i < this._type.inputs.length ? this._type.inputs[i] : { name: i === 0 ? 'input' : i.toString() };
-            const count = input.list ? node.input.length - i : 1;
-            const list = node.input.slice(i, i + count).map((input) => {
-                if (input.right_tensor) {
-                    return arg(input.right_tensor.type + ':' + input.right_tensor.tensor_id.toString());
+    constructor(metadata, type, node, arg, next) {
+        switch (type) {
+            case 'json': {
+                this._name = node.name || '';
+                if (node.op === 'VSI_NN_OP_NBG' && next && next.type === 'openvx') {
+                    const buffer = next.value;
+                    const model = new openvx.Model(buffer);
+                    this._type = new rknn.Graph(metadata, next.type, 'NBG', model, null);
                 }
-                if (input.right_node) {
-                    return arg(input.right_node.node_id.toString() + ':' + input.right_node.tensor_id.toString());
+                else if (node.op === 'RKNN_OP_NNBG' && next && next.type === 'flatbuffers') {
+                    const buffer = next.value;
+                    const reader = flatbuffers.BinaryReader.open(buffer);
+                    const model = rknn.schema.Model.create(reader);
+                    this._type = new rknn.Graph(metadata, next.type, 'NNBG', model.graphs[0], null);
                 }
-                throw new rknn.Error('Invalid input argument.');
-            });
-            this._inputs.push(new rknn.Parameter(input.name, list));
-            i += count;
-        }
-        node.output = node.output || [];
-        for (let i = 0; i < node.output.length; ) {
-            const output = this._metadata && this._metadata.outputs && i < this._metadata.outputs.length ? this._metadata.outputs[i] : { name: i === 0 ? 'output' : i.toString() };
-            const count = output.list ? node.output.length - i : 1;
-            const list = node.output.slice(i, i + count).map((output) => {
-                if (output.right_tensor) {
-                    return arg(output.right_tensor.type + ':' + output.right_tensor.tensor_id.toString());
+                else {
+                    this._type = Object.assign({}, metadata.type(node.op) || { name: node.op });
+                    for (const prefix of [ 'VSI_NN_OP_', 'RKNN_OP_' ]) {
+                        this._type.name = this._type.name.startsWith(prefix) ? this._type.name.substring(prefix.length) : this._type.name;
+                    }
                 }
-                if (output.right_node) {
-                    return arg(output.right_node.node_id.toString() + ':' + output.right_node.tensor_id.toString());
+                this._inputs = [];
+                this._outputs = [];
+                this._attributes = [];
+                node.input = node.input || [];
+                for (let i = 0; i < node.input.length; ) {
+                    const input = this._type && this._type.inputs && i < this._type.inputs.length ? this._type.inputs[i] : { name: i === 0 ? 'input' : i.toString() };
+                    const count = input.list ? node.input.length - i : 1;
+                    const list = node.input.slice(i, i + count).map((input) => {
+                        if (input.right_tensor) {
+                            return arg(input.right_tensor.type + ':' + input.right_tensor.tensor_id.toString());
+                        }
+                        if (input.right_node) {
+                            return arg(input.right_node.node_id.toString() + ':' + input.right_node.tensor_id.toString());
+                        }
+                        throw new rknn.Error('Invalid input argument.');
+                    });
+                    this._inputs.push(new rknn.Parameter(input.name, list));
+                    i += count;
                 }
-                throw new rknn.Error('Invalid output argument.');
-            });
-            this._outputs.push(new rknn.Parameter(output.name, list));
-            i += count;
-        }
-        if (node.nn) {
-            const nn = node.nn;
-            for (const key of Object.keys(nn)) {
-                const params = nn[key];
-                for (const name of Object.keys(params)) {
-                    const value = params[name];
-                    this._attributes.push(new rknn.Attribute(name, value));
+                node.output = node.output || [];
+                for (let i = 0; i < node.output.length; ) {
+                    const output = this._metadata && this._metadata.outputs && i < this._metadata.outputs.length ? this._metadata.outputs[i] : { name: i === 0 ? 'output' : i.toString() };
+                    const count = output.list ? node.output.length - i : 1;
+                    const list = node.output.slice(i, i + count).map((output) => {
+                        if (output.right_tensor) {
+                            return arg(output.right_tensor.type + ':' + output.right_tensor.tensor_id.toString());
+                        }
+                        if (output.right_node) {
+                            return arg(output.right_node.node_id.toString() + ':' + output.right_node.tensor_id.toString());
+                        }
+                        throw new rknn.Error('Invalid output argument.');
+                    });
+                    this._outputs.push(new rknn.Parameter(output.name, list));
+                    i += count;
+                }
+                if (node.nn) {
+                    const nn = node.nn;
+                    for (const key of Object.keys(nn)) {
+                        const params = nn[key];
+                        for (const name of Object.keys(params)) {
+                            const value = params[name];
+                            this._attributes.push(new rknn.Attribute(name, value));
+                        }
+                    }
                 }
+                break;
+            }
+            case 'flatbuffers': {
+                this._name = node.name;
+                this._type = metadata.type(node.type);
+                this._inputs = Array.from(node.inputs).map((input, index) => {
+                    const argument = arg(input);
+                    return new rknn.Parameter(index.toString(), [ argument ]);
+                });
+                this._outputs = Array.from(node.outputs).map((output, index) => {
+                    const argument = arg(output);
+                    return new rknn.Parameter(index.toString(), [ argument ]);
+                });
+                this._attributes = [];
+                break;
+            }
+            case 'openvx': {
+                this._name = '';
+                this._type = metadata.type(node.type);
+                this._inputs = [];
+                this._outputs = [];
+                this._attributes = [];
+                break;
+            }
+            default: {
+                throw new rknn.Error("Unsupported RKNN node type '" + type + "'.");
             }
         }
     }
@@ -286,6 +411,7 @@ rknn.Tensor = class {
             case 'float16': size = 2; break;
             case 'float32': size = 4; break;
             case 'float64': size = 8; break;
+            case 'vdata': size = 1; break;
             default: throw new rknn.Error("Unsupported tensor data type '" + this._type.dataType + "'.");
         }
         const shape = type.shape.dimensions;
@@ -469,69 +595,173 @@ rknn.TensorShape = class {
     }
 };
 
-rknn.Reader = class {
+rknn.Container = class {
 
     static open(context) {
         const stream = context.stream;
-        if (stream.length >= 8) {
-            const buffer = stream.read(8);
-            const decoder = new TextDecoder();
-            const signature = decoder.decode(buffer);
-            if (signature === 'RKNN\0\0\0\0' || signature === 'CYPTRKNN') {
-                return new rknn.Reader(stream, signature);
-            }
-        }
-        return null;
+        const container = new rknn.Container(stream, stream.length);
+        return container.type ? container : null;
     }
 
-    constructor(stream, signature) {
+    constructor(stream, length) {
         this._stream = stream;
-        this._signature = signature;
+        this._length = length;
+        this._type = '';
+        if ((stream.position + 16) <= this._length) {
+            const signature = [ 0x52, 0x4B, 0x4E, 0x4E ]; // RKNN
+            if (stream.peek(signature.length).every((value, index) => value === signature[index])) {
+                this._type = 'json';
+            }
+        }
+        if ((stream.position + 16) <= this._length) {
+            const signature = [ 0x43, 0x59, 0x50, 0x54, 0x52, 0x4B, 0x4E, 0x4E ]; // RKNN
+            if (stream.peek(signature.length).every((value, index) => value === signature[index])) {
+                this._type = 'crypt';
+            }
+        }
+        if ((stream.position + 8) <= this._length) {
+            const signature = [ 0x52, 0x4B, 0x4E, 0x4E ]; // RKNN
+            if (stream.peek(8).subarray(4, 8).every((value, index) => value === signature[index])) {
+                this._type = 'flatbuffers';
+            }
+        }
+        if ((stream.position + 8) <= this._length) {
+            const signature = [ 0x56, 0x50, 0x4D, 0x4E ]; // VPMN
+            if (stream.peek(signature.length).every((value, index) => value === signature[index])) {
+                this._type = 'openvx';
+            }
+        }
     }
 
-    get version() {
-        this._decode();
-        return this._version;
+    get type() {
+        return this._type;
     }
 
-    get weights() {
-        this._decode();
-        return this._weights;
+    get value() {
+        this.read();
+        return this._value;
     }
 
-    get model() {
-        this._decode();
-        return this._model;
+    get next() {
+        this.read();
+        return this._next;
     }
 
-    _decode() {
+    read() {
         if (this._stream) {
-            if (this._signature === 'CYPTRKNN') {
-                throw new rknn.Error('Invalid file content. File contains undocumented encrypted RKNN data.');
+            const stream = this._stream;
+            delete this._stream;
+            switch (this._type) {
+                case 'crypt': {
+                    throw new rknn.Error('Invalid file content. File contains undocumented encrypted RKNN data.');
+                }
+                case 'json': {
+                    const uint64 = () => {
+                        const buffer = stream.read(8);
+                        const reader = new base.BinaryReader(buffer);
+                        return reader.uint64();
+                    };
+                    stream.skip(8);
+                    const version = uint64();
+                    const data_size = uint64();
+                    switch (version) {
+                        case 0x0001:
+                        case 0x1001:
+                            break;
+                        case 0x0002:
+                            if (data_size > 0) {
+                                stream.skip(40);
+                            }
+                            break;
+                        default:
+                            throw new rknn.Error("Unsupported container version '" + version + "'.");
+                    }
+                    this._next = new rknn.Container(stream, data_size);
+                    this._next.read();
+                    const value_size = uint64(stream);
+                    this._value = stream.read(value_size);
+                    break;
+                }
+                case 'flatbuffers': {
+                    this._value = stream.read(this._length);
+                    this._next = null;
+                    break;
+                }
+                case 'openvx': {
+                    this._value = stream.read(this._length);
+                    this._next = null;
+                    break;
+                }
+                case '': {
+                    this._value = stream.read(this._length);
+                    this._next = null;
+                    break;
+                }
+                default: {
+                    throw new rknn.Error("Unsupported container type '" + this._format + "'.");
+                }
             }
-            this._version = this._uint64();
-            const weights_size = this._uint64();
-            if (this._version > 1) {
-                this._stream.read(40);
+        }
+    }
+};
+
+var openvx = openvx || {};
+
+openvx.Model = class {
+
+    constructor(buffer) {
+        const reader = new openvx.BinaryReader(buffer);
+        reader.skip(4); // signature
+        const major = reader.uint16();
+        /* const minor = */ reader.uint16();
+        reader.skip(4);
+        this._name = reader.string(64);
+        this._nodes = new Array(reader.uint32());
+        if (major > 3) {
+            reader.skip(296);
+        }
+        else if (major > 1) {
+            reader.skip(288);
+        }
+        else {
+            reader.skip(32);
+        }
+        /* const inputOffset = */ reader.uint32();
+        /* const inputSize = */ reader.uint32();
+        /* const outputOffset = */ reader.uint32();
+        /* const outputSize = */ reader.uint32();
+        const nodeOffset = reader.uint32();
+        /* const nodeSize = */ reader.uint32();
+        reader.seek(nodeOffset);
+        for (let i = 0; i < this._nodes.length; i++) {
+            const type = reader.string(64);
+            const node = { type: type };
+            node.index = reader.uint32();
+            node.c = reader.uint32();
+            if (major > 3) {
+                node.d = reader.uint32();
             }
-            this._weights = this._stream.read(weights_size);
-            const model_size = this._uint64();
-            const model_buffer = this._stream.read(model_size);
-            const reader = json.TextReader.open(model_buffer);
-            this._model = reader.read();
-            delete this._stream;
+            this._nodes[i] = node;
         }
     }
 
-    _uint64() {
-        const buffer = this._stream.read(8);
-        const view = new DataView(buffer.buffer, buffer.byteOffset, buffer.byteLength);
-        return view.getUint64(0, true).toNumber();
+    get name() {
+        return this._name;
     }
 
-    _read() {
-        const size = this._uint64();
-        return this._stream.read(size);
+    get nodes() {
+        return this._nodes;
+    }
+};
+
+openvx.BinaryReader = class extends base.BinaryReader {
+
+    string(length) {
+        const buffer = this.read(length);
+        const index = buffer.indexOf(0);
+        const data = index === -1 ? buffer : buffer.subarray(0, index);
+        this._decoder = this._decoder || new TextDecoder('ascii');
+        return this._decoder.decode(data);
     }
 };
 

+ 1 - 2
source/view.js

@@ -1588,7 +1588,7 @@ view.ModelFactoryService = class {
         this.register('./bigdl', [ '.model', '.bigdl' ]);
         this.register('./darknet', [ '.cfg', '.model', '.txt', '.weights' ]);
         this.register('./weka', [ '.model' ]);
-        this.register('./rknn', [ '.rknn', '.onnx' ]);
+        this.register('./rknn', [ '.rknn', '.nb', '.onnx' ]);
         this.register('./dlc', [ '.dlc', 'model', '.params' ]);
         this.register('./armnn', [ '.armnn', '.json' ]);
         this.register('./mnn', ['.mnn']);
@@ -2084,7 +2084,6 @@ view.ModelFactoryService = class {
                 { name: 'TSD header', value: /^%TSD-Header-###%/ },
                 { name: 'AppleDouble data', value: /^\x00\x05\x16\x07/ },
                 { name: 'TensorFlow Hub module', value: /^\x08\x03$/, identifier: 'tfhub_module.pb' },
-                { name: 'OpenVX network binary graph data', value: /^VPMN/ }, // network_binary.nb
                 { name: 'ViSQOL model', value: /^svm_type\snu_svr/ }
             ];
             /* eslint-enable no-control-regex */

+ 35 - 0
test/models.json

@@ -5375,6 +5375,13 @@
     "format":   "RKNN v1.3.0",
     "link":     "https://github.com/lutzroeder/netron/issues/639"
   },
+  {
+    "type":     "rknn",
+    "target":   "det_monitor.nb",
+    "source":   "https://github.com/lutzroeder/netron/files/8886768/det_monitor.nb.zip[det_monitor.nb]",
+    "format":   "RKNN OpenVX",
+    "link":     "https://github.com/lutzroeder/netron/issues/639"
+  },
   {
     "type":     "rknn",
     "target":   "deepfusion.rknn",
@@ -5389,6 +5396,27 @@
     "format":   "RKNN v1.3.2",
     "link":     "https://github.com/lutzroeder/netron/issues/639"
   },
+  {
+    "type":     "rknn",
+    "target":   "mobilenet_v1.rknn",
+    "source":   "https://github.com/lutzroeder/netron/files/8886811/mobilenet_v1.rknn.zip[mobilenet_v1.rknn]",
+    "format":   "RKNN Lite v1.2.6",
+    "link":     "https://github.com/lutzroeder/netron/issues/639"
+  },
+  {
+    "type":     "rknn",
+    "target":   "mobilenetv2.rknn",
+    "source":   "https://github.com/lutzroeder/netron/files/8886972/mobilenetv2.rknn.zip[mobilenetv2.rknn]",
+    "format":   "RKNN v1.2.0",
+    "link":     "https://github.com/lutzroeder/netron/issues/639"
+  },
+  {
+    "type":     "rknn",
+    "target":   "resize_area.rknn",
+    "source":   "https://github.com/lutzroeder/netron/files/8886403/resize_area.rknn.zip[resize_area.rknn]",
+    "format":   "RKNN v1.3.0",
+    "link":     "https://github.com/lutzroeder/netron/issues/639"
+  },
   {
     "type":     "rknn",
     "target":   "resnet_18.rknn",
@@ -5396,6 +5424,13 @@
     "format":   "RKNN v1.3.2",
     "link":     "https://github.com/lutzroeder/netron/issues/639"
   },
+  {
+    "type":     "rknn",
+    "target":   "yolov5s-640-640.rknn",
+    "source":   "https://github.com/lutzroeder/netron/files/8886404/yolov5s-640-640.rknn.zip[yolov5s-640-640.rknn]",
+    "format":   "RKNN v1.2.5",
+    "link":     "https://github.com/lutzroeder/netron/issues/639"
+  },
   {
     "type":     "sklearn",
     "target":   "best_boston.pb",

+ 20 - 0
tools/rknn

@@ -0,0 +1,20 @@
+#!/bin/bash
+
+set -e
+pushd $(cd $(dirname ${0})/..; pwd) > /dev/null
+
+schema() {
+    echo "rknn schema"
+    [[ $(grep -U $'\x0D' ./source/rknn-schema.js) ]] && crlf=1
+    node ./tools/flatc.js --root rknn --out ./source/rknn-schema.js ./tools/rknn.fbs
+    if [[ -n ${crlf} ]]; then
+        unix2dos --quiet --newfile ./source/rknn-schema.js ./source/rknn-schema.js
+    fi
+}
+
+while [ "$#" != 0 ]; do
+    command="$1" && shift
+    case "${command}" in
+        "schema") schema;;
+    esac
+done

+ 77 - 0
tools/rknn.fbs

@@ -0,0 +1,77 @@
+namespace rknn;
+
+file_identifier 'RKNN'
+root_type Model
+
+table Model {
+    var1: int;
+    format: string;
+    graphs: [Graph];
+    generator: string;
+    var2: [Type1];
+    var3: int;
+    var4: int;
+    compiler: string;
+    runtime: string;
+    source: string;
+    var5: bool;
+    var6: int;
+    input_json: string;
+    output_json: string;
+}
+
+table Graph {
+    tensors: [Tensor];
+    nodes: [Node];
+    inputs: [int];
+    outputs: [int];
+    var1: [Type2];
+}
+
+table Node {
+    var1: int;
+    type: string;
+    name: string;
+    var2: byte;
+    inputs: [int];
+    outputs: [int];
+    var3: [Type3];
+    var4: byte;
+    var5: int;
+    var6: int;
+}
+
+table Tensor {
+    var01: int;
+    var02: int;
+    var03: int;
+    var04: int;
+    var05: int;
+    name: string;
+    var06: int;
+    var07: int;
+    var08: int;
+    var09: int;
+    var10: int;
+    var11: int;
+    var12: int;
+    var13: int;
+    var14: int;
+    var15: int;
+    var16: int;
+    var17: int;
+}
+
+table Type1 {
+    var1: int;
+}
+
+table Type2 {
+    var1: [int];
+    var2: [int];
+    var3: [int];
+}
+
+table Type3 {
+    var1: int;
+}