Skip to content
Merged
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
21 changes: 20 additions & 1 deletion backends/arm/runtime/VGFBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ void vkml_free_basics(

class VGFBackend final : public ::executorch::runtime::BackendInterface {
public:
VGFBackend() {
VGFBackend() = default;

// Lazy Vulkan init — runs on first use, not in the constructor.
void ensure_initialized() {
if (is_initialized_) {
return;
}

VkResult result;

// Fetch basic vulkan objects once
Expand Down Expand Up @@ -122,6 +129,7 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {

bool is_available() const override {
ET_LOG(Info, "Checking VGFBackend is available");
const_cast<VGFBackend*>(this)->ensure_initialized();
if (!is_initialized_) {
return false;
}
Expand All @@ -134,6 +142,7 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
ArrayRef<CompileSpec> compile_specs) const override {
ET_LOG(Info, "Entered VGF init");

const_cast<VGFBackend*>(this)->ensure_initialized();
if (!is_initialized_) {
ET_LOG(
Error,
Expand Down Expand Up @@ -334,6 +343,16 @@ VkResult vkml_allocate_basics(
}
volkLoadInstance(*instance);

// Bail out if the driver lacks ARM tensor/datagraph extensions.
if (!vkCreateTensorARM) {
ET_LOG(
Error,
"Vulkan driver does not support ARM tensor extensions (VK_ARM_tensors)");
vkDestroyInstance(*instance, nullptr);
*instance = VK_NULL_HANDLE;
return VK_ERROR_FEATURE_NOT_PRESENT;
}

// Pick first GPU
uint32_t gpu_count = 0;
vkEnumeratePhysicalDevices(*instance, &gpu_count, nullptr);
Expand Down
2 changes: 1 addition & 1 deletion backends/arm/runtime/VGFSetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,7 @@ bool VgfRepr::process_vgf(const char* vgf_data, ArrayRef<CompileSpec> specs) {
.pNext = nullptr,
.flags = 0,
.bindingCount = static_cast<uint32_t>(layout_bindings.size()),
layout_bindings.data(),
.pBindings = layout_bindings.data(),
};
result =
vkCreateDescriptorSetLayout(vk_device, &layout_info, nullptr, &vk_layout);
Expand Down
32 changes: 32 additions & 0 deletions backends/arm/runtime/targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,35 @@ def define_common_targets():
"fbsource//third-party/ethos-u-core-driver:core_driver",
],
)
runtime.cxx_library(
name = "vgf_backend",
srcs = [
"VGFBackend.cpp",
"VGFSetup.cpp",
# Volk must be compiled directly into this target so its global
# function-pointer variables live in the same linkage unit.
# Linking from a separate static library causes the linker to
# drop the symbols when building a shared library.
"fbsource//third-party/vulkan-headers-1.4.343/v1.4.343/src:volk_arm_src",
],
exported_headers = ["VGFSetup.h"],
# @lint-ignore BUCKLINT: Avoid `link_whole=True` (https://fburl.com/avoid-link-whole)
link_whole = True,
supports_python_dlopen = True,
compiler_flags = [
"-Wno-global-constructors",
"-Wno-header-hygiene",
"-Wno-unused-variable",
"-Wno-missing-field-initializers",
"-DUSE_VULKAN_WRAPPER",
"-DUSE_VULKAN_VOLK",
],
visibility = ["PUBLIC"],
deps = [
"//executorch/runtime/backend:interface",
"//executorch/runtime/core:core",
"fbsource//third-party/arm-vgf-library/v0.8.0/src:vgf",
"fbsource//third-party/vulkan-headers-1.4.343/v1.4.343/src:volk_arm",
"fbsource//third-party/vulkan-headers-1.4.343/v1.4.343/src:vulkan-headers",
],
)
7 changes: 5 additions & 2 deletions backends/arm/test/runner_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@
from executorch.backends.arm.tosa.compile_spec import TosaCompileSpec
from executorch.backends.arm.tosa.specification import Tosa_1_00, TosaSpecification
from executorch.backends.arm.vgf import VgfCompileSpec
from executorch.backends.arm.vgf.model_converter import find_model_converter_binary
from executorch.backends.arm.vgf.model_converter import (
find_model_converter_binary,
model_converter_env,
)
from executorch.exir import ExecutorchProgramManager, ExportedProgram
from executorch.exir.lowered_backend_module import LoweredBackendModule
from torch.fx.node import Node
Expand Down Expand Up @@ -818,7 +821,7 @@ def model_converter_installed() -> bool:
return False

try:
_run_cmd([model_converter, "--version"], check=True)
_run_cmd([model_converter, "--version"], check=True, env=model_converter_env())
except Exception:
return False

Expand Down
14 changes: 13 additions & 1 deletion backends/arm/test/targets.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,21 @@ def define_arm_tests():
resources = ["conftest.py"],
compile = "with-source",
typing = False,
env = {} if runtime.is_oss else {
"MODEL_CONVERTER_PATH": "$(location fbsource//third-party/pypi/ai-ml-sdk-model-converter/0.8.0:model-converter-bin)",
"MODEL_CONVERTER_LIB_DIR": "$(location fbsource//third-party/nvidia-nsight-systems:linux-x86_64)/host-linux-x64",
"LAVAPIPE_LIB_PATH": "$(location fbsource//third-party/mesa/src/gallium/frontends/lavapipe:vulkan_lvp)",
"EMULATION_LAYER_TENSOR_SO": "$(location fbsource//third-party/arm-ml-emulation-layer/v0.9.0/src:libVkLayer_Tensor)",
"EMULATION_LAYER_GRAPH_SO": "$(location fbsource//third-party/arm-ml-emulation-layer/v0.9.0/src:libVkLayer_Graph)",
"EMULATION_LAYER_TENSOR_JSON": "$(location fbsource//third-party/arm-ml-emulation-layer/v0.9.0/src:VkLayer_Tensor_json)",
"EMULATION_LAYER_GRAPH_JSON": "$(location fbsource//third-party/arm-ml-emulation-layer/v0.9.0/src:VkLayer_Graph_json)",
},
preload_deps = [
"//executorch/kernels/quantized:custom_ops_generated_lib",
],
] + ([] if runtime.is_oss else [
"fbsource//third-party/khronos:vulkan",
"//executorch/backends/arm/runtime:vgf_backend",
]),
deps = [
"//executorch/backends/arm/test:arm_tester" if runtime.is_oss else "//executorch/backends/arm/test/tester/fb:arm_tester_fb",
"//executorch/backends/arm/test:conftest",
Expand Down
7 changes: 6 additions & 1 deletion backends/arm/vgf/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
VgfCompileSpec,
)
from executorch.backends.arm.vgf.model_converter import ( # type: ignore[import-not-found]
model_converter_env,
require_model_converter_binary,
)
from executorch.exir.backend.backend_details import ( # type: ignore[import-not-found]
Expand Down Expand Up @@ -157,7 +158,11 @@ def vgf_compile(
]
try:
subprocess.run( # nosec B602, B603 - shell invocation constrained to trusted converter binary with trusted inputs
conversion_command, shell=False, check=True, capture_output=True
conversion_command,
shell=False,
check=True,
capture_output=True,
env=model_converter_env(),
)
except subprocess.CalledProcessError as process_error:
conversion_command_str = " ".join(conversion_command)
Expand Down
22 changes: 21 additions & 1 deletion backends/arm/vgf/model_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import os
from shutil import which
from typing import Optional

Expand All @@ -13,14 +14,33 @@


def find_model_converter_binary() -> Optional[str]:
"""Return the name of the first model converter executable on PATH."""
"""Return the path/name of the first model converter executable found."""
env_path = os.environ.get("MODEL_CONVERTER_PATH")
if env_path and os.path.isfile(env_path) and os.access(env_path, os.X_OK):
return env_path

for candidate in (MODEL_CONVERTER_BINARY, _MODEL_CONVERTER_FALLBACK_BINARY):
if which(candidate):
return candidate
return None


def model_converter_env() -> dict[str, str]:
"""Return an env dict suitable for running model-converter as a subprocess.

If MODEL_CONVERTER_LIB_DIR is set, it is prepended to LD_LIBRARY_PATH so the
binary can find a compatible libstdc++ (or other shared libs) without
polluting the parent process environment.

"""
env = dict(os.environ)
lib_dir = env.pop("MODEL_CONVERTER_LIB_DIR", None)
if lib_dir:
existing = env.get("LD_LIBRARY_PATH", "")
env["LD_LIBRARY_PATH"] = f"{lib_dir}:{existing}" if existing else lib_dir
return env


def require_model_converter_binary() -> str:
"""Return a usable model converter executable or raise a helpful error."""

Expand Down
Loading