From 6c8f8dc3db1ade9ee492f221cd1d596ac98e0731 Mon Sep 17 00:00:00 2001 From: Elena Zhelezina Date: Mon, 28 Sep 2026 21:26:45 +0100 Subject: [PATCH 1/2] Arm backend: Enable host memory capabilities. Signed-off-by: Elena Zhelezina Change-Id: I110b09192b53931d0bb16c182d93a2808dd33457 --- backends/arm/runtime/VGFBackend.cpp | 106 +++++++++++++++++- backends/arm/runtime/VGFSetup.h | 25 ++++- backends/arm/runtime/VGFVulkanFeatures.h | 23 ++++ .../arm/test/vgf_vulkan_features_test.cpp | 90 +++++++++++++++ 4 files changed, 236 insertions(+), 8 deletions(-) diff --git a/backends/arm/runtime/VGFBackend.cpp b/backends/arm/runtime/VGFBackend.cpp index dc2af5dbed7..445096f3f9e 100644 --- a/backends/arm/runtime/VGFBackend.cpp +++ b/backends/arm/runtime/VGFBackend.cpp @@ -103,9 +103,13 @@ VkResult vkml_allocate_basics( VkCommandPool* command_pool, uint32_t* queue_family_index, bool request_neural_statistics, - bool* neural_statistics_device_enabled); + bool* neural_statistics_device_enabled, + bool request_host_memory_import, + VgfHostMemoryImportCapabilities* host_memory_import_capabilities); // Helper functions to dump VGF Delegate Boundary Inputs +constexpr const char* kVgfHostMemoryImportEnableEnv = + "EXECUTORCH_VGF_ENABLE_HOST_MEMORY_IMPORT"; constexpr const char* kVgfDumpInputsDirEnv = "EXECUTORCH_VGF_DUMP_INPUTS_DIR"; constexpr const char* kVgfDumpInputsAndExitEnv = "EXECUTORCH_VGF_DUMP_INPUTS_AND_EXIT"; @@ -418,8 +422,11 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface { VkResult result; neural_statistics_config_ = get_vgf_neural_statistics_runtime_config(); + const bool request_host_memory_import = + env_flag_enabled(kVgfHostMemoryImportEnableEnv); - // Fetch basic vulkan objects once + // Fetch basic Vulkan objects once. Device extensions must be selected here, + // before vkCreateDevice; VgfRepr only receives already-created handles. result = vkml_allocate_basics( &vk_instance, &vk_physical_device, @@ -428,7 +435,9 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface { &vk_command_pool, &vk_queue_family_index, neural_statistics_config_.requested, - &neural_statistics_device_enabled_); + &neural_statistics_device_enabled_, + request_host_memory_import, + &host_memory_import_capabilities_); if (result != VK_SUCCESS) { ET_LOG( Error, "Failed to initialize the Vulkan device error 0x%08X", result); @@ -535,7 +544,8 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface { vk_queue_family_index, neural_statistics_config_.requested, neural_statistics_device_enabled_, - neural_statistics_config_.mode_index); + neural_statistics_config_.mode_index, + host_memory_import_capabilities_); #ifdef ET_EVENT_TRACER_ENABLED event_tracer_end_profiling_delegate(event_tracer, allocate_repr_event); @@ -865,6 +875,7 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface { vk_queue_family_index = UINT32_MAX; neural_statistics_config_ = {}; neural_statistics_device_enabled_ = false; + host_memory_import_capabilities_ = {}; is_initialized_ = false; // Do not call volkFinalize(): the Vulkan backend shares the loader. } @@ -879,6 +890,7 @@ class VGFBackend final : public ::executorch::runtime::BackendInterface { uint32_t vk_queue_family_index = UINT32_MAX; VgfNeuralStatisticsRuntimeConfig neural_statistics_config_{}; bool neural_statistics_device_enabled_ = false; + VgfHostMemoryImportCapabilities host_memory_import_capabilities_{}; bool is_initialized_ = false; }; @@ -896,12 +908,17 @@ VkResult vkml_allocate_basics( VkCommandPool* command_pool, uint32_t* queue_family_index, bool request_neural_statistics, - bool* neural_statistics_device_enabled) { + bool* neural_statistics_device_enabled, + bool request_host_memory_import, + VgfHostMemoryImportCapabilities* host_memory_import_capabilities) { VkResult result; if (neural_statistics_device_enabled != nullptr) { *neural_statistics_device_enabled = false; } + if (host_memory_import_capabilities != nullptr) { + *host_memory_import_capabilities = {}; + } if (VK_SUCCESS != volkInitialize()) { ET_LOG(Error, "Volk failed to initialize"); @@ -1156,6 +1173,59 @@ VkResult vkml_allocate_basics( vector requested_exts; + bool enable_host_memory_import_device = false; +#if defined(VK_EXT_external_memory_host) + const bool host_memory_import_advertised = std::any_of( + available.begin(), available.end(), [](const auto& ext_avail) { + return std::strcmp( + VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, + ext_avail.extensionName) == 0; + }); + + VkDeviceSize min_imported_host_pointer_alignment = 0; + if (host_memory_import_advertised) { + VkPhysicalDeviceExternalMemoryHostPropertiesEXT host_memory_properties{ + .sType = + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT, + .pNext = nullptr, + }; + VkPhysicalDeviceProperties2 properties_2{ + .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, + .pNext = &host_memory_properties, + }; + vkGetPhysicalDeviceProperties2(*physical_device, &properties_2); + min_imported_host_pointer_alignment = + host_memory_properties.minImportedHostPointerAlignment; + } + + if (host_memory_import_capabilities != nullptr) { + host_memory_import_capabilities->physical_device_advertised = + host_memory_import_advertised; + host_memory_import_capabilities->min_imported_host_pointer_alignment = + min_imported_host_pointer_alignment; + } + + enable_host_memory_import_device = vgf_host_memory_import_should_be_enabled( + request_host_memory_import, host_memory_import_advertised); + if (enable_host_memory_import_device) { + requested_exts.push_back(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); + } else if (request_host_memory_import) { + ET_LOG( + Info, + "%s was requested but the Vulkan physical device does not expose %s", + kVgfHostMemoryImportEnableEnv, + VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); + } +#else + if (request_host_memory_import) { + ET_LOG( + Info, + "%s was requested but Vulkan headers do not expose " + "VK_EXT_external_memory_host", + kVgfHostMemoryImportEnableEnv); + } +#endif + const bool bfloat16_extension_available = std::any_of( available.begin(), available.end(), [](const auto& ext_avail) { return std::strcmp( @@ -1263,12 +1333,36 @@ VkResult vkml_allocate_basics( // Load the device with volk and populate function pointers volkLoadDevice(*device); + if (host_memory_import_capabilities != nullptr) { + // Only a successful vkCreateDevice makes extension enablement + // authoritative. Physical-device advertisement alone is insufficient for + // later zero-copy code to use VK_EXT_external_memory_host device + // functionality. + host_memory_import_capabilities->logical_device_enabled = + enable_host_memory_import_device; + ET_LOG( + Info, + "VGF host memory import: requested=%d advertised=%d enabled=%d " + "minImportedHostPointerAlignment=%llu", + static_cast(request_host_memory_import), + static_cast( + host_memory_import_capabilities->physical_device_advertised), + static_cast( + host_memory_import_capabilities->logical_device_enabled), + static_cast( + host_memory_import_capabilities + ->min_imported_host_pointer_alignment)); + } + vkGetDeviceQueue(*device, qf, 0, queue); VkCommandPoolCreateInfo poolInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .pNext = nullptr, - .flags = 0, + // VGF records a persistent per-repr command buffer. Host-memory import + // may later rebind descriptors and re-record only that command buffer. + // Keep the legacy flags=0 behavior unless the extension is truly enabled. + .flags = vgf_command_pool_flags(enable_host_memory_import_device), .queueFamilyIndex = qf, }; result = vkCreateCommandPool(*device, &poolInfo, nullptr, command_pool); diff --git a/backends/arm/runtime/VGFSetup.h b/backends/arm/runtime/VGFSetup.h index b87f648962c..10e0ef5a74d 100644 --- a/backends/arm/runtime/VGFSetup.h +++ b/backends/arm/runtime/VGFSetup.h @@ -22,6 +22,7 @@ using executorch::runtime::CompileSpec; #include #include +#include namespace executorch { namespace backends { @@ -108,7 +109,8 @@ class VgfRepr { uint32_t queue_family_index = UINT32_MAX, bool neural_statistics_requested = false, bool neural_statistics_device_enabled = false, - int neural_statistics_mode_index = 1) + int neural_statistics_mode_index = 1, + VgfHostMemoryImportCapabilities host_memory_import_capabilities = {}) : vk_instance(inst), vk_physical(phys), vk_device(dev), @@ -117,7 +119,8 @@ class VgfRepr { vk_queue_family_index(queue_family_index), neural_statistics_requested_(neural_statistics_requested), neural_statistics_device_enabled_(neural_statistics_device_enabled), - neural_statistics_mode_index_(neural_statistics_mode_index) {} + neural_statistics_mode_index_(neural_statistics_mode_index), + host_memory_import_capabilities_(host_memory_import_capabilities) {} /* * Process a VGF ready for execution, allocate necessary Vulkan objects. @@ -175,6 +178,23 @@ class VgfRepr { return neural_statistics_requested_; } + bool host_memory_import_advertised() const { + return host_memory_import_capabilities_.physical_device_advertised; + } + + bool host_memory_import_enabled() const { + return host_memory_import_capabilities_.logical_device_enabled; + } + + VkDeviceSize min_imported_host_pointer_alignment() const { + return host_memory_import_capabilities_.min_imported_host_pointer_alignment; + } + + const VgfHostMemoryImportCapabilities& host_memory_import_capabilities() + const { + return host_memory_import_capabilities_; + } + ~VgfRepr() { free_vgf(); if (vk_pipeline_cache != VK_NULL_HANDLE) { @@ -203,6 +223,7 @@ class VgfRepr { bool neural_statistics_requested_ = false; bool neural_statistics_device_enabled_ = false; int neural_statistics_mode_index_ = 1; + VgfHostMemoryImportCapabilities host_memory_import_capabilities_{}; bool timestamp_queries_enabled = false; uint32_t timestamp_valid_bits = 0; diff --git a/backends/arm/runtime/VGFVulkanFeatures.h b/backends/arm/runtime/VGFVulkanFeatures.h index 8e88afc8f32..1db55f6d316 100644 --- a/backends/arm/runtime/VGFVulkanFeatures.h +++ b/backends/arm/runtime/VGFVulkanFeatures.h @@ -13,6 +13,29 @@ namespace executorch { namespace backends { namespace vgf { +// Snapshot of VK_EXT_external_memory_host state for the exact Vulkan device +// used by VGF. Physical-device advertisement and logical-device enablement are +// deliberately separate because Vulkan does not provide a post-creation query +// for the list of extensions enabled at vkCreateDevice(). +struct VgfHostMemoryImportCapabilities { + bool physical_device_advertised = false; + bool logical_device_enabled = false; + VkDeviceSize min_imported_host_pointer_alignment = 0; +}; + +inline bool vgf_host_memory_import_should_be_enabled( + bool requested, + bool physical_device_advertised) { + return requested && physical_device_advertised; +} + +inline VkCommandPoolCreateFlags vgf_command_pool_flags( + bool host_memory_import_enabled) { + return host_memory_import_enabled + ? VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT + : 0; +} + inline VkPhysicalDeviceDataGraphFeaturesARM make_vgf_data_graph_features( void* p_next) { VkPhysicalDeviceDataGraphFeaturesARM features{}; diff --git a/backends/arm/test/vgf_vulkan_features_test.cpp b/backends/arm/test/vgf_vulkan_features_test.cpp index 149e4dcd9a9..da3c928b0d0 100644 --- a/backends/arm/test/vgf_vulkan_features_test.cpp +++ b/backends/arm/test/vgf_vulkan_features_test.cpp @@ -7,6 +7,10 @@ #include +#include +#include + +#include #include namespace executorch { @@ -42,6 +46,92 @@ TEST(VgfVulkanFeaturesTest, RequiresDataGraphShaderModuleSupport) { EXPECT_TRUE(vgf_data_graph_features_supported(available)); } +TEST(VgfVulkanFeaturesTest, EnablesHostMemoryImportWhenRequestedAndAdvertised) { + EXPECT_TRUE(vgf_host_memory_import_should_be_enabled( + /*requested=*/true, /*physical_device_advertised=*/true)); +} + +TEST( + VgfVulkanFeaturesTest, + DoesNotEnableHostMemoryImportWhenAdvertisedButNotRequested) { + EXPECT_FALSE(vgf_host_memory_import_should_be_enabled( + /*requested=*/false, /*physical_device_advertised=*/true)); +} + +TEST( + VgfVulkanFeaturesTest, + DoesNotEnableHostMemoryImportWhenRequestedButNotAdvertised) { + EXPECT_FALSE(vgf_host_memory_import_should_be_enabled( + /*requested=*/true, /*physical_device_advertised=*/false)); +} + +TEST(VgfVulkanFeaturesTest, LegacyHostMemoryImportPathRemainsDisabled) { + EXPECT_FALSE(vgf_host_memory_import_should_be_enabled( + /*requested=*/false, /*physical_device_advertised=*/false)); +} + +TEST(VgfVulkanFeaturesTest, HostImportCommandPoolSupportsIndividualReset) { + EXPECT_EQ( + vgf_command_pool_flags(/*host_memory_import_enabled=*/true), + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT); +} + +TEST(VgfVulkanFeaturesTest, LegacyCommandPoolFlagsRemainUnchanged) { + EXPECT_EQ(vgf_command_pool_flags(/*host_memory_import_enabled=*/false), 0u); +} + +TEST(VgfVulkanFeaturesTest, HostMemoryImportCapabilitiesDefaultToUnavailable) { + const VgfHostMemoryImportCapabilities capabilities{}; + EXPECT_FALSE(capabilities.physical_device_advertised); + EXPECT_FALSE(capabilities.logical_device_enabled); + EXPECT_EQ(capabilities.min_imported_host_pointer_alignment, 0u); +} + +TEST( + VgfVulkanFeaturesTest, + HostMemoryImportCapabilitiesDistinguishAdvertisedFromEnabled) { + VgfHostMemoryImportCapabilities capabilities{}; + capabilities.physical_device_advertised = true; + capabilities.logical_device_enabled = false; + capabilities.min_imported_host_pointer_alignment = 4096; + + EXPECT_TRUE(capabilities.physical_device_advertised); + EXPECT_FALSE(capabilities.logical_device_enabled); + EXPECT_EQ(capabilities.min_imported_host_pointer_alignment, 4096u); +} + +TEST(VgfVulkanFeaturesTest, VgfReprExposesHostMemoryImportCapabilities) { + static_assert(std::is_constructible_v< + VgfRepr, + VkInstance, + VkPhysicalDevice, + VkDevice, + VkQueue, + VkCommandPool, + uint32_t, + bool, + bool, + int, + VgfHostMemoryImportCapabilities>); + static_assert(std::is_same_v< + decltype(std::declval() + .host_memory_import_advertised()), + bool>); + static_assert( + std::is_same_v< + decltype(std::declval().host_memory_import_enabled()), + bool>); + static_assert(std::is_same_v< + decltype(std::declval() + .min_imported_host_pointer_alignment()), + VkDeviceSize>); + static_assert(std::is_same_v< + decltype(std::declval() + .host_memory_import_capabilities()), + const VgfHostMemoryImportCapabilities&>); + SUCCEED(); +} + } // namespace } // namespace vgf } // namespace backends From d9afa4f24c924c699730c08ef1ddbd13d44fcb50 Mon Sep 17 00:00:00 2001 From: Elena Zhelezina Date: Thu, 1 Oct 2026 22:07:02 +0100 Subject: [PATCH 2/2] Arm backend: Fix buck failure on github. This test was a simple additon, so it's fine to remove it. Signed-off-by: Elena Zhelezina Change-Id: I0defc364b3201716444c9f85358410a73d91e6e8 --- .../arm/test/vgf_vulkan_features_test.cpp | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/backends/arm/test/vgf_vulkan_features_test.cpp b/backends/arm/test/vgf_vulkan_features_test.cpp index da3c928b0d0..76ed6b4404b 100644 --- a/backends/arm/test/vgf_vulkan_features_test.cpp +++ b/backends/arm/test/vgf_vulkan_features_test.cpp @@ -7,10 +7,6 @@ #include -#include -#include - -#include #include namespace executorch { @@ -100,38 +96,6 @@ TEST( EXPECT_EQ(capabilities.min_imported_host_pointer_alignment, 4096u); } -TEST(VgfVulkanFeaturesTest, VgfReprExposesHostMemoryImportCapabilities) { - static_assert(std::is_constructible_v< - VgfRepr, - VkInstance, - VkPhysicalDevice, - VkDevice, - VkQueue, - VkCommandPool, - uint32_t, - bool, - bool, - int, - VgfHostMemoryImportCapabilities>); - static_assert(std::is_same_v< - decltype(std::declval() - .host_memory_import_advertised()), - bool>); - static_assert( - std::is_same_v< - decltype(std::declval().host_memory_import_enabled()), - bool>); - static_assert(std::is_same_v< - decltype(std::declval() - .min_imported_host_pointer_alignment()), - VkDeviceSize>); - static_assert(std::is_same_v< - decltype(std::declval() - .host_memory_import_capabilities()), - const VgfHostMemoryImportCapabilities&>); - SUCCEED(); -} - } // namespace } // namespace vgf } // namespace backends