Skip to content

Commit 2091626

Browse files
bdemirbBaris Demir
andauthored
Arm backend: Fix VKML init and env for MacOS (pytorch#18286)
cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell Signed-off-by: Baris Demir <baris.demir@arm.com> Co-authored-by: Baris Demir <baris.demir@arm.com>
1 parent d2ce595 commit 2091626

3 files changed

Lines changed: 106 additions & 17 deletions

File tree

backends/arm/runtime/VGFBackend.cpp

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2025 Arm Limited and/or its affiliates.
2+
* Copyright 2025-2026 Arm Limited and/or its affiliates.
33
*
44
* This source code is licensed under the BSD-style license found in the
55
* LICENSE file in the root directory of this source tree.
@@ -75,7 +75,9 @@ void vkml_free_basics(
7575
VkInstance* instance,
7676
VkDevice* device,
7777
VkCommandPool* command_pool) {
78-
vkDestroyCommandPool(*device, *command_pool, nullptr);
78+
if (*device != VK_NULL_HANDLE && *command_pool != VK_NULL_HANDLE) {
79+
vkDestroyCommandPool(*device, *command_pool, nullptr);
80+
}
7981
// Note: These primitives are used by the emulation layer for vulkan
8082
// object allocation, the vulkan objects are freed in in library
8183
// shutdown, so we can't yet destroy these here without causing
@@ -111,21 +113,19 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
111113
result);
112114
return;
113115
}
116+
117+
is_initialized_ = true;
114118
}
115119
~VGFBackend() {
116120
vkml_free_basics(&vk_instance, &vk_device, &vk_command_pool);
117121
}
118122

119123
bool is_available() const override {
120-
VkResult result;
121-
122124
ET_LOG(Info, "Checking VGFBackend is available");
123-
// Query the device prepared in constructor for needed extensions
124-
result = vkml_load_extensions(&vk_device);
125-
if (result != VK_SUCCESS)
125+
if (!is_initialized_) {
126126
return false;
127-
128-
return true;
127+
}
128+
return vkml_load_extensions(&vk_device) == VK_SUCCESS;
129129
}
130130

131131
Result<DelegateHandle*> init(
@@ -134,6 +134,13 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
134134
ArrayRef<CompileSpec> compile_specs) const override {
135135
ET_LOG(Info, "Entered VGF init");
136136

137+
if (!is_initialized_) {
138+
ET_LOG(
139+
Error,
140+
"VGF backend is unavailable because Vulkan initialization failed");
141+
return Error::NotSupported;
142+
}
143+
137144
const char* vgf_data = reinterpret_cast<const char*>(processed->data());
138145

139146
MemoryAllocator* allocator = context.get_runtime_allocator();
@@ -230,11 +237,12 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
230237
}
231238

232239
private:
233-
VkInstance vk_instance;
234-
VkPhysicalDevice vk_physical_device;
235-
VkDevice vk_device;
236-
VkQueue vk_queue;
237-
VkCommandPool vk_command_pool;
240+
VkInstance vk_instance = VK_NULL_HANDLE;
241+
VkPhysicalDevice vk_physical_device = VK_NULL_HANDLE;
242+
VkDevice vk_device = VK_NULL_HANDLE;
243+
VkQueue vk_queue = VK_NULL_HANDLE;
244+
VkCommandPool vk_command_pool = VK_NULL_HANDLE;
245+
bool is_initialized_ = false;
238246
};
239247

240248
namespace {
@@ -253,6 +261,7 @@ VkResult vkml_allocate_basics(
253261

254262
if (VK_SUCCESS != volkInitialize()) {
255263
ET_LOG(Error, "Volk failed to initialize");
264+
return VK_ERROR_INITIALIZATION_FAILED;
256265
}
257266

258267
VkApplicationInfo app_info{

backends/arm/test/runner_utils.py

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import re
1111
import shutil
1212
import subprocess # nosec B404 - invoked only for trusted toolchain binaries
13+
import sys
1314
import tempfile
1415
from pathlib import Path
1516

@@ -364,7 +365,7 @@ def run_vkml_emulation_layer(
364365
cmd_line += input_string
365366
cmd_line = cmd_line.split()
366367

367-
result = _run_cmd(cmd_line)
368+
result = _run_cmd(cmd_line, env=_get_vkml_runtime_env())
368369

369370
# TODO: Add regex to check for error or fault messages in stdout from Emulation Layer
370371
result_stdout = result.stdout.decode() # noqa: F841
@@ -590,7 +591,80 @@ def save_bytes(
590591
return file_path
591592

592593

593-
def _run_cmd(cmd: List[str], check=True) -> subprocess.CompletedProcess[bytes]:
594+
def _prepend_env_path(existing: str | None, value: str) -> str:
595+
if not existing:
596+
return value
597+
598+
parts = [part for part in existing.split(os.path.pathsep) if part]
599+
if value in parts:
600+
return existing
601+
return os.path.pathsep.join([value, *parts])
602+
603+
604+
def _find_local_vulkan_sdk_root() -> Path | None:
605+
repo_root = Path(__file__).resolve().parents[3]
606+
sdk_base_dir = repo_root / "examples/arm/arm-scratch/vulkan_sdk"
607+
if not sdk_base_dir.is_dir():
608+
return None
609+
610+
if sys.platform == "darwin":
611+
candidates = sorted(
612+
path for path in sdk_base_dir.glob("*/macOS") if path.is_dir()
613+
)
614+
else:
615+
arch = os.uname().machine
616+
arch_aliases = [arch]
617+
if arch == "arm64":
618+
arch_aliases.append("aarch64")
619+
candidates = sorted(
620+
path
621+
for alias in arch_aliases
622+
for path in sdk_base_dir.glob(f"*/{alias}")
623+
if path.is_dir()
624+
)
625+
626+
if not candidates:
627+
return None
628+
return candidates[-1]
629+
630+
631+
def _get_vkml_runtime_env() -> dict[str, str]:
632+
"""Return an environment with the Vulkan runtime variables needed for
633+
VKML.
634+
"""
635+
env = os.environ.copy()
636+
sdk_root = _find_local_vulkan_sdk_root()
637+
if sdk_root is None:
638+
return env
639+
640+
env["VULKAN_SDK"] = str(sdk_root)
641+
env["PATH"] = _prepend_env_path(env.get("PATH"), str(sdk_root / "bin"))
642+
643+
if sys.platform == "darwin":
644+
env["DYLD_LIBRARY_PATH"] = _prepend_env_path(
645+
env.get("DYLD_LIBRARY_PATH"), str(sdk_root / "lib")
646+
)
647+
moltenvk_icd = sdk_root / "share/vulkan/icd.d/MoltenVK_icd.json"
648+
if moltenvk_icd.is_file():
649+
env["VK_DRIVER_FILES"] = _prepend_env_path(
650+
env.get("VK_DRIVER_FILES"), str(moltenvk_icd)
651+
)
652+
else:
653+
logger.debug(
654+
"MoltenVK ICD file not found at %s; leaving VK_DRIVER_FILES unset.",
655+
moltenvk_icd,
656+
)
657+
else:
658+
env["LD_LIBRARY_PATH"] = _prepend_env_path(
659+
env.get("LD_LIBRARY_PATH"), str(sdk_root / "lib")
660+
)
661+
662+
return env
663+
664+
665+
def _run_cmd(
666+
cmd: List[str], check=True, env: dict[str, str] | None = None
667+
) -> subprocess.CompletedProcess[bytes]:
594668
"""Run a command and check for errors.
595669
596670
Args:
@@ -599,7 +673,7 @@ def _run_cmd(cmd: List[str], check=True) -> subprocess.CompletedProcess[bytes]:
599673
"""
600674
try:
601675
result = subprocess.run( # nosec B603 - cmd constructed from trusted inputs
602-
cmd, check=check, capture_output=True
676+
cmd, check=check, capture_output=True, env=env
603677
)
604678
return result
605679
except subprocess.CalledProcessError as e:

examples/arm/setup.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ function create_setup_path(){
200200
clear_setup_path
201201
log_step "path" "Generating setup path scripts at ${setup_path_script}"
202202

203+
if [[ -n "${VIRTUAL_ENV:-}" && -d "${VIRTUAL_ENV}/bin" ]]; then
204+
prepend_env_in_setup_path PATH "${VIRTUAL_ENV}/bin"
205+
elif [[ -d "${et_dir}/env/bin" ]]; then
206+
prepend_env_in_setup_path PATH "${et_dir}/env/bin"
207+
fi
208+
203209
local use_mlsdk_pip=0
204210
if use_mlsdk_pip_package; then
205211
use_mlsdk_pip=1

0 commit comments

Comments
 (0)