From 778d723a5d6e729175d53c4f55542546300b4534 Mon Sep 17 00:00:00 2001 From: uranus0515 <110005227+uranus0515@users.noreply.github.com> Date: Mon, 4 Dec 2023 16:28:45 +0800 Subject: [PATCH 01/12] Fix/retinaface similarity bugs (#1141) * change cost for resize image * add strict infor for combineQuantize * refuse to concat quant if no quant * add tuple is current decison --------- Co-authored-by: guodongliang --- src/Nncase.Evaluator/Imaging/ResizeImage.cs | 1 + .../Rules/Neutral/CombineQuantize.cs | 30 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Nncase.Evaluator/Imaging/ResizeImage.cs b/src/Nncase.Evaluator/Imaging/ResizeImage.cs index e25db7b8c0..0e9f1c2e09 100644 --- a/src/Nncase.Evaluator/Imaging/ResizeImage.cs +++ b/src/Nncase.Evaluator/Imaging/ResizeImage.cs @@ -163,6 +163,7 @@ public Cost Visit(ICostEvaluateContext context, ResizeImage target) { [CostFactorNames.MemoryLoad] = CostUtility.GetMemoryAccess(inputType), [CostFactorNames.MemoryStore] = CostUtility.GetMemoryAccess(returnType), + [CostFactorNames.CPUCycles] = CostUtility.GetCPUCycles(returnType, 4), }; } diff --git a/src/Nncase.Passes/Rules/Neutral/CombineQuantize.cs b/src/Nncase.Passes/Rules/Neutral/CombineQuantize.cs index 9519b011c7..6d80c05be1 100644 --- a/src/Nncase.Passes/Rules/Neutral/CombineQuantize.cs +++ b/src/Nncase.Passes/Rules/Neutral/CombineQuantize.cs @@ -36,10 +36,10 @@ public sealed partial class CombineQuantizeConcat : RewriteRule IsConcat( "concat", _ => true, - IsTuple(IsVArgsRepeat("tupleInputs", () => IsWildcard()))), + IsTuple("tuple", IsVArgsRepeat("tupleInputs", () => IsWildcard()))), IsWildcard("quantParam")); - private Expr? GetReplace(Quantize quantize, IReadOnlyList tupleInputs, IR.Tensors.Concat concat, Expr quantParam, RunPassContext options) + private Expr? GetReplace(Quantize quantize, IReadOnlyList tupleInputs, IR.Tensors.Concat concat, Expr quantParam, RunPassContext options, Expr tuple) { if (options.Driver is DataflowPass) { @@ -50,7 +50,31 @@ public sealed partial class CombineQuantizeConcat : RewriteRule { if (userAnalysis[e].Count() > 1) { - return null; + foreach (var user in userAnalysis[e]) + { + if (user is Call { Target: Nncase.IR.Math.Quantize } userCall) + { + var quantUser = userCall.Arguments[Nncase.IR.Math.Quantize.QuantParam.Index]; + if (quantUser != quantParam) + { + return null; + } + } + else + { + if (user is not Nncase.IR.Tuple) + { + return null; + } + else + { + if (user != tuple) + { + return null; + } + } + } + } } } } From 67b77b3b9dac7baeca0d35705b09e636110d32ef Mon Sep 17 00:00:00 2001 From: zhangyang2057 Date: Fri, 8 Dec 2023 17:34:47 +0800 Subject: [PATCH 02/12] Add accuracy test. (#1140) * Add accuracy test. * Move if_quant/w_quaint into remark. * enlarge qsize for CI server and reset postprocess result for nncase. * Add '\n' for each item in remark. --- tests/accuracy_test/__init__.py | 0 tests/accuracy_test/compare_util.py | 175 ++++++++ tests/accuracy_test/config.toml | 133 ++++++ tests/accuracy_test/evaluator.py | 72 ++++ tests/accuracy_test/generator.py | 114 ++++++ tests/accuracy_test/inference.py | 267 ++++++++++++ .../accuracy_test/nuc_proxy_accuracy_test.py | 358 ++++++++++++++++ tests/accuracy_test/onnx_test_runner.py | 266 ++++++++++++ tests/accuracy_test/preprocess_utils.py | 23 ++ tests/accuracy_test/test_runner.py | 383 ++++++++++++++++++ tests/accuracy_test/test_utils.py | 101 +++++ tests/accuracy_test/tflite_test_runner.py | 145 +++++++ 12 files changed, 2037 insertions(+) create mode 100644 tests/accuracy_test/__init__.py create mode 100644 tests/accuracy_test/compare_util.py create mode 100644 tests/accuracy_test/config.toml create mode 100644 tests/accuracy_test/evaluator.py create mode 100644 tests/accuracy_test/generator.py create mode 100644 tests/accuracy_test/inference.py create mode 100644 tests/accuracy_test/nuc_proxy_accuracy_test.py create mode 100644 tests/accuracy_test/onnx_test_runner.py create mode 100644 tests/accuracy_test/preprocess_utils.py create mode 100644 tests/accuracy_test/test_runner.py create mode 100644 tests/accuracy_test/test_utils.py create mode 100644 tests/accuracy_test/tflite_test_runner.py diff --git a/tests/accuracy_test/__init__.py b/tests/accuracy_test/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/accuracy_test/compare_util.py b/tests/accuracy_test/compare_util.py new file mode 100644 index 0000000000..7d5a300118 --- /dev/null +++ b/tests/accuracy_test/compare_util.py @@ -0,0 +1,175 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +import math +import os +import re +import struct +import numpy as np +from typing import List, Tuple +from pathlib import Path +from operator import le, lt, ge, gt, eq +import test_utils + + +def cosine(gt: np.ndarray, pred: np.ndarray, *args): + # remove the NaN values in the same location. + if np.isnan(gt).any() and np.isnan(pred).any(): + gt_mask = np.isnan(gt) + pred_mask = np.isnan(pred) + mask = gt_mask & pred_mask + gt = gt[~mask] + pred = pred[~mask] + + # remove the INF values in the same location. + if np.isinf(gt).any() and np.isinf(pred).any(): + gt_mask = np.isinf(gt) + pred_mask = np.isinf(pred) + mask = gt_mask & pred_mask + gt = gt[~mask] + pred = pred[~mask] + + # return -1 if the nan/inf value is still in the array. + if np.isnan(gt).any() or np.isnan(pred).any() or np.isinf(gt).any() or np.isinf(pred).any(): + return -1 + + # exclude the situation of all zeros in array. + if compare_arrays(gt, pred): + return 1 + + result = (gt @ pred) / (np.linalg.norm(gt, 2) * np.linalg.norm(pred, 2)) + + return -1 if math.isnan(result) else result + + +def compare_arrays(gt: np.ndarray, pred: np.ndarray): + return np.array_equal(gt, pred) + + +def euclidean(gt: np.ndarray, pred: np.ndarray, *args): + return np.linalg.norm(gt.reshape(-1) - pred.reshape(-1)) + + +# def mse(gt: np.ndarray, pred: np.ndarray, *args): +# return np.mean((gt - pred) ** 2) + +def divide(gt: np.ndarray, pred: np.ndarray): + + # remove the zero values in the same location. + gt_mask = np.equal(gt, 0) + pred_mask = np.equal(pred, 0) + mask = gt_mask & pred_mask + gt = gt[~mask] + pred = pred[~mask] + + # to avoid divide zero. + pred = np.where(np.equal(pred, 0), 1e-7, pred) + + result = np.divide(gt, pred) + return result + + +def mean(gt: np.ndarray): + return np.mean(gt) + + +def allclose(gt: np.ndarray, pred: np.ndarray, thresh: float): + return np.allclose(gt, pred, atol=thresh) + + +def segment_close(gt: np.ndarray, pred: np.ndarray): + bucket = np.digitize(gt, [0, 64, 128, 10 ** 18]) + seg_1 = (bucket == 1) + seg_2 = (bucket == 2) + seg_3 = (bucket == 3) + ret = True + if seg_1.size: + ret &= np.allclose(gt[seg_1], pred[seg_1], atol=0.6) + if seg_2.size: + ret &= np.allclose(gt[seg_2], pred[seg_2], atol=2) + if seg_3.size: + ret &= np.allclose(gt[seg_3], pred[seg_3], rtol=8 / 128) + return ret + + +similarity_func = { + 'cosine': cosine, + 'euclidean': euclidean, + 'allclose': np.allclose, + 'segment': segment_close +} + + +def compare_binfile(result_path: Tuple[str, str], + ground_truth_path: Tuple[str, str], + dtype, + similarity_name: str = 'cosine', + threshold: float = 0.99, + hist: bool = True) -> bool: + # NOTE the result_path is Tuple[ bin_path, txt_path ] + ground_truth_path_bin, ground_truth_path_txt = result_path + result_path_bin, result_path_txt = ground_truth_path + if 'npy' in ground_truth_path_bin: # bfloat16 + # gt, pred = bytes.fromhex(gt.strip()), bytes.fromhex(pred.strip()) + # gt, pred = struct.unpack('>H', gt)[0], struct.unpack('>H', pred)[0] + raise NotImplemented("need support bfloat16 judge!") + else: + gt_arr = np.fromfile(ground_truth_path_bin, dtype).astype(np.float32) + pred_arr = np.fromfile(result_path_bin, dtype).astype(np.float32) + if gt_arr.size == pred_arr.size: + similarity = similarity_func[similarity_name](gt_arr, pred_arr) + else: + raise ValueError("The number of elements in gt and result not match\n") + if hist and not test_utils.in_ci: + y, x = np.histogram(gt_arr - pred_arr, 100) + p = Path(result_path_bin) + np.savetxt(str(p.parent / (p.stem + '_hist.csv')), + np.stack((x[:-1], y)).T, fmt='%f', delimiter=',') + similarity_info = f"\n{similarity_name} similarity = {similarity}, threshold = {threshold}\n" + if similarity_name in ['cosine', 'segment']: + compare_op = lt + else: + compare_op = gt + if compare_op(similarity, threshold): + return False, similarity_info + if (mean(divide(gt_arr, pred_arr)) > 1.5 or mean(divide(gt_arr, pred_arr)) < 0.6): + return False, similarity_info, f"\nmaybe a case of multiples" + return True, similarity_info + + +def compare_ndarray(expected: np.ndarray, + actual: np.ndarray, + similarity_name: str = 'cosine', + threshold: float = 0.99, + dump_hist: bool = True, + dump_file: str = 'hist.csv') -> bool: + if expected.size == actual.size: + similarity = similarity_func[similarity_name](expected.flatten(), actual.flatten()) + else: + return False, f"The numbers of elements in gt({expected.size}) and result({actual.size}) are not match.\n" + + if dump_hist: + y, x = np.histogram(expected - actual, 100) + np.savetxt(dump_file, np.stack((x[:-1], y)).T, fmt='%f', delimiter=',') + similarity_info = f"{similarity_name} similarity = {similarity}, threshold = {threshold}\n" + + if similarity_name in ['cosine', 'segment']: + compare_op = lt + else: + compare_op = gt + + if compare_op(similarity, threshold): + return False, similarity_info + return True, similarity_info diff --git a/tests/accuracy_test/config.toml b/tests/accuracy_test/config.toml new file mode 100644 index 0000000000..310e1f5958 --- /dev/null +++ b/tests/accuracy_test/config.toml @@ -0,0 +1,133 @@ +name = 'default_config' +root = 'tests_output' +kmodel_name = 'test.kmodel' +desc_name = 'kmodel.desc' + +[compile_opt] +preprocess = false +swapRB = false +input_type = 'uint8' +input_shape = [1, 224, 224, 3] +input_range = [0, 255] +input_file = "" +mean = [0, 0, 0] +std = [1, 1, 1] +input_layout = 'NHWC' +output_layout = 'NHWC' +model_layout = 'NHWC' +letterbox_value = 0 +dump_asm = true +dump_ir = false +shape_bucket_enable = false +shape_bucket_range_info = { } +shape_bucket_segments_count = 4 +shape_bucket_fix_var_map = { } + +[ptq_opt] +use_mix_quant = false +use_mse_quant_w = true +export_quant_scheme = false +export_weight_range_by_channel = true +dump_quant_error = false +dump_quant_error_symmetric_for_signed = true +quant_type = "uint8" +w_quant_type = "uint8" +# ['NoClip', 'Kld'] +calibrate_method = 'NoClip' +# ['NoFineTuneWeights', 'UseSquant'] +finetune_weights_method = 'NoFineTuneWeights' +input_mean = 0.5 +input_std = 0.5 +quant_scheme = "" +quant_scheme_strict_mode = false + +[infer_report_opt] +enabled = false +priority = 100 +kind = 'N/A' +model_name = 'N/A' +report_name = 'infer_report.json' + +[generator] +[generator.inputs] +# ['random', 'bin', 'image', 'constant_of_shape'] +method = 'random' +number = 1 +batch = 1 + +[generator.inputs.random] +args = false + +[generator.inputs.bin] +# /path/to/bin directory +roofline_args = '' +nncase_args = '' + +[generator.inputs.image] +# /path/to/image directory +args = '' + +[generator.inputs.constant_of_shape] +# shape +args = [] + +[generator.calibs] +method = 'random' +number = 5 +batch = 1 + +[generator.calibs.random] +args = false + +[generator.calibs.bin] +# /path/to/bin directory +args = '' + +[generator.calibs.image] +# /path/to/image directory +args = '' + +[generator.calibs.constant_of_shape] +# shape +args = [] + +[target] + +[target.cpu] +eval = false +infer = false +similarity_name = 'cosine' + +[target.cpu.mode.noptq] +enabled = false +threshold = 0.999 + +[target.cpu.mode.ptq] +enabled = true +threshold = 0.98 + +[target.k510] +eval = true +infer = true +similarity_name = 'cosine' + +[target.k510.mode.noptq] +enabled = false +threshold = 0.99 + +[target.k510.mode.ptq] +enabled = true +threshold = 0.98 + +[target.k230] +eval = false +infer = true +similarity_name = 'cosine' + +[target.k230.mode.noptq] +enabled = false +threshold = 0.999 + +[target.k230.mode.ptq] +enabled = true +threshold = 0.96 diff --git a/tests/accuracy_test/evaluator.py b/tests/accuracy_test/evaluator.py new file mode 100644 index 0000000000..fcfffb2ec1 --- /dev/null +++ b/tests/accuracy_test/evaluator.py @@ -0,0 +1,72 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +from typing import List, Dict, Union, Tuple +import os +import nncase +import numpy as np +import test_utils +import preprocess_utils +from test_utils import * + + +class Evaluator: + def run_evaluator(self, compiler, dump_dir): + evaluator = compiler.create_evaluator(3) + + # transform input + new_inputs = [] + for idx, value in enumerate(self.inputs): + new_value = self.transform_input( + value['data'], self.cfg['compile_opt']['input_type'], "infer") + new_inputs.append(new_value) + + outputs = [] + number = self.cfg['generator']['inputs']['number'] + for n in range(number): + # set input + for idx, value in enumerate(self.inputs): + evaluator.set_input_tensor(idx, nncase.RuntimeTensor.from_numpy(new_inputs[idx][n])) + + # run + evaluator.run() + + # get output + output = self.dump_outputs(evaluator, dump_dir) + + outputs.append(output) + return outputs + + def dump_outputs(self, evaluator, eval_dir): + results = [] + compile_opt = self.cfg['compile_opt'] + for i in range(evaluator.outputs_size): + result = evaluator.get_output_tensor(i).to_numpy() + if compile_opt['preprocess']: + if(compile_opt['output_layout'] == 'NHWC' and self.model_type in ['caffe', 'onnx']): + result = np.transpose(result, [0, 3, 1, 2]) + elif (compile_opt['output_layout'] == 'NCHW' and self.model_type in ['tflite']): + result = np.transpose(result, [0, 2, 3, 1]) + elif compile_opt['output_layout'] not in ["NCHW", "NHWC"] and compile_opt['output_layout'] != "": + tmp_perm = [int(idx) for idx in compile_opt['output_layout'].split(",")] + result = np.transpose( + result, preprocess_utils.get_source_transpose_index(tmp_perm)) + os.makedirs(eval_dir, exist_ok=True) + if not test_utils.in_ci(): + dump_bin_file(os.path.join(eval_dir, f'nncase_result_{i}.bin'), result) + dump_txt_file(os.path.join(eval_dir, f'nncase_result_{i}.txt'), result) + + results.append(result) + return results diff --git a/tests/accuracy_test/generator.py b/tests/accuracy_test/generator.py new file mode 100644 index 0000000000..20d7bcd0a2 --- /dev/null +++ b/tests/accuracy_test/generator.py @@ -0,0 +1,114 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +from typing import Any, Dict, List, Tuple, Union +import numpy as np +import os +import cv2 + + +class Generator: + + def from_random(self, shape: List[int], dtype: np.dtype, abs: bool = False) -> np.ndarray: + if dtype == np.uint8: + data = np.random.randint(0, 256, shape) + elif dtype == np.int8: + data = np.random.randint(-128, 128, shape) + elif dtype == bool: + data = np.random.rand(*shape) > 0.5 + elif dtype == np.int32: + data = np.random.randint(1, 5, size=shape, dtype='int32') + elif dtype == np.int64: + data = np.random.randint(1, 5, size=shape, dtype='int64') + # data = np.random.randint(1, 128, size=shape, dtype='int64') + else: + data = np.random.rand(*shape) + data = data.astype(dtype=dtype) + + if abs: + return np.abs(data) + return data + + def from_bin(self, shape: List[int], dtype: np.dtype, bin_file: str): + '''read data, file name with increase index and data type. e.g. calib_0_0.bin, calib_0_1.bin + [generator.inputs] + method = 'bin' + + [generator.inputs.bin] + args = '/mnt/nncase/tests_output/test_mobile_retinaface_preprocess_3/input' + + [generator.calibs] + method = 'bin' + + [generator.calibs.bin] + args = '/mnt/nncase/tests_output/test_mobile_retinaface_preprocess_3/calib' + ''' + + data = np.fromfile(bin_file, dtype) + return np.reshape(data, shape) + + def from_image(self, shape: List[int], dtype: np.dtype, img_file: str) -> np.ndarray: + """ read rgb image , return the preprocessed image. + [generator.inputs] + method = 'image' + + [generator.inputs.image] + args = '/mnt/mb_retinaface_input/' + + [generator.calibs] + method = 'image' + + [generator.calibs.image] + args = '/mnt/calibration_dataset/' + """ + + transpose = True + + if shape[1] == 3: + h = shape[2] + w = shape[3] + transpose = True + elif shape[3] == 3: + h = shape[1] + w = shape[2] + transpose = False + + padded_img = np.ones((h, w, 3), dtype=np.uint8) * 114 + img = cv2.imread(img_file) + + # resize + r = min(h / img.shape[0], w / img.shape[1]) + resized_img = cv2.resize( + img, + (int(img.shape[1] * r), int(img.shape[0] * r)), + interpolation=cv2.INTER_LINEAR, + ).astype(np.uint8) + + padded_img[: int(img.shape[0] * r), : int(img.shape[1] * r)] = resized_img + + # bgr2rgb + padded_img = cv2.cvtColor(padded_img, cv2.COLOR_BGR2RGB) + + # transpose + if transpose: + padded_img = padded_img.transpose((2, 0, 1)) + + padded_img = np.ascontiguousarray(padded_img) + padded_img = np.reshape(padded_img, shape) + padded_img = padded_img.astype(dtype=dtype) + return padded_img + + def from_constant_of_shape(self, shape: List[int], dtype: np.dtype) -> np.ndarray: + return np.array(shape, dtype=dtype) diff --git a/tests/accuracy_test/inference.py b/tests/accuracy_test/inference.py new file mode 100644 index 0000000000..930346f9a9 --- /dev/null +++ b/tests/accuracy_test/inference.py @@ -0,0 +1,267 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +from typing import List, Dict, Union, Tuple +import os +import nncase +import numpy as np +import test_utils +import preprocess_utils +import socket +import struct +import json +from test_utils import * +import time +import subprocess +from html import escape +import threading +import queue + + +def data_shape_list_string(data): + return '\n'.join(map(lambda d: ' '.join(map(lambda x: str(x), d['model_shape'])), data)) + + +class Inference: + def run_inference(self, compiler, target, ptq_enabled, infer_dir): + in_ci = test_utils.in_ci() + kpu_targets = test_utils.kpu_targets() + nuc_ip = test_utils.nuc_ip() + nuc_port = test_utils.nuc_port() + test_executable = test_utils.test_executable(target) + running_on_evb = target in kpu_targets and nuc_ip is not None and nuc_port is not None and test_executable is not None and len( + self.inputs) > 0 and len(self.outputs) > 0 + + if self.cfg['infer_report_opt']['enabled']: + self.infer_report_dict['priority'] = self.cfg['infer_report_opt']['priority'] + self.infer_report_dict['kind'] = self.cfg['infer_report_opt']['kind'] + self.infer_report_dict['model'] = self.cfg['infer_report_opt']['model_name'] + self.infer_report_dict['shape'] = ',
'.join( + map(lambda d: '[' + ','.join(map(lambda x: str(x), d['model_shape'])) + ']', self.inputs)) + if ptq_enabled: + self.set_quant_opt(compiler) + if self.cfg['infer_report_opt']['enabled']: + if_quant_type = self.cfg['ptq_opt']['quant_type'] + w_quant_type = self.cfg['ptq_opt']['w_quant_type'] + self.infer_report_dict['remark'] += f',\nnncase(if_quant_type={if_quant_type}, w_quant_type={w_quant_type})' + + compiler.compile() + kmodel = compiler.gencode_tobytes() + os.makedirs(infer_dir, exist_ok=True) + if self.dynamic: + self.dump_kmodel_desc(os.path.join(infer_dir, self.cfg['desc_name'])) + if not in_ci: + with open(os.path.join(infer_dir, self.cfg['kmodel_name']), 'wb') as f: + f.write(kmodel) + + compile_opt = self.cfg['compile_opt'] + if running_on_evb: + self.run_evb(target, kmodel, compile_opt, infer_dir) + else: + self.run_simulator(target, kmodel, compile_opt, infer_dir) + + def run_simulator(self, target, kmodel, compile_opt, infer_dir): + generator_cfg = self.cfg['generator']['inputs'] + method = generator_cfg['method'] + batch_number = generator_cfg['number'] + args = os.path.join(test_utils.test_root(), generator_cfg[method]['nncase_args']) + + # get input file list + file_list = [] + assert(os.path.isdir(args)) + for file in os.listdir(args): + if file.endswith('.bin'): + file_list.append(os.path.join(args, file)) + file_list.sort() + + sim = nncase.Simulator() + sim.load_model(kmodel) + + number = generator_cfg['number'] + q = queue.Queue(maxsize=self.postprocess_qsize) + t = threading.Thread(target=self.postprocess, args=(q, )) + t.start() + for n in range(number): + # set input + for idx, value in enumerate(self.inputs): + # TODO: dtype + dtype = compile_opt['input_type'] + data = np.fromfile(file_list[n], dtype=np.uint8) + sim.set_input_tensor(idx, nncase.RuntimeTensor.from_numpy(data)) + + # run + sim.run() + + # get output + outputs = self.dump_infer_output(sim, compile_opt, infer_dir) + + # postprocess + q.put(outputs) + + t.join() + + def dump_kmodel_desc(self, file): + input_shapes = data_shape_list_string(self.inputs) + output_shapes = data_shape_list_string(self.outputs) + s = f"{len(self.inputs)} {len(self.outputs)}\n{input_shapes}\n{output_shapes}" + with open(file, "w+") as f: + f.write(s) + + def dump_infer_output(self, sim, compile_opt, infer_dir): + outputs = [] + for i in range(sim.outputs_size): + output = sim.get_output_tensor(i).to_numpy() + if compile_opt['preprocess']: + if(compile_opt['output_layout'] == 'NHWC' and self.model_type in ['caffe', 'onnx']): + output = np.transpose(output, [0, 3, 1, 2]) + elif (compile_opt['output_layout'] == 'NCHW' and self.model_type in ['tflite']): + output = np.transpose(output, [0, 2, 3, 1]) + elif compile_opt['output_layout'] not in ["NCHW", "NHWC"] and compile_opt['output_layout'] != "": + tmp_perm = [int(idx) for idx in compile_opt['output_layout'].split(",")] + output = np.transpose( + output, preprocess_utils.get_source_transpose_index(tmp_perm)) + outputs.append(output) + if not test_utils.in_ci(): + dump_bin_file(os.path.join(infer_dir, f'nncase_result_{i}.bin'), output) + dump_txt_file(os.path.join(infer_dir, f'nncase_result_{i}.txt'), output) + return outputs + + def send_msg(self, sock, msg): + # Prefix each message with a 4-byte length (network byte order) + msg = struct.pack('>I', len(msg)) + msg + sock.sendall(msg) + + def recv_msg(self, sock): + # Read message length and unpack it into an integer + raw_msglen = self.recvall(sock, 4) + if not raw_msglen: + return None + msglen = struct.unpack('>I', raw_msglen)[0] + # Read the message data + return self.recvall(sock, msglen) + + def recvall(self, sock, n): + # Helper function to recv n bytes or return None if EOF is hit + data = bytearray() + while len(data) < n: + packet = sock.recv(n - len(data)) + if not packet: + return None + data.extend(packet) + return data + + def run_evb(self, target, kmodel, compile_opt, infer_dir): + outputs = [] + number = self.cfg['generator']['inputs']['number'] + ip = test_utils.nuc_ip() + port = test_utils.nuc_port() + test_executable = test_utils.test_executable(target) + + generator_cfg = self.cfg['generator']['inputs'] + method = generator_cfg['method'] + dataset_path = generator_cfg[method]['nncase_args'] + dataset_number = generator_cfg['number'] + + # connect server + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client_socket.connect((ip, int(port))) + + # send target + dummy = self.recv_msg(client_socket) + target_dict = {} + target_dict['target'] = target + self.send_msg(client_socket, json.dumps(target_dict).encode()) + + # send header + dummy = self.recv_msg(client_socket) + header_dict = {} + header_dict['case'] = os.path.basename(self.case_dir) + header_dict['app'] = 1 + header_dict['kmodel'] = 1 + header_dict['description'] = 1 if self.dynamic else 0 + # header_dict['inputs'] = len(self.inputs) + header_dict['outputs'] = len(self.outputs) + header_dict['dataset_path'] = dataset_path + header_dict['dataset_number'] = dataset_number + self.send_msg(client_socket, json.dumps(header_dict).encode()) + + # send app + dummy = self.recv_msg(client_socket) + file_dict = {} + file_dict['file_name'] = os.path.basename(test_executable) + file_dict['file_size'] = os.path.getsize(test_executable) + self.send_msg(client_socket, json.dumps(file_dict).encode()) + dummy = self.recv_msg(client_socket) + with open(test_executable, 'rb') as f: + client_socket.sendall(f.read()) + + # send kmodel + dummy = self.recv_msg(client_socket) + file_dict['file_name'] = self.cfg['kmodel_name'] + file_dict['file_size'] = len(kmodel) + self.send_msg(client_socket, json.dumps(file_dict).encode()) + dummy = self.recv_msg(client_socket) + client_socket.sendall(kmodel) + + # send kmodel.desc + if self.dynamic: + dummy = self.recv_msg(client_socket) + desc_file = os.path.join(infer_dir, self.cfg['desc_name']) + file_dict['file_name'] = os.path.basename(desc_file) + file_dict['file_size'] = os.path.getsize(desc_file) + self.send_msg(client_socket, json.dumps(file_dict).encode()) + dummy = self.recv_msg(client_socket) + with open(desc_file, 'rb') as f: + client_socket.sendall(f.read()) + + # get infer result + header_dict = {} + ret = self.recv_msg(client_socket) + header_dict = json.loads(ret.decode()) + if header_dict['type'].find('finish') != -1: + q = queue.Queue(maxsize=self.postprocess_qsize) + t = threading.Thread(target=self.postprocess, args=(q, )) + t.start() + for i in range(dataset_number): + self.send_msg(client_socket, f"pls send outputs".encode()) + + # recv outputs + outputs = [] + for j in range(len(self.outputs)): + header = self.recv_msg(client_socket) + file_dict = json.loads(header.decode()) + file_size = file_dict['file_size'] + self.send_msg(client_socket, f"pls send file".encode()) + + buffer = bytearray(file_size) + buffer = self.recvall(client_socket, file_size) + + output = np.frombuffer(buffer, dtype=self.outputs[j]['dtype']).reshape( + self.outputs[j]['model_shape']) + outputs.append(output) + if not test_utils.in_ci(): + dump_bin_file(os.path.join(infer_dir, f'nncase_result_{i}_{j}.bin'), output) + # dump_txt_file(os.path.join(infer_dir, f'nncase_result_{i}_{j}.txt'), output) + outputs.append(output) + + # postprocess + q.put(outputs) + + t.join() + else: + if self.cfg['infer_report_opt']['enabled']: + self.infer_report_dict['remark'] += ',\n' + header_dict['msg'] + + client_socket.close() diff --git a/tests/accuracy_test/nuc_proxy_accuracy_test.py b/tests/accuracy_test/nuc_proxy_accuracy_test.py new file mode 100644 index 0000000000..b5f5b45207 --- /dev/null +++ b/tests/accuracy_test/nuc_proxy_accuracy_test.py @@ -0,0 +1,358 @@ +import os +import argparse +import stat +import socket +import json +import threading +import queue +import logging +import logging.handlers +import serial +import shutil +import struct +import time +import toml +from typing import Tuple + + +class MySerial: + def __init__(self, port, baudrate, separator, logger): + self.s = None + self.port = port + self.baudrate = baudrate + self.separator = separator + self.logger = logger + self.timeout = 60 * 60 * 24 + + def open(self): + self.logger.debug(f'open {self.port} begin') + self.s = serial.Serial(port=self.port, baudrate=self.baudrate, timeout=self.timeout) + if (self.s.isOpen()): + self.logger.debug(f'open {self.port} succeed end') + else: + self.logger.debug(f'open {self.port} failed end') + + def close(self): + self.logger.debug(f'close {self.port} begin') + self.s.close() + self.logger.debug(f'close {self.port} end') + + def write(self, cmd): + self.logger.debug(f'write {cmd} begin') + cmd = cmd + '\r' + self.s.write(cmd.encode()) + self.s.flush() + self.logger.debug('write end') + + # There is something wrong for self.s.read_until() api, refer to https://github.com/pyserial/pyserial/issues/181 + def read_until(self, expected: str, size=None) -> Tuple[str, bool]: + self.logger.debug('read begin: expected = {0}'.format(expected)) + if isinstance(expected, str): + expected = expected.encode() + lenterm = len(expected) + data = bytearray() + expired = True + timeout = serial.Timeout(self.s.timeout) + while True: + c = self.s.read(1) + if c: + data += c + if data[-lenterm:] == expected: + expired = False + break + if size is not None and len(data) >= size: + break + else: + break + if timeout.expired(): + break + self.logger.debug('read end: data = {0}, size = {1}'.format(data, len(data))) + return bytes(data).decode(), expired + + def run_cmd(self, cmd, expected=''): + data = '' + expired = False + self.open() + self.write(cmd) + + str = expected if expected != '' else self.separator + data, expired = self.read_until(str) + self.close() + return data, expired + + +class Target: + def __init__(self, name, cfg, host_nfs, clear_queue): + self.name = name + self.infer_queue = queue.Queue(maxsize=clear_queue.maxsize) + self.clear_queue = clear_queue + self.username = cfg['username'] + self.password = cfg['password'] + self.working_dir = cfg['working_dir'] + self.target_nfs = os.path.join(cfg['target_nfs'], name) + + # host_nfs + self.host_nfs = os.path.join(host_nfs, name) + if not os.path.exists(self.host_nfs): + os.makedirs(self.host_nfs) + + # logging + mylogger = logging.getLogger() + mylogger.setLevel(logging.DEBUG) + rf_handler = logging.handlers.RotatingFileHandler( + f'nuc_proxy_{name}.log', mode='a', maxBytes=32 * 1024 * 1024, backupCount=10) + rf_handler.setLevel(logging.DEBUG) + rf_handler.setFormatter(logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')) + mylogger.addHandler(rf_handler) + self.logger = mylogger + + # serial + self.s0 = MySerial(cfg['uart0'], cfg['baudrate0'], cfg['separator0'], self.logger) + self.s1 = MySerial(cfg['uart1'], cfg['baudrate1'], cfg['separator1'], self.logger) + + def reboot(self): + self.s0.run_cmd('reboot') + time.sleep(30) + + +def send_msg(sock, msg): + # Prefix each message with a 4-byte length (network byte order) + msg = struct.pack('>I', len(msg)) + msg + sock.sendall(msg) + + +def recv_msg(sock): + # Read message length and unpack it into an integer + raw_msglen = recvall(sock, 4) + if not raw_msglen: + return None + msglen = struct.unpack('>I', raw_msglen)[0] + # Read the message data + return recvall(sock, msglen) + + +def recvall(sock, n): + # Helper function to recv n bytes or return None if EOF is hit + data = bytearray() + while len(data) < n: + packet = sock.recv(n - len(data)) + if not packet: + return None + data.extend(packet) + return data + + +def recv_file(conn, case_dir, logger): + send_msg(conn, f"pls send file info".encode()) + header = recv_msg(conn) + file_dict = json.loads(header.decode()) + file_name = file_dict['file_name'] + file_size = file_dict['file_size'] + logger.debug('recv begin: file = {0}, size = {1}'.format(file_name, file_size)) + send_msg(conn, f"pls send {file_name}".encode()) + + full_file = os.path.join(case_dir, file_name) + with open(full_file, 'wb') as f: + data = recvall(conn, file_size) + f.write(data) + + os.chmod(full_file, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) + logger.debug('recv end') + return file_name + + +def recv_worker(conn, target): + # recv header + send_msg(conn, f"pls send header".encode()) + header = recv_msg(conn) + dict = json.loads(header.decode()) + new_case = dict['case'] + str(int(time.time())) + target.logger.info("test case = {0}".format(new_case)) + case_dir = os.path.join(target.host_nfs, new_case) + os.makedirs(case_dir) + file_num = dict['app'] + dict['kmodel'] + dict['description'] + + # recv all kinds of files(app + kmodel + desc) + if dict['app'] == 1: + dict['app'] = recv_file(conn, case_dir, target.logger) + + if dict['kmodel'] == 1: + dict['kmodel'] = recv_file(conn, case_dir, target.logger) + + if dict['description'] == 1: + dict['description'] = recv_file(conn, case_dir, target.logger) + + target.infer_queue.put((conn, case_dir, dict)) + + +def infer_worker(target): + while True: + conn, case_dir, header_dict = target.infer_queue.get() + test_case = os.path.basename(case_dir) + s1_separator = test_case + target.s1.separator + ret = '' + timeout = False + + # try to login serial + target.s0.run_cmd(target.username) + target.s0.run_cmd(target.password) + target.s1.run_cmd('q\r') + + # copy test case from nfs to evb + nfs_test_case = os.path.join(target.target_nfs, test_case) + target.s0.run_cmd(f'cp -r {nfs_test_case} {target.working_dir}') + target.s0.run_cmd(f'rm {nfs_test_case}/*') + + # dataset + host_dataset = os.path.join(target.host_nfs, '../' + header_dict['dataset_path']) + target_dataset = os.path.join(target.working_dir, header_dict['dataset_path']) + + # cd the target test case dir + cmd = f'cd {target.working_dir}/{test_case}' + target.s1.run_cmd(cmd, s1_separator) + + # generate cmd to run on evb + cmd = f'./' + cmd = cmd + header_dict['app'] + cmd = cmd + ' ' + header_dict['kmodel'] + cmd = cmd + ' ' + target_dataset + cmd = cmd + ' ' + str(header_dict['dataset_number']) + if header_dict['description'] != 0: + cmd = cmd + ' ' + header_dict['description'] + + ret, timeout = target.s1.run_cmd(cmd, s1_separator) + + # infer result + dict = {'type': 'finish', 'msg': ''} + if ret.find('terminate') != -1 or ret.find('Exception') != -1: + err = 'infer exception' + target.logger.error(err) + dict['type'] = 'exception' + dict['msg'] = err + send_msg(conn, json.dumps(dict).encode()) + target.reboot() + elif timeout: + err = 'infer timeout' + target.logger.error(err) + dict['type'] = 'timeout' + dict['msg'] = err + send_msg(conn, json.dumps(dict).encode()) + target.reboot() + else: + # copy outputs from evb to host nfs + target.s0.run_cmd( + f'find {target.working_dir}/{test_case} -name *.bin -exec cp' + ' {} ' + f'{nfs_test_case} \;') + + host_test_case = os.path.join(target.host_nfs, test_case) + output_bin_list = [] + for bin in os.listdir(host_test_case): + output_bin_list.append(os.path.join(host_test_case, bin)) + output_bin_list.sort() + assert(header_dict['dataset_number'] * header_dict['outputs'] == len(output_bin_list)) + + # send header + dict['type'] = 'finish' + dict['msg'] = '' + send_msg(conn, json.dumps(dict).encode()) + + # send outputs + c = 0 + for i in range(header_dict['dataset_number']): + dummy = recv_msg(conn) + for j in range(header_dict['outputs']): + file = output_bin_list[c] + file_size = os.path.getsize(file) + + file_dict = {} + # file_dict['file_name'] = os.path.basename(file) + file_dict['file_size'] = os.path.getsize(file) + send_msg(conn, json.dumps(file_dict).encode()) + dummy = recv_msg(conn) + target.logger.debug('after sending header, dummy= {0}'.format(dummy)) + + target.logger.debug( + 'send begin: file = {0}, size = {1}'.format(file, file_size)) + with open(file, 'rb') as f: + conn.sendall(f.read()) + target.logger.debug('send end') + c = c + 1 + target.logger.debug(f'infer finish') + + target.s0.run_cmd(f'rm -rf {target.working_dir}/{test_case}') + + conn.close() + target.clear_queue.put(case_dir) + + +def clear_worker(q): + while True: + case_dir = q.get() + if os.path.exists(case_dir): + shutil.rmtree(case_dir) + + +def main(): + # default config + config = ''' + ip = '10.100.105.139' + port = 10001 + host_nfs = '/data/nfs' + [k230] + username = 'root' + password = '' + working_dir = '/sharefs' + target_nfs = '/nfs' + uart0 = '/dev/ttyUSB0' + baudrate0 = 115200 + separator0 = ']#' + uart1 = '/dev/ttyUSB1' + baudrate1 = 115200 + separator1 = '>' + ''' + + # args + parser = argparse.ArgumentParser(prog="nuc_proxy") + parser.add_argument("--config", help='config string or file', type=str, default=config) + args = parser.parse_args() + size = 256 + cfg = {} + dict = {} + + # load config + if os.path.isfile(args.config): + cfg = toml.load(args.config) + else: + cfg = toml.loads(args.config) + + # clear thread + clear_queue = queue.Queue(maxsize=size) + clear_thread = threading.Thread(target=clear_worker, args=(clear_queue,)) + clear_thread.start() + + # start server + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.bind((cfg['ip'], cfg['port'])) + server_socket.listen(size) + while True: + conn, addr = server_socket.accept() + + # recv target name + send_msg(conn, f"pls send your target".encode()) + info = recv_msg(conn) + target_dict = json.loads(info.decode()) + target_name = target_dict['target'] + + # create target instance + if target_name not in dict: + target = Target(target_name, cfg[target_name], cfg['host_nfs'], clear_queue) + infer_thread = threading.Thread(target=infer_worker, args=(target,)) + infer_thread.start() + dict[target_name] = target + + # start recv thread + recv_thread = threading.Thread(target=recv_worker, args=(conn, dict[target_name])) + recv_thread.start() + + +if __name__ == '__main__': + main() diff --git a/tests/accuracy_test/onnx_test_runner.py b/tests/accuracy_test/onnx_test_runner.py new file mode 100644 index 0000000000..bf7f8be73d --- /dev/null +++ b/tests/accuracy_test/onnx_test_runner.py @@ -0,0 +1,266 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +from onnx import version_converter, helper, external_data_helper +import onnxsim +import onnxruntime as ort +import onnx +import torch +import shutil +import os +import numpy as np +from test_runner import * +from test_utils import * +from collections import ChainMap +import threading +import queue + + +class OnnxTestRunner(TestRunner): + def __init__(self, case_name, overwrite_configs: str = None): + super().__init__(case_name, overwrite_configs) + self.model_type = "onnx" + + def from_torch(self, module, in_shape, opset_version=11): + # export model + dummy_input = torch.randn(*in_shape) + model_file = os.path.join(self.case_dir, 'test.onnx') + torch.onnx.export(module, dummy_input, model_file, + operator_export_type=torch.onnx.OperatorExportTypes.ONNX_ATEN_FALLBACK, opset_version=opset_version) + return model_file + + def from_onnx_helper(self, model_def): + try: + onnx.checker.check_model(model_def) + except onnx.checker.ValidationError as e: + print('The model is invalid: %s' % e) + else: + print('The model is valid!') + + model_file = os.path.join(self.case_dir, 'test.onnx') + onnx.save(model_def, model_file) + + return model_file + + def run(self, model_file): + if model_file.startswith('examples'): + model_file = os.path.join(os.path.dirname(__file__), '..', model_file) + elif model_file.startswith('onnx-models'): + model_file = os.path.join(os.getenv('ONNX_MODELS_DIR'), + model_file[len('onnx-models/'):]) + if self.case_dir != os.path.dirname(model_file): + new_file = os.path.join(self.case_dir, 'test.onnx') + shutil.copy(model_file, new_file) + for tensor in external_data_helper._get_all_tensors(onnx.load(model_file, load_external_data=False)): + if external_data_helper.uses_external_data(tensor): + info = external_data_helper.ExternalDataInfo(tensor) + file_location = external_data_helper._sanitize_path(info.location) + external_data_src_path = os.path.join( + os.path.dirname(model_file), file_location) + external_data_dst_path = os.path.join( + self.case_dir, file_location) + if not os.path.exists(external_data_dst_path): + os.symlink(external_data_src_path, external_data_dst_path) + model_file = new_file + + if not self.inputs: + self.parse_model(model_file) + + model_file = self.do_preprocess(model_file) + + super().run(model_file) + + def do_preprocess(self, model_file): + # preprocess model + old_onnx_model = onnx.load(model_file) + onnx_model = self.preprocess_model(old_onnx_model) + onnx_model = onnx_model or self.preprocess_model( + old_onnx_model, convert_version=False) + onnx_model = onnx_model or self.preprocess_model( + old_onnx_model, simplify=False) + onnx_model = onnx_model or self.preprocess_model( + old_onnx_model, convert_version=False, simplify=False) + onnx_model = onnx_model or self.preprocess_model( + old_onnx_model, fix_bn=False, convert_version=False, simplify=False) + model_file = os.path.join( + os.path.dirname(model_file), 'simplified.onnx') + onnx.save_model(onnx_model, model_file, + save_as_external_data=True if onnx_model.ByteSize() > 2147483648 else False) + return model_file + + def preprocess_model(self, onnx_model, fix_bn=True, convert_version=True, simplify=True, import_test=True): + args = {'fix_bn': fix_bn, 'convert_version': convert_version, + 'simplify': simplify, 'import_test': import_test} + try: + if fix_bn: + # fix https://github.com/onnx/models/issues/242 + for node in onnx_model.graph.node: + if(node.op_type == "BatchNormalization"): + for attr in node.attribute: + if (attr.name == "spatial"): + attr.i = 1 + + if convert_version: + curret_version = onnx_model.opset_import[0].version + for i in range(curret_version, 8): + onnx_model = version_converter.convert_version( + onnx_model, i + 1) + + if simplify: + onnx_model = onnx.shape_inference.infer_shapes(onnx_model) + input_shapes = {} + for input in self.inputs: + input_shapes[input['name']] = input['shape'] + + onnx_model, check = onnxsim.simplify( + onnx_model, input_shapes=input_shapes, dynamic_input_shape=self.dynamic) + assert check, "Simplified ONNX model could not be validated" + + print('[info]: preprocess ONNX model success: ', args) + return onnx_model + except Exception as e: + print('[info]: preprocess ONNX model failed: ', args) + print(e) + # traceback.print_exc() + return None + + def parse_model(self, model_file: str): + onnx_model = onnx.load(model_file) + input_all = [node.name for node in onnx_model.graph.input] + input_initializer = [node.name for node in onnx_model.graph.initializer] + input_names = list(set(input_all) - set(input_initializer)) + input_tensors = [node for node in onnx_model.graph.input if node.name in input_names] + + def to_dim_value(d, default_d): + """ + if dim_value is not digit, it should be fixed. + dim_value range: [0, inf) + """ + if d.dim_param != "": + if len(self.shape_vars): + # we should eval dim_param instead of get var value + # e.g. dim_param = dec_len - 1 + return eval(f"{d.dim_param}", self.shape_vars) + else: + # if not set shape vars, then return default d + # if it has multi input that all of them have var, must set shape var + # e.g. + # input0: [8,24,dec_len-1] + # input1: [8,dec_len-1,24] + return default_d + else: + return d.dim_value + + def translate_shape(shape, default_shape): + return [to_dim_value(d, def_d) for d, def_d in zip(shape, default_shape)] + + # input + for _, e in enumerate(input_tensors): + onnx_type = e.type.tensor_type + input_dict = {} + input_dict['name'] = e.name + input_dict['dtype'] = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[onnx_type.elem_type] + shape = translate_shape(onnx_type.shape.dim, self.default_shape) + input_dict['shape'] = shape + input_dict['model_shape'] = shape + self.inputs.append(input_dict) + self.calibs.append(copy.deepcopy(input_dict)) + # self.dump_range_data.append(copy.deepcopy(input_dict)) + + def is_dynamic(output): + dims = output.type.tensor_type.shape.dim + return any(dim.dim_param != '' for dim in dims) + + outputs = onnx_model.graph.output + self.dynamic = any(is_dynamic(output) for output in outputs) + # make a static model for infer output + if self.dynamic and onnx_model.ByteSize() < 2147483648: + input_shapes = list(map(lambda input: {input['name']: input['shape']}, self.inputs)) + input_shapes = dict(ChainMap(*input_shapes)) + (onnx_model, _) = onnxsim.simplify(onnx_model, input_shapes=input_shapes) + + # output + for e in onnx_model.graph.output: + output_dict = {} + onnx_type = e.type.tensor_type + output_dict['name'] = e.name + if onnx_type.elem_type == 0: + output_dict['dtype'] = 'float32' + else: + output_dict['dtype'] = onnx.mapping.TENSOR_TYPE_TO_NP_TYPE[onnx_type.elem_type] + output_dict['model_shape'] = [i.dim_value for i in onnx_type.shape.dim] + self.outputs.append(output_dict) + + def cpu_infer(self, model_file: bytes): + generator_cfg = self.cfg['generator']['inputs'] + method = generator_cfg['method'] + number = generator_cfg['number'] + args = os.path.join(test_utils.test_root(), generator_cfg[method]['roofline_args']) + + file_list = [] + assert(os.path.isdir(args)) + for file in os.listdir(args): + if file.endswith('.bin'): + file_list.append(os.path.join(args, file)) + file_list.sort() + + # create session + try: + print('[onnx]: using simplified model') + sess = ort.InferenceSession(model_file) + except Exception as e: + print(e) + try: + print('[onnx]: using origin model') + model_file = os.path.join(self.case_dir, 'test.onnx') + sess = ort.InferenceSession(model_file) + except Exception as e: + print(e) + print('[onnx]: using converted model') + onnx_model = onnx.load(model_file) + onnx_model = version_converter.convert_version(onnx_model, 8) + model_file = os.path.join(self.case_dir, 'converted.onnx') + onnx.save_model(onnx_model, model_file) + sess = ort.InferenceSession(model_file) + + q = queue.Queue(maxsize=self.postprocess_qsize) + t = threading.Thread(target=self.postprocess, args=(q, )) + t.start() + + c = 0 + for i in range(number): + # set input + input_dict = {} + for idx, value in enumerate(self.inputs): + input_dict[value['name']] = np.fromfile( + file_list[c], dtype=value['dtype']).reshape(value['model_shape']) + c = c + 1 + + # run + outputs = sess.run(None, input_dict) + + # postprocess + q.put(outputs) + + # debug + if not test_utils.in_ci(): + for j, output in enumerate(outputs): + dump_bin_file(os.path.join(self.case_dir, f'cpu_result_{i}_{j}.bin'), output) + # dump_txt_file(os.path.join(self.case_dir, f'cpu_result_{i}_{j}.txt'), output) + + t.join() + + def import_model(self, compiler, model_content, import_options): + compiler.import_onnx(model_content, import_options) diff --git a/tests/accuracy_test/preprocess_utils.py b/tests/accuracy_test/preprocess_utils.py new file mode 100644 index 0000000000..0ece47e4cd --- /dev/null +++ b/tests/accuracy_test/preprocess_utils.py @@ -0,0 +1,23 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +def get_source_transpose_index(perm): + """ + transpose model output with postprocess to framework output + """ + src_output_perm = [] + for idx, i in enumerate(perm): + src_output_perm.append(perm.index(idx)) + return src_output_perm diff --git a/tests/accuracy_test/test_runner.py b/tests/accuracy_test/test_runner.py new file mode 100644 index 0000000000..f236ac4fac --- /dev/null +++ b/tests/accuracy_test/test_runner.py @@ -0,0 +1,383 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +import copy +import os +import re +import shutil +import struct +from abc import ABCMeta, abstractmethod +from itertools import product +from functools import reduce +from pathlib import Path +from typing import Any, Dict, List, Tuple, Union +import collections.abc + +import nncase +import copy +import cv2 +from PIL import Image +import numpy as np +import toml +from generator import * +from inference import * +from evaluator import * +from compare_util import * +from test_utils import * +from html import escape +import sys + + +class TestRunner(Evaluator, Inference, metaclass=ABCMeta): + def __init__(self, case_name, override_cfg: str = None) -> None: + config_root = os.path.dirname(__file__) + default_cfg = toml.load(os.path.join(config_root, 'config.toml')) + + new_cfg = None + if override_cfg is not None: + if os.path.isfile(override_cfg): + new_cfg = toml.load(override_cfg) + else: + new_cfg = toml.loads(override_cfg) + config = self.update_config(default_cfg, new_cfg) + self.cfg = self.validte_config(config) + + case_name = case_name.replace('[', '_').replace(']', '_') + self.case_dir = os.path.join(self.cfg['root'], case_name) + self.clear(self.case_dir) + os.makedirs(self.case_dir) + + self.inputs: List[Dict] = [] + self.calibs: List[Dict] = [] + self.outputs: List[Dict] = [] + self.model_type: str = "" + self.pre_process: List[Dict] = [] + self.postprocess_qsize = 4096 + self.postprocess_result = '' + + self.num_pattern = re.compile("(\d+)") + # [n, c, h, w].zip default_shape => [(n, 1), (c, 1), (h, 48), (w, 48)] + self.default_shape = [1, 1, 48, 48, 24, 24] + self.shape_vars = {} + # used for tag dynamic model + self.dynamic = False + + if self.cfg['infer_report_opt']['enabled']: + self.infer_report_file = test_utils.infer_report_file( + self.cfg['infer_report_opt']['report_name']) + self.infer_report_dict = { + 'priority': 100, + 'kind': 'N/A', + 'model': 'N/A', + 'shape': 'N/A', + 'official_result': '', + 'tflite_onnx_result': '', + 'nncase_result': '', + 'remark': '' + } + + def transform_input(self, values: List[np.ndarray], type: str, stage: str) -> List[np.ndarray]: + new_values = [] + compile_opt = self.cfg['compile_opt'] + for value in values: + new_value = copy.deepcopy(value) + if compile_opt['preprocess']: + if stage == "CPU": + # onnx \ caffe + if (self.model_type == "onnx" or self.model_type == "caffe"): + if len(new_value.shape) == 4: + new_value = np.transpose(new_value, [0, 3, 1, 2]) + else: + new_value = np.transpose( + new_value, [int(idx) for idx in compile_opt['input_layout'].split(",")]) + + if type == 'float32': + new_value = new_value.astype(np.float32) + elif type == 'uint8': + if new_value.dtype == np.float32: + new_value = (new_value * 255).astype(np.uint8) + elif type == 'int8': + if new_value.dtype == np.float32: + new_value = (new_value * 255 - 128).astype(np.int8) + else: + raise TypeError(" Not support type for quant input") + + new_values.append(new_value) + return new_values + + def validte_config(self, config): + # disable all dump in CI + if test_utils.in_ci(): + config['dump_hist'] = False + config['compile_opt']['dump_asm'] = False + config['compile_opt']['dump_quant_error'] = False + + # check target + for k, v in config['target'].items(): + if not nncase.check_target(k): + v['eval'] = False + v['infer'] = False + print("WARN: target[{0}] not found".format(k)) + + # disable cpu target in k230/k510 CI + if test_utils.in_ci() and test_utils.kpu_targets() != ['']: + config['target']['cpu']['eval'] = False + config['target']['cpu']['infer'] = False + + return config + + def update_config(self, config: Dict, override_cfg: Dict) -> Dict: + if override_cfg: + for k, v in override_cfg.items(): + if isinstance(v, collections.abc.Mapping): + config[k] = self.update_config(config.get(k, {}), v) + else: + config[k] = v + return config + + return config + + def clear(self, case_dir): + if os.path.exists(case_dir): + shutil.rmtree(case_dir) + + @ abstractmethod + def parse_model(self, model_path: Union[List[str], str]): + pass + + @abstractmethod + def cpu_infer(self, case_dir: str, model_content: Union[List[str], str]): + pass + + @abstractmethod + def import_model(self, compiler, model_content, import_options): + pass + + def postprocess(self, result): + pass + + def get_official_result(self): + return 'N/A' + + def get_postprocess_result(self): + return self.postprocess_result + + def get_remark(self): + return 'N/A' + + def run(self, model_file: Union[List[str], str]): + if not self.inputs: + self.parse_model(model_file) + + self.generate_all_data() + self.write_compile_opt() + + # onnx/tflite runtime + self.cpu_infer(model_file) + tflite_onnx_result = self.get_postprocess_result() + + if self.cfg['infer_report_opt']['enabled']: + self.infer_report_dict['official_result'] = escape( + self.get_official_result()).replace('\n', '
') + self.infer_report_dict['tflite_onnx_result'] = escape( + tflite_onnx_result).replace('\n', '
') + self.infer_report_dict['remark'] = self.get_remark() + + # nncase + self.postprocess_result = '' + targets = self.cfg['target'] + model_content = self.read_model_file(model_file) + import_options = nncase.ImportOptions() + + compiler = None + for k_target, v_target in targets.items(): + tmp_dir = os.path.join(self.case_dir, 'tmp') + if v_target['eval'] or v_target['infer']: + compile_options = self.get_compile_options(k_target, tmp_dir) + compiler = nncase.Compiler(compile_options) + self.import_model(compiler, model_content, import_options) + + for stage in ['eval', 'infer']: + if v_target[stage]: + for k_mode, v_mode in v_target['mode'].items(): + if v_mode['enabled']: + os.makedirs(tmp_dir, exist_ok=True) + if stage == 'eval': + self.run_evaluator(compiler, tmp_dir) + else: + self.run_inference(compiler, k_target, v_mode['enabled'], tmp_dir) + target_dir = os.path.join(self.case_dir, stage, k_target) + os.makedirs(target_dir, exist_ok=True) + mode_dir = os.path.join(target_dir, k_mode) + shutil.move(tmp_dir, mode_dir) + + result = self.get_postprocess_result() + if stage == 'infer' and self.cfg['infer_report_opt']['enabled']: + self.infer_report_dict['nncase_result'] = escape( + result).replace('\n', '
') + self.infer_report_dict['remark'] = escape( + self.infer_report_dict['remark']).replace('\n', '
') + prefix, suffix = os.path.splitext(self.infer_report_file) + json_file = f'{prefix}_{os.path.basename(self.case_dir)}{suffix}' + dump_dict_to_json(self.infer_report_dict, json_file) + + if test_utils.in_ci(): + self.clear(self.case_dir) + + def translate_shape(self, shape): + if reduce(lambda x, y: x * y, shape) == 0: + return [(i if i != 0 else d) for i, d in zip(shape, [3, 4, 256, 256])] + else: + return shape + + def set_shape_var(self, dict: Dict[str, int]): + self.shape_vars = dict + + def set_quant_opt(self, compiler: nncase.Compiler): + compile_opt = self.cfg['compile_opt'] + ptq_opt = self.cfg['ptq_opt'] + + ptq_options = nncase.PTQTensorOptions() + e = '"' + for k, v in ptq_opt.items(): + exec(f"ptq_options.{k} = {e + v + e if isinstance(v, str) else v}") + + ptq_options.samples_count = self.cfg['generator']['calibs']['number'] + data = [self.transform_input( + input['data'], compile_opt['input_type'], "infer") for input in self.calibs] + ptq_options.set_tensor_data(data) + + compiler.use_ptq(ptq_options) + + def write_compile_opt(self): + dict = self.cfg['compile_opt'] + if dict['preprocess'] == True: + str_compile_opt = "" + pre_list = [] + for key, value in dict.items(): + pre_list.append(str_compile_opt.join("{0}:{1}".format(key, value))) + with open(os.path.join(self.case_dir, 'test_result.txt'), 'a+') as f: + f.write("\n----compile option----\n") + f.write('\n'.join(pre_list[:]) + "\n") + f.write("-------------------------\n") + + def generate_all_data(self): + # self.generate_data('input', self.inputs, + # self.cfg['compile_opt'], self.cfg['generator']['inputs']) + self.generate_data('calib', self.calibs, + self.cfg['compile_opt'], self.cfg['generator']['calibs']) + + def get_compile_options(self, target, dump_dir): + compile_options = nncase.CompileOptions() + + # update preprocess option + compile_opt = self.cfg['compile_opt'] + e = '"' + for k, v in compile_opt.items(): + # TODO: support model with unusual layout e.g.: onnx->NHWC + if k == "model_layout" or k == "model_shape": + continue + exec(f"compile_options.{k} = {e + v + e if isinstance(v, str) else v}") + + compile_options.target = target + compile_options.dump_dir = dump_dir + + return compile_options + + @staticmethod + def split_value(kwcfg: List[Dict[str, str]]) -> Tuple[List[str], List[str]]: + arg_names = [] + arg_values = [] + for d in kwcfg: + arg_names.append(d.name) + arg_values.append(d.values) + return (arg_names, arg_values) + + def read_model_file(self, model_file: Union[List[str], str]): + if isinstance(model_file, str): + with open(model_file, 'rb') as f: + return f.read() + elif isinstance(model_file, list): + model_content = [] + with open(model_file[0], 'rb') as f: + model_content.append(f.read()) + with open(model_file[1], 'rb') as f: + model_content.append(f.read()) + return model_content + + def generate_data(self, name: str, inputs: List[Dict], compile_opt, generator_cfg): + os.mkdir(os.path.join(self.case_dir, name)) + method = generator_cfg['method'] + batch_number = generator_cfg['number'] + args = generator_cfg[method]['args'] + file_list = [] + + if method == 'random': + assert args in (True, False) + elif method == 'bin': + assert(os.path.isdir(args)) + for file in os.listdir(args): + if file.endswith('.bin'): + file_list.append(os.path.join(args, file)) + file_list.sort() + elif method == 'image': + file_list.extend([os.path.join(args, p) for p in os.listdir(args)]) + file_list.sort() + elif method == 'constant_of_shape': + assert len(args) != 0 + else: + assert '{0} : not supported generator method'.format(method) + + generator = Generator() + for input_idx, input in enumerate(inputs): + samples = [] + files = [] + input_shape = [] + dtype = input['dtype'] + if compile_opt['preprocess'] and compile_opt['input_shape'] != []: + input_shape = copy.deepcopy(compile_opt['input_shape']) + if compile_opt['input_type'] == "uint8": + dtype = np.uint8 + elif compile_opt['input_type'] == "float32": + dtype = np.float32 + else: + input_shape = copy.deepcopy(input['model_shape']) + if input_shape != [] and input_shape[0] != generator_cfg['batch']: + input_shape[0] *= generator_cfg['batch'] + + for batch_idx in range(batch_number): + if method == 'random': + data = generator.from_random(input_shape, dtype, args) + elif method == 'bin': + idx = input_idx * batch_number + batch_idx + assert(idx < len(file_list)) + data = generator.from_bin(input_shape, dtype, file_list[idx]) + files.append(file_list[idx]) + elif method == 'image': + idx = input_idx * batch_number + batch_idx + assert(idx < len(file_list)) + data = generator.from_image(input_shape, dtype, file_list[idx]) + files.append(file_list[idx]) + elif method == 'constant_of_shape': + data = generator.from_constant_of_shape(args, dtype) + + if not test_utils.in_ci(): + dump_bin_file(os.path.join(self.case_dir, name, + f'{name}_{input_idx}_{batch_idx}.bin'), data) + dump_txt_file(os.path.join(self.case_dir, name, + f'{name}_{input_idx}_{batch_idx}.txt'), data) + samples.append(data) + input['data'] = samples + input['file'] = files diff --git a/tests/accuracy_test/test_utils.py b/tests/accuracy_test/test_utils.py new file mode 100644 index 0000000000..0725e4a04c --- /dev/null +++ b/tests/accuracy_test/test_utils.py @@ -0,0 +1,101 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +import os +import json +import numpy as np +from pathlib import Path + + +def dump_bin_file(file: str, ndarray: np.array): + ndarray.tofile(file) + + +def dump_npy_file(file: str, ndarray: np.array): + np.save(file, ndarray) + + +def dump_txt_file(save_path, ndarray: np.array, bit_16_represent=False): + if bit_16_represent: + np.save(save_path, _cast_bfloat16_then_float32(ndarray)) + else: + if ndarray.dtype == np.uint8: + fmt = '%u' + elif ndarray.dtype == np.int8: + fmt = '%d' + else: + fmt = '%f' + np.savetxt(save_path, ndarray.flatten(), fmt=fmt, header=str(ndarray.shape)) + + print("----> %s" % save_path) + + +def _cast_bfloat16_then_float32(values: np.array): + shape = values.shape + values = values.reshape([-1]) + for i, value in enumerate(values): + value = float(value) + value = 1 + packed = struct.pack('!f', value) + integers = [c for c in packed][:2] + [0, 0] + value = struct.unpack('!f', bytes(integers))[0] + values[i] = value + + +def dump_dict_to_json(dict, json_file): + json_list = [] + if os.path.exists(json_file): + with open(json_file, 'r') as f: + json_list = json.load(f) + + json_list.append(dict) + with open(json_file, 'w') as f: + json.dump(json_list, f) + + +def in_ci(): + return os.getenv('CI', False) + + +def kpu_targets(): + return os.getenv('KPU_TARGETS', "").split(',') + + +def nuc_ip(): + return os.getenv('NUC_PROXY_IP') + + +def nuc_port(): + return os.getenv('NUC_PROXY_PORT') + + +def test_executable(target: str): + return os.getenv('TEST_EXECUTABLE_{0}'.format(target.upper())) + + +def infer_report_file(default: str): + return os.getenv('INFER_REPORT_FILE', default) + + +def test_root(): + return os.getenv('TEST_ROOT') + + +def search_file(dir: str, file: str): + p = '' + for path in Path(dir).rglob(file): + p = path + break + return p diff --git a/tests/accuracy_test/tflite_test_runner.py b/tests/accuracy_test/tflite_test_runner.py new file mode 100644 index 0000000000..95d7eb9e74 --- /dev/null +++ b/tests/accuracy_test/tflite_test_runner.py @@ -0,0 +1,145 @@ +# Copyright 2019-2021 Canaan Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# pylint: disable=invalid-name, unused-argument, import-outside-toplevel + +import tensorflow as tf +from test_runner import * +import os +import shutil +from test_utils import * +import threading +import queue + + +class TfliteTestRunner(TestRunner): + def __init__(self, case_name, overwrite_configs: str = None): + super().__init__(case_name, overwrite_configs) + self.model_type = "tflite" + self.interp = None + + def from_tensorflow(self, module): + # export model + tf.saved_model.save(module, self.case_dir) + converter = tf.lite.TFLiteConverter.from_saved_model(self.case_dir) + + # convert model + tflite_model = converter.convert() + model_file = os.path.join(self.case_dir, 'test.tflite') + with open(model_file, 'wb') as f: + f.write(tflite_model) + + return model_file + + def from_keras(self, keras_model): + converter = tf.lite.TFLiteConverter.from_keras_model(keras_model) + tflite_model = converter.convert() + model_file = os.path.join(self.case_dir, 'test.tflite') + with open(model_file, 'wb') as f: + f.write(tflite_model) + + return model_file + + def run(self, model_file): + if model_file.startswith('examples'): + model_file = os.path.join(os.path.dirname(__file__), '..', model_file) + if self.case_dir != os.path.dirname(model_file): + shutil.copy(model_file, self.case_dir) + model_file = os.path.join( + self.case_dir, os.path.basename(model_file)) + super().run(model_file) + + def parse_model(self, model_path: str): + self.interp = tf.lite.Interpreter(model_path=model_path) + + def translate_shape(shape): + return [i if i > 0 else self.shape_vars["-1"] for i in shape] + + for item in self.interp.get_input_details(): + input_dict = {} + input_dict['index'] = item['index'] + input_dict['name'] = item['name'] + input_dict['dtype'] = item['dtype'] + + if len(self.shape_vars) == 0: + # fixed shape + shape = item['shape'] + else: + # dynamic shape + shape = item['shape_signature'] + + if len(shape) <= 4: + input_dict['model_shape'] = translate_shape(shape) + else: + if -1 in shape: + raise "tflite test_runner not supported dynamic shape which rank > 4" + input_dict['model_shape'] = shape + + if -1 in shape: + self.dynamic = True + self.interp.resize_tensor_input(item['index'], input_dict['model_shape']) + self.inputs.append(input_dict) + self.calibs.append(copy.deepcopy(input_dict)) + + for item in self.interp.get_output_details(): + output_dict = {} + output_dict['index'] = item['index'] + output_dict['name'] = item['name'] + output_dict['dtype'] = item['dtype'] + output_dict['model_shape'] = item['shape'] + self.outputs.append(output_dict) + + def cpu_infer(self, model_file: bytes): + generator_cfg = self.cfg['generator']['inputs'] + method = generator_cfg['method'] + number = generator_cfg['number'] + args = os.path.join(test_utils.test_root(), generator_cfg[method]['roofline_args']) + + file_list = [] + assert(os.path.isdir(args)) + for file in os.listdir(args): + if file.endswith('.bin'): + file_list.append(os.path.join(args, file)) + file_list.sort() + + q = queue.Queue(maxsize=self.postprocess_qsize) + t = threading.Thread(target=self.postprocess, args=(q, )) + t.start() + + self.interp.allocate_tensors() + c = 0 + for i in range(number): + # set input + for idx, value in enumerate(self.inputs): + data = np.fromfile(file_list[c], dtype=value['dtype']).reshape(value['model_shape']) + c = c + 1 + self.interp.set_tensor(value["index"], data) + + # run + self.interp.invoke() + + # get output + outputs = [] + for j, value in enumerate(self.outputs): + output = self.interp.get_tensor(value['index']) + outputs.append(output) + if not test_utils.in_ci(): + dump_bin_file(os.path.join(self.case_dir, f'cpu_result_{i}_{j}.bin'), output) + # dump_txt_file(os.path.join(self.case_dir, f'cpu_result_{i}_{j}.txt'), output) + + # postprocess + q.put(outputs) + t.join() + + def import_model(self, compiler, model_content, import_options): + compiler.import_tflite(model_content, import_options) From c4f7b5fb142f4b2521d082e55c3936e34ec15ced Mon Sep 17 00:00:00 2001 From: sunnycase Date: Wed, 13 Dec 2023 14:09:15 +0800 Subject: [PATCH 03/12] Fix ordering of PytestCalibrationDatasetProvider (#1143) --- .../Quantization/PytestCalibrationDatasetProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Nncase.Quantization/Quantization/PytestCalibrationDatasetProvider.cs b/src/Nncase.Quantization/Quantization/PytestCalibrationDatasetProvider.cs index 66b040ec0f..6aef026d82 100644 --- a/src/Nncase.Quantization/Quantization/PytestCalibrationDatasetProvider.cs +++ b/src/Nncase.Quantization/Quantization/PytestCalibrationDatasetProvider.cs @@ -42,7 +42,7 @@ public PytestCalibrationDatasetProvider(IReadOnlyList vars, string dataset) } // group by the samples - _sampleSets = sampleItems.GroupBy(item => item.Number).Select(g => g.OrderBy(item => item.InputIndex).ToArray()).ToArray(); + _sampleSets = sampleItems.GroupBy(item => item.Number).OrderBy(x => x.Key).Select(g => g.OrderBy(item => item.InputIndex).ToArray()).ToArray(); Count = _sampleSets.Length; Samples = _sampleSets.Select(samples => From 980f97747c1a77ca356fd913fa63e5f1d5ff7fa9 Mon Sep 17 00:00:00 2001 From: sunnycase Date: Wed, 13 Dec 2023 15:48:01 +0800 Subject: [PATCH 04/12] Fix memory leak: Avoid disposing python stream in finalizer thread (#1142) --- src/Native/include/nncase/compiler.h | 3 +++ src/Nncase.Compiler/Interop/CApi.cs | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/Native/include/nncase/compiler.h b/src/Native/include/nncase/compiler.h index 7339d0b546..1ef12f990d 100644 --- a/src/Native/include/nncase/compiler.h +++ b/src/Native/include/nncase/compiler.h @@ -109,6 +109,7 @@ typedef struct { clr_object_handle_t (*calibration_dataset_provider_create)( clr_object_handle_t dataset, size_t samplesCount, clr_object_handle_t fn_params); + void (*handle_dispose)(clr_object_handle_t handle); void (*handle_free)(clr_object_handle_t handle); clr_object_handle_t (*compile_options_create)(); void (*compile_options_set_input_file)(clr_object_handle_t compile_options, @@ -478,6 +479,8 @@ class cstream : public clr_object_base { cstream(const nncase_stream_mt_t *mt, void *handle) { obj_ = nncase_clr_api()->stream_create(mt, handle); } + + ~cstream() { nncase_clr_api()->handle_dispose(obj_.get()); } }; class compile_options : public clr_object_base { diff --git a/src/Nncase.Compiler/Interop/CApi.cs b/src/Nncase.Compiler/Interop/CApi.cs index 69bacc8397..2c0451d14e 100644 --- a/src/Nncase.Compiler/Interop/CApi.cs +++ b/src/Nncase.Compiler/Interop/CApi.cs @@ -41,6 +41,7 @@ public unsafe struct CApiMT public delegate* unmanaged ArrayGetItemPtr; public delegate* unmanaged ArrayGetLengthPtr; public delegate* unmanaged CalibrationDatasetProviderCreatePtr; + public delegate* unmanaged ClrHandleDisposePtr; public delegate* unmanaged ClrHandleFreePtr; public delegate* unmanaged CompileOptionsCreatePtr; public delegate* unmanaged CompileOptionsSetInputFilePtr; @@ -112,6 +113,7 @@ public static void Initialize(CApiMT* mt) mt->ArrayGetItemPtr = &ArrayGetItem; mt->ArrayGetLengthPtr = &ArrayGetLength; mt->CalibrationDatasetProviderCreatePtr = &CalibrationDatasetProviderCreate; + mt->ClrHandleDisposePtr = &ClrHandleDispose; mt->ClrHandleFreePtr = &ClrHandleFree; mt->CompileOptionsCreatePtr = &CompileOptionsCreate; mt->CompileOptionsSetInputFilePtr = &CompileOptionsSetInputFile; @@ -225,6 +227,12 @@ private static IntPtr CalibrationDatasetProviderCreate(IntPtr datasetHandle, nui return GCHandle.ToIntPtr(GCHandle.Alloc(new CCalibrationDatasetProvider(samples, (int)samplesCount))); } + [UnmanagedCallersOnly] + private static void ClrHandleDispose(IntPtr handle) + { + Get(handle).Dispose(); + } + [UnmanagedCallersOnly] private static void ClrHandleFree(IntPtr handle) { From 3a533466ee8c01934b1db887adabcac1cd87fde3 Mon Sep 17 00:00:00 2001 From: huochenghai Date: Wed, 20 Dec 2023 10:17:56 +0800 Subject: [PATCH 05/12] Feature/k800 qemu (#1145) * add cmake of k800 toolchain * add xpu in config.toml * distributed type for where * fix noptq --- src/Nncase.Core/IR/Tensors/Where.cs | 11 ++- src/Nncase.Evaluator/Tensors/Where.cs | 74 +++++++++++++++++-- .../Neutral/FoldPrePostReshapeSoftmax.cs | 38 ---------- tests/config.toml | 13 ++++ tests/test_runner.py | 2 +- toolchains/k800.linux.toolchain.cmake | 29 ++++++++ 6 files changed, 118 insertions(+), 49 deletions(-) delete mode 100644 src/Nncase.Passes/Rules/Neutral/FoldPrePostReshapeSoftmax.cs create mode 100644 toolchains/k800.linux.toolchain.cmake diff --git a/src/Nncase.Core/IR/Tensors/Where.cs b/src/Nncase.Core/IR/Tensors/Where.cs index 5506b6afea..d64e2472ad 100644 --- a/src/Nncase.Core/IR/Tensors/Where.cs +++ b/src/Nncase.Core/IR/Tensors/Where.cs @@ -21,17 +21,22 @@ public sealed partial class Where : Op /// /// Gets condition. /// - public static readonly ParameterInfo Cond = new(typeof(Where), 0, "cond"); + public static readonly ParameterInfo Cond = new(typeof(Where), 0, "cond", ParameterKind.Input); /// /// Gets x. /// - public static readonly ParameterInfo X = new(typeof(Where), 1, "x"); + public static readonly ParameterInfo X = new(typeof(Where), 1, "x", ParameterKind.Input); /// /// Gets y. /// - public static readonly ParameterInfo Y = new(typeof(Where), 2, "y"); + public static readonly ParameterInfo Y = new(typeof(Where), 2, "y", ParameterKind.Input); public bool IsTfWhere { get; } + + public override string DisplayProperty() + { + return $"IsTfWhere: {IsTfWhere}"; + } } diff --git a/src/Nncase.Evaluator/Tensors/Where.cs b/src/Nncase.Evaluator/Tensors/Where.cs index db614bc6d9..f45d41b737 100644 --- a/src/Nncase.Evaluator/Tensors/Where.cs +++ b/src/Nncase.Evaluator/Tensors/Where.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; using NetFabric.Hyperlinq; using Nncase.CostModel; using Nncase.IR; @@ -45,9 +46,20 @@ public IValue Visit(IEvaluateContext context, Where where) /// public IRType Visit(ITypeInferenceContext context, Where target) { - var cond = context.CheckArgumentType(target, Where.Cond); - var x = context.CheckArgumentType(target, Where.X); - var y = context.CheckArgumentType(target, Where.Y); + var cond = context.CheckArgumentType(target, Where.Cond); + var x = context.CheckArgumentType(target, Where.X); + var y = context.CheckArgumentType(target, Where.Y); + + return (cond, x, y) switch + { + (DistributedType a, DistributedType b, DistributedType c) => Visit(a, b, c, target), + (TensorType a, TensorType b, TensorType c) => Visit(a, b, c, target), + _ => new InvalidType(cond.GetType().ToString()), + }; + } + + public IRType Visit(TensorType cond, TensorType x, TensorType y, Where target) + { if (target.IsTfWhere) { return new TensorType(DataTypes.Int64, new Shape(Dimension.Unknown, cond.Shape.Rank)); @@ -56,12 +68,60 @@ public IRType Visit(ITypeInferenceContext context, Where target) return TypeInference.BroadcastType(x.DType, cond, x, y); } + public IRType Visit(DistributedType cond, DistributedType x, DistributedType y, Where target) + { + var invalid = new InvalidType($"{cond}, {x}, {y} not support"); + if (cond.Placement != x.Placement || x.Placement != y.Placement) + { + return invalid; + } + + if (target.IsTfWhere) + { + return invalid; + } + + var targetType = (TensorType)TypeInference.BroadcastType(x.TensorType.DType, cond.TensorType, x.TensorType, y.TensorType); + if (cond.TensorType.Shape != targetType.Shape) + { + return invalid; + } + + var ndsbp = new SBP[cond.Placement.Rank]; + + for (int i = 0; i < cond.Placement.Rank; i++) + { + switch (cond.NdSBP[i], x.NdSBP[i], y.NdSBP[i]) + { + case (SBPSplit { Axis: int ic }, SBPSplit { Axis: int }, SBPSplit { Axis: int }): + ndsbp[i] = SBP.S(ic); + break; + case (SBPSplit { Axis: int ic }, SBPBroadCast, SBPSplit { Axis: int }): + ndsbp[i] = SBP.S(ic); + break; + case (SBPSplit { Axis: int ic }, SBPSplit { Axis: int }, SBPBroadCast): + ndsbp[i] = SBP.S(ic); + break; + case (SBPSplit { Axis: int ic }, SBPBroadCast, SBPBroadCast): + ndsbp[i] = SBP.S(ic); + break; + case (SBPBroadCast, SBPBroadCast, SBPBroadCast): + ndsbp[i] = SBP.B; + break; + default: + return invalid; + } + } + + return new DistributedType(targetType, ndsbp, cond.Placement); + } + public Cost Visit(ICostEvaluateContext context, Where target) { - var cond = context.GetArgumentType(target, Where.Cond); - var x = context.GetArgumentType(target, Where.X); - var y = context.GetArgumentType(target, Where.Y); - var ret = context.GetReturnType(); + var cond = context.GetArgumentType(target, Where.Cond); + var x = context.GetArgumentType(target, Where.X); + var y = context.GetArgumentType(target, Where.Y); + var ret = context.GetReturnType(); return new() { [CostFactorNames.MemoryLoad] = CostUtility.GetMemoryAccess(cond, x, y), diff --git a/src/Nncase.Passes/Rules/Neutral/FoldPrePostReshapeSoftmax.cs b/src/Nncase.Passes/Rules/Neutral/FoldPrePostReshapeSoftmax.cs deleted file mode 100644 index 83edfe5e5e..0000000000 --- a/src/Nncase.Passes/Rules/Neutral/FoldPrePostReshapeSoftmax.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Canaan Inc. All rights reserved. -// Licensed under the Apache license. See LICENSE file in the project root for full license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Nncase.IR; -using Nncase.IR.NN; -using Nncase.PatternMatch; -using static Nncase.IR.F.NN; -using static Nncase.IR.F.Tensors; -using static Nncase.IR.TypePatternUtility; -using static Nncase.PatternMatch.F.Math; -using static Nncase.PatternMatch.F.NN; -using static Nncase.PatternMatch.F.Tensors; -using static Nncase.PatternMatch.Utility; - -namespace Nncase.Passes.Rules.Neutral; - -/// -/// Fold nop . -/// -[RuleGenerator] -public sealed partial class FoldPrePostReshapeSoftmax : IRewriteRule -{ - /// - public IPattern Pattern { get; } = IsReshape( - "reshape", - "reshapeCall", - _ => true, - IsSoftmax("softmax", IsReshape("rehsape2", "reshapeCall2", _ => true, IsWildcard("input"), IsTensorConst("shape2"))), - IsTensorConst("shape1")); - - private Expr? GetReplace(Expr input) - { - return Softmax(input, 3); - } -} diff --git a/tests/config.toml b/tests/config.toml index 56f1c0f1bb..41c7216f3d 100644 --- a/tests/config.toml +++ b/tests/config.toml @@ -131,3 +131,16 @@ threshold = 0.999 [target.k230.mode.ptq] enabled = true threshold = 0.96 + +[target.xpu] +eval = false +infer = true +similarity_name = 'cosine' + +[target.xpu.mode.noptq] +enabled = true +threshold = 0.999 + +[target.xpu.mode.ptq] +enabled = false +threshold = 0.9 \ No newline at end of file diff --git a/tests/test_runner.py b/tests/test_runner.py index 3aa5ce0777..58906311d2 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -277,7 +277,7 @@ def run(self, model_file: Union[List[str], str]): actual = self.run_evaluator(compiler, tmp_dir) else: actual = self.run_inference( - compiler, k_target, v_mode['enabled'], tmp_dir) + compiler, k_target, k_mode == "ptq" and v_mode['enabled'], tmp_dir) target_dir = os.path.join(self.case_dir, stage, k_target) os.makedirs(target_dir, exist_ok=True) mode_dir = os.path.join(target_dir, k_mode) diff --git a/toolchains/k800.linux.toolchain.cmake b/toolchains/k800.linux.toolchain.cmake new file mode 100644 index 0000000000..660754fc95 --- /dev/null +++ b/toolchains/k800.linux.toolchain.cmake @@ -0,0 +1,29 @@ +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR riscv64) + +if(DEFINED ENV{RISCV_ROOT_PATH}) + file(TO_CMAKE_PATH $ENV{RISCV_ROOT_PATH} RISCV_ROOT_PATH) +endif() + +if(NOT RISCV_ROOT_PATH) + message(FATAL_ERROR "RISCV_ROOT_PATH env must be defined") +endif() + +set(RISCV_ROOT_PATH ${RISCV_ROOT_PATH} CACHE STRING "root path to riscv toolchain") + +set(CMAKE_C_COMPILER "${RISCV_ROOT_PATH}/bin/riscv64-unknown-linux-gnu-gcc") +set(CMAKE_CXX_COMPILER "${RISCV_ROOT_PATH}/bin/riscv64-unknown-linux-gnu-g++") + +set(CMAKE_FIND_ROOT_PATH "${RISCV_ROOT_PATH}/riscv64-unknown-linux-gnu/") + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(ENABLE_VULKAN_RUNTIME OFF) +set(ENABLE_OPENMP OFF) +set(ENABLE_VULKAN OFF) +set(ENABLE_HALIDE OFF) +set(BUILD_PYTHON_BINDING OFF) + +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv64gv") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gv") From f7b4c7c18edd8cc5393150b2e3ece2dd45256a06 Mon Sep 17 00:00:00 2001 From: FusionBolt <59008347+FusionBolt@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:55:12 +0800 Subject: [PATCH 06/12] Refactor Quantize (#1144) * Fix memory leak: Avoid disposing python stream in finalizer thread * refactor * update * update * update * speedup but a little leak * update * fix * clear print * update * update * update * update * replace tensor with span * remove print * Apply code-format changes * update * Apply code-format changes * update --------- Co-authored-by: sunnycase Co-authored-by: FusionBolt --- .../Quantization/QuantUtility.cs | 231 +++++++----------- .../Quantization/Quantizer.cs | 44 ++-- 2 files changed, 111 insertions(+), 164 deletions(-) diff --git a/src/Nncase.Quantization/Quantization/QuantUtility.cs b/src/Nncase.Quantization/Quantization/QuantUtility.cs index e05e347492..3a5dcef39d 100644 --- a/src/Nncase.Quantization/Quantization/QuantUtility.cs +++ b/src/Nncase.Quantization/Quantization/QuantUtility.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Data; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; +using Nncase.Evaluator; using Nncase.IR; using OrtKISharp; using SMath = System.Math; @@ -38,14 +40,12 @@ public static Tensor SquantWeights(Tensor inputWeights, Tensor (long)x.FixedValue).ToArray(); OrtKISharp.Tensor x, delta, zeroPoint; if (inputWeightsShape.Length == 4) { var outChannel = inputWeightsShape[0]; - var inChannel = inputWeightsShape[1]; - var filterH = inputWeightsShape[2]; - var filterW = inputWeightsShape[3]; - x = OrtKISharp.Tensor.MakeTensor(inputWeights.PinBuffer(), OrtDataType.Float, new long[] { outChannel, inChannel, filterH, filterW }); + x = inputWeights.ToOrtTensor(); if (isByChannel) { @@ -53,7 +53,7 @@ public static Tensor SquantWeights(Tensor inputWeights, Tensor { var xMin = inputWeightsRanges[c, 0]; var xMax = inputWeightsRanges[c, 1]; @@ -64,10 +64,9 @@ public static Tensor SquantWeights(Tensor inputWeights, Tensor SquantWeights(Tensor inputWeights, Tensor { var xMin = inputWeightsRanges[c, 0]; var xMax = inputWeightsRanges[c, 1]; var deltaTmp = (xMax - xMin) / (qMax - qMin); var zeroPointTmp = System.Math.Round(((xMax * qMin) - (xMin * qMax)) / (xMax - xMin)); + for (int i = 0; i < eachChannelSize; i++) { deltaArr[(c * eachChannelSize) + i] = deltaTmp; zeroPointArr[(c * eachChannelSize) + i] = (float)zeroPointTmp; } - } + }); - delta = OrtKISharp.Tensor.MakeTensor(deltaArr, new long[] { outChannel, inChannel }); - zeroPoint = OrtKISharp.Tensor.MakeTensor(zeroPointArr, new long[] { outChannel, inChannel }); + delta = OrtKISharp.Tensor.MakeTensor(deltaArr, inWShape); + zeroPoint = OrtKISharp.Tensor.MakeTensor(zeroPointArr, inWShape); } else { @@ -111,151 +112,102 @@ public static Tensor SquantWeights(Tensor inputWeights, Tensor(qMin), OrtKISharp.Tensor.FromScalar(qMax)); var xDequant = (xQuant - zeroPoint) * delta; - - return Tensor.From(xDequant.ToArray(), inputWeights.Shape); + var res = Tensor.From(xDequant.ToArray(), inputWeights.Shape); + quantTensor.Dispose(); + xQuant.Dispose(); + xDequant.Dispose(); + return res; } - private static void RoundingForward(float roundingErrorSum, OrtKISharp.Tensor roundingNumber, OrtKISharp.Tensor roundingError, OrtKISharp.Tensor number, OrtKISharp.Tensor error, OrtKISharp.Tensor priority, OrtKISharp.Tensor order, OrtKISharp.Tensor priority1) + private static void RoundingForward(float roundingErrorSum, Span roundingNumberMem, Span roundingErrorMem, Span numberMem, Span errorMem, Span priorityMem, Span orderMem, Span priority1Mem) { - var roundingNumberMem = MemoryMarshal.Cast(roundingNumber.BytesBuffer); - var roundingErrorMem = MemoryMarshal.Cast(roundingError.BytesBuffer); - var priorityMem = MemoryMarshal.Cast(priority.BytesBuffer); - var priority1Mem = MemoryMarshal.Cast(priority1.BytesBuffer); int topK = (int)System.Math.Round(System.Math.Abs(roundingErrorSum)); bool overSquant = topK >= System.Math.Abs(roundingErrorSum); if (topK > 0) { - var starts = OrtKISharp.Tensor.MakeTensor(new long[] { 0 }, new long[] { 1 }); - var ends = OrtKISharp.Tensor.MakeTensor(new long[] { topK }, new long[] { 1 }); - var axes = OrtKISharp.Tensor.MakeTensor(new long[] { 0 }, new long[] { 1 }); - var steps = OrtKISharp.Tensor.MakeTensor(new long[] { 1 }, new long[] { 1 }); - - var orderTmp = OrtKI.Slice(order, starts, ends, axes, steps); - - var orderTmpArr = orderTmp.ToArray(); - var orderArr = order.ToArray(); - var errorArr = error.ToArray(); - var numberArr = number.ToArray(); - for (int i = 0; i < orderTmp.Length; i++) + var orderTmpArr = orderMem.Slice(0, topK); + + for (int i = 0; i < orderTmpArr.Length; i++) { - var index = orderTmpArr[i]; - roundingErrorMem[(int)index] = errorArr[index]; - roundingNumberMem[(int)index] = numberArr[index]; + var index = (int)orderTmpArr[i]; + roundingErrorMem[index] = errorMem[index]; + roundingNumberMem[index] = numberMem[index]; } if (overSquant) { - var index = orderArr[topK - 1]; + var index = (int)orderMem[topK - 1]; priority1Mem[index] = System.Math.Abs(roundingErrorMem[index]); } else { - var index = orderArr[topK]; + var index = (int)orderMem[topK]; priorityMem[index] = System.Math.Abs(roundingErrorMem[index]); } } } - private static void SQuantFunc(OrtKISharp.Tensor roundingErrorSum, OrtKISharp.Tensor roundingNumber, OrtKISharp.Tensor roundingError, OrtKISharp.Tensor upNumber, OrtKISharp.Tensor upError, OrtKISharp.Tensor upPriority, OrtKISharp.Tensor upOrder, OrtKISharp.Tensor downNumber, OrtKISharp.Tensor downError, OrtKISharp.Tensor downPriority, OrtKISharp.Tensor downOrder) + private static void SQuantFunc(OrtKISharp.Tensor roundingErrorSum, OrtKISharp.Tensor roundingNumber, OrtKISharp.Tensor roundingError, OrtKISharp.Tensor upNumber, OrtKISharp.Tensor upError, OrtKISharp.Tensor upPriority, OrtKISharp.Tensor upOrder, OrtKISharp.Tensor downNumber, OrtKISharp.Tensor downError, OrtKISharp.Tensor downPriority, OrtKISharp.Tensor downOrder, bool getNumberOnly) { - var roundingNumberShape = roundingNumber.Shape; - var batches = roundingNumberShape[0]; - var inputChannel = roundingNumberShape[1]; - long totalSize = 1; - for (int i = 0; i < roundingNumberShape.Length; i++) + var roundingNumberShape = roundingNumber.Shape.Select(x => (int)x).ToArray(); + if (roundingNumberShape.Length != 3) { - totalSize *= roundingNumberShape[i]; + throw new InvalidOperationException("Error"); } - var oneBatchSize = totalSize / batches; - var oneInputChannelSize = oneBatchSize / inputChannel; - - var roundingNumberMem = MemoryMarshal.Cast(roundingNumber.BytesBuffer); - var roundingErrorMem = MemoryMarshal.Cast(roundingError.BytesBuffer); - var upPriorityMem = MemoryMarshal.Cast(upPriority.BytesBuffer); - var downPriorityMem = MemoryMarshal.Cast(downPriority.BytesBuffer); - + var batches = roundingNumberShape[0]; + var inputChannel = roundingNumberShape[1]; + var sizePreChannel = roundingNumberShape[2]; var roundingErrorSumArr = roundingErrorSum.ToArray(); - - for (var n = 0; n < batches; n++) + var loopSize = (long)batches * inputChannel; + Parallel.For(0, loopSize, currentIndex => { - for (var c = 0; c < inputChannel; c++) + var n = currentIndex / inputChannel; + var c = currentIndex % inputChannel; + using var starts = OrtKISharp.Tensor.MakeTensor(new long[] { n, c }, new long[] { 2 }); + using var ends = OrtKISharp.Tensor.MakeTensor(new long[] { n + 1, c + 1 }, new long[] { 2 }); + using var axes = OrtKISharp.Tensor.MakeTensor(new long[] { 0, 1 }, new long[] { 2 }); + using var steps = OrtKISharp.Tensor.MakeTensor(new long[] { 1, 1 }, new long[] { 2 }); + + Span Sl(OrtKISharp.Tensor tensor) { - var starts = OrtKISharp.Tensor.MakeTensor(new long[] { n, c }, new long[] { 2 }); - var ends = OrtKISharp.Tensor.MakeTensor(new long[] { n + 1, c + 1 }, new long[] { 2 }); - var axes = OrtKISharp.Tensor.MakeTensor(new long[] { 0, 1 }, new long[] { 2 }); - var steps = OrtKISharp.Tensor.MakeTensor(new long[] { 1, 1 }, new long[] { 2 }); - var roundingNumberTmp = OrtKI.Squeeze(OrtKI.Slice(roundingNumber, starts, ends, axes, steps), axes); - var roundingErrorTmp = OrtKI.Squeeze(OrtKI.Slice(roundingError, starts, ends, axes, steps), axes); - var upNumberSlice = OrtKI.Squeeze(OrtKI.Slice(upNumber, starts, ends, axes, steps), axes); - var upErrorSlice = OrtKI.Squeeze(OrtKI.Slice(upError, starts, ends, axes, steps), axes); - var upOrderSlice = OrtKI.Squeeze(OrtKI.Slice(upOrder, starts, ends, axes, steps), axes); - var downNumberSlice = OrtKI.Squeeze(OrtKI.Slice(downNumber, starts, ends, axes, steps), axes); - var downErrorSlice = OrtKI.Squeeze(OrtKI.Slice(downError, starts, ends, axes, steps), axes); - var downOrderSlice = OrtKI.Squeeze(OrtKI.Slice(downOrder, starts, ends, axes, steps), axes); - - if (roundingErrorSumArr[(n * inputChannel) + c] < 0) - { - var priorityTmp = OrtKI.Squeeze(OrtKI.Slice(upPriority, starts, ends, axes, steps), axes); - var priority1Tmp = OrtKI.Squeeze(OrtKI.Slice(downPriority, starts, ends, axes, steps), axes); - RoundingForward(roundingErrorSumArr[(n * inputChannel) + c], roundingNumberTmp, roundingErrorTmp, upNumberSlice, upErrorSlice, priorityTmp, upOrderSlice, priority1Tmp); - - var roundingNumberTmpArr = roundingNumberTmp.ToArray(); - var roundingErrorTmpArr = roundingErrorTmp.ToArray(); - var priorityTmpArr = priorityTmp.ToArray(); - var priority1TmpArr = priority1Tmp.ToArray(); - for (int i = 0; i < roundingNumberTmp.Length; i++) - { - roundingNumberMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = roundingNumberTmpArr[i]; - } - - for (int i = 0; i < roundingErrorTmp.Length; i++) - { - roundingErrorMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = roundingErrorTmpArr[i]; - } - - for (int i = 0; i < priorityTmp.Length; i++) - { - upPriorityMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = priorityTmpArr[i]; - } + var span = MemoryMarshal.Cast(tensor.BytesBuffer); + var begin = currentIndex * sizePreChannel; + return span.Slice((int)begin, sizePreChannel); + } - for (int i = 0; i < priority1Tmp.Length; i++) - { - downPriorityMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = priority1TmpArr[i]; - } - } - else - { - var priorityTmp = OrtKI.Squeeze(OrtKI.Slice(downPriority, starts, ends, axes, steps), axes); - var priority1Tmp = OrtKI.Squeeze(OrtKI.Slice(upPriority, starts, ends, axes, steps), axes); - RoundingForward(roundingErrorSumArr[(n * inputChannel) + c], roundingNumberTmp, roundingErrorTmp, downNumberSlice, downErrorSlice, priorityTmp, downOrderSlice, priority1Tmp); - - var roundingNumberTmpArr = roundingNumberTmp.ToArray(); - var roundingErrorTmpArr = roundingErrorTmp.ToArray(); - var priorityTmpArr = priorityTmp.ToArray(); - var priority1TmpArr = priority1Tmp.ToArray(); - for (int i = 0; i < roundingNumberTmp.Length; i++) - { - roundingNumberMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = roundingNumberTmpArr[i]; - } + Span SlInt(OrtKISharp.Tensor tensor) + { + var span = MemoryMarshal.Cast(tensor.BytesBuffer); + var begin = currentIndex * sizePreChannel; + return span.Slice((int)begin, sizePreChannel); + } - for (int i = 0; i < roundingErrorTmp.Length; i++) - { - roundingErrorMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = roundingErrorTmpArr[i]; - } + var roundingNumberTmp = Sl(roundingNumber); + var roundingErrorTmp = Sl(roundingError); - for (int i = 0; i < priorityTmp.Length; i++) - { - downPriorityMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = priorityTmpArr[i]; - } + var upNumberSlice = Sl(upNumber); + var upErrorSlice = Sl(upError); + var upOrderSlice = SlInt(upOrder); + var downNumberSlice = Sl(downNumber); + var downErrorSlice = Sl(downError); + var downOrderSlice = SlInt(downOrder); - for (int i = 0; i < priority1Tmp.Length; i++) - { - upPriorityMem[(n * (int)oneBatchSize) + (c * (int)oneInputChannelSize) + i] = priority1TmpArr[i]; - } - } + Span priorityTmp; + Span priority1Tmp; + if (roundingErrorSumArr[currentIndex] < 0) + { + priorityTmp = Sl(upPriority); + priority1Tmp = Sl(downPriority); + RoundingForward(roundingErrorSumArr[currentIndex], roundingNumberTmp, roundingErrorTmp, upNumberSlice, upErrorSlice, priorityTmp, upOrderSlice, priority1Tmp); } - } + else + { + priorityTmp = Sl(downPriority); + priority1Tmp = Sl(upPriority); + RoundingForward(roundingErrorSumArr[currentIndex], roundingNumberTmp, roundingErrorTmp, downNumberSlice, downErrorSlice, priorityTmp, downOrderSlice, priority1Tmp); + } + }); } private static OrtKISharp.Tensor AdaptiveRound(OrtKISharp.Tensor x, float tMin, float tMax) @@ -295,11 +247,13 @@ private static OrtKISharp.Tensor AdaptiveRound(OrtKISharp.Tensor x, float tMin, if (squantK) { var roundingErrorSum = OrtKI.ReduceSum(OrtKI.Reshape(roundingError, converShape, 0), new long[] { -1 }, 0, 0); - var upPriorityK = OrtKI.Reshape(upPriority, converShape, 0).Shape[OrtKI.Reshape(upPriority, converShape, 0).Shape.Length - 1]; - var sortRet = OrtKI.TopK(OrtKI.Reshape(upPriority, converShape, 0), OrtKISharp.Tensor.MakeTensor(new long[] { upPriorityK }, new long[] { 1 }), -1, 1, 1); + var reshapeUpPriority = OrtKI.Reshape(upPriority, converShape, 0); + var upPriorityK = reshapeUpPriority.Shape[^1]; + var sortRet = OrtKI.TopK(reshapeUpPriority, OrtKISharp.Tensor.MakeTensor(new long[] { upPriorityK }, new long[] { 1 }), -1, 1, 1); var upOrder = sortRet[1]; - var downPriorityK = OrtKI.Reshape(downPriority, converShape, 0).Shape[OrtKI.Reshape(downPriority, converShape, 0).Shape.Length - 1]; - sortRet = OrtKI.TopK(OrtKI.Reshape(downPriority, converShape, 0), OrtKISharp.Tensor.MakeTensor(new long[] { downPriorityK }, new long[] { 1 }), -1, 1, 1); + var reshapeDownPriority = OrtKI.Reshape(downPriority, converShape, 0); + var downPriorityK = reshapeDownPriority.Shape[^1]; + sortRet = OrtKI.TopK(reshapeDownPriority, OrtKISharp.Tensor.MakeTensor(new long[] { downPriorityK }, new long[] { 1 }), -1, 1, 1); var downOrder = sortRet[1]; upPriority *= 0.0f; downPriority *= 0.0f; @@ -312,7 +266,7 @@ private static OrtKISharp.Tensor AdaptiveRound(OrtKISharp.Tensor x, float tMin, downNumber = OrtKI.Reshape(downNumber, converShape, 0); downError = OrtKI.Reshape(downError, converShape, 0); downPriority = OrtKI.Reshape(downPriority, converShape, 0); - SQuantFunc(roundingErrorSum, roundingNumber, roundingError, upNumber, upError, upPriority, upOrder, downNumber, downError, downPriority, downOrder); + SQuantFunc(roundingErrorSum, roundingNumber, roundingError, upNumber, upError, upPriority, upOrder, downNumber, downError, downPriority, downOrder, false); roundingNumber = OrtKI.Reshape(roundingNumber, x.Shape, 0); roundingError = OrtKI.Reshape(roundingError, x.Shape, 0); upPriority = OrtKI.Reshape(upPriority, x.Shape, 0); @@ -323,11 +277,13 @@ private static OrtKISharp.Tensor AdaptiveRound(OrtKISharp.Tensor x, float tMin, { converShape = new long[] { 1, x.Shape[0], -1 }; var roundingErrorSum = OrtKI.ReduceSum(OrtKI.Reshape(roundingError, converShape, 0), new long[] { -1 }, 0, 0); - var upPriorityK = OrtKI.Reshape(upPriority, converShape, 0).Shape[OrtKI.Reshape(upPriority, converShape, 0).Shape.Length - 1]; - var sortRet = OrtKI.TopK(OrtKI.Reshape(upPriority, converShape, 0), OrtKISharp.Tensor.MakeTensor(new long[] { upPriorityK }, new long[] { 1 }), -1, 1, 1); + var reshapePriority = OrtKI.Reshape(upPriority, converShape, 0); + var upPriorityK = reshapePriority.Shape[^1]; + var sortRet = OrtKI.TopK(reshapePriority, OrtKISharp.Tensor.MakeTensor(new long[] { upPriorityK }, new long[] { 1 }), -1, 1, 1); var upOrder = sortRet[1]; - var downPriorityK = OrtKI.Reshape(downPriority, converShape, 0).Shape[OrtKI.Reshape(downPriority, converShape, 0).Shape.Length - 1]; - sortRet = OrtKI.TopK(OrtKI.Reshape(downPriority, converShape, 0), OrtKISharp.Tensor.MakeTensor(new long[] { downPriorityK }, new long[] { 1 }), -1, 1, 1); + var reshapeDownPriority = OrtKI.Reshape(downPriority, converShape, 0); + var downPriorityK = reshapeDownPriority.Shape[^1]; + sortRet = OrtKI.TopK(reshapeDownPriority, OrtKISharp.Tensor.MakeTensor(new long[] { downPriorityK }, new long[] { 1 }), -1, 1, 1); var downOrder = sortRet[1]; roundingNumber = OrtKI.Reshape(roundingNumber, converShape, 0); @@ -338,13 +294,10 @@ private static OrtKISharp.Tensor AdaptiveRound(OrtKISharp.Tensor x, float tMin, downNumber = OrtKI.Reshape(downNumber, converShape, 0); downError = OrtKI.Reshape(downError, converShape, 0); downPriority = OrtKI.Reshape(downPriority, converShape, 0); - SQuantFunc(roundingErrorSum, roundingNumber, roundingError, upNumber, upError, upPriority, upOrder, downNumber, downError, downPriority, downOrder); + SQuantFunc(roundingErrorSum, roundingNumber, roundingError, upNumber, upError, upPriority, upOrder, downNumber, downError, downPriority, downOrder, true); } roundingNumber = OrtKI.Reshape(roundingNumber, x.Shape, 0); - _ = OrtKI.Reshape(roundingError, x.Shape, 0); - _ = OrtKI.Reshape(upPriority, x.Shape, 0); - _ = OrtKI.Reshape(downPriority, x.Shape, 0); return roundingNumber!; } diff --git a/src/Nncase.Quantization/Quantization/Quantizer.cs b/src/Nncase.Quantization/Quantization/Quantizer.cs index c9b5fa9daa..257ee001eb 100644 --- a/src/Nncase.Quantization/Quantization/Quantizer.cs +++ b/src/Nncase.Quantization/Quantization/Quantizer.cs @@ -325,25 +325,21 @@ public async Task RunAsync(RunPassContext options) _graph.Rebuild(); } - private async Task RunPassAsync(ICalibrationDatasetProvider calibrationDataset, Action, IReadOnlyDictionary> func) + private async Task RunForHistogramsAsync(ICalibrationDatasetProvider calibrationDataset, Action> func) { await foreach (var sample in calibrationDataset.Samples) { - IReadOnlyDictionary values, childrenValues; - using (var dumpScope = new DumpScope("ep1")) - { - var evaluator = new CalibrationEvaluator(sample, _rangeOfs); - values = evaluator.Evaluate(); - } - + IReadOnlyDictionary childrenValues; using (var dumpScope2 = new DumpScope("ep2")) { var childrenEvaluator = new CalibrationEvaluator(sample, _childrenOfRangeOfs); - childrenValues = childrenEvaluator.Evaluate(); + var tmpChildrenValues = childrenEvaluator.Evaluate().ToList(); + childrenValues = tmpChildrenValues.Zip(_rangeOfs).ToDictionary(pair => pair.Second, pair => pair.First.Value); } // values are children op range values(only two scalars for each value: Min and Max), childrenValues are children op tensor values. - func(values, childrenValues); + func(childrenValues); + GC.Collect(); } } @@ -355,6 +351,7 @@ private async Task RunPassAsync(ICalibrationDatasetProvider calibrationDataset, var evaluator = new CalibrationEvaluator(sample, _rangeOfs); var values = evaluator.Evaluate(); func(values); + GC.Collect(); } } @@ -511,26 +508,23 @@ private void AssignDataTypeFromConfig(QuantScheme quantScheme) private async Task>> GetHistogramsAsync(ICalibrationDatasetProvider calibrationDataset, IDictionary> ranges, int srcBinSize, int dstBinSize) { var histograms = new Dictionary>(ReferenceEqualityComparer.Instance); - await RunPassAsync(calibrationDataset, (values, childrenValues) => + foreach (var (key, value) in ranges) { - var valuesList = values.ToList(); - var childrenValuesList = childrenValues.ToList(); - for (int i = 0; i < valuesList.Count; i++) + var initSrcBin = new List(new float[srcBinSize]); + histograms[key] = new QuantizeHistogram(initSrcBin, initSrcBin); + } + + await RunForHistogramsAsync(calibrationDataset, childrenValues => + { + foreach (var (key, value) in childrenValues) { - var r = ranges[valuesList[i].Key].Max - ranges[valuesList[i].Key].Min; + var r = ranges[key].Max - ranges[key].Min; var srcBinInterval = r / srcBinSize; - if (!histograms.TryGetValue(valuesList[i].Key, out var histogram)) - { - var initSrcBin = new List(new float[srcBinSize]); - var initDstBin = new List(new float[dstBinSize]); - histogram = new QuantizeHistogram(initSrcBin, initDstBin); - histograms.Add(valuesList[i].Key, histogram); - } - var childrenTensor = childrenValuesList[i].Value.Cast(); + var childrenTensor = value.Cast(); var childrenBuffer = childrenTensor.Buffer.Span; - var valueRange = ranges[valuesList[i].Key]; - + var valueRange = ranges[key]; + var histogram = histograms[key]; foreach (var buf in childrenBuffer) { var r_index = (buf - valueRange.Min) / srcBinInterval; From d355a380f7715cb6898c18c268bd7e1a770c3b10 Mon Sep 17 00:00:00 2001 From: FusionBolt <59008347+FusionBolt@users.noreply.github.com> Date: Fri, 22 Dec 2023 13:56:41 +0800 Subject: [PATCH 07/12] Fix Reshape (#1147) --- src/Nncase.Evaluator/Tensors/Reshape.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Nncase.Evaluator/Tensors/Reshape.cs b/src/Nncase.Evaluator/Tensors/Reshape.cs index 7488739f1e..4d0b9245f6 100644 --- a/src/Nncase.Evaluator/Tensors/Reshape.cs +++ b/src/Nncase.Evaluator/Tensors/Reshape.cs @@ -40,7 +40,8 @@ public IRType Visit(ITypeInferenceContext context, Reshape target) TensorType tensorType => Visit(context, target, tensorType), DistributedType distributedType => Visit(context, target, distributedType), AnyType => AnyType.Default, - _ => throw new NotImplementedException(), + InvalidType => input, + _ => new InvalidType($"Not Support Input Type {input.GetType().Name}"), }; } From c232f96948eb82bf2e4f5e132ad1daed2e800b75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=83=91=E5=90=AF=E8=88=AA?= <597323109@qq.com> Date: Fri, 22 Dec 2023 17:06:21 +0800 Subject: [PATCH 08/12] GNNE-1980 Xpu Sram Codegen (#1148) * fix * add TryGetNonUniformDividedSlice * add floordiv and ceildiv * fix bug * refactor unfold block * add method * fix nest builder * TensorConst support DistributedType * support distrubute type * fix typeinfer * Apply code-format changes * fix build --------- Co-authored-by: huochenghai Co-authored-by: zhen8838 --- src/Nncase.Core/CompilerServices.cs | 26 +++++ src/Nncase.Core/Enum/BinaryOp.cs | 10 ++ src/Nncase.Core/IR/Buffers/Allocate.cs | 17 ++- src/Nncase.Core/IR/Buffers/Functional.cs | 2 + src/Nncase.Core/IR/TensorConst.cs | 52 ++++++--- .../Passes/Mutators/UnFoldBlock.cs | 31 +++++ src/Nncase.Core/PatternMatch/ConstPattern.cs | 8 +- .../TIR/Builders/NestBodyExprBuilder.cs | 2 +- src/Nncase.Core/TIR/Script.cs | 61 +++++++++- src/Nncase.Core/TensorUtilities.cs | 2 +- .../Utilities/DistributedUtility.cs | 106 ++++++++++++------ .../Diagnostics/ILPrintVisitor.cs | 2 + src/Nncase.Evaluator/Buffers/Allocate.cs | 2 +- src/Nncase.Evaluator/Buffers/DDrOf.cs | 9 +- src/Nncase.Evaluator/Math/Binary.cs | 10 ++ src/Nncase.Evaluator/Math/MatMul.cs | 4 +- src/Nncase.Importer/Onnx/QLinearConv.cs | 10 +- src/Nncase.Importer/Onnx/QLinearMatmul.cs | 2 +- src/Nncase.Importer/Onnx/Quantize.cs | 2 +- src/Nncase.Passes/DDrBufferSchdeulePass.cs | 4 +- src/Nncase.Passes/ModulePass.cs | 4 +- src/Nncase.Tests/Core/IR/UnitTestConst.cs | 30 ++--- .../Core/IR/UnitTestTensorConst.cs | 28 ++--- src/Nncase.Tests/Core/UnitTestTIR.cs | 2 +- 24 files changed, 321 insertions(+), 105 deletions(-) diff --git a/src/Nncase.Core/CompilerServices.cs b/src/Nncase.Core/CompilerServices.cs index 9c666bcd21..2f731a7fdf 100644 --- a/src/Nncase.Core/CompilerServices.cs +++ b/src/Nncase.Core/CompilerServices.cs @@ -208,6 +208,15 @@ public interface ICompilerServicesProvider /// Options. /// Rewrited expression. Expr ERewrite(Expr expr, IEnumerable rules, RunPassContext options); + + /// + /// Using EGraph rewrite expression. + /// + /// Expression. + /// Rewrite rules. + /// Options. + /// Rewrited expression. + IEGraph ERewrite(IEGraph expr, IEnumerable rules, RunPassContext options); } internal interface ICompilerServicesProviderInternal @@ -409,6 +418,18 @@ public static Expr ERewrite(Expr expr, IEnumerable rules, RunPassC return Provider.ERewrite(expr, rules, options); } + /// + /// Using EGraph rewrite expression. + /// + /// Expression. + /// Rewrite rules. + /// Options. + /// Rewrited expression. + public static IEGraph ERewrite(IEGraph graph, IEnumerable rules, RunPassContext options) + { + return Provider.ERewrite(graph, rules, options); + } + /// /// Match enodes as root. /// @@ -677,4 +698,9 @@ public Expr ERewrite(Expr expr, IEnumerable rules, RunPassContext { return _eGraphrewriteProvider.ERewrite(expr, rules, options); } + + public IEGraph ERewrite(IEGraph graph, IEnumerable rules, RunPassContext options) + { + return _eGraphrewriteProvider.ERewrite(graph, rules, options); + } } diff --git a/src/Nncase.Core/Enum/BinaryOp.cs b/src/Nncase.Core/Enum/BinaryOp.cs index 45afba68cc..fa4092fa48 100644 --- a/src/Nncase.Core/Enum/BinaryOp.cs +++ b/src/Nncase.Core/Enum/BinaryOp.cs @@ -93,4 +93,14 @@ public enum BinaryOp : byte /// Right Shift. /// RightShift, + + /// + /// Floor Div. + /// + FloorDiv, + + /// + /// Ceil Div. + /// + CeilDiv, } diff --git a/src/Nncase.Core/IR/Buffers/Allocate.cs b/src/Nncase.Core/IR/Buffers/Allocate.cs index 14b44010ec..ff7bdd13c0 100644 --- a/src/Nncase.Core/IR/Buffers/Allocate.cs +++ b/src/Nncase.Core/IR/Buffers/Allocate.cs @@ -13,5 +13,20 @@ namespace Nncase.IR.Buffers; /// public sealed partial class Allocate : Op { - public TensorType ElemType { get; } + /// + /// Get the input parameter. + /// + public static readonly ParameterInfo Size = new(typeof(Allocate), 0, "size", TypePatternUtility.IsIntegralScalar()); + + /// + /// Gets the alloacted buffer type. + /// + public DataType ElemType { get; } + + public TIR.MemoryLocation Location { get; } + + /// + public override bool CanFoldConstCall => false; + + public override string DisplayProperty() => $"{ElemType}, {Location}"; } diff --git a/src/Nncase.Core/IR/Buffers/Functional.cs b/src/Nncase.Core/IR/Buffers/Functional.cs index a2e3507a5f..463c4f1e2c 100644 --- a/src/Nncase.Core/IR/Buffers/Functional.cs +++ b/src/Nncase.Core/IR/Buffers/Functional.cs @@ -42,4 +42,6 @@ public static Call BaseMentOf(Expr input) => /// create the uninitialized buffer. /// public static Call Uninitialized(DataType dataType, TIR.MemoryLocation memoryLocation, Expr shape) => new Call(new Uninitialized(dataType, memoryLocation), shape); + + public static Call Allocate(Expr size, DataType dataType, TIR.MemoryLocation location) => new Call(new Allocate(dataType, location), size); } diff --git a/src/Nncase.Core/IR/TensorConst.cs b/src/Nncase.Core/IR/TensorConst.cs index 9e651978ed..07dccfbcc3 100644 --- a/src/Nncase.Core/IR/TensorConst.cs +++ b/src/Nncase.Core/IR/TensorConst.cs @@ -20,12 +20,18 @@ public TensorConst(Tensor tensor) Value = tensor; } + public TensorConst(Tensor tensor, IRArray ndsbp, Placement placement) + : base(new DistributedType(new TensorType(tensor.ElementType, tensor.Shape), ndsbp, placement)) + { + Value = tensor; + } + public Tensor Value { get; } /// /// Gets value type. /// - public new TensorType ValueType => (TensorType)base.ValueType; + public new IRType ValueType => base.ValueType; /// /// Create TensorConstant from a . @@ -122,25 +128,43 @@ public TensorConst(Tensor tensor) public static bool operator !=(TensorConst? left, TensorConst? right) => !(left == right); /// - public override string ToString() => ValueType switch + public override string ToString() { - var x when x.IsScalar => - x.DType switch - { - var dtype when DataTypes.IsIntegral(dtype) => Value.ToScalar().ToString(), - var dtype when DataTypes.IsFloat(dtype) => Value.ToScalar().ToString(), - var dtype when DataTypes.IsPointer(dtype) => Value.ToScalar().ToString(), - var dtype when dtype == DataTypes.Boolean => Value.ToScalar().ToString(), - _ => $"{x.DType.GetDisplayName()} {x.Shape}", - }, - _ => $"{ValueType.DType.GetDisplayName()} {ValueType.Shape}", - }; + var type = ValueType switch + { + DistributedType dt => dt.TensorType, + TensorType tt => tt, + _ => throw new NotSupportedException("Not supported const type: " + ValueType), + }; + + return type switch + { + var x when x.IsScalar => + x.DType switch + { + var dtype when DataTypes.IsIntegral(dtype) => Value.ToScalar().ToString(), + var dtype when DataTypes.IsFloat(dtype) => Value.ToScalar().ToString(), + var dtype when DataTypes.IsPointer(dtype) => Value.ToScalar().ToString(), + var dtype when dtype == DataTypes.Boolean => Value.ToScalar().ToString(), + _ => $"{x.DType.GetDisplayName()} {x.Shape}", + }, + _ => $"{type.DType.GetDisplayName()} {type.Shape}", + }; + } /// public override TExprResult Accept(ExprFunctor functor, TContext context) => functor.VisitTensorConst(this, context); - public TensorConst With(Tensor? value = null) => new TensorConst(value ?? Value); + public TensorConst With(Tensor? value = null) + { + if (value is null && ValueType is DistributedType dt) + { + return new TensorConst(Value, dt.NdSBP, dt.Placement); + } + + return new TensorConst(value ?? Value); + } /// public override bool Equals(object? obj) => Equals(obj as TensorConst); diff --git a/src/Nncase.Core/Passes/Mutators/UnFoldBlock.cs b/src/Nncase.Core/Passes/Mutators/UnFoldBlock.cs index 7559954a49..1a64b55917 100644 --- a/src/Nncase.Core/Passes/Mutators/UnFoldBlock.cs +++ b/src/Nncase.Core/Passes/Mutators/UnFoldBlock.cs @@ -23,6 +23,37 @@ protected override Expr RewriteLeafBlock(Block expr) { if (predicate) { + if (expr.AllocBuffers.Length > 0) + { + var lets = expr.AllocBuffers.ToArray().Select(b => (T.Let(out var v, b.MemSpan.Start, b.Name + "_ptr"), v)).ToArray(); + for (int i = 0; i < lets.Length - 1; i++) + { + lets[i].Item1.Body(lets[i + 1].Item1); + } + + var map = new Dictionary(ReferenceEqualityComparer.Instance); + for (int i = 0; i < expr.AllocBuffers.Length; i++) + { + map.Add(expr.AllocBuffers[i].MemSpan.Start, lets[i].v); + } + + var mutator = new Substitutor(e => + { + if (map.TryGetValue(e, out var r)) + { + return r; + } + + return null; + }); + + var initBody = mutator.Visit(expr.InitBody, Unit.Default); + var body = mutator.Visit(expr.Body, Unit.Default); + + lets[^1].Item1.Body(initBody, body); + return lets[0].Item1.Build(); + } + return T.Sequential(expr.InitBody, expr.Body); } else diff --git a/src/Nncase.Core/PatternMatch/ConstPattern.cs b/src/Nncase.Core/PatternMatch/ConstPattern.cs index cb9a52aebe..bca5ec63a1 100644 --- a/src/Nncase.Core/PatternMatch/ConstPattern.cs +++ b/src/Nncase.Core/PatternMatch/ConstPattern.cs @@ -66,9 +66,9 @@ public static partial class Utility public static TensorConstPattern IsConst(string? name, Func cond) => new( x => { - if (DataTypes.IsFloat(x.ValueType.DType)) + if (DataTypes.IsFloat(x.CheckedDataType)) { - if (x.ValueType.IsScalar) + if (x.CheckedShape.IsScalar) { return cond(x.Value.ToScalar()); } @@ -93,9 +93,9 @@ public static partial class Utility public static TensorConstPattern IsConst(string? name, Func cond) => new( x => { - if (DataTypes.IsIntegral(x.ValueType.DType)) + if (DataTypes.IsIntegral(x.CheckedDataType)) { - if (x.ValueType.IsScalar) + if (x.CheckedShape.IsScalar) { return cond(x.Value.ToScalar()); } diff --git a/src/Nncase.Core/TIR/Builders/NestBodyExprBuilder.cs b/src/Nncase.Core/TIR/Builders/NestBodyExprBuilder.cs index 60d79a6b89..2add795b71 100644 --- a/src/Nncase.Core/TIR/Builders/NestBodyExprBuilder.cs +++ b/src/Nncase.Core/TIR/Builders/NestBodyExprBuilder.cs @@ -51,7 +51,7 @@ public T Build() public ISequentialBuilder InsertBody(int index, params object[] exprOrBuilders) { - _subBuilders[_subBuilders.Length - 1].InsertBody(index, exprOrBuilders); + _subBuilders[index < 0 ? _subBuilders.Length + index : index].Body(exprOrBuilders); return this; } } diff --git a/src/Nncase.Core/TIR/Script.cs b/src/Nncase.Core/TIR/Script.cs index 28740e43ab..ffda2527df 100644 --- a/src/Nncase.Core/TIR/Script.cs +++ b/src/Nncase.Core/TIR/Script.cs @@ -134,8 +134,16 @@ public static ISequentialBuilder Grid(out Var[] loopVars, LoopMode loopMode { string[] names = { "i", "j", "k", "l" }; var newLoopVars = loopVars = new Var[ranges.Length]; - return new NestBodyExprBuilder(ranges.Select((rg, i) => - T.ForLoop(out newLoopVars[i], rg, loopMode, names[i % 4] + (i / 4 == 0 ? string.Empty : (i / 4).ToString())).Body()).ToArray()); + var newLoops = ranges.Select((rg, i) => T.ForLoop(out newLoopVars[i], rg, loopMode, names[i % 4] + (i / 4 == 0 ? string.Empty : (i / 4).ToString())).Body()).ToArray(); + return new NestBodyExprBuilder(newLoops); + } + + public static ISequentialBuilder Grid(out Var[] loopVars, out ISequentialBuilder[] loops, LoopMode loopMode, params TIR.Range[] ranges) + { + string[] names = { "i", "j", "k", "l" }; + var newLoopVars = loopVars = new Var[ranges.Length]; + var newLoops = loops = ranges.Select((rg, i) => T.ForLoop(out newLoopVars[i], rg, loopMode, names[i % 4] + (i / 4 == 0 ? string.Empty : (i / 4).ToString())).Body()).ToArray(); + return new NestBodyExprBuilder(loops); } /// @@ -223,6 +231,49 @@ public static Buffer CreateBuffer(TensorType tensorType, MemoryLocation location return buffer; } + /// + /// create the buffer by expressions. + /// + public static Buffer CreateBuffer(DataType dataType, Expr[] dimensions, MemoryLocation location, out Buffer buffer, [CallerArgumentExpression("buffer")] string name = "") + { + if (name.StartsWith("var ")) + { + name = name[4..]; + } + + var strides = TensorUtilities.GetStrides(dimensions); + var size = TensorUtilities.GetProduct(dimensions.ToArray()) * dataType.SizeInBytes; + var memspan = new MemSpan(size, location); + buffer = new Buffer(name, dataType, memspan, dimensions, strides); + return buffer; + } + + public static Buffer CreateBuffer(DataType dataType, Expr[] dimensions, Expr[] strides, MemSpan memSpan, out Buffer buffer, [CallerArgumentExpression("buffer")] string name = "") + { + if (name.StartsWith("var ")) + { + name = name[4..]; + } + + buffer = new Buffer(name, dataType, memSpan, dimensions, strides); + return buffer; + } + + public static Buffer AttachBuffer(Expr start, TensorType tensorType, MemoryLocation location, out Buffer buffer, [CallerArgumentExpression("buffer")] string name = "") + { + if (name.StartsWith("var ")) + { + name = name[4..]; + } + + var dimensions = tensorType.Shape.ToValueArray(); + var strides = TensorUtilities.GetStrides(dimensions); + var size = (int)TensorUtilities.GetProduct(dimensions.ToArray()) * tensorType.DType.SizeInBytes; + var memspan = new MemSpan(start, size, location); + buffer = new Buffer(name, tensorType.DType, memspan, dimensions.Select(i => (Expr)i).ToArray(), strides.Select(i => (Expr)i).ToArray()); + return buffer; + } + /// /// create buffer by const. /// @@ -233,11 +284,11 @@ public static Buffer AttachBuffer(TensorConst @const, out Buffer buffer, [Caller name = name[4..]; } - var dimensions = @const.ValueType.Shape.ToValueArray(); + var dimensions = @const.CheckedShape.ToValueArray(); var strides = TensorUtilities.GetStrides(dimensions); - var size = (int)TensorUtilities.GetProduct(dimensions.ToArray()) * @const.ValueType.DType.SizeInBytes; + var size = (int)TensorUtilities.GetProduct(dimensions.ToArray()) * @const.CheckedDataType.SizeInBytes; var memspan = new MemSpan(IR.F.Buffer.DDrOf(@const), size, MemoryLocation.Rdata); - buffer = new Buffer(name, @const.ValueType.DType, memspan, dimensions.Select(i => (Expr)i).ToArray(), strides.Select(i => (Expr)i).ToArray()); + buffer = new Buffer(name, @const.CheckedDataType, memspan, dimensions.Select(i => (Expr)i).ToArray(), strides.Select(i => (Expr)i).ToArray()); return buffer; } diff --git a/src/Nncase.Core/TensorUtilities.cs b/src/Nncase.Core/TensorUtilities.cs index 79f658aefa..146e5c6cfa 100644 --- a/src/Nncase.Core/TensorUtilities.cs +++ b/src/Nncase.Core/TensorUtilities.cs @@ -69,7 +69,7 @@ public static Expr GetProduct(ReadOnlySpan dimensions, int startIndex = 0) for (int i = startIndex; i < dimensions.Length; i++) { var dimension = dimensions[i]; - product *= IR.F.Math.Require(dimension >= 0, dimension, "Dimension is out of range."); + product *= dimension; } return product; diff --git a/src/Nncase.Core/Utilities/DistributedUtility.cs b/src/Nncase.Core/Utilities/DistributedUtility.cs index 2061a40958..eb4a84be0d 100644 --- a/src/Nncase.Core/Utilities/DistributedUtility.cs +++ b/src/Nncase.Core/Utilities/DistributedUtility.cs @@ -2,6 +2,7 @@ // Licensed under the Apache license. See LICENSE file in the project root for full license information. using System.Diagnostics.CodeAnalysis; +using NetFabric.Hyperlinq; using Nncase.IR; namespace Nncase.Utilities; @@ -26,11 +27,7 @@ public static IReadOnlyList> GetLeafCandidateNDSBPs(TensorType tens ndsbps.Add(ndsbp); } - return ndsbps.CartesianProduct(). - Select(ndsbp => ndsbp.ToArray()). - Where(ndsbp => IsDistributable(tensorType, ndsbp, placement)). - Select(ndsbp => new IRArray(ndsbp)). - ToArray(); + return ndsbps.CartesianProduct().Select(ndsbp => ndsbp.ToArray()).Where(ndsbp => IsDistributable(tensorType, ndsbp, placement)).Select(ndsbp => new IRArray(ndsbp)).ToArray(); } public static IReadOnlyList> GetPartialCandidateNDSBPs(DistributedType distributedType) @@ -65,11 +62,7 @@ public static IReadOnlyList> GetPartialCandidateNDSBPs(DistributedT } } - return candidateNdsbps.CartesianProduct(). - Select(ndsbp => ndsbp.ToArray()). - Where(ndsbp => IsDistributable(tensorType, ndsbp, placement)). - Select(ndsbp => new IRArray(ndsbp)). - ToArray(); + return candidateNdsbps.CartesianProduct().Select(ndsbp => ndsbp.ToArray()).Where(ndsbp => IsDistributable(tensorType, ndsbp, placement)).Select(ndsbp => new IRArray(ndsbp)).ToArray(); } public static bool IsDistributable(TensorType tensorType, ReadOnlySpan ndsbp, Placement placement) @@ -131,24 +124,74 @@ public static Expr[] TryGetNonUniformDividedShape(DistributedType distributedTyp } return hierarchies.Select((divs, axis) => + { + Expr dim; + if (divs.Any()) { - Expr dim; - if (divs.Any()) + var divsor = (int)TensorUtilities.GetProduct(divs.Select(h => distributedType.Placement.Hierarchy[h]).ToArray()); + var (res, rem) = Math.DivRem(shape[axis], divsor); + if (rem == 0) { - var divsor = (int)TensorUtilities.GetProduct(divs.Select(h => distributedType.Placement.Hierarchy[h]).ToArray()); - var (res, rem) = Math.DivRem(shape[axis], divsor); - dim = IR.F.Math.Select( - TensorUtilities.GetIndex(hierarchyStrides.TakeLast(divs.Count).Select(s => (Expr)s).ToArray(), divs.Select(h => ids[h]).ToArray()) < (divsor - 1), - res, - res + rem); + return res; } - else + + dim = IR.F.Math.Select( + TensorUtilities.GetIndex(hierarchyStrides.TakeLast(divs.Count).Select(s => (Expr)s).ToArray(), divs.Select(h => ids[h]).ToArray()) < (divsor - 1), + res, + res + rem); + } + else + { + dim = distributedType.TensorType.Shape[axis].FixedValue; + } + + return dim; + }).ToArray(); + } + + public static List TryGetNonUniformDividedSlice(DistributedType distributedType) + { + var shape = distributedType.TensorType.Shape.ToValueArray(); + var hierarchies = Enumerable.Range(0, shape.Length).Select(i => new List()).ToArray(); + for (int i = 0; i < distributedType.NdSBP.Count; i++) + { + if (distributedType.NdSBP[i] is SBPSplit { Axis: int axis }) + { + hierarchies[axis].Add(i); + } + } + + var spliList = hierarchies.Select, int[]>((divs, axis) => + { + int[] dim; + if (divs.Any()) + { + var divsor = (int)TensorUtilities.GetProduct(divs.Select(h => distributedType.Placement.Hierarchy[h]).ToArray()); + var (res, rem) = Math.DivRem(shape[axis], divsor); + if (rem == 0) { - dim = distributedType.TensorType.Shape[axis].FixedValue; + return new[] { res }; } - return dim; - }).ToArray(); + dim = new[] { res, res + rem }; + } + else + { + dim = distributedType.TensorType.Shape.ToValueArray().Skip(axis).Take(1).ToArray(); + } + + return dim; + }).ToList(); + + IEnumerable ret = new[] { Array.Empty() }; + foreach (int[] array in spliList) + { + ret = from seq in ret + from item in array + select seq.Concat(new[] { item }).ToArray(); + } + + return ret.ToList(); } public static bool IsDivideBy(int input, int divisor) @@ -174,17 +217,14 @@ public static bool IsDivideExactly(int input, int divisor) public static float GetDividedTensorEfficiency(DistributedType distributedType, int burstLength) { var (tiles, shape) = GetDividedTile(distributedType); - return Enumerable.Range(0, tiles.Count). - Select(i => tiles[i].Ranges(0, shape[i])). - CartesianProduct(). - Select(rgs => - { - var slice = rgs.ToArray(); - var iscontiguous = TensorUtilities.IsContiguousSlice(shape.ToArray(), slice, out var contiguousStart); - var size = TensorUtilities.GetProduct(tiles.ToArray(), contiguousStart) * distributedType.TensorType.DType.SizeInBytes; - var (div, rem) = Math.DivRem(size, burstLength); - return ((div * 1.0f) + ((float)rem / burstLength)) / (div + 1); - }).Average(); + return Enumerable.Range(0, tiles.Count).Select(i => tiles[i].Ranges(0, shape[i])).CartesianProduct().Select(rgs => + { + var slice = rgs.ToArray(); + var iscontiguous = TensorUtilities.IsContiguousSlice(shape.ToArray(), slice, out var contiguousStart); + var size = TensorUtilities.GetProduct(tiles.ToArray(), contiguousStart) * distributedType.TensorType.DType.SizeInBytes; + var (div, rem) = Math.DivRem(size, burstLength); + return ((div * 1.0f) + ((float)rem / burstLength)) / (div + 1); + }).Average(); } public static TensorType GetDividedTensorType(DistributedType distributedType) diff --git a/src/Nncase.Diagnostics/Diagnostics/ILPrintVisitor.cs b/src/Nncase.Diagnostics/Diagnostics/ILPrintVisitor.cs index 4a447073b8..079dafc907 100644 --- a/src/Nncase.Diagnostics/Diagnostics/ILPrintVisitor.cs +++ b/src/Nncase.Diagnostics/Diagnostics/ILPrintVisitor.cs @@ -248,6 +248,8 @@ public ILPrintVisitor(TextWriter textWriter, bool display_callable, int indent_l _scope = new(textWriter, indent_level); } + public override string DefaultVisitType(IRType type) => type.ToString(); + /// public override string VisitType(AnyType type) => "any"; diff --git a/src/Nncase.Evaluator/Buffers/Allocate.cs b/src/Nncase.Evaluator/Buffers/Allocate.cs index d1a75d3375..d09f1654f3 100644 --- a/src/Nncase.Evaluator/Buffers/Allocate.cs +++ b/src/Nncase.Evaluator/Buffers/Allocate.cs @@ -14,6 +14,6 @@ public partial class AllocateEvaluator : ITypeInferencer /// public IRType Visit(ITypeInferenceContext context, Allocate target) { - return TensorType.Pointer(target.ElemType.DType); + return TensorType.Pointer(target.ElemType); } } diff --git a/src/Nncase.Evaluator/Buffers/DDrOf.cs b/src/Nncase.Evaluator/Buffers/DDrOf.cs index 86c53e04b7..b329ee1787 100644 --- a/src/Nncase.Evaluator/Buffers/DDrOf.cs +++ b/src/Nncase.Evaluator/Buffers/DDrOf.cs @@ -12,8 +12,13 @@ namespace Nncase.Evaluator.Buffers; [TypeInferGenerator] public partial class DDrOfEvaluator : ITypeInferencer { - private IRType Visit(TensorType input) + private IRType Visit(IRType input) { - return TensorType.Pointer(input.DType); + return input switch + { + DistributedType d => TensorType.Pointer(d.TensorType.DType), + TensorType t => TensorType.Pointer(t.DType), + _ => new InvalidType(input.GetType().Name), + }; } } diff --git a/src/Nncase.Evaluator/Math/Binary.cs b/src/Nncase.Evaluator/Math/Binary.cs index 1ac424b0be..4e8ab2e493 100755 --- a/src/Nncase.Evaluator/Math/Binary.cs +++ b/src/Nncase.Evaluator/Math/Binary.cs @@ -214,6 +214,8 @@ private IRType Visit(Binary target, DistributedType a, DistributedType b) BinaryOp.Sub => a - b, BinaryOp.Mul => a * b, BinaryOp.Div => a / b, + BinaryOp.FloorDiv => (int)System.Math.Floor((float)a / b), + BinaryOp.CeilDiv => (int)System.Math.Ceiling((float)a / b), BinaryOp.Mod => a % b, BinaryOp.Min => System.Math.Min(a, b), BinaryOp.Max => System.Math.Max(a, b), @@ -227,6 +229,8 @@ private IRType Visit(Binary target, DistributedType a, DistributedType b) BinaryOp.Sub => a - b, BinaryOp.Mul => a * b, BinaryOp.Div => a / b, + BinaryOp.FloorDiv => (uint)System.Math.Floor((float)a / b), + BinaryOp.CeilDiv => (uint)System.Math.Ceiling((float)a / b), BinaryOp.Mod => a % b, BinaryOp.Min => System.Math.Min(a, b), BinaryOp.Max => System.Math.Max(a, b), @@ -242,6 +246,8 @@ private IRType Visit(Binary target, DistributedType a, DistributedType b) BinaryOp.Sub => a - b, BinaryOp.Mul => a * b, BinaryOp.Div => a / b, + BinaryOp.FloorDiv => (ulong)System.Math.Floor((float)a / b), + BinaryOp.CeilDiv => (ulong)System.Math.Ceiling((float)a / b), BinaryOp.Mod => a % b, BinaryOp.Min => System.Math.Min(a, b), BinaryOp.Max => System.Math.Max(a, b), @@ -262,6 +268,8 @@ private IRType Visit(Binary target, DistributedType a, DistributedType b) BinaryOp.Sub => a - b, BinaryOp.Mul => a * b, BinaryOp.Div => a / b, + BinaryOp.FloorDiv => (long)System.Math.Floor((float)a / b), + BinaryOp.CeilDiv => (long)System.Math.Ceiling((float)a / b), BinaryOp.Mod => a % b, BinaryOp.Min => System.Math.Min(a, b), BinaryOp.Max => System.Math.Max(a, b), @@ -298,6 +306,8 @@ static OrtKISharp.Tensor Mod(OrtKISharp.Tensor a, OrtKISharp.Tensor b) BinaryOp.Sub => a - b, BinaryOp.Mul => a * b, BinaryOp.Div => a / b, + BinaryOp.FloorDiv => OrtKI.Floor(a.Cast(OrtDataType.Float) / b.Cast(OrtDataType.Float)).Cast(a.DataType), + BinaryOp.CeilDiv => OrtKI.Ceil(a.Cast(OrtDataType.Float) / b.Cast(OrtDataType.Float)).Cast(a.DataType), BinaryOp.Mod => Mod(a, b), BinaryOp.Min => OrtKI.Min(new[] { a, b }), BinaryOp.Max => OrtKI.Max(new[] { a, b }), diff --git a/src/Nncase.Evaluator/Math/MatMul.cs b/src/Nncase.Evaluator/Math/MatMul.cs index 1f19b64388..4642a4f8d5 100644 --- a/src/Nncase.Evaluator/Math/MatMul.cs +++ b/src/Nncase.Evaluator/Math/MatMul.cs @@ -23,7 +23,7 @@ public static IRType VisitDistributedType(DistributedType a, DistributedType b) { if (VisitTensorType(a.TensorType, b.TensorType) is not TensorType outType) { - return new InvalidType(string.Empty); + return new InvalidType($"{a.TensorType} {b.TensorType} not support"); } if (a.Placement != b.Placement) @@ -162,7 +162,7 @@ public IRType Visit(ITypeInferenceContext context, MatMul target) { (DistributedType a, DistributedType b) => VisitDistributedType(a, b), (TensorType a, TensorType b) => VisitTensorType(a, b), - _ => new InvalidType(string.Empty), + _ => new InvalidType($"{lhs} {rhs} not support"), }; } diff --git a/src/Nncase.Importer/Onnx/QLinearConv.cs b/src/Nncase.Importer/Onnx/QLinearConv.cs index 0ab9ebc99c..3e1bb7e07a 100644 --- a/src/Nncase.Importer/Onnx/QLinearConv.cs +++ b/src/Nncase.Importer/Onnx/QLinearConv.cs @@ -29,13 +29,13 @@ private Expr VisitQLinearConv(in NodeProto op) var group = GetIntAttribute(op, "group", 1); var strides = GetStrideAttribute(op); - int? stridesValueLen = ((TensorConst)strides).ValueType.Shape[0].Value; + int? stridesValueLen = ((TensorConst)strides).CheckedShape[0].Value; for (var i = 0; i < stridesValueLen; i++) { System.Diagnostics.Trace.Assert(((TensorConst)strides).Value.Cast()[i] <= (long)int.MaxValue); } - int? dilationValueLen = ((TensorConst)dilation).ValueType.Shape[0].Value; + int? dilationValueLen = ((TensorConst)dilation).CheckedShape[0].Value; for (var i = 0; i < dilationValueLen; i++) { System.Diagnostics.Trace.Assert(((TensorConst)dilation).Value.Cast()[i] <= (long)int.MaxValue); @@ -63,16 +63,16 @@ private Expr VisitQLinearConv(in NodeProto op) if (bias == null) { - int? ocNumber = ((TensorConst)weights).ValueType.Shape[0].Value; + int? ocNumber = ((TensorConst)weights).CheckedShape[0].Value; var zeroBias = new TensorConst(new int[ocNumber == null ? default(int) : ocNumber.Value]); var conv = F.NN.Conv2D(inputDeq, weightsDeq, zeroBias, strideConst, pads, dilationConst, PadMode.Constant, group); - return Quantize(conv, new QuantParam(((TensorConst)yZeroPoint).Value.ToScalar(), ((TensorConst)yScale).Value.ToScalar()), ((TensorConst)yZeroPoint).ValueType.DType); + return Quantize(conv, new QuantParam(((TensorConst)yZeroPoint).Value.ToScalar(), ((TensorConst)yScale).Value.ToScalar()), ((TensorConst)yZeroPoint).CheckedDataType); } else { var biasDeq = Dequantize(bias, new QuantParam(0, ((TensorConst)xScale).Value.ToScalar() * ((TensorConst)wScale).Value.ToScalar()), DataTypes.Float32); var conv = F.NN.Conv2D(inputDeq, weightsDeq, biasDeq, strideConst, pads, dilationConst, PadMode.Constant, group); - return Quantize(conv, new QuantParam(((TensorConst)yZeroPoint).Value.ToScalar(), ((TensorConst)yScale).Value.ToScalar()), ((TensorConst)yZeroPoint).ValueType.DType); + return Quantize(conv, new QuantParam(((TensorConst)yZeroPoint).Value.ToScalar(), ((TensorConst)yScale).Value.ToScalar()), ((TensorConst)yZeroPoint).CheckedDataType); } } } diff --git a/src/Nncase.Importer/Onnx/QLinearMatmul.cs b/src/Nncase.Importer/Onnx/QLinearMatmul.cs index 5ab2ad73d8..892a6b6c2d 100644 --- a/src/Nncase.Importer/Onnx/QLinearMatmul.cs +++ b/src/Nncase.Importer/Onnx/QLinearMatmul.cs @@ -25,7 +25,7 @@ private Expr VisitQLinearMatMul(in NodeProto op) var aDeq = Dequantize(input_a, new QuantParam(((TensorConst)aZeroPoint).Value.ToScalar(), ((TensorConst)aScale).Value.ToScalar()), DataTypes.Float32); var bDeq = Dequantize(input_b, new QuantParam(((TensorConst)bZeroPoint).Value.ToScalar(), ((TensorConst)bScale).Value.ToScalar()), DataTypes.Float32); var matmul = F.Tensors.MatMul(aDeq, bDeq); - return Quantize(matmul, new QuantParam(((TensorConst)yZeroPoint).Value.ToScalar(), ((TensorConst)yScale).Value.ToScalar()), ((TensorConst)yZeroPoint).ValueType.DType); + return Quantize(matmul, new QuantParam(((TensorConst)yZeroPoint).Value.ToScalar(), ((TensorConst)yScale).Value.ToScalar()), ((TensorConst)yZeroPoint).CheckedDataType); } } } diff --git a/src/Nncase.Importer/Onnx/Quantize.cs b/src/Nncase.Importer/Onnx/Quantize.cs index cc33583711..6a4c771cb4 100644 --- a/src/Nncase.Importer/Onnx/Quantize.cs +++ b/src/Nncase.Importer/Onnx/Quantize.cs @@ -23,7 +23,7 @@ private Expr VisitQuantizeLinear(in NodeProto op) new QuantParam( biasConst.Value.ToScalar(), scaleConst.Value.ToScalar()), - ((TensorConst)bias).ValueType.DType); + ((TensorConst)bias).CheckedDataType); } throw new NotImplementedException("Onnx importer not impl for dynamic scale and bias"); diff --git a/src/Nncase.Passes/DDrBufferSchdeulePass.cs b/src/Nncase.Passes/DDrBufferSchdeulePass.cs index 80aebda267..15a6505686 100644 --- a/src/Nncase.Passes/DDrBufferSchdeulePass.cs +++ b/src/Nncase.Passes/DDrBufferSchdeulePass.cs @@ -134,7 +134,7 @@ protected override Expr RewriteLeafBuffer(TIR.Buffer expr) protected override TIR.MemSpan RewriteLeafMemSpan(TIR.MemSpan memSpan) { - if (memSpan is { Location: MemoryLocation.Rdata, Start: Call { Target: IR.Buffers.DDrOf, Arguments: var arg } } && arg[0] is Const { ValueType: TensorType constType } @const) + if (memSpan is { Location: MemoryLocation.Rdata, Start: Call { Target: IR.Buffers.DDrOf, Arguments: var arg } } && arg[0] is Const @const) { if (!ModuleRdataMaps.TryGetValue(Entry.ModuleKind, out var moduleRdataMap)) { @@ -163,7 +163,7 @@ protected override TIR.MemSpan RewriteLeafMemSpan(TIR.MemSpan memSpan) Changed = true; } - return memSpan.With(new TensorConst(Tensor.FromPointer((ulong)memRange.Min, constType.DType)), memRange.Max - memRange.Min); + return memSpan.With(new TensorConst(Tensor.FromPointer((ulong)memRange.Min, @const.CheckedDataType)), memRange.Max - memRange.Min); } return memSpan; diff --git a/src/Nncase.Passes/ModulePass.cs b/src/Nncase.Passes/ModulePass.cs index dc35cd0843..25f8edd98f 100644 --- a/src/Nncase.Passes/ModulePass.cs +++ b/src/Nncase.Passes/ModulePass.cs @@ -30,7 +30,7 @@ protected override Task OnPassStartAsync(IRModule input, RunPassContext context) { foreach (var func in input.Functions) { - DumpScope.Current.DumpIR(func, func.Name, "Start"); + DumpScope.Current.DumpIR(func, string.Empty, "Start"); } } @@ -44,7 +44,7 @@ protected override Task OnPassEndAsync(IRModule post, RunPassContext context) { foreach (var func in post.Functions) { - DumpScope.Current.DumpIR(func, func.Name, "End"); + DumpScope.Current.DumpIR(func, string.Empty, "End"); } } diff --git a/src/Nncase.Tests/Core/IR/UnitTestConst.cs b/src/Nncase.Tests/Core/IR/UnitTestConst.cs index 80b8a5e669..45e4e4585f 100644 --- a/src/Nncase.Tests/Core/IR/UnitTestConst.cs +++ b/src/Nncase.Tests/Core/IR/UnitTestConst.cs @@ -18,7 +18,7 @@ public void TestByte() byte expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -30,7 +30,7 @@ public void TestUshort() ushort expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -42,7 +42,7 @@ public void TestUint() uint expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -54,7 +54,7 @@ public void TestUlong() ulong expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -66,7 +66,7 @@ public void TestSbyte() sbyte expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -78,7 +78,7 @@ public void TestShort() short expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -90,7 +90,7 @@ public void TestInt() int expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -102,7 +102,7 @@ public void TestLong() long expected = 1; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -114,7 +114,7 @@ public void TestHalf() var expected = (Half)1F; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -126,7 +126,7 @@ public void TestFloat() var expected = 1F; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -138,7 +138,7 @@ public void TestDouble() var expected = 1D; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -150,7 +150,7 @@ public void TestBfloat16() var expected = (BFloat16)1F; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -162,7 +162,7 @@ public void TestBool() var expected = false; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -175,7 +175,7 @@ public void TestUtf8Char() Utf8Char expected = b; Const c = expected; var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -188,7 +188,7 @@ public void TestFromTensorValue() var tv = new TensorValue(b); var c = Const.FromValue(tv); var tc = (TensorConst)c; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(b, list[0]); diff --git a/src/Nncase.Tests/Core/IR/UnitTestTensorConst.cs b/src/Nncase.Tests/Core/IR/UnitTestTensorConst.cs index c444a0e45b..733fe17b09 100644 --- a/src/Nncase.Tests/Core/IR/UnitTestTensorConst.cs +++ b/src/Nncase.Tests/Core/IR/UnitTestTensorConst.cs @@ -18,7 +18,7 @@ public void TestByte() { byte expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -29,7 +29,7 @@ public void TestUshort() { ushort expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -40,7 +40,7 @@ public void TestUint() { uint expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -51,7 +51,7 @@ public void TestUlong() { ulong expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -62,7 +62,7 @@ public void TestSbyte() { sbyte expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -73,7 +73,7 @@ public void TestShort() { short expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -84,7 +84,7 @@ public void TestInt() { int expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -95,7 +95,7 @@ public void TestLong() { long expected = 1; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -106,7 +106,7 @@ public void TestHalf() { var expected = (Half)1F; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -117,7 +117,7 @@ public void TestFloat() { var expected = 1F; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -128,7 +128,7 @@ public void TestDouble() { var expected = 1D; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -139,7 +139,7 @@ public void TestBfloat16() { var expected = (BFloat16)1F; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -150,7 +150,7 @@ public void TestBool() { var expected = false; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); @@ -162,7 +162,7 @@ public void TestUtf8Char() byte b = 1; Utf8Char expected = b; TensorConst tc = expected; - Assert.True(tc.ValueType.IsScalar); + Assert.True(tc.CheckedShape.IsScalar); Assert.Equal(DataType.FromType(), tc.Value.ElementType); var list = (IList)tc.Value; Assert.Equal(expected, list[0]); diff --git a/src/Nncase.Tests/Core/UnitTestTIR.cs b/src/Nncase.Tests/Core/UnitTestTIR.cs index f0c40be178..79b72f431a 100644 --- a/src/Nncase.Tests/Core/UnitTestTIR.cs +++ b/src/Nncase.Tests/Core/UnitTestTIR.cs @@ -99,7 +99,7 @@ public void TestForSegment() public void TestGrid() { var grid1 = T.Grid(out _, LoopMode.Serial, new Range(-1f, 1f, 1)); - var grid2 = T.Grid(out _, out _, new(1, 1)); + var grid2 = T.Grid(out _, LoopMode.Serial, new Range(1, 1, 1)); Assert.Equal(grid1.GetType(), grid2.GetType()); } From a0e3ff5c90203d1e24ec550f86ee7a574c220221 Mon Sep 17 00:00:00 2001 From: uranus0515 <110005227+uranus0515@users.noreply.github.com> Date: Mon, 25 Dec 2023 11:52:12 +0800 Subject: [PATCH 09/12] adjust for processor counter (#1150) Co-authored-by: guodongliang --- src/Nncase.EGraph/Passes/EGraphExtractors/SatExtractor.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Nncase.EGraph/Passes/EGraphExtractors/SatExtractor.cs b/src/Nncase.EGraph/Passes/EGraphExtractors/SatExtractor.cs index 038873173b..dbb9d54a9a 100644 --- a/src/Nncase.EGraph/Passes/EGraphExtractors/SatExtractor.cs +++ b/src/Nncase.EGraph/Passes/EGraphExtractors/SatExtractor.cs @@ -77,7 +77,7 @@ public Expr Extract(EClass root, IEGraph eGraph, out IReadOnlyDictionary Date: Mon, 25 Dec 2023 18:22:59 +0800 Subject: [PATCH 10/12] Fix Studio (#1151) * fix load calib * update * update * Apply code-format changes * update --------- Co-authored-by: FusionBolt --- src/Nncase.Studio/Assets/calib.png | Bin 0 -> 27380 bytes src/Nncase.Studio/Util/DataUtil.cs | 12 ++-- src/Nncase.Studio/Util/ViewModelContext.cs | 2 +- .../ViewModels/CompileViewModel.cs | 4 +- .../ViewModels/QuantizeViewModel.cs | 66 ++++++++++++------ .../ViewModels/SimulateViewModel.cs | 29 ++++++-- .../Views/QuantizeCalibWindow.axaml | 13 ++++ .../Views/QuantizeCalibWindow.axaml.cs | 17 +++++ src/Nncase.Studio/Views/QuantizeView.axaml | 32 +++++---- src/Nncase.Studio/Views/SimulateView.axaml | 23 +++--- 10 files changed, 139 insertions(+), 59 deletions(-) create mode 100644 src/Nncase.Studio/Assets/calib.png create mode 100644 src/Nncase.Studio/Views/QuantizeCalibWindow.axaml create mode 100644 src/Nncase.Studio/Views/QuantizeCalibWindow.axaml.cs diff --git a/src/Nncase.Studio/Assets/calib.png b/src/Nncase.Studio/Assets/calib.png new file mode 100644 index 0000000000000000000000000000000000000000..b1524fda7ee5b53c042d05c9aa194912d45aa0ec GIT binary patch literal 27380 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 literal 0 HcmV?d00001 diff --git a/src/Nncase.Studio/Util/DataUtil.cs b/src/Nncase.Studio/Util/DataUtil.cs index 3144a5c518..c15ffcf2da 100644 --- a/src/Nncase.Studio/Util/DataUtil.cs +++ b/src/Nncase.Studio/Util/DataUtil.cs @@ -45,15 +45,17 @@ public static (string[] InputFiles, Tensor[] InputList) ReadMultiInputs(List ReadInput(string[] file) { return file .Where(f => Path.GetExtension(f) == ".npy") - .Select(f => - { - var tensor = np.load(f); - return Tensor.FromBytes(new TensorType(DataType.FromType(tensor.dtype), tensor.shape), tensor.ToByteArray()); - }).ToList(); + .Select(ReadNumpyAsTensor).ToList(); } public static DataType QuantTypeToDataType(QuantType qt) diff --git a/src/Nncase.Studio/Util/ViewModelContext.cs b/src/Nncase.Studio/Util/ViewModelContext.cs index 70e476dafe..c96587302e 100644 --- a/src/Nncase.Studio/Util/ViewModelContext.cs +++ b/src/Nncase.Studio/Util/ViewModelContext.cs @@ -32,7 +32,7 @@ public ViewModelContext(MainWindowViewModel windowViewModel) public ViewModelBase[] ViewModelBases { get; set; } = Array.Empty(); - public Function? Entry { get; set; } + public Var[] Params { get; set; } = Array.Empty(); public async Task> OpenFile(FilePickerOpenOptions options) { diff --git a/src/Nncase.Studio/ViewModels/CompileViewModel.cs b/src/Nncase.Studio/ViewModels/CompileViewModel.cs index 8977f73139..1d67483ea0 100644 --- a/src/Nncase.Studio/ViewModels/CompileViewModel.cs +++ b/src/Nncase.Studio/ViewModels/CompileViewModel.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows.Input; @@ -73,12 +74,13 @@ public async Task Compile() var compileSession = CompileSession.Create(target, options); var compiler = compileSession.Compiler; var module = await compiler.ImportModuleAsync(options.InputFormat, options.InputFile, options.IsBenchmarkOnly); - Context.Entry = (Function)module.Entry!; + Context.Params = ((Function)module.Entry!).Parameters.ToArray(); if (options.QuantizeOptions.ModelQuantMode != ModelQuantMode.NoQuant) { var calib = ((QuantizeViewModel)Context.ViewModelLookup(typeof(QuantizeViewModel))).LoadCalibFiles(); if (calib == null) { + Context.OpenDialog("矫正集为空"); return; } diff --git a/src/Nncase.Studio/ViewModels/QuantizeViewModel.cs b/src/Nncase.Studio/ViewModels/QuantizeViewModel.cs index 974e3b147d..130c23c80c 100644 --- a/src/Nncase.Studio/ViewModels/QuantizeViewModel.cs +++ b/src/Nncase.Studio/ViewModels/QuantizeViewModel.cs @@ -10,6 +10,7 @@ using Avalonia.Media.Fonts; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using NetFabric.Hyperlinq; using Nncase.IR; using Nncase.Quantization; using Nncase.Studio.Util; @@ -49,7 +50,7 @@ public partial class QuantizeViewModel : ViewModelBase [ObservableProperty] private string _calibDir = string.Empty; - private string[] _inputFiles = Array.Empty(); + private string[][] _multiInputFiles = Array.Empty(); public QuantizeViewModel(ViewModelContext context) { @@ -90,6 +91,23 @@ public async Task SelectCalibrationDataSet() } var inputFiles = Directory.GetFiles(path); + + try + { + var n = inputFiles.Where(f => Path.GetExtension(f) == ".npy").GroupBy(s => Path.GetFileName(s).Split("_")[0]); + _multiInputFiles = n.Select(group => + { + var value = group.ToArray(); + var one = value.OrderBy(s => int.Parse(Path.GetFileName(s).Split("_")[1])).ToArray(); + return one; + }).ToArray(); + } + catch (Exception e) + { + Context.OpenDialog($"文件夹中的文件解析失败,请检查文件名是否符合格式。\n{e.Message}"); + return; + } + if (inputFiles.Length == 0) { Context.OpenDialog("empty dir"); @@ -97,37 +115,39 @@ public async Task SelectCalibrationDataSet() } CalibDir = path; - _inputFiles = inputFiles; + } + + [RelayCommand] + public void ShowCalibFormat() + { + new QuantizeCalibWindow().Show(); } public ICalibrationDatasetProvider? LoadCalibFiles() { - Tensor[] input; try { - input = DataUtil.ReadInput(_inputFiles).ToArray(); + var samples = _multiInputFiles.Select(files => + { + var input = files.Select(DataUtil.ReadNumpyAsTensor).ToArray(); + var samples = Context.Params.Zip(input) + .ToDictionary(pair => pair.First, pair => (IValue)Value.FromTensor(pair.Second)); + return samples; + }).ToArray(); + + if (Context.Params.Length == 0) + { + Context.OpenDialog("Should Import Model first"); + return null; + } + + return new SelfInputCalibrationDatasetProvider(samples); } catch (Exception e) { Context.OpenDialog(e.Message); return null; } - - if (input.Length == 0) - { - Context.OpenDialog("no file is loaded, only support .npy"); - return null; - } - - if (Context.Entry == null) - { - Context.OpenDialog("Should Import Model first"); - return null; - } - - var samples = Context.Entry!.Parameters.ToArray().Zip(input) - .ToDictionary(pair => pair.First, pair => (IValue)Value.FromTensor(pair.Second)); - return new SelfInputCalibrationDatasetProvider(samples); } public override void UpdateViewModelCore(CompileConfig config) @@ -171,7 +191,7 @@ public override List CheckViewModel() { if (Directory.Exists(CalibDir)) { - if (_inputFiles.Length == 0) + if (_multiInputFiles.Length == 0) { list.Add("CalibDir don't exist any .npy file"); } @@ -200,9 +220,9 @@ public sealed class SelfInputCalibrationDatasetProvider : ICalibrationDatasetPro private readonly IAsyncEnumerable> _samples; - public SelfInputCalibrationDatasetProvider(IReadOnlyDictionary sample) + public SelfInputCalibrationDatasetProvider(IReadOnlyDictionary[] samples) { - _samples = new[] { sample }.ToAsyncEnumerable(); + _samples = samples.ToAsyncEnumerable(); } public int? Count => _count; diff --git a/src/Nncase.Studio/ViewModels/SimulateViewModel.cs b/src/Nncase.Studio/ViewModels/SimulateViewModel.cs index c7fc1a62d5..2f6b0521cb 100644 --- a/src/Nncase.Studio/ViewModels/SimulateViewModel.cs +++ b/src/Nncase.Studio/ViewModels/SimulateViewModel.cs @@ -60,6 +60,17 @@ public async Task SetRuntimeInput() try { (inputFiles, input) = DataUtil.ReadMultiInputs(path); + if (CanBeSort(inputFiles)) + { + var pairList = inputFiles.Zip(input) + .OrderBy(pair => int.Parse(Path.GetFileName(pair.First).Split("_")[0])); + inputFiles = pairList.Select(x => x.First).ToArray(); + input = pairList.Select(x => x.Second).ToArray(); + } + else + { + Context.OpenDialog("输入文件未排序,可能出现输入无法正确对应的情况"); + } } catch (Exception e) { @@ -68,6 +79,14 @@ public async Task SetRuntimeInput() } UpdateRuntimeInputUI(input, inputFiles); + + bool CanBeSort(string[] inputFilePathList) + { + var fileNames = inputFilePathList.Select(Path.GetFileName).ToArray(); + var canBeSort = fileNames.All(x => + x!.Contains("_", StringComparison.Ordinal) && int.TryParse(x.Split("_")[0], out int _)); + return canBeSort; + } } [RelayCommand] @@ -163,12 +182,12 @@ public override void UpdateConfig(CompileConfig config) private bool CheckInput() { - if (Context.Entry == null) + if (Context.Params.Length == 0) { return true; } - var paramList = Context.Entry!.Parameters.ToArray(); + var paramList = Context.Params!; foreach ((var tensor, var param) in RuntimeInput.Zip(paramList)) { var tt = (TensorType)param.TypeAnnotation; @@ -176,7 +195,7 @@ private bool CheckInput() { Context.OpenDialog($"{param.Name} input datatype mismatch"); { - return true; + return false; } } @@ -185,12 +204,12 @@ private bool CheckInput() { Context.OpenDialog($"{param.Name} input shape mismatch"); { - return true; + return false; } } } - return false; + return true; } [RelayCommand] diff --git a/src/Nncase.Studio/Views/QuantizeCalibWindow.axaml b/src/Nncase.Studio/Views/QuantizeCalibWindow.axaml new file mode 100644 index 0000000000..b4072da7c9 --- /dev/null +++ b/src/Nncase.Studio/Views/QuantizeCalibWindow.axaml @@ -0,0 +1,13 @@ + + + 数据集文件夹中仅会读取npy格式的文件 + 文件名需要以"第几组数据集_第几个输入_文件名.bin"的格式,例如下图中 + + + diff --git a/src/Nncase.Studio/Views/QuantizeCalibWindow.axaml.cs b/src/Nncase.Studio/Views/QuantizeCalibWindow.axaml.cs new file mode 100644 index 0000000000..44502dad93 --- /dev/null +++ b/src/Nncase.Studio/Views/QuantizeCalibWindow.axaml.cs @@ -0,0 +1,17 @@ +// Copyright (c) Canaan Inc. All rights reserved. +// Licensed under the Apache license. See LICENSE file in the project root for full license information. + +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Nncase.Studio.Views +{ + public partial class QuantizeCalibWindow : Window + { + public QuantizeCalibWindow() + { + InitializeComponent(); + } + } +} diff --git a/src/Nncase.Studio/Views/QuantizeView.axaml b/src/Nncase.Studio/Views/QuantizeView.axaml index 68ac94469e..ba0a0d010e 100644 --- a/src/Nncase.Studio/Views/QuantizeView.axaml +++ b/src/Nncase.Studio/Views/QuantizeView.axaml @@ -42,23 +42,27 @@ Content="选择" x:CompileBindings="False" Command="{Binding SelectCalibrationDataSetCommand}"> - - - - - - - - + + - - - - + + - - + + + + + + + + + + + + + diff --git a/src/Nncase.Studio/Views/SimulateView.axaml b/src/Nncase.Studio/Views/SimulateView.axaml index 4e6f895ff6..b6eca40cf3 100644 --- a/src/Nncase.Studio/Views/SimulateView.axaml +++ b/src/Nncase.Studio/Views/SimulateView.axaml @@ -20,16 +20,19 @@ - - - - - - - - - + + + + + + + + + + + +