From b27150ea081145c572343494ac3a6d78a50bb8a3 Mon Sep 17 00:00:00 2001 From: Oscar Andersson Date: Mon, 9 Feb 2026 14:59:54 +0100 Subject: [PATCH] Arm backend: Add infer_shapes to test infra. For tosa-flatbuffers with unresolved shapes, we need to invoke infer_shapes to resolve shapes before invoking tosa_reference_model. This change will make sure that infer_shapes is called before reference_model when shape-extension is enabled and any input has a symbolic dimension. Signed-off-by: Oscar Andersson Change-Id: I3a9aa6fe8013e319fa974b531ad44afd9b1ce829 --- backends/arm/test/misc/test_runner_utils.py | 116 +++++ .../misc/test_tosa_shape_node_visitors.py | 402 ++++++++++++++++++ backends/arm/test/runner_utils.py | 142 +++++-- 3 files changed, 636 insertions(+), 24 deletions(-) diff --git a/backends/arm/test/misc/test_runner_utils.py b/backends/arm/test/misc/test_runner_utils.py index 3c78b21e008..54d41548a22 100644 --- a/backends/arm/test/misc/test_runner_utils.py +++ b/backends/arm/test/misc/test_runner_utils.py @@ -3,9 +3,13 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import json from pathlib import Path +from types import SimpleNamespace from typing import Any, cast +import numpy as np +import torch from executorch.backends.arm.test import runner_utils @@ -113,3 +117,115 @@ def test_get_elf_path_accepts_nested_runner_output(monkeypatch, tmp_path: Path) monkeypatch.setattr(runner_utils, "_elf_search_roots", lambda: [tmp_path]) assert runner_utils.get_elf_path("corstone-300") == str(elf_path) + + +def test_shape_inference_json_uses_tosa_input_layout(tmp_path: Path) -> None: + test_case_path = tmp_path / "test_case.json" + artifact_path = tmp_path / "model.tosa" + input_tensor = torch.randn(1, 3, 4, 5).to(memory_format=torch.channels_last) + + runner_utils.TosaReferenceModelDispatch()._generate_shape_inference_json( + b"", + artifact_path, + test_case_path, + ["input"], + (input_tensor,), + ) + + test_case = json.loads(test_case_path.read_text(encoding="utf-8")) + + assert test_case == { + "tosa_file": str(artifact_path), + "shapes": {"input": [1, 4, 5, 3]}, + } + + +def test_numpy_to_torch_tensor_converts_dynamic_nhwc_output(monkeypatch) -> None: + symbolic_dim = object() + output_tensor = SimpleNamespace( + shape=(1, 3, symbolic_dim, 5), + dtype=torch.float32, + dim_order=lambda: runner_utils.NHWC_ORDER, + ) + monkeypatch.setattr( + runner_utils, "get_first_fake_tensor", lambda output_node: output_tensor + ) + array = np.arange(60, dtype=np.float32).reshape(1, 4, 5, 3) + + result = runner_utils.numpy_to_torch_tensor(array, cast(Any, object())) + + assert result.shape == (1, 3, 4, 5) + assert result.is_contiguous(memory_format=torch.channels_last) + torch.testing.assert_close(result, torch.from_numpy(array).permute(0, 3, 1, 2)) + + +def test_numpy_to_torch_tensor_converts_dynamic_nnhwc_output(monkeypatch) -> None: + symbolic_dim = object() + output_tensor = SimpleNamespace( + shape=(1, 2, 3, symbolic_dim, 5), + dtype=torch.float32, + dim_order=lambda: runner_utils.NNHWC_ORDER, + ) + monkeypatch.setattr( + runner_utils, "get_first_fake_tensor", lambda output_node: output_tensor + ) + array = np.arange(120, dtype=np.float32).reshape(1, 2, 4, 5, 3) + + result = runner_utils.numpy_to_torch_tensor(array, cast(Any, object())) + + assert result.shape == (1, 2, 3, 4, 5) + assert result.dim_order() == runner_utils.NNHWC_ORDER + torch.testing.assert_close(result, torch.from_numpy(array).permute(0, 1, 4, 2, 3)) + + +def _program_with_user_input(name: str) -> SimpleNamespace: + return SimpleNamespace( + graph_signature=SimpleNamespace(user_inputs=[name]), + graph=SimpleNamespace(nodes=[SimpleNamespace(op="placeholder", name=name)]), + ) + + +def test_user_inputs_need_shape_inference_rejects_static_input(monkeypatch) -> None: + monkeypatch.setattr( + runner_utils, + "get_first_fake_tensor", + lambda node: SimpleNamespace(shape=(1, 2)), + ) + + assert not runner_utils.user_inputs_need_shape_inference( + cast(Any, _program_with_user_input("input")) + ) + + +def test_user_inputs_need_shape_inference_accepts_symbolic_input(monkeypatch) -> None: + symbolic_dim = object() + monkeypatch.setattr( + runner_utils, + "get_first_fake_tensor", + lambda node: SimpleNamespace(shape=(1, symbolic_dim)), + ) + + assert runner_utils.user_inputs_need_shape_inference( + cast(Any, _program_with_user_input("input")) + ) + + +def test_user_inputs_need_shape_inference_ignores_non_user_inputs(monkeypatch) -> None: + program = SimpleNamespace( + graph_signature=SimpleNamespace(user_inputs=["input"]), + graph=SimpleNamespace( + nodes=[ + SimpleNamespace(op="placeholder", name="input"), + SimpleNamespace(op="placeholder", name="param"), + ] + ), + ) + + def fake_tensor(node): + if node.name == "input": + return SimpleNamespace(shape=(1, 2)) + return SimpleNamespace(shape=(1, object())) + + monkeypatch.setattr(runner_utils, "get_first_fake_tensor", fake_tensor) + + assert not runner_utils.user_inputs_need_shape_inference(cast(Any, program)) diff --git a/backends/arm/test/misc/test_tosa_shape_node_visitors.py b/backends/arm/test/misc/test_tosa_shape_node_visitors.py index 01c436a1674..d88424599e8 100644 --- a/backends/arm/test/misc/test_tosa_shape_node_visitors.py +++ b/backends/arm/test/misc/test_tosa_shape_node_visitors.py @@ -3,15 +3,20 @@ # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. +import math +import shutil +from pathlib import Path from types import SimpleNamespace from typing import Any, cast import pytest +import torch import tosa_serializer as ts # type: ignore from executorch.backends.arm.operators.node_visitor import ( get_node_visitors, NodeVisitor, ) +from executorch.backends.arm.test.runner_utils import TosaReferenceModelDispatch from executorch.backends.arm.tosa.mapping import TosaArg from executorch.backends.arm.tosa.specification import TosaSpecification from torch.fx import Node @@ -100,6 +105,125 @@ def _define_node( ) +def _serialized_tensor_shapes(tosa_buffer: bytes) -> dict[str, list[int]]: + graph = TosaGraph.TosaGraph.GetRootAsTosaGraph(tosa_buffer, 0) + block = graph.Regions(0).Blocks(0) + return { + block.Tensors(index) + .Name() + .decode(): [ + block.Tensors(index).Shape(dim) + for dim in range(block.Tensors(index).ShapeLength()) + ] + for index in range(block.TensorsLength()) + } + + +def _add_const_shape( + visitors: dict[str, NodeVisitor], + tosa_graph: ts.TosaSerializer, + name: str, + values: list[int], +) -> str: + _define_node( + visitors["tosa.CONST_SHAPE.default"], + SimpleNamespace(name=name, meta={"val": values}, kwargs={}), + tosa_graph, + [SimpleNamespace(special=values)], + SimpleNamespace(name=name, shape=(len(values),)), + ) + return name + + +def _add_const_tensor( + tosa_graph: ts.TosaSerializer, + name: str, + values: list[int], +) -> str: + tosa_graph.addConst([len(values)], ts.DType.INT32, values, name=name) + return name + + +def _add_shape_op( + visitors: dict[str, NodeVisitor], + tosa_graph: ts.TosaSerializer, + target: str, + name: str, + input_names: list[str], + *, + output_rank: int = 1, + kwargs: dict[str, object] | None = None, + concat_inputs: bool = False, +) -> str: + if concat_inputs: + inputs = [ + SimpleNamespace(special=[SimpleNamespace(name=n) for n in input_names]) + ] + else: + inputs = [SimpleNamespace(name=n) for n in input_names] + _define_node( + visitors[target], + SimpleNamespace(name=name, kwargs=kwargs or {}), + tosa_graph, + inputs, + SimpleNamespace(name=name, shape=(output_rank,)), + ) + return name + + +def _add_reshape_from_shape( + tosa_graph: ts.TosaSerializer, + input_shapes: dict[str, list[int]], + expected_outputs: dict[str, list[int]], + name: str, + shape_name: str, + expected_shape: list[int], +) -> None: + data_name = f"data_{name}" + output_name = f"out_{name}" + tosa_graph.addInputTensor( + ts.TosaSerializerTensor(data_name, [-1], ts.DType.INT32, data=None) + ) + input_shapes[data_name] = [math.prod(expected_shape)] + tosa_graph.currRegion.currBasicBlock.addTensor( + output_name, + [-1] * len(expected_shape), + ts.DType.INT32, + ) + attr = ts.TosaSerializerAttribute() + attr.ReshapeAttribute() + tosa_graph.addOperator( + ts.Op.RESHAPE, + [data_name, shape_name], + [output_name], + attr, + ) + tosa_graph.addOutputTensor( + tosa_graph.currRegion.currBasicBlock.tensors[output_name] + ) + expected_outputs[output_name] = expected_shape + + +def _run_infer_shapes( + infer_shapes: str, + tosa_graph: ts.TosaSerializer, + input_shapes: dict[str, list[int]], + tmp_path: Path, +) -> dict[str, list[int]]: + input_names = list(input_shapes) + inputs = tuple( + torch.empty(shape, dtype=torch.int32) for shape in input_shapes.values() + ) + resolved_buffer = TosaReferenceModelDispatch()._run_infer_shapes( + tosa_graph.serialize(), + input_names, + inputs, + tmp_path, + infer_shapes, + ) + return _serialized_tensor_shapes(resolved_buffer) + + def test_all_tosa_shape_ops_have_node_visitors() -> None: visitors = get_node_visitors(_shape_spec()) @@ -179,3 +303,281 @@ def test_dim_shape_node_visitor_serializes_operator() -> None: ) assert _serialized_op_codes(tosa_graph) == [ts.Op.DIM] + + +def test_shape_node_visitors_round_trip_through_infer_shapes(tmp_path: Path) -> None: + infer_shapes = shutil.which("infer_shapes") + if infer_shapes is None: + pytest.skip("infer_shapes binary is not available") + + visitors = get_node_visitors(_shape_spec()) + tosa_graph = _serializer() + input_shapes: dict[str, list[int]] = {} + expected_outputs: dict[str, list[int]] = {} + + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "const", + _add_const_shape(visitors, tosa_graph, "shape_const", [6]), + [6], + ) + + tosa_graph.addInputTensor( + ts.TosaSerializerTensor("dim_source", [-1], ts.DType.INT32, data=None) + ) + input_shapes["dim_source"] = [6] + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "dim", + _add_shape_op( + visitors, + tosa_graph, + "tosa.DIM.default", + "shape_dim", + ["dim_source"], + kwargs={"axis": 0}, + ), + [6], + ) + + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "add", + _add_shape_op( + visitors, + tosa_graph, + "tosa.ADD_SHAPE.default", + "shape_add", + [ + _add_const_shape(visitors, tosa_graph, "add_lhs", [4]), + _add_const_shape(visitors, tosa_graph, "add_rhs", [2]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "sub", + _add_shape_op( + visitors, + tosa_graph, + "tosa.SUB_SHAPE.default", + "shape_sub", + [ + _add_const_shape(visitors, tosa_graph, "sub_lhs", [8]), + _add_const_shape(visitors, tosa_graph, "sub_rhs", [2]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "mul", + _add_shape_op( + visitors, + tosa_graph, + "tosa.MUL_SHAPE.default", + "shape_mul", + [ + _add_const_shape(visitors, tosa_graph, "mul_lhs", [3]), + _add_const_shape(visitors, tosa_graph, "mul_rhs", [2]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "div_ceil", + _add_shape_op( + visitors, + tosa_graph, + "tosa.DIV_CEIL_SHAPE.default", + "shape_div_ceil", + [ + _add_const_shape(visitors, tosa_graph, "div_ceil_lhs", [11]), + _add_const_shape(visitors, tosa_graph, "div_ceil_rhs", [2]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "div_floor", + _add_shape_op( + visitors, + tosa_graph, + "tosa.DIV_FLOOR_SHAPE.default", + "shape_div_floor", + [ + _add_const_shape(visitors, tosa_graph, "div_floor_lhs", [13]), + _add_const_shape(visitors, tosa_graph, "div_floor_rhs", [2]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "mod", + _add_shape_op( + visitors, + tosa_graph, + "tosa.MOD_SHAPE.default", + "shape_mod", + [ + _add_const_shape(visitors, tosa_graph, "mod_lhs", [20]), + _add_const_shape(visitors, tosa_graph, "mod_rhs", [7]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "max", + _add_shape_op( + visitors, + tosa_graph, + "tosa.MAX_SHAPE.default", + "shape_max", + [ + _add_const_shape(visitors, tosa_graph, "max_lhs", [6]), + _add_const_shape(visitors, tosa_graph, "max_rhs", [2]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "min", + _add_shape_op( + visitors, + tosa_graph, + "tosa.MIN_SHAPE.default", + "shape_min", + [ + _add_const_shape(visitors, tosa_graph, "min_lhs", [6]), + _add_const_shape(visitors, tosa_graph, "min_rhs", [8]), + ], + ), + [6], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "exp2", + _add_shape_op( + visitors, + tosa_graph, + "tosa.EXP2_SHAPE.default", + "shape_exp2", + [_add_const_shape(visitors, tosa_graph, "exp2_in", [3])], + ), + [8], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "log2_ceil", + _add_shape_op( + visitors, + tosa_graph, + "tosa.LOG2_CEIL_SHAPE.default", + "shape_log2_ceil", + [_add_const_shape(visitors, tosa_graph, "log2_ceil_in", [9])], + ), + [4], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "log2_floor", + _add_shape_op( + visitors, + tosa_graph, + "tosa.LOG2_FLOOR_SHAPE.default", + "shape_log2_floor", + [_add_const_shape(visitors, tosa_graph, "log2_floor_in", [15])], + ), + [3], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "concat", + _add_shape_op( + visitors, + tosa_graph, + "tosa.CONCAT_SHAPE.default", + "shape_concat", + [ + _add_const_shape(visitors, tosa_graph, "concat_lhs", [2]), + _add_const_shape(visitors, tosa_graph, "concat_rhs", [4]), + ], + output_rank=2, + concat_inputs=True, + ), + [2, 4], + ) + _add_reshape_from_shape( + tosa_graph, + input_shapes, + expected_outputs, + "slice", + _add_shape_op( + visitors, + tosa_graph, + "tosa.SLICE_SHAPE.default", + "shape_slice", + [ + _add_const_shape(visitors, tosa_graph, "slice_input", [2, 6, 4]), + _add_const_tensor(tosa_graph, "slice_start", [1]), + _add_const_tensor(tosa_graph, "slice_size", [1]), + ], + ), + [6], + ) + _add_shape_op( + visitors, + tosa_graph, + "tosa.ASSERT_EQUAL_SHAPE.default", + "shape_assert", + [ + _add_const_shape(visitors, tosa_graph, "assert_lhs", [6]), + _add_const_shape(visitors, tosa_graph, "assert_rhs", [6]), + ], + kwargs={"allow_broadcast": False}, + ) + + resolved_shapes = _run_infer_shapes( + infer_shapes, + tosa_graph, + input_shapes, + tmp_path, + ) + + assert { + name: resolved_shapes[name] for name in expected_outputs + } == expected_outputs diff --git a/backends/arm/test/runner_utils.py b/backends/arm/test/runner_utils.py index ff26d17ee13..9a63452e325 100644 --- a/backends/arm/test/runner_utils.py +++ b/backends/arm/test/runner_utils.py @@ -2,10 +2,10 @@ # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. - import importlib.resources as _resources import json import logging +import numbers import os import re import shutil @@ -14,13 +14,11 @@ import tempfile from collections.abc import Iterable from pathlib import Path - from types import NoneType from typing import Any, cast, Dict, List, Optional, Tuple import executorch.backends.arm.test as arm_test_package import executorch.backends.arm.tosa.schemas as tosa_schemas_package - import numpy as np import torch from executorch.backends.arm._passes.arm_pass_utils import get_first_fake_tensor @@ -31,7 +29,6 @@ NNHWC_INVERSE_ORDER, NNHWC_ORDER, ) - from executorch.backends.arm.ethosu import EthosUCompileSpec from executorch.backends.arm.tosa.compile_spec import TosaCompileSpec from executorch.backends.arm.tosa.specification import Tosa_1_00, TosaSpecification @@ -43,7 +40,6 @@ from executorch.exir import ExecutorchProgramManager, ExportedProgram from executorch.exir.lowered_backend_module import LoweredBackendModule from torch.fx.node import Node - from torch.overrides import TorchFunctionMode from tosa.TosaGraph import TosaGraph # type: ignore[import-not-found, import-untyped] @@ -79,6 +75,7 @@ "corstone-320", "vkml_emulation_layer", } +INFER_SHAPES_PATH = "infer_shapes" class QuantizationParams: @@ -102,7 +99,9 @@ def __init__( self.dtype = dtype -def get_input_names(program: ExportedProgram) -> list[str]: +def get_input_names( + program: ExportedProgram, is_lowered_module: bool = False +) -> list[str]: """Get a list[str] with the names of the inputs to this model. Args: @@ -111,7 +110,15 @@ def get_input_names(program: ExportedProgram) -> list[str]: A list of strings with the names of the model input. """ - return [spec.arg.name for spec in program.graph_signature.input_specs] + + if not is_lowered_module: + return [spec.arg.name for spec in program.graph_signature.input_specs] + else: + return [ + user_input + for user_input in program.graph_signature.user_inputs + if isinstance(user_input, str) + ] def get_input_quantization_params( @@ -204,25 +211,59 @@ def torch_tensor_to_numpy(tensor: torch.Tensor) -> np.ndarray: return tensor.numpy() +def torch_tensor_to_tosa_shape(tensor: torch.Tensor) -> list[int]: + shape = list(tensor.shape) + dim_order = tensor.dim_order() + if dim_order in (NHWC_ORDER, NNHWC_ORDER): + shape = [shape[index] for index in dim_order] + return [int(dim) for dim in shape] + + +def user_inputs_need_shape_inference(program: ExportedProgram) -> bool: + user_inputs = { + user_input + for user_input in program.graph_signature.user_inputs + if isinstance(user_input, str) + } + for node in program.graph.nodes: + if node.op != "placeholder" or node.name not in user_inputs: + continue + input_tensor = get_first_fake_tensor(node) + if any(not isinstance(dim, numbers.Integral) for dim in input_tensor.shape): + return True + return False + + def numpy_to_torch_tensor(array: np.ndarray, output_node: Node) -> torch.Tensor: output_tensor = get_first_fake_tensor(output_node) shape = output_tensor.shape dim_order = output_tensor.dim_order() + + def is_concrete_shape(shape_like) -> bool: + return all(isinstance(dim, numbers.Integral) for dim in shape_like) + + def to_torch_tensor() -> torch.Tensor: + if array.dtype.type is np.void: + # If dtype is void, "cheat" and use the output_tensor dtype. + return torch.frombuffer(array, dtype=output_tensor.dtype) + return torch.from_numpy(array) + if dim_order == NHWC_ORDER: - shape_with_dim_order = [shape[i] for i in NHWC_ORDER] - tensor = torch.from_numpy(array).reshape(shape_with_dim_order) + tensor = to_torch_tensor() + if is_concrete_shape(shape): + tensor = tensor.reshape([shape[i] for i in NHWC_ORDER]) return tensor.permute(NHWC_INVERSE_ORDER).to(memory_format=torch.channels_last) elif dim_order == NNHWC_ORDER: - shape_with_dim_order = [shape[i] for i in NNHWC_ORDER] - tensor = torch.from_numpy(array).reshape(shape_with_dim_order) - return tensor.permute(NNHWC_INVERSE_ORDER).to(memory_format=torch.channels_last) + tensor = to_torch_tensor() + if is_concrete_shape(shape): + tensor = tensor.reshape([shape[i] for i in NNHWC_ORDER]) + return tensor.permute(NNHWC_INVERSE_ORDER) else: - if array.dtype.type is np.void: - # If dtype is void, "cheat" and use the output_tensor dtype. - tensor = torch.frombuffer(array, dtype=output_tensor.dtype) - else: - tensor = torch.from_numpy(array) - return tensor.reshape(shape) + tensor = to_torch_tensor() + + if is_concrete_shape(shape): + return tensor.reshape(shape) + return tensor class TosaReferenceModelDispatch(TorchFunctionMode): @@ -234,12 +275,65 @@ def __init__(self): self.ran_tosa_dispatch = False super().__init__() + def _generate_shape_inference_json( + self, + tosa_buffer: bytes, + artifact_path: Path, + test_case_path: Path, + input_names: list[str], + inputs: Tuple[torch.Tensor, ...], + ): + shapes = dict( + zip(input_names, [torch_tensor_to_tosa_shape(input) for input in inputs]) + ) + with open(test_case_path, "w", encoding="utf-8") as f: + json.dump({"tosa_file": str(artifact_path), "shapes": shapes}, f, indent=2) + + def _run_infer_shapes( + self, + tosa_buffer: bytes, + input_names: list[str], + inputs: Tuple[torch.Tensor, ...], + temp_dir_path: Path, + infer_shapes_path: str = INFER_SHAPES_PATH, + ) -> bytes: + model_suffix = "model.tosa" + tosa_sym_int_model = temp_dir_path / model_suffix + tosa_sym_int_model.write_bytes(tosa_buffer) + test_case_file = temp_dir_path / "test_case.json" + + self._generate_shape_inference_json( + tosa_buffer, tosa_sym_int_model, test_case_file, input_names, inputs + ) + subprocess.run( + [ + infer_shapes_path, + f"{test_case_file}", + ], + check=True, + capture_output=True, + text=True, + ) # nosec + resolved_file = temp_dir_path / f"resolved_{model_suffix}" + with open(resolved_file, "rb") as f: + return f.read() + def _tosa_dispatch(self, lowered_backend_module: LoweredBackendModule, inputs): tosa_buffer = lowered_backend_module.processed_bytes compile_spec = TosaCompileSpec._from_list(lowered_backend_module.compile_specs) - + tosa_spec = compile_spec.tosa_spec output_node = lowered_backend_module.original_module.graph.output_node() - return run_tosa_graph(tosa_buffer, compile_spec.tosa_spec, inputs, output_node) + if tosa_spec.support_extension("shape") and user_inputs_need_shape_inference( + lowered_backend_module.original_module + ): + input_names = get_input_names(lowered_backend_module.original_module, True) + # Generate json file for shape inference extension, which is required by the reference model. + with tempfile.TemporaryDirectory() as temp_dir: + tosa_buffer = self._run_infer_shapes( + tosa_buffer, input_names, inputs, Path(temp_dir) + ) + + return run_tosa_graph(tosa_buffer, tosa_spec, inputs, output_node) def __exit__(self, exc_type, exc_val, exc_tb): super().__exit__(exc_type, exc_val, exc_tb) @@ -282,7 +376,7 @@ def __torch_function__(self, func, types, args=..., kwargs=None): def run_target( executorch_program_manager: ExecutorchProgramManager, - inputs: Tuple[torch.Tensor], + inputs: Tuple[torch.Tensor, ...], intermediate_path: str | Path, target_board: str, elf_path: str | Path, @@ -310,7 +404,7 @@ def run_target( def save_inputs_to_file( exported_program: ExportedProgram, - inputs: Tuple[torch.Tensor], + inputs: Tuple[torch.Tensor, ...], intermediate_path: str | Path, ): input_file_paths: list[str] = [] @@ -342,7 +436,7 @@ def get_output_from_file( def run_vkml_emulation_layer( executorch_program_manager: ExecutorchProgramManager, - inputs: Tuple[torch.Tensor], + inputs: Tuple[torch.Tensor, ...], intermediate_path: str | Path, elf_path: str | Path, ): @@ -390,7 +484,7 @@ def run_vkml_emulation_layer( def run_corstone( executorch_program_manager: ExecutorchProgramManager, - inputs: Tuple[torch.Tensor], + inputs: Tuple[torch.Tensor, ...], intermediate_path: str | Path, target_board: str, elf_path: str | Path,