Skip to content

Commit f4bd7de

Browse files
Arm backend: Add partial vulkan runtime support for VgfPipeline (#13471)
- Add XfailIfNoVKMLEmulationLayer for VGF unit tests - Introduce run_target_board() to unify FVP and VKML execution paths - Implement run_vkml_emulation_layer() with NotImplementedError for output parsing, as the VGF runtime doesn't dump the output tensors in a usable format at the moment. Signed-off-by: Yufeng Shi <[email protected]>
1 parent f4fa279 commit f4bd7de

File tree

5 files changed

+184
-50
lines changed

5 files changed

+184
-50
lines changed

backends/arm/test/common.py

Lines changed: 47 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
corstone300_installed,
2020
corstone320_installed,
2121
model_converter_installed,
22+
vkml_emulation_layer_installed,
2223
)
2324
from executorch.backends.arm.tosa_specification import TosaSpecification
2425
from executorch.exir.backend.compile_spec_schema import CompileSpec
@@ -90,39 +91,6 @@ def get_tosa_compile_spec_unbuilt(
9091
return compile_spec_builder
9192

9293

93-
def get_vgf_compile_spec(
94-
tosa_spec: str | TosaSpecification,
95-
compiler_flags: Optional[str] = "",
96-
custom_path=None,
97-
) -> list[CompileSpec]:
98-
"""
99-
Default compile spec for VGF tests.
100-
"""
101-
return get_vgf_compile_spec_unbuilt(tosa_spec, compiler_flags, custom_path).build()
102-
103-
104-
def get_vgf_compile_spec_unbuilt(
105-
tosa_spec: str | TosaSpecification,
106-
compiler_flags: Optional[str] = "",
107-
custom_path=None,
108-
) -> ArmCompileSpecBuilder:
109-
"""Get the ArmCompileSpecBuilder for the default VGF tests, to modify
110-
the compile spec before calling .build() to finalize it.
111-
"""
112-
if not custom_path:
113-
custom_path = maybe_get_tosa_collate_path()
114-
115-
if custom_path is not None:
116-
os.makedirs(custom_path, exist_ok=True)
117-
compile_spec_builder = (
118-
ArmCompileSpecBuilder()
119-
.vgf_compile_spec(tosa_spec, compiler_flags)
120-
.dump_intermediate_artifacts_to(custom_path)
121-
)
122-
123-
return compile_spec_builder
124-
125-
12694
def get_u55_compile_spec(
12795
macs: int = 128,
12896
system_config: str = "Ethos_U55_High_End_Embedded",
@@ -165,6 +133,17 @@ def get_u85_compile_spec(
165133
).build()
166134

167135

136+
def get_vgf_compile_spec(
137+
tosa_spec: str | TosaSpecification,
138+
compiler_flags: Optional[str] = "",
139+
custom_path=None,
140+
) -> list[CompileSpec]:
141+
"""
142+
Default compile spec for VGF tests.
143+
"""
144+
return get_vgf_compile_spec_unbuilt(tosa_spec, compiler_flags, custom_path).build()
145+
146+
168147
def get_u55_compile_spec_unbuilt(
169148
macs: int,
170149
system_config: str,
@@ -228,6 +207,33 @@ def get_u85_compile_spec_unbuilt(
228207
return compile_spec # type: ignore[return-value]
229208

230209

210+
def get_vgf_compile_spec_unbuilt(
211+
tosa_spec: str | TosaSpecification,
212+
compiler_flags: Optional[str] = "",
213+
custom_path=None,
214+
) -> ArmCompileSpecBuilder:
215+
"""Get the ArmCompileSpecBuilder for the default VGF tests, to modify
216+
the compile spec before calling .build() to finalize it.
217+
"""
218+
if "FP" in repr(tosa_spec):
219+
artifact_path = custom_path or tempfile.mkdtemp(prefix="arm_vgf_fp_")
220+
elif "INT" in repr(tosa_spec):
221+
artifact_path = custom_path or tempfile.mkdtemp(prefix="arm_vgf_int_")
222+
else:
223+
raise ValueError(f"Unsupported vgf compile_spec: {repr(tosa_spec)}")
224+
225+
if not os.path.exists(artifact_path):
226+
os.makedirs(artifact_path, exist_ok=True)
227+
228+
compile_spec_builder = (
229+
ArmCompileSpecBuilder()
230+
.vgf_compile_spec(tosa_spec, compiler_flags)
231+
.dump_intermediate_artifacts_to(artifact_path)
232+
)
233+
234+
return compile_spec_builder
235+
236+
231237
XfailIfNoCorstone300 = pytest.mark.xfail(
232238
condition=not (
233239
corstone300_installed() and arm_executor_runner_exists("corstone-300")
@@ -251,7 +257,14 @@ def get_u85_compile_spec_unbuilt(
251257
raises=FileNotFoundError,
252258
reason="Did not find model-converter on path",
253259
)
254-
"""Xfails a test if model-converter is not installed"""
260+
"""Skips a test if model-converter is not installed"""
261+
262+
XfailfNoVKMLEmulationLayer = pytest.mark.xfail(
263+
condition=not (vkml_emulation_layer_installed()),
264+
raises=TypeError,
265+
reason="VKML environment is not set properly or executor_runner path is misused",
266+
)
267+
"""Xfails a test if VKML Emulation Layer is not installed"""
255268

256269
xfail_type = str | tuple[str, type[Exception]]
257270

backends/arm/test/ops/test_add.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from typing import Tuple
99

10+
import pytest
1011
import torch
1112
from executorch.backends.arm.quantizer import arm_quantizer
1213
from executorch.backends.arm.test import common, conftest
@@ -187,9 +188,19 @@ def test_add_tensor_u85_INT_2(test_data: input_t2):
187188

188189
@common.parametrize("test_data", Add.test_data)
189190
@common.SkipIfNoModelConverter
191+
@common.XfailfNoVKMLEmulationLayer
192+
@pytest.mark.xfail(
193+
reason="VGF runtime is not yet fully supported for FP pipeline (MLETORCH-1234)",
194+
strict=True,
195+
)
190196
def test_add_tensor_vgf_FP(test_data: input_t1):
191197
pipeline = VgfPipeline[input_t1](
192-
Add(), test_data(), aten_op, exir_op, tosa_version="TOSA-1.0+FP"
198+
Add(),
199+
test_data(),
200+
aten_op,
201+
exir_op,
202+
tosa_version="TOSA-1.0+FP",
203+
run_on_vulkan_runtime=True,
193204
)
194205
pipeline.run()
195206

backends/arm/test/runner_utils.py

Lines changed: 112 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import numpy as np
1919
import torch
2020

21-
from executorch.backends.arm.arm_backend import is_tosa
21+
from executorch.backends.arm.arm_backend import is_tosa, is_vgf
2222
from executorch.backends.arm.test.conftest import is_option_enabled
2323
from executorch.backends.arm.tosa_specification import (
2424
get_tosa_spec,
@@ -57,6 +57,8 @@
5757
torch.complex128: np.complex128,
5858
}
5959

60+
VALID_TARGET = {"corstone-300", "corstone-320", "vkml_emulation_layer"}
61+
6062

6163
class QuantizationParams:
6264
__slots__ = ["node_name", "zp", "scale", "qmin", "qmax", "dtype"]
@@ -218,6 +220,69 @@ def __torch_function__(self, func, types, args=..., kwargs=None):
218220
return func(*args, **kwargs)
219221

220222

223+
def run_target(
224+
executorch_program_manager: ExecutorchProgramManager,
225+
inputs: Tuple[torch.Tensor],
226+
intermediate_path: str | Path,
227+
target_board: Literal["corestone-300", "corestone-320", "vkml_emulation_layer"],
228+
elf_path: str | Path,
229+
timeout: int = 120, # s
230+
):
231+
if target_board not in VALID_TARGET:
232+
raise ValueError(f"Unsupported target: {target_board}")
233+
234+
if target_board in ("corstone-300", "corstone-320"):
235+
return run_corstone(
236+
executorch_program_manager,
237+
inputs,
238+
intermediate_path,
239+
target_board,
240+
elf_path,
241+
timeout,
242+
)
243+
elif target_board == "vkml_emulation_layer":
244+
return run_vkml_emulation_layer(
245+
executorch_program_manager,
246+
intermediate_path,
247+
elf_path,
248+
)
249+
250+
251+
def run_vkml_emulation_layer(
252+
executorch_program_manager: ExecutorchProgramManager,
253+
intermediate_path: str | Path,
254+
elf_path: str | Path,
255+
):
256+
"""Executes an inference of the exported_program on ML Emulation Layer for Vulkan
257+
Args:
258+
`executorch_program_manager`: The executorch program to run.
259+
`intermediate_path`: Directory to save the .pte and capture outputs.
260+
`elf_path`: Path to the Vulkan-capable executor_runner binary.
261+
"""
262+
263+
intermediate_path = Path(intermediate_path)
264+
intermediate_path.mkdir(exist_ok=True)
265+
elf_path = Path(elf_path)
266+
if not elf_path.exists():
267+
raise FileNotFoundError(f"Did not find elf file {elf_path}")
268+
269+
# Save pte to file
270+
pte_path = os.path.join(intermediate_path, "program.pte")
271+
with open(pte_path, "wb") as f:
272+
f.write(executorch_program_manager.buffer)
273+
274+
cmd_line = [elf_path, "-model_path", pte_path]
275+
result = _run_cmd(cmd_line)
276+
277+
result_stdout = result.stdout.decode() # noqa: F841
278+
# TODO: MLETORCH-1234: Support VGF e2e tests in VgfPipeline
279+
# TODO: Add regex to check for error or fault messages in stdout from Emulation Layer
280+
# TODO: Retrieve and return the output tensors once VGF runtime is able to dump them.
281+
raise NotImplementedError(
282+
"Output parsing from VKML Emulation Layer is not yet implemented. "
283+
)
284+
285+
221286
def run_corstone(
222287
executorch_program_manager: ExecutorchProgramManager,
223288
inputs: Tuple[torch.Tensor],
@@ -229,7 +294,7 @@ def run_corstone(
229294
"""Executes an inference of the exported_program on FVP.
230295
Returns a list of tensors with the output.
231296
Args:
232-
`executorch_program_manager`: the executorch program to run.
297+
`executorch_program_manager`: The executorch program to run.
233298
The output of a EdgeProgramManager.to_executorch() call.
234299
`inputs`: A list of tensors with the inputs of the inference.
235300
`dump_path`: A directory where the .pte and inputs are saved to file.
@@ -558,18 +623,52 @@ def model_converter_installed() -> bool:
558623
return True
559624

560625

561-
def get_elf_path(target_board):
562-
elf_path = os.path.join(
563-
"arm_test",
564-
f"arm_semihosting_executor_runner_{target_board}",
565-
"arm_executor_runner",
566-
)
626+
def vkml_emulation_layer_installed() -> bool:
627+
# Check VK_INSTANCE_LAYERS
628+
vk_instance_layers = os.environ.get("VK_INSTANCE_LAYERS", "")
629+
required_layers = {
630+
"VK_LAYER_ML_Graph_Emulation",
631+
"VK_LAYER_ML_Tensor_Emulation",
632+
}
633+
existing_layers = set(vk_instance_layers.split(":"))
634+
layers_exists = required_layers.issubset(existing_layers)
635+
636+
# Check LD_LIBRARY_PATH for "emulation-layer/deploy"
637+
ld_library_path = os.environ.get("LD_LIBRARY_PATH", "")
638+
deploy_exists = False
639+
for path in ld_library_path.split(os.path.pathsep):
640+
if "emulation-layer/deploy" in path and os.path.isdir(path):
641+
deploy_exists = True
642+
643+
return layers_exists and deploy_exists
644+
645+
646+
def assert_elf_path_exists(elf_path):
567647
if not os.path.exists(elf_path):
568648
raise FileNotFoundError(
569-
f"Did not find build arm_executor_runner in path {elf_path}, run setup_testing.sh?"
649+
f"Did not find build arm_executor_runner or executor_runner in path {elf_path}, run setup_testing.sh?"
570650
)
571-
else:
572-
return elf_path
651+
652+
653+
def get_elf_path(target_board):
654+
if target_board not in VALID_TARGET:
655+
raise ValueError(f"Unsupported target: {target_board}")
656+
657+
if target_board in ("corstone-300", "corstone-320"):
658+
elf_path = os.path.join(
659+
"arm_test",
660+
f"arm_semihosting_executor_runner_{target_board}",
661+
"arm_executor_runner",
662+
)
663+
assert_elf_path_exists(elf_path)
664+
elif target_board == "vkml_emulation_layer":
665+
elf_path = os.path.join(
666+
"cmake-out",
667+
"executor_runner",
668+
)
669+
assert_elf_path_exists(elf_path)
670+
671+
return elf_path
573672

574673

575674
def arm_executor_runner_exists(target_board):
@@ -629,6 +728,8 @@ def transpose_data_format(data: list[np.ndarray], to: Literal["NHWC", "NCHW"]):
629728

630729

631730
def get_target_board(compile_spec: list[CompileSpec]) -> str | None:
731+
if is_vgf(compile_spec):
732+
return "vkml_emulation_layer"
632733
for spec in compile_spec:
633734
if spec.key == "compile_flags":
634735
flags = spec.value.decode()

backends/arm/test/tester/arm_tester.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@
5151
get_output_nodes,
5252
get_output_quantization_params,
5353
get_target_board,
54-
run_corstone,
54+
run_target,
5555
TosaReferenceModelDispatch,
5656
)
5757

@@ -212,7 +212,7 @@ def run_artifact(self, inputs):
212212
f"Did not find build arm_executor_runner in path {elf_path}, run setup_testing.sh?"
213213
)
214214

215-
return run_corstone(
215+
return run_target(
216216
self.executorch_program_manager,
217217
inputs_flattened,
218218
intermediate_path,

backends/arm/test/tester/test_pipeline.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,9 @@ class VgfPipeline(BasePipelineMaker, Generic[T]):
892892
exir_ops: Exir dialect ops expected to be found in the graph after to_edge.
893893
if not using use_edge_to_transform_and_lower.
894894
895-
run_on_vulkan_runtime: Not yet supported.
895+
run_on_vulkan_runtime: Partially supported. However, comparison between reference and model
896+
outputs is expected to fail, as the VGF runtime doesn't dump the output tensors in a usable
897+
format at the moment.
896898
897899
vgf_compiler_flags: Optional compiler flags.
898900
@@ -992,4 +994,11 @@ def __init__(
992994
)
993995

994996
if run_on_vulkan_runtime:
995-
pass
997+
self.add_stage(self.tester.serialize)
998+
self.add_stage(
999+
self.tester.run_method_and_compare_outputs,
1000+
atol=atol,
1001+
rtol=rtol,
1002+
qtol=qtol,
1003+
inputs=self.test_data,
1004+
)

0 commit comments

Comments
 (0)