Skip to content

Commit 8081eb8

Browse files
authored
Arm backend: Add shared GPU runtime (pytorch#22607)
This is Phase 2 of RFC pytorch#19298: shared runtime support for allowing the Vulkan and VGF delegates to resolve and safely share a Vulkan context. The new `backends/vulkan_shared` component is **not a standalone backend**. It provides common runtime infrastructure that allows delegates to resolve and safely share the same Vulkan context. * `SharedVulkanContext` for sharing Vulkan instance, device, queue, queue family, enabled extensions, and lifetime ownership. * A process-wide `SharedVulkanContextRegistry` supporting lookup, registration, `lookup_or_create`, external contexts, and explicit unregister. * Shared queue synchronization via `with_locked_queue()`. * Runtime configuration for selecting a shared context using: * `vulkan_shared_context_name` * `vulkan_shared_group_id` * `vulkan_shared_context_mode` * CMake/Buck integration and unit tests covering concurrency, lifetime management, queue synchronization, configuration parsing, and context validation. Applications can also register an externally-created Vulkan context, allowing delegates to reuse an existing graphics or compute Vulkan pipeline. This PR only adds the shared runtime infrastructure. Integration into the Vulkan and VGF delegates is left for the follow-up phases 3 and 4 per RFC. --------- Signed-off-by: Elena Zhelezina <elena.zhelezina@arm.com>
1 parent b75c738 commit 8081eb8

15 files changed

Lines changed: 1407 additions & 0 deletions

‎CMakeLists.txt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,9 @@ configure_file(
840840
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/executorch-backend-dependencies.cmake
841841
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/ExecuTorch
842842
)
843+
if(EXECUTORCH_BUILD_VULKAN OR EXECUTORCH_BUILD_VGF)
844+
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/backends/vulkan_shared)
845+
endif()
843846

844847
if(EXECUTORCH_BUILD_ARM_BAREMETAL
845848
OR EXECUTORCH_BUILD_ARM_ETHOSU_LINUX

‎backends/vulkan_shared/BUCK‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
load(
2+
"@fbcode_macros//build_defs:build_file_migration.bzl",
3+
"fbcode_target",
4+
"non_fbcode_target",
5+
)
6+
load(":targets.bzl", "define_common_targets")
7+
8+
oncall("executorch")
9+
10+
non_fbcode_target(_kind = define_common_targets,)
11+
fbcode_target(_kind = define_common_targets,)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright 2026 Arm Limited and/or its affiliates.
2+
#
3+
# This source code is licensed under the BSD-style license found in the
4+
# LICENSE file in the root directory of this source tree.
5+
6+
cmake_minimum_required(VERSION 3.19)
7+
8+
if(NOT EXECUTORCH_ROOT)
9+
set(EXECUTORCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../..)
10+
endif()
11+
12+
include(GNUInstallDirs)
13+
find_package(Threads REQUIRED)
14+
15+
set(VULKAN_SHARED_HEADERS_PATH
16+
${EXECUTORCH_ROOT}/backends/vulkan/third-party/Vulkan-Headers
17+
)
18+
19+
if(NOT EXISTS "${VULKAN_SHARED_HEADERS_PATH}/include/vulkan/vulkan.h")
20+
message(
21+
FATAL_ERROR
22+
"The shared Vulkan runtime requires the vendored Vulkan-Headers submodule. "
23+
"Run from the repository root:\n"
24+
" git submodule update --init "
25+
"backends/vulkan/third-party/Vulkan-Headers"
26+
)
27+
endif()
28+
29+
# SHARED on purpose: VGF and Vulkan may be separate delegate DSOs, but they must
30+
# observe one process-wide registry. A static copy in each DSO would create
31+
# independent registries and defeat context sharing.
32+
add_library(
33+
executorch_vulkan_shared_runtime SHARED
34+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/SharedVulkanContext.cpp
35+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/SharedVulkanContextRegistry.cpp
36+
${CMAKE_CURRENT_SOURCE_DIR}/runtime/SharedVulkanRuntimeConfig.cpp
37+
)
38+
add_library(
39+
executorch::vulkan_shared_runtime ALIAS executorch_vulkan_shared_runtime
40+
)
41+
42+
target_compile_definitions(
43+
executorch_vulkan_shared_runtime PRIVATE EXECUTORCH_VULKAN_SHARED_BUILDING
44+
)
45+
46+
target_include_directories(
47+
executorch_vulkan_shared_runtime
48+
PUBLIC $<BUILD_INTERFACE:${EXECUTORCH_ROOT}/..>
49+
$<BUILD_INTERFACE:${EXECUTORCH_ROOT}/runtime/core/portable_type/c10>
50+
$<BUILD_INTERFACE:${VULKAN_SHARED_HEADERS_PATH}/include>
51+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
52+
)
53+
54+
target_link_libraries(
55+
executorch_vulkan_shared_runtime
56+
PUBLIC executorch_core
57+
PRIVATE Threads::Threads
58+
)
59+
60+
set_target_properties(
61+
executorch_vulkan_shared_runtime
62+
PROPERTIES CXX_STANDARD 17
63+
CXX_STANDARD_REQUIRED YES
64+
CXX_VISIBILITY_PRESET hidden
65+
VISIBILITY_INLINES_HIDDEN YES
66+
)
67+
68+
install(
69+
TARGETS executorch_vulkan_shared_runtime
70+
EXPORT ExecuTorchTargets
71+
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
72+
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
73+
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
74+
INCLUDES
75+
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
76+
)
77+
78+
install(
79+
DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/runtime/
80+
DESTINATION
81+
${CMAKE_INSTALL_INCLUDEDIR}/executorch/backends/vulkan_shared/runtime
82+
FILES_MATCHING
83+
PATTERN "*.h"
84+
PATTERN "test" EXCLUDE
85+
)
86+
87+
# SharedVulkanContext.h is a public installed header and includes
88+
# <vulkan/vulkan.h>. Install the same vendored headers used by the build so an
89+
# installed ExecuTorch package does not depend on an unrelated system Vulkan
90+
# SDK.
91+
install(DIRECTORY ${VULKAN_SHARED_HEADERS_PATH}/include/
92+
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
93+
)
94+
95+
if(EXECUTORCH_BUILD_TESTS)
96+
add_subdirectory(runtime/test)
97+
endif()

‎backends/vulkan_shared/README.md‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Shared Vulkan runtime
2+
3+
This component is not a standalone ExecuTorch backend. It does not
4+
partition graphs, register a backend, or execute delegated operators.
5+
6+
This component is the backend-neutral runtime bridge used by the VGF and
7+
ExecuTorch Vulkan delegates. It deliberately does not introduce a unified
8+
partitioner or a wrapper delegate.
9+
10+
The standard AOT flow remains explicit partitioner composition, with VGF
11+
claiming supported regions first and Vulkan filling the remaining regions:
12+
13+
```python
14+
lowered = to_edge_transform_and_lower(
15+
exported,
16+
partitioner=[
17+
VgfPartitioner(vgf_compile_spec),
18+
VulkanPartitioner(vulkan_compile_spec),
19+
],
20+
)
21+
```
22+
23+
## Runtime options
24+
25+
Context selection is configured at model load time through `RuntimeSpec` /
26+
`BackendOptions`, not through serialized `CompileSpec` values:
27+
28+
| Key | Type | Default | Accepted values |
29+
| --- | --- | --- | --- |
30+
| `vulkan_shared_context_name` | string | `default` | Any non-empty context_name |
31+
| `vulkan_shared_context_mode` | string | `lookup_or_create` | `disabled`, `lookup_only`, `lookup_or_create`, `create_only` |
32+
| `vulkan_shared_group_id` | int | `0` | Any `int` value |
33+
34+
Both delegates must receive the same option values to resolve the same registry
35+
key.
36+
37+
## Ownership and validation
38+
39+
`SharedVulkanContext` carries Vulkan handles but never calls Vulkan entry points
40+
itself. Every registered context must provide a non-null `lifetime_anchor` whose
41+
lifetime guarantees that the Vulkan instance, physical device, device, and queue
42+
remain valid until the final `SharedVulkanContextPtr` is released. For a
43+
backend-created context, the anchor can own the backend runtime and perform
44+
teardown through that backend's Vulkan dispatch mechanism. For externally
45+
created Vulkan objects, the application must provide an anchor whose ownership
46+
keeps those objects alive for the same period.
47+
48+
`unregister_context()` removes the context from the registry and prevents new
49+
lookups; it does not revoke `SharedVulkanContextPtr` instances already held by
50+
delegates. Actual Vulkan teardown is therefore safe only after the final
51+
outstanding context reference releases its `lifetime_anchor`. The registry
52+
itself is intentionally process-lifetime; call `unregister_context()` to remove
53+
registry discoverability before deterministic teardown.
54+
55+
The `VkQueue` is shared process state and Vulkan queue operations require
56+
external host synchronization. Consumers must issue queue operations through
57+
`SharedVulkanContext::with_locked_queue()` so independently initialized delegates
58+
serialize access using the mutex stored in the shared context rather than
59+
backend-local locks.
60+
61+
The registrant also declares the device extensions enabled at `VkDevice`
62+
creation. A consuming backend must check its required extensions with
63+
`has_device_extension()` before using the context; Vulkan does not expose a
64+
post-creation query for the list of extensions that were enabled.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2026 Arm Limited and/or its affiliates.
3+
*
4+
* This source code is licensed under the BSD-style license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include <executorch/backends/vulkan_shared/runtime/SharedVulkanContext.h>
9+
10+
#include <algorithm>
11+
#include <utility>
12+
13+
namespace executorch {
14+
namespace backends {
15+
namespace vulkan_shared {
16+
17+
SharedVulkanContext::SharedVulkanContext(
18+
SharedVulkanContextCreateInfo create_info)
19+
: create_info_(std::move(create_info)) {}
20+
21+
SharedVulkanContext::~SharedVulkanContext() = default;
22+
23+
const SharedVulkanContextKey& SharedVulkanContext::key() const {
24+
return create_info_.key;
25+
}
26+
27+
VkInstance SharedVulkanContext::instance() const {
28+
return create_info_.instance;
29+
}
30+
31+
VkPhysicalDevice SharedVulkanContext::physical_device() const {
32+
return create_info_.physical_device;
33+
}
34+
35+
VkDevice SharedVulkanContext::device() const {
36+
return create_info_.device;
37+
}
38+
39+
uint32_t SharedVulkanContext::queue_family_index() const {
40+
return create_info_.queue_family_index;
41+
}
42+
43+
bool SharedVulkanContext::has_device_extension(
44+
std::string_view extension_name) const {
45+
return std::any_of(
46+
create_info_.enabled_device_extensions.begin(),
47+
create_info_.enabled_device_extensions.end(),
48+
[extension_name](const std::string& enabled_extension) {
49+
return enabled_extension == extension_name;
50+
});
51+
}
52+
53+
bool SharedVulkanContext::is_valid() const {
54+
return create_info_.key.valid() && create_info_.instance != VK_NULL_HANDLE &&
55+
create_info_.physical_device != VK_NULL_HANDLE &&
56+
create_info_.device != VK_NULL_HANDLE &&
57+
create_info_.queue != VK_NULL_HANDLE &&
58+
create_info_.queue_family_index != std::numeric_limits<uint32_t>::max() &&
59+
create_info_.lifetime_anchor != nullptr;
60+
}
61+
62+
} // namespace vulkan_shared
63+
} // namespace backends
64+
} // namespace executorch
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright 2026 Arm Limited and/or its affiliates.
3+
*
4+
* This source code is licensed under the BSD-style license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <executorch/backends/vulkan_shared/runtime/export.h>
11+
12+
#include <vulkan/vulkan.h>
13+
14+
#include <cstdint>
15+
#include <limits>
16+
#include <memory>
17+
#include <mutex>
18+
#include <string>
19+
#include <string_view>
20+
#include <utility>
21+
#include <vector>
22+
23+
namespace executorch {
24+
namespace backends {
25+
namespace vulkan_shared {
26+
27+
struct SharedVulkanContextKey final {
28+
std::string context_name;
29+
int group_id = 0;
30+
31+
bool valid() const {
32+
return !context_name.empty();
33+
}
34+
35+
friend bool operator==(
36+
const SharedVulkanContextKey& lhs,
37+
const SharedVulkanContextKey& rhs) {
38+
return lhs.group_id == rhs.group_id && lhs.context_name == rhs.context_name;
39+
}
40+
41+
friend bool operator!=(
42+
const SharedVulkanContextKey& lhs,
43+
const SharedVulkanContextKey& rhs) {
44+
return !(lhs == rhs);
45+
}
46+
};
47+
48+
// The shared layer carries Vulkan handles but deliberately does not call Vulkan
49+
// entry points itself. Every registered context must provide a lifetime_anchor
50+
// whose lifetime guarantees that instance, physical_device, device, and queue
51+
// remain valid until the final SharedVulkanContext reference is released. The
52+
// anchor destructor may perform backend/application Vulkan teardown.
53+
struct SharedVulkanContextCreateInfo final {
54+
SharedVulkanContextKey key;
55+
VkInstance instance = VK_NULL_HANDLE;
56+
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
57+
VkDevice device = VK_NULL_HANDLE;
58+
VkQueue queue = VK_NULL_HANDLE;
59+
uint32_t queue_family_index = std::numeric_limits<uint32_t>::max();
60+
std::vector<std::string> enabled_device_extensions;
61+
std::shared_ptr<void> lifetime_anchor;
62+
};
63+
64+
class EXECUTORCH_VULKAN_SHARED_API SharedVulkanContext final {
65+
public:
66+
explicit SharedVulkanContext(SharedVulkanContextCreateInfo create_info);
67+
~SharedVulkanContext();
68+
69+
SharedVulkanContext(const SharedVulkanContext&) = delete;
70+
SharedVulkanContext& operator=(const SharedVulkanContext&) = delete;
71+
72+
const SharedVulkanContextKey& key() const;
73+
VkInstance instance() const;
74+
VkPhysicalDevice physical_device() const;
75+
VkDevice device() const;
76+
77+
// Vulkan queue operations require external host synchronization. All
78+
// delegates sharing this context must issue queue operations through this
79+
// callback so they synchronize on the same mutex. The VkQueue must not be
80+
// retained and used after the callback returns.
81+
template <typename Fn>
82+
decltype(auto) with_locked_queue(Fn&& fn) const {
83+
std::lock_guard<std::mutex> lock(queue_mutex_);
84+
return std::forward<Fn>(fn)(create_info_.queue);
85+
}
86+
87+
uint32_t queue_family_index() const;
88+
bool has_device_extension(std::string_view extension_name) const;
89+
bool is_valid() const;
90+
91+
private:
92+
SharedVulkanContextCreateInfo create_info_;
93+
mutable std::mutex queue_mutex_;
94+
};
95+
96+
using SharedVulkanContextPtr = std::shared_ptr<SharedVulkanContext>;
97+
98+
} // namespace vulkan_shared
99+
} // namespace backends
100+
} // namespace executorch

0 commit comments

Comments
 (0)