Skip to content

Vulkan: export allocator for custom contexts#8871

Open
xFile3160 wants to merge 3 commits intohalide:mainfrom
xFile3160:main
Open

Vulkan: export allocator for custom contexts#8871
xFile3160 wants to merge 3 commits intohalide:mainfrom
xFile3160:main

Conversation

@xFile3160
Copy link
Copy Markdown

@xFile3160 xFile3160 commented Nov 17, 2025

This is for applications that already own a Vulkan context and want Halide to
run work on that same context.

The case I care about is an application with its own VkInstance, VkDevice,
and queue. The application passes those handles to Halide from the Vulkan
context callback. Halide should not create or destroy that context, but it still
needs its own runtime allocator for shader modules, staging buffers, descriptor
resources, and other internal Vulkan objects.

This PR exposes acquire/release helpers for Halide's opaque Vulkan allocator.
The intended flow is:

  1. The application owns the Vulkan context.
  2. The application asks Halide to create or validate an allocator for that
    context.
  3. The application stores that allocator next to its Vulkan context state.
  4. Later halide_vulkan_acquire_context calls return the same allocator and
    Vulkan handles back to Halide.
  5. When the external context is torn down, the application releases the Halide
    allocator before destroying its VkDevice.

The release helper only tears down Halide-owned allocator and shader-module
state. It does not destroy the caller's Vulkan instance, device, queue, or
debug messenger.

This also adds setter functions for the Vulkan acquire/release context
callbacks. The weak-symbol override path still works, but setters are simpler
when Halide is linked into a larger library or used in an environment where
symbol interposition is not reliable.

The Vulkan compilation cache is keyed by allocator instead of raw VkDevice,
so cache cleanup follows the same lifetime as the allocator being released.

The acquire/release AOT test now covers the Vulkan path as well. It creates real
Vulkan handles, installs the callback setters, runs Halide through the callback
path with an externally stored allocator, and releases that allocator before
restoring the default context handling.

@xFile3160
Copy link
Copy Markdown
Author

Full discussion available here: #8715 (comment)

@alexreinking
Copy link
Copy Markdown
Member

@derek-gerstmann could you review this PR? Seems you were discussing the related issue.

@xFile3160
Copy link
Copy Markdown
Author

Any news about this? Should I rebase?

@alexreinking
Copy link
Copy Markdown
Member

Hi @xFile3160 -- happy new year! Yes, please rebase (looks like you just did). I'll ping @derek-gerstmann again to look at this since he has in-depth knowledge of the Vulkan backend.

@derek-gerstmann
Copy link
Copy Markdown
Contributor

Thanks for the reminder! I'll look this over this week!

Comment thread src/runtime/HalideRuntimeVulkan.h Outdated
// with the same locking used by the custom acquire/release implementations. This allows the allocator to be
// saved for future halide_vulkan_acquire_context calls that Halide will automatically issue to retrieve
// the custom context.
extern int halide_vulkan_export_memory_allocator(void *user_context,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the need for this method, or for the corresponding release method. The allocator should be stored in your custom context, and held onto for the lifetime of the context. The context manages lifespan of the allocator.

Comment thread src/runtime/HalideRuntimeVulkan.h Outdated
// - halide_vulkan_memory_allocator_release
// releases the internally allocated memory allocator, important for proper memory cleanup. Must have overridden halide_vulkan_acquire_context
// and halide_vulkan_release_context, and must coordinate with the same locking as the custom implementations.
extern int halide_vulkan_memory_allocator_release(void *user_context,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above comment.

Comment thread src/runtime/vulkan.cpp Outdated
return is_initialized;
}

WEAK int halide_vulkan_export_memory_allocator(void *user_context, halide_vulkan_memory_allocator *allocator) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't actually do anything other than check to see if the allocator is null.

Comment thread src/runtime/vulkan.cpp Outdated
return destroy_status;
}

WEAK int halide_vulkan_memory_allocator_release(void *user_context,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand the intent ... was it to have a public method to invoke the destructor for the allocator?

Comment thread src/runtime/vulkan_context.h Outdated
error = halide_error_code_device_interface_no_device;
halide_error_no_device_interface(user_context);
}
// If user overrode halide_vulkan_acquire_context and returned nullptr for allocator,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class shouldn't be doing anything other than holding a lock on the context. It's just a convenient wrapper for the internal methods to have a lock that lives within a scope.

Comment thread src/runtime/HalideRuntimeVulkan.h Outdated
// with the same locking used by the custom acquire/release implementations. This allows the allocator to be
// saved for future halide_vulkan_acquire_context calls that Halide will automatically issue to retrieve
// the custom context.
extern int halide_vulkan_export_memory_allocator(void *user_context,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest following the conventions of the context methods and naming this halide_vulkan_acquire_memory_allocator.

Comment thread src/runtime/HalideRuntimeVulkan.h Outdated
// - halide_vulkan_memory_allocator_release
// releases the internally allocated memory allocator, important for proper memory cleanup. Must have overridden halide_vulkan_acquire_context
// and halide_vulkan_release_context, and must coordinate with the same locking as the custom implementations.
extern int halide_vulkan_memory_allocator_release(void *user_context,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. I'd suggest I'd suggest naming this halide_vulkan_release_memory_allocator.

Comment thread src/runtime/vulkan.cpp Outdated
}

WEAK int halide_vulkan_export_memory_allocator(void *user_context, halide_vulkan_memory_allocator *allocator) {
halide_mutex_lock(&thread_lock);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default implementation doesn't actually do anything ... shouldn't it return the allocator associated with the context?

Comment thread src/runtime/vulkan.cpp Outdated
return halide_error_code_buffer_argument_is_null;
}

return vk_release_memory_allocator(user_context, (VulkanMemoryAllocator *)allocator,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lifetime management is an issue here. How do we know there are no remaining uses for the allocator? Also, allocators are specific to the context, so we need to make sure the given allocator matches the one associated with the given context.

Comment thread src/runtime/vulkan_context.h Outdated
halide_start_clock(user_context);
#endif
// make sure halide vulkan is loaded BEFORE creating allocator
debug(user_context) << "VulkanContext: Loading Vulkan function pointers for context override...\n";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the right place to initialize device function pointers. They are specific to the context, and should only be initialized once, which is why they are only done in the acquire_context method.

@xFile3160
Copy link
Copy Markdown
Author

I've changed this patch quiet a bit actually. But @derek-gerstmann your comments make absolutely sense, and I'm going to address/explain the intent and the new API a bit better soon. Sorry, I haven't followed up either updating this patch.

When an application provides its own Vulkan context, Halide still needs its runtime allocator for shader modules, staging buffers, and internal Vulkan resources.

Add public acquire/release helpers for the opaque Vulkan allocator. The acquire helper creates or validates an allocator for the supplied instance/device pair. The release helper tears down Halide-owned allocator state without destroying the caller's Vulkan instance, device, or queue.

Key the Vulkan compilation cache by allocator instead of VkDevice so cache cleanup follows the allocator lifetime.
Weak symbol overrides are not reliable in every embedding environment.

Add setter functions for the Vulkan acquire/release context callbacks, while keeping the existing weak symbols as the public runtime entry points. Passing null restores Halide's default context management.
@xFile3160 xFile3160 marked this pull request as ready for review April 28, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants