Skip to content

Commit b4ad399

Browse files
authored
Arm backend: Enable host memory capabilities. (#23303)
Add Vulkan device capability plumbing required for future VGF zero-copy host-memory import. This change does not import host memory yet. It makes the VGF runtime aware of whether VK_EXT_external_memory_host is supported by the physical device and whether it was actually enabled when creating the logical device. cc @digantdesai @freddan80 @per @zingo @oscarandersson8218 @mansnils @Sebastian-Larsson @robell @rascani Signed-off-by: Elena Zhelezina <elena.zhelezina@arm.com>
1 parent 5054b1b commit b4ad399

4 files changed

Lines changed: 236 additions & 8 deletions

File tree

‎backends/arm/runtime/VGFBackend.cpp‎

Lines changed: 100 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,13 @@ VkResult vkml_allocate_basics(
103103
VkCommandPool* command_pool,
104104
uint32_t* queue_family_index,
105105
bool request_neural_statistics,
106-
bool* neural_statistics_device_enabled);
106+
bool* neural_statistics_device_enabled,
107+
bool request_host_memory_import,
108+
VgfHostMemoryImportCapabilities* host_memory_import_capabilities);
107109

108110
// Helper functions to dump VGF Delegate Boundary Inputs
111+
constexpr const char* kVgfHostMemoryImportEnableEnv =
112+
"EXECUTORCH_VGF_ENABLE_HOST_MEMORY_IMPORT";
109113
constexpr const char* kVgfDumpInputsDirEnv = "EXECUTORCH_VGF_DUMP_INPUTS_DIR";
110114
constexpr const char* kVgfDumpInputsAndExitEnv =
111115
"EXECUTORCH_VGF_DUMP_INPUTS_AND_EXIT";
@@ -418,8 +422,11 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
418422

419423
VkResult result;
420424
neural_statistics_config_ = get_vgf_neural_statistics_runtime_config();
425+
const bool request_host_memory_import =
426+
env_flag_enabled(kVgfHostMemoryImportEnableEnv);
421427

422-
// Fetch basic vulkan objects once
428+
// Fetch basic Vulkan objects once. Device extensions must be selected here,
429+
// before vkCreateDevice; VgfRepr only receives already-created handles.
423430
result = vkml_allocate_basics(
424431
&vk_instance,
425432
&vk_physical_device,
@@ -428,7 +435,9 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
428435
&vk_command_pool,
429436
&vk_queue_family_index,
430437
neural_statistics_config_.requested,
431-
&neural_statistics_device_enabled_);
438+
&neural_statistics_device_enabled_,
439+
request_host_memory_import,
440+
&host_memory_import_capabilities_);
432441
if (result != VK_SUCCESS) {
433442
ET_LOG(
434443
Error, "Failed to initialize the Vulkan device error 0x%08X", result);
@@ -535,7 +544,8 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
535544
vk_queue_family_index,
536545
neural_statistics_config_.requested,
537546
neural_statistics_device_enabled_,
538-
neural_statistics_config_.mode_index);
547+
neural_statistics_config_.mode_index,
548+
host_memory_import_capabilities_);
539549

540550
#ifdef ET_EVENT_TRACER_ENABLED
541551
event_tracer_end_profiling_delegate(event_tracer, allocate_repr_event);
@@ -865,6 +875,7 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
865875
vk_queue_family_index = UINT32_MAX;
866876
neural_statistics_config_ = {};
867877
neural_statistics_device_enabled_ = false;
878+
host_memory_import_capabilities_ = {};
868879
is_initialized_ = false;
869880
// Do not call volkFinalize(): the Vulkan backend shares the loader.
870881
}
@@ -879,6 +890,7 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface {
879890
uint32_t vk_queue_family_index = UINT32_MAX;
880891
VgfNeuralStatisticsRuntimeConfig neural_statistics_config_{};
881892
bool neural_statistics_device_enabled_ = false;
893+
VgfHostMemoryImportCapabilities host_memory_import_capabilities_{};
882894
bool is_initialized_ = false;
883895
};
884896

@@ -896,12 +908,17 @@ VkResult vkml_allocate_basics(
896908
VkCommandPool* command_pool,
897909
uint32_t* queue_family_index,
898910
bool request_neural_statistics,
899-
bool* neural_statistics_device_enabled) {
911+
bool* neural_statistics_device_enabled,
912+
bool request_host_memory_import,
913+
VgfHostMemoryImportCapabilities* host_memory_import_capabilities) {
900914
VkResult result;
901915

902916
if (neural_statistics_device_enabled != nullptr) {
903917
*neural_statistics_device_enabled = false;
904918
}
919+
if (host_memory_import_capabilities != nullptr) {
920+
*host_memory_import_capabilities = {};
921+
}
905922

906923
if (VK_SUCCESS != volkInitialize()) {
907924
ET_LOG(Error, "Volk failed to initialize");
@@ -1156,6 +1173,59 @@ VkResult vkml_allocate_basics(
11561173

11571174
vector<const char*> requested_exts;
11581175

1176+
bool enable_host_memory_import_device = false;
1177+
#if defined(VK_EXT_external_memory_host)
1178+
const bool host_memory_import_advertised = std::any_of(
1179+
available.begin(), available.end(), [](const auto& ext_avail) {
1180+
return std::strcmp(
1181+
VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME,
1182+
ext_avail.extensionName) == 0;
1183+
});
1184+
1185+
VkDeviceSize min_imported_host_pointer_alignment = 0;
1186+
if (host_memory_import_advertised) {
1187+
VkPhysicalDeviceExternalMemoryHostPropertiesEXT host_memory_properties{
1188+
.sType =
1189+
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT,
1190+
.pNext = nullptr,
1191+
};
1192+
VkPhysicalDeviceProperties2 properties_2{
1193+
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
1194+
.pNext = &host_memory_properties,
1195+
};
1196+
vkGetPhysicalDeviceProperties2(*physical_device, &properties_2);
1197+
min_imported_host_pointer_alignment =
1198+
host_memory_properties.minImportedHostPointerAlignment;
1199+
}
1200+
1201+
if (host_memory_import_capabilities != nullptr) {
1202+
host_memory_import_capabilities->physical_device_advertised =
1203+
host_memory_import_advertised;
1204+
host_memory_import_capabilities->min_imported_host_pointer_alignment =
1205+
min_imported_host_pointer_alignment;
1206+
}
1207+
1208+
enable_host_memory_import_device = vgf_host_memory_import_should_be_enabled(
1209+
request_host_memory_import, host_memory_import_advertised);
1210+
if (enable_host_memory_import_device) {
1211+
requested_exts.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
1212+
} else if (request_host_memory_import) {
1213+
ET_LOG(
1214+
Info,
1215+
"%s was requested but the Vulkan physical device does not expose %s",
1216+
kVgfHostMemoryImportEnableEnv,
1217+
VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME);
1218+
}
1219+
#else
1220+
if (request_host_memory_import) {
1221+
ET_LOG(
1222+
Info,
1223+
"%s was requested but Vulkan headers do not expose "
1224+
"VK_EXT_external_memory_host",
1225+
kVgfHostMemoryImportEnableEnv);
1226+
}
1227+
#endif
1228+
11591229
const bool bfloat16_extension_available = std::any_of(
11601230
available.begin(), available.end(), [](const auto& ext_avail) {
11611231
return std::strcmp(
@@ -1263,12 +1333,36 @@ VkResult vkml_allocate_basics(
12631333
// Load the device with volk and populate function pointers
12641334
volkLoadDevice(*device);
12651335

1336+
if (host_memory_import_capabilities != nullptr) {
1337+
// Only a successful vkCreateDevice makes extension enablement
1338+
// authoritative. Physical-device advertisement alone is insufficient for
1339+
// later zero-copy code to use VK_EXT_external_memory_host device
1340+
// functionality.
1341+
host_memory_import_capabilities->logical_device_enabled =
1342+
enable_host_memory_import_device;
1343+
ET_LOG(
1344+
Info,
1345+
"VGF host memory import: requested=%d advertised=%d enabled=%d "
1346+
"minImportedHostPointerAlignment=%llu",
1347+
static_cast<int>(request_host_memory_import),
1348+
static_cast<int>(
1349+
host_memory_import_capabilities->physical_device_advertised),
1350+
static_cast<int>(
1351+
host_memory_import_capabilities->logical_device_enabled),
1352+
static_cast<unsigned long long>(
1353+
host_memory_import_capabilities
1354+
->min_imported_host_pointer_alignment));
1355+
}
1356+
12661357
vkGetDeviceQueue(*device, qf, 0, queue);
12671358

12681359
VkCommandPoolCreateInfo poolInfo{
12691360
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
12701361
.pNext = nullptr,
1271-
.flags = 0,
1362+
// VGF records a persistent per-repr command buffer. Host-memory import
1363+
// may later rebind descriptors and re-record only that command buffer.
1364+
// Keep the legacy flags=0 behavior unless the extension is truly enabled.
1365+
.flags = vgf_command_pool_flags(enable_host_memory_import_device),
12721366
.queueFamilyIndex = qf,
12731367
};
12741368
result = vkCreateCommandPool(*device, &poolInfo, nullptr, command_pool);

‎backends/arm/runtime/VGFSetup.h‎

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ using executorch::runtime::CompileSpec;
2222
#include <executorch/backends/vulkan/runtime/vk_api/vk_api.h>
2323

2424
#include <executorch/backends/arm/runtime/VGFNeuralStatistics.h>
25+
#include <executorch/backends/arm/runtime/VGFVulkanFeatures.h>
2526

2627
namespace executorch {
2728
namespace backends {
@@ -108,7 +109,8 @@ class VgfRepr {
108109
uint32_t queue_family_index = UINT32_MAX,
109110
bool neural_statistics_requested = false,
110111
bool neural_statistics_device_enabled = false,
111-
int neural_statistics_mode_index = 1)
112+
int neural_statistics_mode_index = 1,
113+
VgfHostMemoryImportCapabilities host_memory_import_capabilities = {})
112114
: vk_instance(inst),
113115
vk_physical(phys),
114116
vk_device(dev),
@@ -117,7 +119,8 @@ class VgfRepr {
117119
vk_queue_family_index(queue_family_index),
118120
neural_statistics_requested_(neural_statistics_requested),
119121
neural_statistics_device_enabled_(neural_statistics_device_enabled),
120-
neural_statistics_mode_index_(neural_statistics_mode_index) {}
122+
neural_statistics_mode_index_(neural_statistics_mode_index),
123+
host_memory_import_capabilities_(host_memory_import_capabilities) {}
121124

122125
/*
123126
* Process a VGF ready for execution, allocate necessary Vulkan objects.
@@ -175,6 +178,23 @@ class VgfRepr {
175178
return neural_statistics_requested_;
176179
}
177180

181+
bool host_memory_import_advertised() const {
182+
return host_memory_import_capabilities_.physical_device_advertised;
183+
}
184+
185+
bool host_memory_import_enabled() const {
186+
return host_memory_import_capabilities_.logical_device_enabled;
187+
}
188+
189+
VkDeviceSize min_imported_host_pointer_alignment() const {
190+
return host_memory_import_capabilities_.min_imported_host_pointer_alignment;
191+
}
192+
193+
const VgfHostMemoryImportCapabilities& host_memory_import_capabilities()
194+
const {
195+
return host_memory_import_capabilities_;
196+
}
197+
178198
~VgfRepr() {
179199
free_vgf();
180200
if (vk_pipeline_cache != VK_NULL_HANDLE) {
@@ -203,6 +223,7 @@ class VgfRepr {
203223
bool neural_statistics_requested_ = false;
204224
bool neural_statistics_device_enabled_ = false;
205225
int neural_statistics_mode_index_ = 1;
226+
VgfHostMemoryImportCapabilities host_memory_import_capabilities_{};
206227

207228
bool timestamp_queries_enabled = false;
208229
uint32_t timestamp_valid_bits = 0;

‎backends/arm/runtime/VGFVulkanFeatures.h‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,29 @@ namespace executorch {
1313
namespace backends {
1414
namespace vgf {
1515

16+
// Snapshot of VK_EXT_external_memory_host state for the exact Vulkan device
17+
// used by VGF. Physical-device advertisement and logical-device enablement are
18+
// deliberately separate because Vulkan does not provide a post-creation query
19+
// for the list of extensions enabled at vkCreateDevice().
20+
struct VgfHostMemoryImportCapabilities {
21+
bool physical_device_advertised = false;
22+
bool logical_device_enabled = false;
23+
VkDeviceSize min_imported_host_pointer_alignment = 0;
24+
};
25+
26+
inline bool vgf_host_memory_import_should_be_enabled(
27+
bool requested,
28+
bool physical_device_advertised) {
29+
return requested && physical_device_advertised;
30+
}
31+
32+
inline VkCommandPoolCreateFlags vgf_command_pool_flags(
33+
bool host_memory_import_enabled) {
34+
return host_memory_import_enabled
35+
? VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
36+
: 0;
37+
}
38+
1639
inline VkPhysicalDeviceDataGraphFeaturesARM make_vgf_data_graph_features(
1740
void* p_next) {
1841
VkPhysicalDeviceDataGraphFeaturesARM features{};

‎backends/arm/test/vgf_vulkan_features_test.cpp‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
#include <gtest/gtest.h>
99

10+
#include <type_traits>
11+
#include <utility>
12+
13+
#include <executorch/backends/arm/runtime/VGFSetup.h>
1014
#include <executorch/backends/arm/runtime/VGFVulkanFeatures.h>
1115

1216
namespace executorch {
@@ -42,6 +46,92 @@ TEST(VgfVulkanFeaturesTest, RequiresDataGraphShaderModuleSupport) {
4246
EXPECT_TRUE(vgf_data_graph_features_supported(available));
4347
}
4448

49+
TEST(VgfVulkanFeaturesTest, EnablesHostMemoryImportWhenRequestedAndAdvertised) {
50+
EXPECT_TRUE(vgf_host_memory_import_should_be_enabled(
51+
/*requested=*/true, /*physical_device_advertised=*/true));
52+
}
53+
54+
TEST(
55+
VgfVulkanFeaturesTest,
56+
DoesNotEnableHostMemoryImportWhenAdvertisedButNotRequested) {
57+
EXPECT_FALSE(vgf_host_memory_import_should_be_enabled(
58+
/*requested=*/false, /*physical_device_advertised=*/true));
59+
}
60+
61+
TEST(
62+
VgfVulkanFeaturesTest,
63+
DoesNotEnableHostMemoryImportWhenRequestedButNotAdvertised) {
64+
EXPECT_FALSE(vgf_host_memory_import_should_be_enabled(
65+
/*requested=*/true, /*physical_device_advertised=*/false));
66+
}
67+
68+
TEST(VgfVulkanFeaturesTest, LegacyHostMemoryImportPathRemainsDisabled) {
69+
EXPECT_FALSE(vgf_host_memory_import_should_be_enabled(
70+
/*requested=*/false, /*physical_device_advertised=*/false));
71+
}
72+
73+
TEST(VgfVulkanFeaturesTest, HostImportCommandPoolSupportsIndividualReset) {
74+
EXPECT_EQ(
75+
vgf_command_pool_flags(/*host_memory_import_enabled=*/true),
76+
VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT);
77+
}
78+
79+
TEST(VgfVulkanFeaturesTest, LegacyCommandPoolFlagsRemainUnchanged) {
80+
EXPECT_EQ(vgf_command_pool_flags(/*host_memory_import_enabled=*/false), 0u);
81+
}
82+
83+
TEST(VgfVulkanFeaturesTest, HostMemoryImportCapabilitiesDefaultToUnavailable) {
84+
const VgfHostMemoryImportCapabilities capabilities{};
85+
EXPECT_FALSE(capabilities.physical_device_advertised);
86+
EXPECT_FALSE(capabilities.logical_device_enabled);
87+
EXPECT_EQ(capabilities.min_imported_host_pointer_alignment, 0u);
88+
}
89+
90+
TEST(
91+
VgfVulkanFeaturesTest,
92+
HostMemoryImportCapabilitiesDistinguishAdvertisedFromEnabled) {
93+
VgfHostMemoryImportCapabilities capabilities{};
94+
capabilities.physical_device_advertised = true;
95+
capabilities.logical_device_enabled = false;
96+
capabilities.min_imported_host_pointer_alignment = 4096;
97+
98+
EXPECT_TRUE(capabilities.physical_device_advertised);
99+
EXPECT_FALSE(capabilities.logical_device_enabled);
100+
EXPECT_EQ(capabilities.min_imported_host_pointer_alignment, 4096u);
101+
}
102+
103+
TEST(VgfVulkanFeaturesTest, VgfReprExposesHostMemoryImportCapabilities) {
104+
static_assert(std::is_constructible_v<
105+
VgfRepr,
106+
VkInstance,
107+
VkPhysicalDevice,
108+
VkDevice,
109+
VkQueue,
110+
VkCommandPool,
111+
uint32_t,
112+
bool,
113+
bool,
114+
int,
115+
VgfHostMemoryImportCapabilities>);
116+
static_assert(std::is_same_v<
117+
decltype(std::declval<const VgfRepr&>()
118+
.host_memory_import_advertised()),
119+
bool>);
120+
static_assert(
121+
std::is_same_v<
122+
decltype(std::declval<const VgfRepr&>().host_memory_import_enabled()),
123+
bool>);
124+
static_assert(std::is_same_v<
125+
decltype(std::declval<const VgfRepr&>()
126+
.min_imported_host_pointer_alignment()),
127+
VkDeviceSize>);
128+
static_assert(std::is_same_v<
129+
decltype(std::declval<const VgfRepr&>()
130+
.host_memory_import_capabilities()),
131+
const VgfHostMemoryImportCapabilities&>);
132+
SUCCEED();
133+
}
134+
45135
} // namespace
46136
} // namespace vgf
47137
} // namespace backends

0 commit comments

Comments
 (0)