浏览代码

Add kmodel test file (#871)

Lutz Roeder 4 月之前
父节点
当前提交
17f9a5f0cd
共有 2 个文件被更改,包括 955 次插入63 次删除
  1. 913 63
      source/kmodel.js
  2. 42 0
      test/models.json

+ 913 - 63
source/kmodel.js

@@ -252,7 +252,7 @@ kmodel.Reader = class {
     }
 
     read() {
-        if (this.version < 3 || this.version > 8) {
+        if (this.version < 3 || this.version > 7) {
             throw new kmodel.Error(`Unsupported model version '${this.version}'.`);
         }
         const types = new Map();
@@ -722,7 +722,14 @@ kmodel.Reader = class {
                     layer.inputs[0].value[0].shape = reader.runtime_shape_t();
                     layer.perm = reader.runtime_shape_t();
                 });
-                register(  0x0D, 'strided_slice', 'Tensor');
+                register(  0x0D, 'strided_slice', 'Tensor', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.begin = reader.runtime_shape_t();
+                    layer.end = reader.runtime_shape_t();
+                    layer.strides = reader.runtime_shape_t();
+                });
                 register(  0x0E, 'unary', '', (layer, reader) => {
                     layer.inputs = [reader.parameter('input')];
                     layer.outputs = [reader.parameter('output')];
@@ -800,18 +807,135 @@ kmodel.Reader = class {
                     layer.inputs = [reader.parameter('input'), reader.parameter('table')];
                     layer.outputs = [reader.parameter('output')];
                 });
-                register(  0x13, 'conv2d_transpose', 'Layer');
+                register(  0x13, 'conv2d_transpose', 'Layer', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.groups = reader.int32();
+                    layer.out_channels = reader.int32();
+                    layer.padding_h = reader.padding();
+                    layer.padding_w = reader.padding();
+                    layer.filter_h = reader.int32();
+                    layer.filter_w = reader.int32();
+                    layer.stride_h = reader.int32();
+                    layer.stride_w = reader.int32();
+                    layer.dilation_h = reader.int32();
+                    layer.dilation_w = reader.int32();
+                    layer.fused_activation = [reader.float32(), reader.float32()];
+                    const weights_shape = [layer.out_channels, layer.inputs[0].value[0].shape[1] / layer.groups, layer.filter_h, layer.filter_w];
+                    const weights_size = 4 * weights_shape.reduce((a, b) => a * b);
+                    layer.inputs.push({
+                        name: 'weights',
+                        value: [{
+                            name: '',
+                            datatype: 'float32',
+                            shape: weights_shape,
+                            data: reader.read(weights_size)
+                        }]
+                    });
+                    const bias_shape = [layer.out_channels];
+                    const bias_size = 4 * layer.out_channels;
+                    layer.inputs.push({
+                        name: 'bias',
+                        value: [{
+                            name: '',
+                            datatype: 'float32',
+                            shape: bias_shape,
+                            data: reader.read(bias_size)
+                        }]
+                    });
+                });
                 register(  0x14, 'nnil_unary_method', '', (layer, reader, size) => {
                     const position = reader.position;
                     layer.inputs = [reader.parameter('input')];
                     layer.outputs = [reader.parameter('output')];
                     layer.body = reader.read(size - (reader.position - position));
                 });
-                register(0x1001, 'cpu_conv2d', 'Layer');
-                register(0x1002, 'cpu_depthwise_conv2d', 'Layer');
-                register(0x1003, 'cpu_reduce_window2d');
-                register(0x1004, 'cpu_quantized_conv2d', 'Layer');
-                register(0x1005, 'cpu_quantized_depthwise_conv2d', 'Layer');
+                register(0x1001, 'cpu_conv2d', 'Layer', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.groups = reader.int32();
+                    layer.out_channels = reader.int32();
+                    layer.padding_h = reader.padding();
+                    layer.padding_w = reader.padding();
+                    layer.filter_h = reader.int32();
+                    layer.filter_w = reader.int32();
+                    layer.stride_h = reader.int32();
+                    layer.stride_w = reader.int32();
+                    layer.dilation_h = reader.int32();
+                    layer.dilation_w = reader.int32();
+                    layer.fused_activation = [reader.float32(), reader.float32()];
+                });
+                register(0x1002, 'cpu_depthwise_conv2d', 'Layer', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.out_channels = reader.int32();
+                    layer.padding_h = reader.padding();
+                    layer.padding_w = reader.padding();
+                    layer.filter_h = reader.int32();
+                    layer.filter_w = reader.int32();
+                    layer.stride_h = reader.int32();
+                    layer.stride_w = reader.int32();
+                    layer.dilation_h = reader.int32();
+                    layer.dilation_w = reader.int32();
+                    layer.fused_activation = [reader.float32(), reader.float32()];
+                });
+                register(0x1003, 'cpu_reduce_window2d', 'Pool', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.reduce_op = reader.reduce_op_t();
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.padding_h = reader.padding();
+                    layer.padding_w = reader.padding();
+                    layer.filter_h = reader.int32();
+                    layer.filter_w = reader.int32();
+                    layer.stride_h = reader.int32();
+                    layer.stride_w = reader.int32();
+                    layer.dilation_h = reader.int32();
+                    layer.dilation_w = reader.int32();
+                    layer.fused_activation = [reader.float32(), reader.float32()];
+                });
+                register(0x1004, 'cpu_quantized_conv2d', 'Layer', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.groups = reader.int32();
+                    layer.out_channels = reader.int32();
+                    layer.padding_h = reader.padding();
+                    layer.padding_w = reader.padding();
+                    layer.filter_h = reader.int32();
+                    layer.filter_w = reader.int32();
+                    layer.stride_h = reader.int32();
+                    layer.stride_w = reader.int32();
+                    layer.dilation_h = reader.int32();
+                    layer.dilation_w = reader.int32();
+                    layer.input_offset = reader.int32();
+                    layer.filter_offset = reader.int32();
+                    layer.output_mul = reader.int32();
+                    layer.output_shift = reader.int32();
+                    layer.output_offset = reader.int32();
+                });
+                register(0x1005, 'cpu_quantized_depthwise_conv2d', 'Layer', (layer, reader) => {
+                    layer.inputs = [reader.parameter('input')];
+                    layer.outputs = [reader.parameter('output')];
+                    layer.inputs[0].value[0].shape = reader.runtime_shape_t();
+                    layer.out_channels = reader.int32();
+                    layer.padding_h = reader.padding();
+                    layer.padding_w = reader.padding();
+                    layer.filter_h = reader.int32();
+                    layer.filter_w = reader.int32();
+                    layer.stride_h = reader.int32();
+                    layer.stride_w = reader.int32();
+                    layer.dilation_h = reader.int32();
+                    layer.dilation_w = reader.int32();
+                    layer.input_offset = reader.int32();
+                    layer.filter_offset = reader.int32();
+                    layer.output_mul = reader.int32();
+                    layer.output_shift = reader.int32();
+                    layer.output_offset = reader.int32();
+                });
                 register(0x2001, 'kpu_upload', '', (layer, reader) => {
                     layer.inputs = [reader.parameter('input')];
                     layer.outputs = [reader.parameter('output')];
@@ -904,68 +1028,74 @@ kmodel.Reader = class {
                 });
                 break;
             }
-            case 5: {
-                const reader = new kmodel.BinaryReader.v5(this.stream);
-                const model_header = reader.model_header();
-                if (model_header.header_size < 32) {
-                    throw new kmodel.Error(`Invalid header size '${model_header.header_size}'.`);
+            case 5:
+            case 6:
+            case 7: {
+                let reader = null;
+                switch (this.version) {
+                    case 5: reader = new kmodel.BinaryReader.v5(this.stream); break;
+                    case 6: reader = new kmodel.BinaryReader.v6(this.stream); break;
+                    case 7: reader = new kmodel.BinaryReader.v7(this.stream); break;
+                    default: throw new kmodel.Error(`Unsupported model version '${this.version}'.`);
                 }
-                if (model_header.header_size > reader.position) {
-                    reader.skip(model_header.header_size - reader.position);
-                }
-                delete model_header.header_size;
+                const model_header = reader.model_header();
                 this.modules = new Array(model_header.modules);
                 for (let i = 0; i < this.modules.length; i++) {
-                    const start = reader.position;
                     const module_header = reader.module_header();
-                    if (module_header.header_size > (reader.position - start)) {
-                        reader.skip(module_header.header_size - (reader.position - start));
-                    }
-                    const mempools = new Array(module_header.mempools);
-                    for (let i = 0; i < mempools.length; i++) {
-                        mempools[i] = reader.mempool_desc();
+                    const mempools = new Array(module_header.mempools || 0);
+                    for (let j = 0; j < mempools.length; j++) {
+                        mempools[j] = reader.mempool_desc();
                     }
-                    const shared_mempools = new Array(module_header.shared_mempools);
-                    for (let i = 0; i < shared_mempools.length; i++) {
+                    const shared_mempools = new Array(module_header.shared_mempools || 0);
+                    for (let j = 0; j < shared_mempools.length; j++) {
                         shared_mempools[i] = reader.mempool_desc();
                     }
                     const function_headers = new Array(module_header.functions);
                     const functions = new Array(module_header.functions);
-                    for (let i = 0; i < functions.length; i++) {
+                    for (let j = 0; j < functions.length; j++) {
                         const position = reader.position;
-                        const function_header = reader.function_header();
-                        const header_size = reader.position - position;
-                        if (function_header.header_size > header_size) {
-                            reader.skip(function_header.header_size - header_size);
-                        }
-                        const inputs = new Array(function_header.inputs);
-                        for (let i = 0; i < inputs.length; i++) {
-                            inputs[i] = reader.parameter(`input${i === 0 ? '' : (i + 1)}`);
-                        }
-                        for (let i = 0; i < inputs.length; i++) {
-                            inputs[i].value[0].shape = reader.shape();
+                        let inputs = [];
+                        let outputs = [];
+                        if (this.version === 5) {
+                            const function_header = reader.function_header();
+                            inputs = new Array(function_header.inputs || 0);
+                            for (let k = 0; k < inputs.length; k++) {
+                                inputs[k] = reader.parameter(`input${k === 0 ? '' : (k + 1)}`);
+                            }
+                            for (let k = 0; k < inputs.length; k++ || 0) {
+                                inputs[k].value[0].shape = reader.shape();
+                            }
+                            outputs = new Array(function_header.outputs || 0);
+                            for (let k = 0; k < outputs.length; k++) {
+                                outputs[k] = reader.parameter(`output${k === 0 ? '' : (k + 1)}`);
+                            }
+                            for (let k = 0; k < outputs.length; k++) {
+                                outputs[k].value[0].shape = reader.shape();
+                            }
+                            reader.align(8);
+                            const size = reader.position - position;
+                            if (function_header.size > size) {
+                                reader.skip(function_header.size - size);
+                            }
+                            function_headers[j] = function_header;
+                        } else {
+                            const func_start = reader.position;
+                            const function_header = reader.function_header();
+                            const header_size = reader.position - func_start;
+                            const remaining_size = function_header.size - header_size;
+                            if (remaining_size > 0) {
+                                reader.skip(remaining_size);
+                            }
+                            function_headers[j] = function_header;
                         }
-                        const outputs = new Array(function_header.outputs);
-                        for (let i = 0; i < outputs.length; i++) {
-                            outputs[i] = reader.parameter(`output${i === 0 ? '' : (i + 1)}`);
-                        }
-                        for (let i = 0; i < outputs.length; i++) {
-                            outputs[i].value[0].shape = reader.shape();
-                        }
-                        reader.align(8);
-                        const size = reader.size - position;
-                        if (function_header.size > size) {
-                            reader.skip(function_header.size - size);
-                        }
-                        function_headers[i] = function_header;
-                        functions[i] = {
+                        functions[j] = {
                             type: { name: 'Unknown' },
                             inputs,
                             outputs
                         };
                     }
                     const sections = new Map();
-                    for (let i = 0; i < module_header.sections; i++) {
+                    for (let j = 0; j < module_header.sections; j++) {
                         const section_header = reader.section_header();
                         reader.skip(section_header.body_start);
                         const body = reader.read(section_header.body_size);
@@ -976,28 +1106,42 @@ kmodel.Reader = class {
                         reader.align(8);
                         sections.set(section_header.name, section);
                     }
-                    for (let i = 0; i < function_headers.length; i++) {
-                        const function_header = function_headers[i];
+                    for (let j = 0; j < function_headers.length; j++) {
+                        const function_header = function_headers[j];
                         const reader = sections.get('.text').reader;
                         reader.seek(function_header.entrypoint);
-                        function_header.text = reader.read(function_header.text_size);
+                        const size = function_header.text_size;
+                        function_header.text = reader.read(size);
                         const layer = functions[i];
                         switch (module_header.type) {
-                            case 'stackvm':
+                            case 'stackvm': {
                                 layer.type = { name: 'stackvm' };
+                                let reader = null;
+                                const buffer = function_header.text;
+                                switch (this.version) {
+                                    case 5: reader = new kmodel.BytecodeReader.v5(buffer); break;
+                                    case 6: reader = new kmodel.BytecodeReader.v6(buffer); break;
+                                    case 7: reader = new kmodel.BytecodeReader.v6(buffer); break;
+                                    default: throw new kmodel.Error(`Unsupported model version '${this.version}'.`);
+                                }
+                                reader = null;
+                                if (reader) {
+                                    layer.operations = reader.read();
+                                    layer.tensor_operations = layer.operations.filter((op) => op.name === 'tensor');
+                                }
                                 break;
+                            }
                             case 'k210':
-                                break;
+                            case 'k230':
                             case 'k510':
                                 break;
                             default:
                                 throw new kmodel.Error(`Unsupported module type '${module_header.type}'.`);
                         }
                     }
-                    const name = this.modules.length > 1 ? i.toString() : '';
                     this.modules[i] = {
-                        name,
                         type: module_header.type,
+                        name: this.modules.length > 1 ? i.toString() : '',
                         layers: functions
                     };
                 }
@@ -1065,6 +1209,10 @@ kmodel.BinaryReader = class {
         return this._reader.uint32();
     }
 
+    uint64() {
+        return this._reader.uint64().toNumber();
+    }
+
     float32() {
         return this._reader.float32();
     }
@@ -1268,7 +1416,7 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
     }
 
     model_header() {
-        return {
+        const model_header = {
             header_size: this.uint32(),
             flags: this.uint32(),
             alignment: this.uint32(),
@@ -1276,6 +1424,14 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
             entry_module: this.uint32(),
             entry_function: this.uint32()
         };
+        if (model_header.header_size < 32) {
+            throw new kmodel.Error(`Invalid header size '${model_header.header_size}'.`);
+        }
+        if (model_header.header_size > this.position) {
+            this.skip(model_header.header_size - this.position);
+        }
+        delete model_header.header_size;
+        return model_header;
     }
 
     module_type_t() {
@@ -1286,7 +1442,8 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
     }
 
     module_header() {
-        return {
+        const start = this.position;
+        const module_header = {
             type: this.module_type_t(),
             version: this.uint32(),
             header_size: this.uint32(),
@@ -1297,6 +1454,10 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
             functions: this.uint32(),
             reserved0: this.uint32()
         };
+        if (module_header.header_size > (this.position - start)) {
+            this.skip(module_header.header_size - (this.position - start));
+        }
+        return module_header;
     }
 
     mempool_desc() {
@@ -1321,7 +1482,8 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
     }
 
     function_header() {
-        return {
+        const position = this.position;
+        const function_header = {
             header_size: this.uint32(),
             size: this.uint32(),
             input_pool_size: this.uint32(),
@@ -1331,6 +1493,11 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
             entrypoint: this.uint32(),
             text_size: this.uint32()
         };
+        const header_size = this.position - position;
+        if (function_header.header_size > header_size) {
+            this.skip(function_header.header_size - header_size);
+        }
+        return function_header;
     }
 
     memory_location_t() {
@@ -1388,6 +1555,689 @@ kmodel.BinaryReader.v5 = class extends kmodel.BinaryReader {
     }
 };
 
+kmodel.BinaryReader.v6 = class extends kmodel.BinaryReader.v5 {
+
+    model_header() {
+        return {
+            flags: this.uint32(),
+            alignment: this.uint32(),
+            modules: this.uint32(),
+            entry_module: this.uint32(),
+            entry_function: this.uint32(),
+            reserved0: this.uint32()
+        };
+    }
+
+    module_header() {
+        return {
+            type: this.module_type_t(),
+            version: this.uint32(),
+            size: this.uint32(),
+            sections: this.uint32(),
+            functions: this.uint32()
+        };
+    }
+
+    function_header() {
+        return {
+            parameters: this.uint32(),
+            entrypoint: this.uint32(),
+            text_size: this.uint32(),
+            size: this.uint32(),
+            sections: this.uint32(),
+            reserved0: this.uint32(),
+        };
+    }
+
+    section_header() {
+        const buffer = this.read(16);
+        const decoder = new TextDecoder('ascii');
+        const name = decoder.decode(buffer);
+        return {
+            name: name.replace(/\0.*$/, ''),
+            size: this.uint32(),
+            flags: this.uint32(),
+            body_start: this.uint32(),
+            body_size: this.uint32(),
+            memory_size: this.uint32(),
+            reserved0: this.uint32()
+        };
+    }
+
+    deserialize_datatype() {
+        const typecode = this.byte();
+        if (typecode === 100) { // dt_pointer
+            const elem_type = this.deserialize_datatype();
+            return { type: 'pointer', elem_type };
+        } else if (typecode === 101) { // dt_valuetype
+            const uuid = this.read(16);
+            const size_bytes = this.uint32();
+            return { type: 'valuetype', uuid, size_bytes };
+        } else if (typecode >= 0 && typecode <= 12) {
+            const types = ['bool', 'int8', 'int16', 'int32', 'int64', 'uint8', 'uint16', 'uint32', 'uint64', 'float16', 'float32', 'float64', 'bfloat16'];
+            return { type: 'prim', name: types[typecode] || `unknown_${typecode}` };
+        }
+        throw new kmodel.Error(`Unknown datatype typecode: ${typecode}`);
+    }
+
+    deserialize_type() {
+        const token = this.byte();
+        switch (token) {
+            case 0: // type_sig_invalid
+                return { type: 'invalid' };
+            case 1: // type_sig_any
+                return { type: 'any' };
+            case 2: { // type_sig_tensor
+                const elem_type = this.deserialize_datatype();
+                const is_scalar = this.byte() === 0;
+                const shape = [];
+                if (!is_scalar) {
+                    let dim_token = 0;
+                    while ((dim_token = this.byte()) !== 255) { // type_sig_end
+                        if (dim_token === 1) { // dim_fixed
+                            shape.push(this.uint32());
+                        } else if (dim_token === 2) { // dim_unknown
+                            shape.push(-1);
+                        } else {
+                            throw new kmodel.Error(`Invalid dim token: ${dim_token}`);
+                        }
+                    }
+                }
+                return { type: 'tensor', elem_type, shape, is_scalar };
+            }
+            case 3: { // type_sig_tuple
+                const fields = [];
+                while (this.peek() !== 255) { // type_sig_end
+                    fields.push(this.deserialize_type());
+                }
+                this.skip(1); // skip end token
+                return { type: 'tuple', fields };
+            }
+            case 4: // type_sig_callable
+                throw new kmodel.Error('Callable types not supported');
+            default:
+                throw new kmodel.Error(`Unknown type signature token: ${token}`);
+        }
+    }
+
+    peek() {
+        const value = this.byte();
+        this.seek(this.position - 1);
+        return value;
+    }
+};
+
+kmodel.BinaryReader.v7 = class extends kmodel.BinaryReader.v6 {
+
+    module_header() {
+        return {
+            type: this.module_type_t(),
+            version: this.uint32(),
+            sections: this.uint32(),
+            functions: this.uint32(),
+            reserved0: this.uint32(),
+            size: this.uint64(),
+        };
+    }
+
+    function_header() {
+        return {
+            parameters: this.uint32(),
+            sections: this.uint32(),
+            entrypoint: this.uint64(),
+            text_size: this.uint64(),
+            size: this.uint64(),
+        };
+    }
+
+    section_header() {
+        const buffer = this.read(16);
+        const decoder = new TextDecoder('ascii');
+        const name = decoder.decode(buffer);
+        return {
+            name: name.replace(/\0.*$/, ''),
+            flags: this.uint32(),
+            reserved0: this.uint32(),
+            size: this.uint64(),
+            body_start: this.uint64(),
+            body_size: this.uint64(),
+            memory_size: this.uint64(),
+        };
+    }
+};
+
+kmodel.BytecodeReader = class {
+
+    constructor(buffer) {
+        this._reader = base.BinaryReader.open(buffer);
+    }
+
+    read() {
+        const operations = [];
+        while (this._reader.position < this._reader.length) {
+            const position = this._reader.position;
+            const opcode = this._reader.byte();
+            if (!this._opcodes.has(opcode)) {
+                throw new kmodel.Error(`Unknown opcode '${opcode}'.`);
+            }
+            const name = this._opcodes.get(opcode);
+            const operation = { name, position };
+            this.operation(operation);
+            // console.log(JSON.stringify(operation));
+            operations.push(operation);
+            if (name === 'ret') {
+                break;
+            }
+        }
+        return operations;
+    }
+
+    strings() {
+        // Read strings until we encounter a null byte as the first character
+        const array = [];
+        while (this._reader.position < this._reader.length) {
+            // Peek at next byte
+            const byte = this._reader.byte();
+            if (byte === 0) {
+                break; // End of array
+            }
+            // Put the byte back by moving position back
+            this._reader.seek(this._reader.position - 1);
+            array.push(this.string());
+        }
+        return array;
+    }
+};
+
+kmodel.BytecodeReader.v5 = class extends kmodel.BytecodeReader {
+
+    constructor(buffer) {
+        super(buffer);
+        this._opcodes = new Map([
+            [0, 'nop'], [1, 'ldnull'], [2, 'ldc_i4'], [3, 'ldc_i4_0'], [4, 'ldc_i4_1'],
+            [5, 'ldc_r4'], [6, 'ldind_i1'], [7, 'ldind_i2'], [8, 'ldind_i4'], [9, 'ldind_i'],
+            [10, 'ldind_u1'], [11, 'ldind_u2'], [12, 'ldind_u4'], [13, 'ldind_u'],
+            [14, 'ldind_br2'], [15, 'ldind_r4'], [16, 'stind_i1'], [17, 'stind_i2'],
+            [18, 'stind_i4'], [19, 'stind_i'], [20, 'stind_br2'], [21, 'stind_r4'],
+            [22, 'lea_gp'], [23, 'lea_buffer'], [24, 'ldelem_i1'], [25, 'ldelem_i2'],
+            [26, 'ldelem_i4'], [27, 'ldelem_i'], [28, 'ldelem_u1'], [29, 'ldelem_u2'],
+            [30, 'ldelem_u4'], [0x1F, 'ldelem_u'], [0x20, 'ldelem_br2'], [33, 'ldelem_r4'],
+            [34, 'stelem_i1'], [35, 'stelem_i2'], [36, 'stelem_i4'], [37, 'stelem_i'],
+            [38, 'stelem_br2'], [39, 'stelem_r4'], [40, 'ldarg'], [41, 'ldarg_0'],
+            [42, 'ldarg_1'], [43, 'ldarg_2'], [44, 'ldarg_3'], [45, 'ldarg_4'],
+            [46, 'ldarg_5'], [47, 'dup'], [48, 'pop'], [0x31, 'stshape'], [50, 'stpaddings'],
+            [51, 'neg'], [52, 'add'], [53, 'sub'], [54, 'mul'], [55, 'div'], [56, 'div_u'],
+            [57, 'rem'], [58, 'rem_u'], [59, 'and'], [60, 'or'], [61, 'xor'], [62, 'not'],
+            [63, 'shl'], [64, 'shr'], [65, 'shr_u'], [66, 'clt'], [67, 'clt_u'],
+            [68, 'cle'], [69, 'cle_u'], [70, 'ceq'], [71, 'cge'], [72, 'cge_u'],
+            [73, 'cgt'], [74, 'cgt_u'], [75, 'cne'], [76, 'conv_i1'], [77, 'conv_i2'],
+            [78, 'conv_i4'], [79, 'conv_i'], [80, 'conv_u1'], [81, 'conv_u2'],
+            [82, 'conv_u4'], [83, 'conv_u'], [84, 'conv_br2'], [85, 'conv_r4'],
+            [86, 'br'], [87, 'br_true'], [88, 'br_false'], [89, 'ret'], [90, 'call'],
+            [91, 'ecall'], [0x5C, 'throw'], [0x5D, 'break'], [0x5E, 'tensor']
+        ]);
+        this._tensorFunctions = new Map([
+            [0x0000, { name: 'batch_to_space', category: 'Transform' }],
+            [0x0001, { name: 'binary', category: '' }],
+            [0x0002, { name: 'broadcast', category: '' }],
+            [0x0003, { name: 'call', category: '' }],
+            [0x0004, { name: 'clamp', category: 'Activation' }],
+            [0x0005, { name: 'conv2d', category: 'Layer' }],
+            [0x0006, { name: 'conv2d_transpose', category: 'Layer' }],
+            [0x0007, { name: 'convert', category: '' }],
+            [0x0008, { name: 'copy', category: '' }],
+            [0x0009, { name: 'cumsum', category: '' }],
+            [0x000A, { name: 'dequantize', category: 'Quantization' }],
+            [0x000B, { name: 'equal', category: '' }],
+            [0x000C, { name: 'gather', category: 'Transform' }],
+            [0x000D, { name: 'gather_nd', category: 'Transform' }],
+            [0x000E, { name: 'hardmax', category: 'Activation' }],
+            [0x000F, { name: 'logistic', category: 'Activation' }],
+            [0x0010, { name: 'lut1d', category: '' }],
+            [0x0011, { name: 'matmul', category: 'Layer' }],
+            [0x0012, { name: 'onehot', category: '' }],
+            [0x0013, { name: 'pad', category: '' }],
+            [0x0014, { name: 'quantize', category: 'Quantization' }],
+            [0x0015, { name: 'random_normal', category: '' }],
+            [0x0016, { name: 'random_uniform', category: '' }],
+            [0x0017, { name: 'reduce', category: 'Reduce' }],
+            [0x0018, { name: 'reduce_arg', category: 'Reduce' }],
+            [0x0019, { name: 'reduce_prod', category: 'Reduce' }],
+            [0x001A, { name: 'reduce_window2d', category: 'Pool' }],
+            [0x001B, { name: 'resize_image', category: 'Transform' }],
+            [0x001C, { name: 'roi_align', category: '' }],
+            [0x001D, { name: 'sigmoid', category: 'Activation' }],
+            [0x001E, { name: 'slice', category: 'Tensor' }],
+            [0x001F, { name: 'softmax', category: 'Activation' }],
+            [0x0020, { name: 'space_to_batch', category: 'Transform' }],
+            [0x0021, { name: 'take', category: '' }],
+            [0x0022, { name: 'ternary', category: '' }],
+            [0x0023, { name: 'topk', category: '' }],
+            [0x0024, { name: 'transpose', category: 'Transform' }],
+            [0x0025, { name: 'trilu', category: '' }],
+            [0x0026, { name: 'unary', category: '' }]
+        ]);
+    }
+
+    operation(operation) {
+        switch (operation.name) {
+            case 'ldc_i4':
+                operation.value = this._reader.int32();
+                break;
+            case 'ldc_r4':
+                operation.imm = this._reader.float32();
+                break;
+            case 'lea_gp':
+                operation.gpid = this._reader.byte();
+                operation.offset = this._reader.int32();
+                break;
+            case 'lea_buffer':
+                operation.location = this._reader.byte();
+                operation.subres_id = this._reader.byte();
+                operation.offset = this._reader.int32();
+                break;
+            case 'ldarg':
+                operation.index = this._reader.uint32();
+                break;
+            case 'stpaddings':
+                operation.rpaddings = this._reader.byte();
+                operation.rank = this._reader.byte();
+                break;
+            case 'stshape':
+                operation.rshape = this._reader.byte();
+                operation.rank = this._reader.byte();
+                break;
+            case 'br':
+            case 'br_true':
+            case 'br_false':
+                operation.target = this._reader.int32();
+                break;
+            case 'call':
+                operation.args = this._reader.byte();
+                operation.target = this._reader.int32();
+                break;
+            case 'ecall':
+                operation.args = this._reader.byte();
+                break;
+            case 'extcall':
+                operation.args = this._reader.uint16();
+                operation.is_prim_func = this._reader.byte() !== 0;
+                break;
+            case 'cuscall':
+                operation.registered_name = this.string();
+                operation.fields_size = this._reader.uint32();
+                this._reader.skip(operation.fields_size);
+                operation.args = this._reader.uint16();
+                break;
+            case 'tensor': {
+                operation.tensor_function = this._reader.uint16();
+                const func = this._tensorFunctions.get(operation.tensor_function);
+                if (func) {
+                    operation.tensor_name = func.name;
+                    operation.tensor_category = func.category;
+                }
+                this.tensor(operation);
+                break;
+            }
+            default:
+                break;
+        }
+    }
+
+    string() {
+        // Read null-terminated string
+        const bytes = [];
+        let byte = this._reader.byte();
+        while (byte !== 0) {
+            bytes.push(byte);
+            byte = this._reader.byte();
+        }
+        return new TextDecoder('utf-8').decode(new Uint8Array(bytes));
+    }
+
+    tensor(operation) {
+        switch (operation.tensor_name) {
+            case 'binary':
+                operation.datatype = this._reader.byte();
+                operation.rshape_src1 = this._reader.byte();
+                operation.rstride_src1 = this._reader.byte();
+                operation.rshape_src2 = this._reader.byte();
+                operation.rstride_src2 = this._reader.byte();
+                operation.rstride_dest = this._reader.byte();
+                operation.binary_op = this._reader.byte();
+                operation.fused_clamp_low = this._reader.float32();
+                operation.fused_clamp_high = this._reader.float32();
+                break;
+            case 'bitcast':
+                operation.type = this._reader.byte();
+                operation.new_type = this._reader.byte();
+                break;
+            case 'call':
+                operation.function_id = this._reader.uint32();
+                operation.module_id = this._reader.uint16();
+                operation.num_src = this._reader.byte();
+                operation.num_dst = this._reader.byte();
+                break;
+            case 'cast':
+                operation.new_type = this._reader.byte();
+                operation.cast_mode = this._reader.uint32();
+                break;
+            case 'compare':
+                operation.compare_op = this._reader.byte();
+                break;
+            case 'concat':
+                operation.axis = this._reader.int32();
+                break;
+            case 'cumsum':
+                operation.datatype = this._reader.byte();
+                operation.rshape_src = this._reader.byte();
+                operation.axis = this._reader.int32();
+                operation.exclusive = this._reader.byte() !== 0;
+                operation.reverse = this._reader.byte() !== 0;
+                break;
+            case 'condition':
+                operation.can_fold_const_call = this._reader.byte() !== 0;
+                break;
+            case 'conv2d':
+                operation.datatype = this._reader.byte();
+                operation.rshape_src = this._reader.byte();
+                operation.rstride_src = this._reader.byte();
+                operation.rshape_kernel = this._reader.byte();
+                operation.rstride_kernel = this._reader.byte();
+                operation.rstride_bias = this._reader.byte();
+                operation.rstride_dest = this._reader.byte();
+                operation.groups = this._reader.uint16();
+                operation.stride_h = this._reader.uint16();
+                operation.stride_w = this._reader.uint16();
+                operation.dilation_h = this._reader.uint16();
+                operation.dilation_w = this._reader.uint16();
+                operation.fused_clamp_low = this._reader.float32();
+                operation.fused_clamp_high = this._reader.float32();
+                break;
+            case 'conv2d_transpose':
+                operation.pad_mode = this._reader.byte();
+                break;
+            case 'dequantize':
+                operation.target_type = this._reader.byte();
+                break;
+            case 'fake_dequantize':
+                operation.target_type = this._reader.byte();
+                break;
+            case 'fake_quantize':
+                operation.target_type = this._reader.byte();
+                break;
+            case 'gather':
+                operation.axis = this._reader.int32();
+                break;
+            case 'layer_norm':
+                operation.axis = this._reader.int32();
+                operation.epsilon = this._reader.float32();
+                operation.use_mean = this._reader.byte() !== 0;
+                break;
+            case 'lstm':
+                operation.direction = this._reader.uint32();
+                operation.layout = this._reader.uint32();
+                operation.activations = this.strings();
+                break;
+            case 'matmul':
+                operation.rshape_src1 = this._reader.byte();
+                operation.rshape_src2 = this._reader.byte();
+                operation.fused_clamp_low = this._reader.float32();
+                operation.fused_clamp_high = this._reader.float32();
+                break;
+            case 'normal':
+                operation.type = this._reader.byte();
+                break;
+            case 'random_normal':
+                operation.datatype_dest = this._reader.byte();
+                operation.rshape_dest = this._reader.byte();
+                operation.mean = this._reader.float32();
+                operation.std = this._reader.float32();
+                operation.seed = this._reader.float32();
+                break;
+            case 'normal_like':
+                operation.type = this._reader.byte();
+                break;
+            case 'one_hot':
+                operation.one_hot_mode = this._reader.byte();
+                break;
+            case 'pad':
+                operation.datatype = this._reader.byte();
+                operation.rshape_src = this._reader.byte();
+                operation.rstride_src = this._reader.byte();
+                operation.rstride_dest = this._reader.byte();
+                operation.rpaddings = this._reader.byte();
+                operation.pad_mode = this._reader.byte();
+                break;
+            case 'quantize':
+                operation.target_type = this._reader.byte();
+                break;
+            case 'quant_param_of':
+                operation.quant_mode = this._reader.uint32();
+                break;
+            case 'range_of':
+                operation.is_range_of_weight = this._reader.byte() !== 0;
+                break;
+            case 'reduce':
+                operation.reduce_op = this._reader.byte();
+                break;
+            case 'reduce_arg':
+                operation.reduce_arg_op = this._reader.byte();
+                operation.dest_type = this._reader.byte();
+                break;
+            case 'reduce_window2d':
+                operation.reduce_op = this._reader.byte();
+                break;
+            case 'require':
+                operation.message = this.string();
+                operation.can_fold_const_call = this._reader.byte() !== 0;
+                break;
+            case 'resize_image':
+                operation.resize_mode = this._reader.byte();
+                operation.transformation_mode = this._reader.uint32();
+                operation.nearest_mode = this._reader.uint32();
+                operation.is_tfresize = this._reader.byte() !== 0;
+                break;
+            case 'unary':
+                operation.unary_op = this._reader.byte();
+                break;
+            case 'uniform':
+                operation.type = this._reader.byte();
+                break;
+            case 'uniform_like':
+                operation.type = this._reader.byte();
+                break;
+            case 'where':
+                operation.is_tf_where = this._reader.byte() !== 0;
+                break;
+            default:
+                break;
+        }
+    }
+};
+
+kmodel.BytecodeReader.v6 = class extends kmodel.BytecodeReader.v5 {
+
+    constructor(reader) {
+        super(reader);
+        this._opcodes = new Map([
+            [0, 'nop'], [1, 'ldnull'], [2, 'ldc_i4'], [3, 'ldc_i4_0'], [4, 'ldc_i4_1'],
+            [5, 'ldc_r4'], [6, 'ldind_i1'], [7, 'ldind_i2'], [8, 'ldind_i4'], [9, 'ldind_i'],
+            [10, 'ldind_u1'], [11, 'ldind_u2'], [12, 'ldind_u4'], [13, 'ldind_u'],
+            [14, 'ldind_br2'], [15, 'ldind_r4'], [16, 'stind_i1'], [17, 'stind_i2'],
+            [18, 'stind_i4'], [19, 'stind_i'], [20, 'stind_br2'], [21, 'stind_r4'],
+            [22, 'lea_gp'], [23, 'ldelem_i1'], [24, 'ldelem_i2'], [25, 'ldelem_i4'],
+            [26, 'ldelem_i'], [27, 'ldelem_u1'], [28, 'ldelem_u2'], [29, 'ldelem_u4'],
+            [30, 'ldelem_u'], [31, 'ldelem_br2'], [32, 'ldelem_r4'], [33, 'stelem_i1'],
+            [34, 'stelem_i2'], [35, 'stelem_i4'], [36, 'stelem_i'], [37, 'stelem_br2'],
+            [38, 'stelem_r4'], [39, 'ldarg'], [40, 'ldarg_0'], [41, 'ldarg_1'],
+            [42, 'ldarg_2'], [43, 'ldarg_3'], [44, 'ldarg_4'], [45, 'ldarg_5'],
+            [46, 'dup'], [47, 'pop'], [48, 'ldlocal'], [49, 'stlocal'], [50, 'ldtuple_elem'],
+            [51, 'ldtuple'], [52, 'lddatatype'], [53, 'ldtensor'], [54, 'ldscalar'],
+            [55, 'neg'], [56, 'add'], [57, 'sub'], [58, 'mul'], [59, 'div'], [60, 'div_u'],
+            [61, 'rem'], [62, 'rem_u'], [63, 'and'], [64, 'or'], [65, 'xor'], [66, 'not'],
+            [67, 'shl'], [68, 'shr'], [69, 'shr_u'], [70, 'clt'], [71, 'clt_u'],
+            [72, 'cle'], [73, 'cle_u'], [74, 'ceq'], [75, 'cge'], [76, 'cge_u'],
+            [77, 'cgt'], [78, 'cgt_u'], [79, 'cne'], [80, 'conv_i1'], [81, 'conv_i2'],
+            [82, 'conv_i4'], [83, 'conv_i'], [84, 'conv_u1'], [85, 'conv_u2'],
+            [86, 'conv_u4'], [87, 'conv_u'], [88, 'conv_br2'], [89, 'conv_r4'],
+            [90, 'br'], [91, 'br_true'], [92, 'br_false'], [93, 'ret'], [94, 'call'],
+            [95, 'ecall'], [96, 'extcall'], [97, 'cuscall'], [98, 'throw'], [99, 'break'],
+            [100, 'tensor']
+        ]);
+        this._tensorFunctions = new Map([
+            [0, { name: 'batch_normalization', category: 'Normalization' }],
+            [1, { name: 'batch_to_space', category: 'Transform' }],
+            [2, { name: 'binary', category: '' }],
+            [3, { name: 'bitcast', category: '' }],
+            [4, { name: 'broadcast', category: '' }],
+            [5, { name: 'broadcast_shape', category: 'Shape' }],
+            [6, { name: 'bucket_pad', category: '' }],
+            [7, { name: 'cast', category: '' }],
+            [8, { name: 'celu', category: 'Activation' }],
+            [9, { name: 'clamp', category: 'Activation' }],
+            [10, { name: 'compare', category: '' }],
+            [11, { name: 'concat', category: 'Tensor' }],
+            [12, { name: 'condition', category: '' }],
+            [13, { name: 'constant_of_shape', category: '' }],
+            [14, { name: 'conv2d', category: 'Layer' }],
+            [15, { name: 'conv2d_shape', category: 'Shape' }],
+            [16, { name: 'conv2d_transpose', category: 'Layer' }],
+            [17, { name: 'conv2d_transpose_shape', category: 'Shape' }],
+            [18, { name: 'cum_sum', category: '' }],
+            [19, { name: 'dequantize', category: 'Quantization' }],
+            [20, { name: 'elu', category: 'Activation' }],
+            [21, { name: 'erf', category: 'Activation' }],
+            [22, { name: 'expand', category: '' }],
+            [23, { name: 'fake_dequantize', category: 'Quantization' }],
+            [24, { name: 'fake_quantize', category: 'Quantization' }],
+            [25, { name: 'fix_shape', category: 'Shape' }],
+            [26, { name: 'flatten', category: 'Shape' }],
+            [27, { name: 'gather', category: 'Transform' }],
+            [28, { name: 'gather_elements', category: 'Transform' }],
+            [29, { name: 'gather_nd', category: 'Transform' }],
+            [30, { name: 'gelu', category: 'Activation' }],
+            [31, { name: 'get_item', category: '' }],
+            [32, { name: 'get_paddings', category: '' }],
+            [33, { name: 'hardmax', category: 'Activation' }],
+            [34, { name: 'hard_sigmoid', category: 'Activation' }],
+            [35, { name: 'hard_swish', category: 'Activation' }],
+            [36, { name: 'index_of', category: '' }],
+            [37, { name: 'instance_normalization', category: 'Normalization' }],
+            [38, { name: 'l2_normalization', category: 'Normalization' }],
+            [39, { name: 'layer_norm', category: 'Normalization' }],
+            [40, { name: 'leaky_relu', category: 'Activation' }],
+            [41, { name: 'log_softmax', category: 'Activation' }],
+            [42, { name: 'lp_normalization', category: 'Normalization' }],
+            [43, { name: 'lrn', category: 'Normalization' }],
+            [44, { name: 'lstm', category: 'Layer' }],
+            [45, { name: 'mat_mul', category: 'Layer' }],
+            [46, { name: 'mat_mul_shape', category: 'Shape' }],
+            [47, { name: 'normal' }],
+            [48, { name: 'normal_like' }],
+            [49, { name: 'one_hot', category: '' }],
+            [50, { name: 'pad', category: '' }],
+            [51, { name: 'prelu', category: 'Activation' }],
+            [52, { name: 'prod', category: '' }],
+            [53, { name: 'quantize', category: 'Quantization' }],
+            [54, { name: 'quant_param_of', category: 'Quantization' }],
+            [55, { name: 'range', category: '' }],
+            [56, { name: 'range_of', category: '' }],
+            [57, { name: 'rank', category: 'Shape' }],
+            [58, { name: 'reduce', category: 'Reduce' }],
+            [59, { name: 'reduce_arg', category: 'Reduce' }],
+            [60, { name: 'reduce_window2d', category: 'Pool' }],
+            [61, { name: 'relu', category: 'Activation' }],
+            [62, { name: 'relu6', category: 'Activation' }],
+            [63, { name: 'require', category: '' }],
+            [64, { name: 'reshape', category: 'Shape' }],
+            [65, { name: 'reshape_shape', category: 'Shape' }],
+            [66, { name: 'resize_image', category: 'Transform' }],
+            [67, { name: 'reverse_sequence', category: '' }],
+            [68, { name: 'scatter_nd', category: 'Transform' }],
+            [69, { name: 'select', category: '' }],
+            [70, { name: 'selu', category: 'Activation' }],
+            [71, { name: 'shape_of', category: 'Shape' }],
+            [72, { name: 'sigmoid', category: 'Activation' }],
+            [73, { name: 'size_of', category: 'Shape' }],
+            [74, { name: 'slice', category: 'Tensor' }],
+            [75, { name: 'softmax', category: 'Activation' }],
+            [76, { name: 'softplus', category: 'Activation' }],
+            [77, { name: 'softsign', category: 'Activation' }],
+            [78, { name: 'space_to_batch', category: 'Transform' }],
+            [79, { name: 'split', category: 'Tensor' }],
+            [80, { name: 'squeeze', category: 'Shape' }],
+            [81, { name: 'squeeze_shape', category: 'Shape' }],
+            [82, { name: 'stack', category: 'Tensor' }],
+            [83, { name: 'swish', category: 'Activation' }],
+            [84, { name: 'tile', category: '' }],
+            [85, { name: 'top_k', category: '' }],
+            [86, { name: 'transpose', category: 'Transform' }],
+            [87, { name: 'transpose_shape', category: 'Shape' }],
+            [88, { name: 'trilu', category: '' }],
+            [89, { name: 'unary', category: '' }],
+            [90, { name: 'uniform' }],
+            [91, { name: 'uniform_like' }],
+            [92, { name: 'unsqueeze', category: 'Shape' }],
+            [93, { name: 'unsqueeze_shape', category: 'Shape' }],
+            [94, { name: 'where', category: '' }]
+        ]);
+    }
+
+    operation(operation) {
+        switch (operation.opcode) {
+            case 'ldarg':
+                operation.index = this._reader.uint16();
+                break;
+            case 'call':
+                operation.args = this._reader.uint16();
+                operation.target = this._reader.int32();
+                break;
+            case 'ecall':
+                operation.args = this._reader.uint16();
+                break;
+            case 'ldlocal':
+            case 'stlocal':
+                operation.index = this._reader.uint16();
+                break;
+            default:
+                super.operation(operation);
+        }
+    }
+
+    tensor(operation) {
+        switch (operation.tensor_name) {
+            case 'binary':
+                operation.binary_op = this._reader.byte();
+                break;
+            case 'matmul':
+                break;
+            case 'normal':
+                operation.type = this._reader.byte();
+                break;
+            case 'random_normal':
+                operation.type = this._reader.byte();
+                break;
+            case 'normal_like':
+                operation.type = this._reader.byte();
+                break;
+            case 'one_hot':
+                operation.one_hot_mode = this._reader.byte();
+                break;
+            case 'pad':
+                operation.pad_mode = this._reader.byte();
+                break;
+            case 'cumsum':
+                break;
+            case 'conv2d':
+                operation.pad_mode = this._reader.byte();
+                break;
+            default:
+                super.tensor(operation);
+        }
+    }
+};
+
 kmodel.Error = class extends Error {
 
     constructor(message) {

+ 42 - 0
test/models.json

@@ -2875,6 +2875,48 @@
     "format":   "kmodel v5",
     "link":     "https://github.com/lutzroeder/netron/issues/871"
   },
+  {
+    "type":     "kmodel",
+    "target":   "face_detection_320.v6.kmodel",
+    "source":   "https://github.com/user-attachments/files/22859146/face_detection_320.v6.kmodel.zip[face_detection_320.v6.kmodel]",
+    "format":   "kmodel v6",
+    "link":     "https://github.com/lutzroeder/netron/issues/871"
+  },
+  {
+    "type":     "kmodel",
+    "target":   "person_detect_yolov5n.v6.kmodel",
+    "source":   "https://github.com/user-attachments/files/22859125/person_detect_yolov5n.v6.kmodel.zip[person_detect_yolov5n.v6.kmodel]",
+    "format":   "kmodel v6",
+    "link":     "https://github.com/lutzroeder/netron/issues/871"
+  },
+  {
+    "type":     "kmodel",
+    "target":   "retinaface.v6.kmodel",
+    "source":   "https://github.com/user-attachments/files/22859164/retinaface.v6.kmodel.zip[retinaface.v6.kmodel]",
+    "format":   "kmodel v6",
+    "link":     "https://github.com/lutzroeder/netron/issues/871"
+  },
+  {
+    "type":     "kmodel",
+    "target":   "face_detection_320.v7.kmodel",
+    "source":   "https://github.com/user-attachments/files/22859159/face_detection_320.v7.kmodel.zip[face_detection_320.v7.kmodel]",
+    "format":   "kmodel v7",
+    "link":     "https://github.com/lutzroeder/netron/issues/871"
+  },
+  {
+    "type":     "kmodel",
+    "target":   "person_detect_yolov5n.v7.kmodel",
+    "source":   "https://github.com/user-attachments/files/22859126/person_detect_yolov5n.v7.kmodel.zip[person_detect_yolov5n.v7.kmodel]",
+    "format":   "kmodel v7",
+    "link":     "https://github.com/lutzroeder/netron/issues/871"
+  },
+  {
+    "type":     "kmodel",
+    "target":   "retinaface.v7.kmodel",
+    "source":   "https://github.com/user-attachments/files/22859154/retinaface.v7.kmodel.zip[retinaface.v7.kmodel]",
+    "format":   "kmodel v7",
+    "link":     "https://github.com/lutzroeder/netron/issues/871"
+  },
   {
     "type":     "kmodel",
     "target":   "RFB-320.kmodel",