Skip to content

Move compile spec to ArmTester interface #2991

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 131 additions & 33 deletions backends/arm/arm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,118 @@
logger.setLevel(logging.INFO)


class ArmCompileSpecBuilder:
def __init__(self):
self.compile_spec: List[CompileSpec] = []
self.compiler_flags = []
self.output_format = None
self.path_for_intermediates = None
self.permute_nhwc = False

def ethosu_compile_spec(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just do this in __init__ or you can create a @classmethod if you want to write another constructor.

self,
config: str,
system_config: Optional[str] = None,
memory_mode: Optional[str] = None,
extra_flags: Optional[str] = None,
config_ini: Optional[str] = "Arm/vela.ini",
):
"""
Generate compile spec for Ethos-U NPU

Args:
config: Ethos-U accelerator configuration, e.g. ethos-u55-128
system_config: System configuration to select from the Vel
configuration file
memory_mode: Memory mode to select from the Vela configuration file
extra_flags: Extra flags for the Vela compiler
config_ini: Vela configuration file(s) in Python ConfigParser .ini
file format
"""
assert (
self.output_format is None
), f"Output format already set to f{self.output_format}"
self.output_format = "vela"
self.compiler_flags = [
f"--accelerator-config={config}",
f"--config={config_ini}",
]
if system_config is not None:
self.compiler_flags.append(f"--system-config={system_config}")
if memory_mode is not None:
self.compiler_flags.append(f"--memory-mode={memory_mode}")
if extra_flags is not None:
self.compiler_flags.append(extra_flags)

return self

def tosa_compile_spec(self):
"""
Generate compile spec for TOSA flatbuffer output
"""
assert (
self.output_format is None
), f"Output format already set: {self.output_format}"
self.output_format = "tosa"
return self

def dump_intermediate_tosa(self, output_path: str):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can all of these be @property?

So you can just do

builder.dump_intermediate_tosa = True

and

if builder.dump_intermediate_tosa

"""
Output intermediate .tosa file
"""
self.path_for_intermediates = output_path
return self

def set_permute_memory_format(self, set_nhwc_permutation: bool = True):
self.permute_nhwc = set_nhwc_permutation
return self

def build(self):
"""
Generate a list of compile spec objects from the builder
"""
if self.output_format == "vela":
self.compile_spec += [
CompileSpec("output_format", "vela".encode()),
CompileSpec("compile_flags", " ".join(self.compiler_flags).encode()),
]
elif self.output_format == "tosa":
self.compile_spec.append(CompileSpec("output_format", "tosa".encode()))

if self.path_for_intermediates is not None:
self.compile_spec.append(
CompileSpec("debug_tosa_path", self.path_for_intermediates.encode())
)

if self.permute_nhwc:
self.compile_spec.append(
CompileSpec("permute_memory_format", "nhwc".encode())
)

return self.compile_spec


def is_permute_memory(compile_spec: List[CompileSpec]) -> bool:
for spec in compile_spec:
if spec.key == "permute_memory_format":
return spec.value.decode() == "nhwc"
return False


def is_tosa(compile_spec: List[CompileSpec]) -> bool:
for spec in compile_spec:
if spec.key == "output_format":
return spec.value.decode() == "tosa"
return False


def get_intermediate_path(compile_spec: List[CompileSpec]) -> str:
for spec in compile_spec:
if spec.key == "debug_tosa_path":
return spec.value.decode()
return None


def generate_ethosu_compile_spec(
config: str,
permute_memory_to_nhwc: Optional[bool] = None,
Expand All @@ -46,45 +158,31 @@ def generate_ethosu_compile_spec(
extra_flags: Optional[str] = None,
config_ini: Optional[str] = "Arm/vela.ini",
) -> List[CompileSpec]:
"""
Generate compile spec for Ethos-U NPU
"""
compiler_flags = [f"--accelerator-config={config}", f"--config={config_ini}"]
if system_config is not None:
compiler_flags.append(f"--system-config={system_config}")
if memory_mode is not None:
compiler_flags.append(f"--memory-mode={memory_mode}")
if extra_flags is not None:
compiler_flags.append(extra_flags)

compile_spec = [
CompileSpec("output_format", "vela".encode()),
CompileSpec("compile_flags", " ".join(compiler_flags).encode()),
]

if permute_memory_to_nhwc:
compile_spec.append(CompileSpec("permute_memory_format", "nhwc".encode()))

return compile_spec
return (
ArmCompileSpecBuilder()
.ethosu_compile_spec(
config,
system_config=system_config,
memory_mode=memory_mode,
extra_flags=extra_flags,
config_ini=config_ini,
)
.set_permute_memory_format(permute_memory_to_nhwc)
.build()
)


def generate_tosa_compile_spec(
permute_memory_to_nhwc: Optional[bool] = None,
output_path: Optional[str] = None,
) -> List[CompileSpec]:
"""
Generate compile spec for TOSA flatbuffer output
"""

compile_spec = [CompileSpec("output_format", "tosa".encode())]

if permute_memory_to_nhwc:
compile_spec.append(CompileSpec("permute_memory_format", "nhwc".encode()))

if output_path is not None:
compile_spec.append(CompileSpec("debug_tosa_path", output_path.encode()))

return compile_spec
return (
ArmCompileSpecBuilder()
.tosa_compile_spec()
.set_permute_memory_format(permute_memory_to_nhwc)
.dump_intermediate_tosa(output_path)
.build()
)


@final
Expand Down
36 changes: 36 additions & 0 deletions backends/arm/test/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
# LICENSE file in the root directory of this source tree.

import shutil
import tempfile

from executorch.backends.arm.arm_backend import ArmCompileSpecBuilder

# TODO: fixme! These globs are a temporary workaround. Reasoning:
# Running the jobs in _unittest.yml will not work since that environment doesn't
Expand All @@ -13,3 +16,36 @@
# should be installed in the CI env.
TOSA_REF_MODEL_INSTALLED = shutil.which("tosa_reference_model")
VELA_INSTALLED = shutil.which("vela")


def get_tosa_compile_spec(permute_memory_to_nhwc=False, custom_path=None):
"""
Default compile spec for TOSA tests.
"""
intermediate_path = custom_path or tempfile.mkdtemp(prefix="arm_tosa_")
compile_spec = (
ArmCompileSpecBuilder()
.tosa_compile_spec()
.set_permute_memory_format(permute_memory_to_nhwc)
.dump_intermediate_tosa(intermediate_path)
.build()
)
return compile_spec


def get_u55_compile_spec(permute_memory_to_nhwc=False):
"""
Default compile spec for Ethos-U55 tests.
"""
compile_spec = (
ArmCompileSpecBuilder()
.ethosu_compile_spec(
"ethos-u55-128",
system_config="Ethos_U55_High_End_Embedded",
memory_mode="Shared_Sram",
extra_flags=None,
)
.set_permute_memory_format(permute_memory_to_nhwc)
.build()
)
return compile_spec
11 changes: 5 additions & 6 deletions backends/arm/test/misc/test_debug_feats.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
import unittest

import torch
from executorch.backends.arm.test.test_models import TosaProfile
from executorch.backends.arm.test.tester.arm_tester import ArmBackendSelector, ArmTester
from executorch.backends.arm.test import common

from executorch.backends.arm.test.tester.arm_tester import ArmTester

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
Expand Down Expand Up @@ -45,8 +46,7 @@ def _tosa_MI_pipeline(self, module: torch.nn.Module, dump_file=None):
ArmTester(
module,
inputs=module.get_inputs(),
profile=TosaProfile.MI,
backend=ArmBackendSelector.TOSA,
compile_spec=common.get_tosa_compile_spec(),
)
.export()
.to_edge()
Expand All @@ -60,8 +60,7 @@ def _tosa_BI_pipeline(self, module: torch.nn.Module, dump_file=None):
ArmTester(
module,
inputs=module.get_inputs(),
profile=TosaProfile.BI,
backend=ArmBackendSelector.TOSA,
compile_spec=common.get_tosa_compile_spec(),
)
.quantize()
.export()
Expand Down
18 changes: 6 additions & 12 deletions backends/arm/test/models/test_mobilenet_v2_arm.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
import torch
import torchvision.models as models
from executorch.backends.arm.test import common
from executorch.backends.arm.test.test_models import TosaProfile
from executorch.backends.arm.test.tester.arm_tester import ArmBackendSelector, ArmTester

from executorch.backends.arm.test.tester.arm_tester import ArmTester
from executorch.backends.xnnpack.test.tester.tester import Quantize
from torchvision.models.mobilenetv2 import MobileNet_V2_Weights

Expand Down Expand Up @@ -46,9 +46,7 @@ def test_mv2_tosa_MI(self):
ArmTester(
self.mv2,
inputs=self.model_inputs,
profile=TosaProfile.MI,
backend=ArmBackendSelector.TOSA,
permute_memory_to_nhwc=True,
compile_spec=common.get_tosa_compile_spec(permute_memory_to_nhwc=True),
)
.export()
.to_edge()
Expand All @@ -62,9 +60,7 @@ def test_mv2_tosa_BI(self):
ArmTester(
self.mv2,
inputs=self.model_inputs,
profile=TosaProfile.BI,
backend=ArmBackendSelector.TOSA,
permute_memory_to_nhwc=True,
compile_spec=common.get_tosa_compile_spec(permute_memory_to_nhwc=True),
)
.quantize(Quantize(calibrate=False))
.export()
Expand All @@ -74,7 +70,7 @@ def test_mv2_tosa_BI(self):
.to_executorch()
)
if common.TOSA_REF_MODEL_INSTALLED:
tester.run_method().compare_outputs()
tester.run_method_and_compare_outputs()
else:
logger.warning(
"TOSA ref model tool not installed, skip numerical correctness tests"
Expand All @@ -89,9 +85,7 @@ def test_mv2_u55_BI(self):
ArmTester(
self.mv2,
inputs=self.model_inputs,
profile=TosaProfile.BI,
backend=ArmBackendSelector.ETHOS_U55,
permute_memory_to_nhwc=True,
compile_spec=common.get_u55_compile_spec(permute_memory_to_nhwc=True),
)
.quantize(Quantize(calibrate=False))
.export()
Expand Down
19 changes: 7 additions & 12 deletions backends/arm/test/ops/test_add.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

import torch
from executorch.backends.arm.test import common
from executorch.backends.arm.test.test_models import TosaProfile
from executorch.backends.arm.test.tester.arm_tester import ArmBackendSelector, ArmTester

from executorch.backends.arm.test.tester.arm_tester import ArmTester
from parameterized import parameterized

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -57,9 +57,7 @@ def _test_add_tosa_MI_pipeline(
ArmTester(
module,
inputs=test_data,
profile=TosaProfile.MI,
backend=ArmBackendSelector.TOSA,
permute_memory_to_nhwc=False,
compile_spec=common.get_tosa_compile_spec(),
)
.export()
.check_count({"torch.ops.aten.add.Tensor": 1})
Expand All @@ -70,7 +68,7 @@ def _test_add_tosa_MI_pipeline(
.to_executorch()
)
if common.TOSA_REF_MODEL_INSTALLED:
tester.run_method().compare_outputs()
tester.run_method_and_compare_outputs()
else:
logger.warning(
"TOSA ref model tool not installed, skip numerical correctness tests"
Expand All @@ -83,9 +81,7 @@ def _test_add_tosa_BI_pipeline(
ArmTester(
module,
inputs=test_data,
profile=TosaProfile.BI,
backend=ArmBackendSelector.TOSA,
permute_memory_to_nhwc=False,
compile_spec=common.get_tosa_compile_spec(),
)
.quantize()
.export()
Expand All @@ -98,7 +94,7 @@ def _test_add_tosa_BI_pipeline(
)

if common.TOSA_REF_MODEL_INSTALLED:
tester.run_method().compare_outputs(qtol=1)
tester.run_method_and_compare_outputs(qtol=1)
else:
logger.warning(
"TOSA ref model tool not installed, skip numerical correctness tests"
Expand All @@ -111,8 +107,7 @@ def _test_add_u55_BI_pipeline(
ArmTester(
module,
inputs=test_data,
profile=TosaProfile.BI,
backend=ArmBackendSelector.ETHOS_U55,
compile_spec=common.get_u55_compile_spec(),
)
.quantize()
.export()
Expand Down
Loading