diff --git a/src/backend/vulkan/runtime/vulkan_device_api.cc b/src/backend/vulkan/runtime/vulkan_device_api.cc index 3dc5f146dd6a..2c56c4fd8a34 100644 --- a/src/backend/vulkan/runtime/vulkan_device_api.cc +++ b/src/backend/vulkan/runtime/vulkan_device_api.cc @@ -34,6 +34,13 @@ namespace runtime { namespace vulkan { VulkanDeviceAPI* VulkanDeviceAPI::Global() { +#ifdef _WIN32 + // DLL static destructors run during loader shutdown, when calling the Vulkan + // driver (e.g. vkDestroyDevice) is no longer safe. Keep the singleton alive + // for the process lifetime, as CUDADeviceAPI does, and let the OS reclaim it. + static auto* inst = new VulkanDeviceAPI(); + return inst; +#else // Most of the TVM Global() functions allocate with "new" and do // not deallocate, as the OS can clean up any leftover buffers at // the end. In this case, we need the VulkanDeviceAPI destructor @@ -41,6 +48,7 @@ VulkanDeviceAPI* VulkanDeviceAPI::Global() { // using some nvidia drivers. static VulkanDeviceAPI inst; return &inst; +#endif } VulkanDeviceAPI::VulkanDeviceAPI() { diff --git a/tests/python/runtime/test_runtime_device_api.py b/tests/python/runtime/test_runtime_device_api.py index 8c4ec430f1da..e8b345ade2b9 100644 --- a/tests/python/runtime/test_runtime_device_api.py +++ b/tests/python/runtime/test_runtime_device_api.py @@ -19,6 +19,8 @@ import subprocess import sys +import pytest + import tvm import tvm.testing @@ -48,5 +50,43 @@ def test_check_if_device_exists(): ) +@pytest.mark.gpu +@pytest.mark.skipif( + tvm.get_global_func("device_api.vulkan", allow_missing=True) is None, + reason="Vulkan runtime is not built", +) +@pytest.mark.parametrize("allocate_tensor", [False, True]) +def test_vulkan_process_exit(allocate_tensor): + """Device initialization and allocation must allow a clean process exit.""" + # Initialize Vulkan only in the child: exit-time failures cannot be caught + # by an in-process assertion and must not crash the pytest process itself. + script = """ +import sys +import numpy as np +import tvm + +dev = tvm.vulkan(0) +if not dev.exist: + sys.exit(77) +if int(sys.argv[1]): + expected = np.arange(128, dtype="float32") + tensor = tvm.runtime.tensor(expected, dev) + np.testing.assert_array_equal(tensor.numpy(), expected) +print("Vulkan work completed", flush=True) +""" + proc = subprocess.run( + [sys.executable, "-c", script, str(int(allocate_tensor))], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + text=True, + timeout=120, + check=False, + ) + if proc.returncode == 77: + pytest.skip("No Vulkan device is available") + assert proc.returncode == 0, f"Vulkan subprocess exited with {proc.returncode}:\n{proc.stdout}" + assert "Vulkan work completed" in proc.stdout + + if __name__ == "__main__": tvm.testing.main()