diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/.gitignore b/plugins/tensorboard-plugins/tb_graph_ascend/.gitignore index 75bc12db6361869d36e80447322dde9292c0c761..70f4e767811d0d93c25fbb8ce2d2b29c4ba3b6e6 100644 --- a/plugins/tensorboard-plugins/tb_graph_ascend/.gitignore +++ b/plugins/tensorboard-plugins/tb_graph_ascend/.gitignore @@ -5,4 +5,7 @@ dist/ build/ tb_graph_ascend.egg-info/ __pycache__/ -/server/static/index.html \ No newline at end of file +/server/static/index.html +report.html +assets/ +/htmlcov/ \ No newline at end of file diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph/tf-graph-scene.ts b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph/tf-graph-scene.ts index a908f6ac61a671a71c83861727da0716dd64aad3..e1829e4140ec2901b00649410081386a2e072674 100644 --- a/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph/tf-graph-scene.ts +++ b/plugins/tensorboard-plugins/tb_graph_ascend/fe/src/tf_graph/tf-graph-scene.ts @@ -702,7 +702,7 @@ class TfGraphScene2 extends LegacyElementMixin(DarkModeMixin(PolymerElement)) im _fireEnableClick(): void { this.fire('enable-click'); } - + // 取消鼠标点击自动居中 _noPanToNode(): void { this.enablePanSignal = false diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/server/app/controllers/match_nodes_controller.py b/plugins/tensorboard-plugins/tb_graph_ascend/server/app/controllers/match_nodes_controller.py index a61154c3ea813656a84f51ad8044d07025aea004..9517d252ca206722497d27ad10c2a4f9bd3c5222 100644 --- a/plugins/tensorboard-plugins/tb_graph_ascend/server/app/controllers/match_nodes_controller.py +++ b/plugins/tensorboard-plugins/tb_graph_ascend/server/app/controllers/match_nodes_controller.py @@ -38,10 +38,12 @@ class MatchNodesController: # 在原始数据上,添加匹配节点,和匹配节点信息 npu_node_data['matched_node_link'] = [bench_node_name] bench_node_data['matched_node_link'] = [npu_node_name] - npu_node_data['data']['precision_index'] = precision_error + # 防止 KeyError 或 TypeError + npu_node_data.setdefault('data', {})['precision_index'] = precision_error # 后端维护一个匹配节点列表,前端展示 npu_match_nodes_list[npu_node_name] = bench_node_name bench_match_nodes_list[bench_node_name] = npu_node_name + graph_data['npu_match_nodes'] = npu_match_nodes_list graph_data['bench_match_nodes'] = bench_match_nodes_list return { @@ -81,8 +83,8 @@ class MatchNodesController: def process_summary_task_add(graph_data, npu_node_name, bench_node_name): npu_match_nodes_list = graph_data.get('npu_match_nodes', {}) bench_match_nodes_list = graph_data.get('bench_match_nodes', {}) - npu_node_data = graph_data.get('NPU', {}).get('node', {}).get(npu_node_name, {}) - bench_node_data = graph_data.get('Bench', {}).get('node', {}).get(bench_node_name, {}) + npu_node_data = graph_data.get('NPU', {}).get('node', {}).get(npu_node_name) + bench_node_data = graph_data.get('Bench', {}).get('node', {}).get(bench_node_name) # 计算统计误差 intput_statistical_diff = MatchNodesController.calculate_statistical_diff( npu_node_data.get('input_data'), bench_node_data.get('input_data'), npu_node_name, bench_node_name @@ -109,7 +111,8 @@ class MatchNodesController: bench_node_data['matched_node_link'] = [npu_node_name] MatchNodesController.update_graph_node_data(npu_node_data.get('input_data'), intput_statistical_diff) MatchNodesController.update_graph_node_data(npu_node_data.get('output_data'), output_statistical_diff) - npu_node_data['data']['precision_index'] = precision_error + # 防止 KeyError 或 TypeError + npu_node_data.setdefault('data', {})['precision_index'] = precision_error # 后端维护一个匹配节点列表,前端展示 npu_match_nodes_list[npu_node_name] = bench_node_name bench_match_nodes_list[bench_node_name] = npu_node_name @@ -138,7 +141,8 @@ class MatchNodesController: MatchNodesController.delete_matched_node_data(npu_node_data.get('output_data')) # 后端维护一个匹配节点列表,前端展示 try: - del npu_node_data['data']['precision_index'] + # 防止 KeyError 或 TypeError + npu_node_data.get('data', {}).pop('precision_index', None) del npu_match_nodes_list[npu_node_name] del bench_match_nodes_list[bench_node_name] except KeyError: @@ -219,6 +223,8 @@ class MatchNodesController: @staticmethod def calculate_md5_diff(npu_data, bench_data): + if npu_data == {} or bench_data == {}: + return 0 # 对比每个NPU和Bench所有数据md值,如果有一个不一样则返回0,否则返回1 for npu_key, bench_key in zip(npu_data, npu_data): npu_md5 = npu_data[npu_key].get('md5', '') diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/server/plugin.py b/plugins/tensorboard-plugins/tb_graph_ascend/server/plugin.py index ab9f5d3dacec68bc5c1f22133ab6217a728486b6..aefd06137b23cef484ec86967c1398dabc11aa4a 100644 --- a/plugins/tensorboard-plugins/tb_graph_ascend/server/plugin.py +++ b/plugins/tensorboard-plugins/tb_graph_ascend/server/plugin.py @@ -354,7 +354,6 @@ class GraphsPlugin(base_plugin.TBPlugin): if json_data.get('StepList', {}) and 'ALL' not in json_data.get('StepList', {}): json_data['StepList'].insert(0, 'ALL') all_node_names = self.get_all_node_names(json_data, request) - # 读取第一个文件中的Colors和OverflowCheck first_run_tag = get_global_value("first_run_tag") first_file_data, _ = GraphUtils.safe_load_data(run, first_run_tag) diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/test/conftest.py b/plugins/tensorboard-plugins/tb_graph_ascend/test/conftest.py new file mode 100644 index 0000000000000000000000000000000000000000..70475d16bd28218a6e07f899b4b9757e8010a16f --- /dev/null +++ b/plugins/tensorboard-plugins/tb_graph_ascend/test/conftest.py @@ -0,0 +1,78 @@ +# Copyright (c) 2025, Huawei Technologies. +# All Rights Reserved. +# +# 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. +# ============================================================================== + +import os +import json +from pathlib import Path +import pytest + + +def load_st_test_cases(): + meta_path = Path(__file__).parent / "data/metadata_st.json" + with open(meta_path) as f: + return json.load(f)["test_cases"] + + +def load_ut_test_cases(): + meta_path = Path(__file__).parent / "data/metadata_ut.json" + with open(meta_path) as f: + return json.load(f)["test_cases"] + + +# 动态生成测试用例 +def pytest_generate_tests(metafunc): + if "meta_data" in metafunc.fixturenames and "operation" in metafunc.fixturenames: + ut_test_cases = load_st_test_cases() + params = [] + for case in ut_test_cases: + meta_data = case["meta_data"] + meta_data["run"] = Path(__file__).parent / meta_data["run"] + for op in case["operations"]: + params.append(pytest.param( + meta_data, + op, + id=f"{meta_data['run']}-{op['type']}" + )) + # 确保参数名称与参数值数量一致 + metafunc.parametrize("meta_data, operation", params) + if "ut_test_case" in metafunc.fixturenames: + ut_test_cases = load_ut_test_cases() + params = [] + for case in ut_test_cases: + params.append(pytest.param( + case, + id=f"{case['type']}-{case['name']}" + )) + # 确保参数名称与参数值数量一致 + metafunc.parametrize("ut_test_case", params) + + +@pytest.fixture +def meta_data(meta_data): + # 返回当前测试的操作配置 + return meta_data + + +@pytest.fixture +def operation_config(operation): + # 返回当前测试的操作配置 + return operation + + +@pytest.fixture +def ut_test_case(ut_test_case): + # 返回当前测试的操作配置 + return ut_test_case diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/test/data/metadata_st.json b/plugins/tensorboard-plugins/tb_graph_ascend/test/data/metadata_st.json new file mode 100644 index 0000000000000000000000000000000000000000..820e11cc39b7c4ed4111690970d2714a82bf1899 --- /dev/null +++ b/plugins/tensorboard-plugins/tb_graph_ascend/test/data/metadata_st.json @@ -0,0 +1,237 @@ +{ + "test_cases": [ + { + "meta_data": { + "run": "data", + "tag": "compare_statis" + }, + "operations": [ + { + "type": "get_node_info", + "node_info": { + "nodeName": "Test.maxpoolMaxPool2.maxpoolpo.tt.ee", + "nodeType": "Bench" + }, + "expected": { + "success": true, + "data": { + "matched_node_link": [], + "data": {}, + "id": "Test.maxpoolMaxPool2.maxpoolpo.tt.ee", + "inputs": [], + "input_data": { + "Test.maxpoolMaxPool2.maxpoolpo.tt.ee.input_arg.0": { + "longlonglonglonglonglongName": "hah", + "type": "torch.Tensor", + "dtype": "torch.float32", + "shape": "[32, 512, 2, 2]", + "Max": "548.2343242", + "Min": "-234.124124234", + "Mean": "-5.23432654", + "Norm": "35555.3406", + "error_key": [ + "type", + "shape" + ] + }, + "Test.maxpoolMaxPool2.maxpoolpo.tt.ee.kwrag_arg.1": { + "type": "torch.Tensor", + "dtype": "torch.float32", + "shape": [ + 64, + 256, + 2, + 2 + ], + "Max": "5348.2343242", + "Min": "-2344.124124234", + "Mean": "-51.23432654", + "Norm": "355555.3406" + } + }, + "is_forward": true, + "node_type": 0, + "outputs": [], + "output_data": { + "output.0": { + "type": "torch.Tensor", + "dtype": "torch.float32", + "shape": [ + 128, + 512, + 2, + 2 + ], + "Max": "5038.2343242", + "Min": "-1234.124124234", + "Mean": "-410.23432654", + "Norm": "3255.3406" + }, + "output.1": { + "type": "torch.Tensor", + "dtype": "torch.float32", + "shape": [ + 16, + 256, + 2, + 2 + ], + "Max": "538.2343242", + "Min": "-234.124124234", + "Mean": "-51.23432654", + "Norm": "35555.3406" + } + }, + "pair": "None", + "subnodes": [ + "AddOne_0", + "AddOne_1" + ], + "type": "AddTwo", + "upnode": "AddThree_0" + } + } + }, + { + "type": "add_match_nodes", + "npu_node_name": "AddOne_0", + "bench_node_name": "AddOne_0", + "expected": { + "success": true, + "data": { + "precision_error": 0.0, + "intput_statistical_diff": { + "AddOne_0.input_arg.0": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + }, + "AddOne_0.input_arg.1": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + } + }, + "output_statistical_diff": { + "AddOne_0.output_arg.0": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + }, + "AddOne_0.output_arg.1": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + } + } + } + } + }, + { + "type": "delete_match_nodes", + "npu_node_name": "AddOne_0", + "bench_node_name": "AddOne_0", + "expected": { + "success": true, + "data": {} + } + }, + { + "type": "add_match_nodes", + "npu_node_name": "AddOne_0", + "bench_node_name": "AddOne_0", + "expected": { + "success": true, + "data": { + "precision_error": 0.0, + "intput_statistical_diff": { + "AddOne_0.input_arg.0": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + }, + "AddOne_0.input_arg.1": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + } + }, + "output_statistical_diff": { + "AddOne_0.output_arg.0": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + }, + "AddOne_0.output_arg.1": { + "MaxAbsErr": 0.0, + "MinAbsErr": 0.0, + "MeanAbsErr": 0.0, + "NormAbsErr": 0.0, + "MaxRelativeErr": "0.0000%", + "MinRelativeErr": "0.0000%", + "MeanRelativeErr": "0.0000%", + "NormRelativeErr": "0.0000%" + } + } + } + } + }, + { + "type": "get_matched_state_list", + "expected": { + "success": true, + "data": { + "npu_match_nodes": { + "AddOne_0": "AddOne_0" + }, + "bench_match_nodes": { + "AddOne_0": "AddOne_0" + } + } + } + }, + { + "type": "save_data", + "expected": { + "success": true + } + } + ] + } + ] +} \ No newline at end of file diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/test/data/metadata_ut.json b/plugins/tensorboard-plugins/tb_graph_ascend/test/data/metadata_ut.json new file mode 100644 index 0000000000000000000000000000000000000000..025fbd14c5fdf5307928ffe4bf3ebd0f102c36f9 --- /dev/null +++ b/plugins/tensorboard-plugins/tb_graph_ascend/test/data/metadata_ut.json @@ -0,0 +1,174 @@ +{ + "test_cases": [ + { + "name": "md5_full_match_success", + "type": "process_md5_task_add", + "input": { + "graph_data": { + "NPU": { + "node": { + "npu_conv2d": { + "input_data": { + "npu_conv2d.data1": { + "md5": "a1b2c3" + }, + "npu_conv2d.data2": { + "md5": "d4e5f6" + } + }, + "output_data": { + "npu_conv2d.out1": { + "md5": "x7y8z9" + } + } + } + } + }, + "Bench": { + "node": { + "bench_conv2d": { + "input_data": { + "bench_conv2d.data1": { + "md5": "a1b2c3" + }, + "bench_conv2d.data2": { + "md5": "d4e5f6" + } + }, + "output_data": { + "bench_conv2d.out1": { + "md5": "x7y8z9" + } + } + } + } + } + }, + "npu_node_name": "npu_conv2d", + "bench_node_name": "bench_conv2d" + }, + "expected": { + "success": true, + "data": { + "precision_error": 1 + } + } + }, + { + "name": "md5_partial_mismatch_failure", + "type": "process_md5_task_add", + "input": { + "graph_data": { + "NPU": { + "node": { + "npu_conv2d": { + "input_data": { + "npu_conv2d.data1": { + "md5": "a1b2c3" + }, + "npu_conv2d.data2": { + "md5": "xxxxxx" + } + } + } + } + }, + "Bench": { + "node": { + "bench_conv2d": { + "input_data": { + "bench_conv2d.data1": { + "md5": "a1b2c3" + }, + "bench_conv2d.data2": { + "md5": "d4e5f6" + } + } + } + } + } + }, + "npu_node_name": "npu_conv2d", + "bench_node_name": "bench_conv2d" + }, + "expected": { + "success": true, + "data": { + "precision_error": 0 + } + } + }, + { + "name": "delete_existing_node_success", + "type": "process_md5_task_delete", + "input": { + "graph_data": { + "npu_match_nodes": { + "npu_conv2d": "bench_conv2d" + }, + "bench_match_nodes": { + "bench_conv2d": "npu_conv2d" + }, + "NPU": { + "node": { + "npu_conv2d": { + "data": { + "precision_index": 1 + }, + "matched_node_link": [ + "bench_conv2d" + ] + } + } + } + }, + "npu_node_name": "npu_conv2d", + "bench_node_name": "bench_conv2d" + }, + "expected": { + "success": true, + "data": {} + } + }, + { + "name": "delete_non_existent_node_failure", + "type": "process_md5_task_delete", + "input": { + "graph_data": { + "NPU": { + "node": { + "npu_conv2d": {} + } + } + }, + "npu_node_name": "npu_conv2d", + "bench_node_name": "bench_conv2d" + }, + "expected": { + "success": false, + "error": "操作失败:删除节点信息失败" + } + }, + { + "name": "missing_input_data_handling", + "type": "process_md5_task_add", + "input": { + "graph_data": { + "NPU": { + "node": { + "npu_conv2d": {} + } + } + }, + "npu_node_name": "npu_conv2d", + "bench_node_name": "bench_conv2d" + }, + "expected": { + "success": true, + "data": { + "precision_error": 0 + } + } + } + ] +} \ No newline at end of file diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/test/integration/service/test_graph_service.py b/plugins/tensorboard-plugins/tb_graph_ascend/test/integration/service/test_graph_service.py new file mode 100644 index 0000000000000000000000000000000000000000..173b741bdfef29e08d9b86e7c4979d0dcfe42c9d --- /dev/null +++ b/plugins/tensorboard-plugins/tb_graph_ascend/test/integration/service/test_graph_service.py @@ -0,0 +1,59 @@ +# Copyright (c) 2025, Huawei Technologies. +# All Rights Reserved. +# +# 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. +# ============================================================================== + +import pytest +from server.app.service.graph_service import GraphService + + +class TestMatchNodesService: + + @staticmethod + def test_service(self, meta_data, operation_config): + op_type = operation_config["type"] + expected = operation_config["expected"] + # 执行操作 + try: + if op_type == "get_node_info": + result = GraphService.get_node_info( + node_info=operation_config["node_info"], + meta_data=meta_data + ) + elif op_type == "add_match_nodes": + result = GraphService.add_match_nodes( + npu_node_name=operation_config["npu_node_name"], + bench_node_name=operation_config["bench_node_name"], + meta_data=meta_data + ) + elif op_type == "delete_match_nodes": + result = GraphService.delete_match_nodes( + npu_node_name=operation_config["npu_node_name"], + bench_node_name=operation_config["bench_node_name"], + meta_data=meta_data + ) + elif op_type == "get_matched_state_list": + result = GraphService.get_matched_state_list( + meta_data=meta_data + ) + elif op_type == "save_data": + result = GraphService.save_data( + meta_data=meta_data + ) + except Exception as e: + result = {"error": type(e).__name__} + + # 验证结果 + assert result == expected, \ + f"Operation {op_type} failed on {operation_config}" diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/test/pytest.ini b/plugins/tensorboard-plugins/tb_graph_ascend/test/pytest.ini new file mode 100644 index 0000000000000000000000000000000000000000..ddb991b93410d7fb17d45cfe3d1e74a9a9b92e3a --- /dev/null +++ b/plugins/tensorboard-plugins/tb_graph_ascend/test/pytest.ini @@ -0,0 +1,9 @@ +[pytest] +testpaths = tests +python_files = test_*.py +norecursedirs = .* venv build dist +addopts = -v --strict-markers +log_cli = true +log_level = DEBUG +markers = + slow: marks tests as slow (deselect with -m 'not slow') \ No newline at end of file diff --git a/plugins/tensorboard-plugins/tb_graph_ascend/test/units/controllers/test_match_nodes_controller.py b/plugins/tensorboard-plugins/tb_graph_ascend/test/units/controllers/test_match_nodes_controller.py new file mode 100644 index 0000000000000000000000000000000000000000..652f9c3b9198b90428b1c71fa35841317435a6e9 --- /dev/null +++ b/plugins/tensorboard-plugins/tb_graph_ascend/test/units/controllers/test_match_nodes_controller.py @@ -0,0 +1,89 @@ +# Copyright (c) 2025, Huawei Technologies. +# All Rights Reserved. +# +# 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. +# ============================================================================== + +import pytest +from server.app.controllers.match_nodes_controller import MatchNodesController +from server.app.utils.graph_utils import GraphUtils + + +class TestMatchNodesController: + + @staticmethod + def test_match_node_controller(ut_test_case): + op_type = ut_test_case.get("type") + input_data = ut_test_case.get('input') + expected = ut_test_case.get("expected") + # 执行操作 + try: + if op_type == "process_md5_task_add": + result = MatchNodesController.process_md5_task_add( + graph_data=input_data.get("graph_data"), + npu_node_name=input_data.get("npu_node_name"), + bench_node_name=input_data.get("bench_node_name"), + ) + elif op_type == "process_md5_task_delete": + result = MatchNodesController.process_md5_task_delete( + graph_data=input_data.get("graph_data"), + npu_node_name=input_data.get("npu_node_name"), + bench_node_name=input_data.get("bench_node_name"), + ) + elif op_type == "process_summary_task_add": + + result = MatchNodesController.process_summary_task_add( + graph_data=input_data.get("graph_data"), + npu_node_name=input_data.get("npu_node_name"), + bench_node_name=input_data.get("bench_node_name"), + ) + elif op_type == "process_summary_task_delete": + result = MatchNodesController.process_summary_task_delete( + npu_data=input_data.get("npu_data"), + bench_data=input_data.get("bench_data"), + npu_node_name=input_data.get("npu_node_name"), + bench_node_name=input_data.get("bench_node_name"), + ) + elif op_type == "calculate_statistical_diff": + result = MatchNodesController.calculate_statistical_diff( + npu_data=input_data.get("npu_data"), + bench_data=input_data.get("bench_data"), + npu_node_name=input_data.get("npu_node_name"), + bench_node_name=input_data.get("bench_node_name"), + ) + elif op_type == "calculate_max_relative_error": + result = MatchNodesController.calculate_max_relative_error( + result=input_data.get("result"), + ) + elif op_type == "calculate_md5_diff": + result = MatchNodesController.calculate_md5_diff( + npu_data=input_data.get("npu_data"), + bench_data=input_data.get("bench_data"), + ) + elif op_type == "update_graph_node_data": + result = MatchNodesController.update_graph_node_data( + graph_npu_node_data=input_data.get("graph_npu_node_data"), + statistical_diff=input_data.get("statistical_diff"), + ) + elif op_type == "delete_matched_node_data": + result = MatchNodesController.delete_matched_node_data( + graph_npu_node_data=input_data.get("graph_npu_node_data"), + ) + else: + return + except Exception as e: + result = {"error": type(e).__name__} + + # 验证结果 + assert result == expected, \ + f"Operation {op_type} failed on {ut_test_case}"