This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 31f8c8ad9f [Runtime][Vulkan] Add timer support for the Vulkan backend
(#20471)
31f8c8ad9f is described below
commit 31f8c8ad9f70b50871a5a96566314feb3de1255e
Author: Raman Shinde <[email protected]>
AuthorDate: Sun Sep 27 18:29:14 2026 +0530
[Runtime][Vulkan] Add timer support for the Vulkan backend (#20471)
## Motivation
The Vulkan backend has no device-specific `Timer` implementation, so
profiling
falls back to the default CPU wall-clock timer. That timer measures
host-side
submission and synchronization, not actual GPU execution, so profiling
results
on Vulkan devices are inaccurate.
## Changes
- Add `VulkanTimerNode` (`vulkan_timer.h` / `vulkan_timer.cc`), which
measures GPU
execution time using Vulkan timestamp queries (`vkCmdWriteTimestamp`)
recorded
on the device's compute stream.
- Store `timestampPeriod` and the compute queue family's
`timestampValidBits` in
`VulkanDeviceProperties`. They are used to convert ticks to nanoseconds
and to
mask invalid upper bits of the timestamp values.
- Check at timer construction that the compute queue supports timestamp
queries
(`timestampValidBits > 0`) and fail with a clear error if it does not.
- Register the Vulkan timer factory so `Timer::Start(dev)` picks it up
automatically for Vulkan devices.
---
src/backend/vulkan/runtime/vulkan_device.cc | 1 +
src/backend/vulkan/runtime/vulkan_device.h | 1 +
src/backend/vulkan/runtime/vulkan_device_api.cc | 13 +++
src/backend/vulkan/runtime/vulkan_timer.cc | 100 ++++++++++++++++++++++++
src/backend/vulkan/runtime/vulkan_timer.h | 100 ++++++++++++++++++++++++
5 files changed, 215 insertions(+)
diff --git a/src/backend/vulkan/runtime/vulkan_device.cc
b/src/backend/vulkan/runtime/vulkan_device.cc
index 40608bc3c8..3592854bf5 100644
--- a/src/backend/vulkan/runtime/vulkan_device.cc
+++ b/src/backend/vulkan/runtime/vulkan_device.cc
@@ -165,6 +165,7 @@ VulkanDeviceProperties::VulkanDeviceProperties(const
VulkanInstance& instance,
max_shared_memory_per_block =
properties.properties.limits.maxComputeSharedMemorySize;
device_name = properties.properties.deviceName;
driver_version = properties.properties.driverVersion;
+ timestamp_period = properties.properties.limits.timestampPeriod;
if (device.HasExtension("VK_KHR_driver_properties")) {
driver_name = driver.driverName;
diff --git a/src/backend/vulkan/runtime/vulkan_device.h
b/src/backend/vulkan/runtime/vulkan_device.h
index 7e75f4eb3e..e1a76ddc8d 100644
--- a/src/backend/vulkan/runtime/vulkan_device.h
+++ b/src/backend/vulkan/runtime/vulkan_device.h
@@ -106,6 +106,7 @@ struct VulkanDeviceProperties {
uint32_t driver_version{0};
uint32_t vulkan_api_version{VK_API_VERSION_1_0};
uint32_t max_spirv_version{0x10000};
+ double timestamp_period{0};
};
/*! \brief Handle to the Vulkan API's VkDevice
diff --git a/src/backend/vulkan/runtime/vulkan_device_api.cc
b/src/backend/vulkan/runtime/vulkan_device_api.cc
index 3dc5f146dd..cbd6378588 100644
--- a/src/backend/vulkan/runtime/vulkan_device_api.cc
+++ b/src/backend/vulkan/runtime/vulkan_device_api.cc
@@ -28,6 +28,7 @@
#include <utility>
#include "vulkan_common.h"
+#include "vulkan_timer.h"
namespace tvm {
namespace runtime {
@@ -452,6 +453,18 @@ VulkanDevice& VulkanDeviceAPI::device(size_t device_id) {
return const_cast<VulkanDevice&>(const_cast<const
VulkanDeviceAPI*>(this)->device(device_id));
}
+TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
+ refl::GlobalDef().def("profiling.timer.vulkan",
+ [](Device dev) { return
Timer(ffi::make_object<VulkanTimerNode>(dev)); });
+}
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
+ refl::GlobalDef().def("runtime.timer.vulkan",
+ [](Device dev) { return
Timer(ffi::make_object<VulkanTimerNode>(dev)); });
+}
+
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef()
diff --git a/src/backend/vulkan/runtime/vulkan_timer.cc
b/src/backend/vulkan/runtime/vulkan_timer.cc
new file mode 100644
index 0000000000..0245ac3d1e
--- /dev/null
+++ b/src/backend/vulkan/runtime/vulkan_timer.cc
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "vulkan_timer.h"
+
+#include <tvm/runtime/logging.h>
+
+#include "vulkan_device_api.h"
+
+namespace tvm {
+namespace runtime {
+namespace vulkan {
+
+VulkanTimerNode::VulkanTimerNode(Device dev) : dev_(dev) {
+ // Get the Vulkan device and stream
+ auto& vk_dev = VulkanDeviceAPI::Global()->device(dev_.device_id);
+ stream_ = &vk_dev.ThreadLocalStream();
+ device_ = vk_dev;
+
+ // Retrieve the timestamp period from device properties
+ timestamp_period_ = vk_dev.device_properties.timestamp_period;
+ TVM_FFI_ICHECK_GT(timestamp_period_, 0) << "Vulkan device does not support
timestamp queries.";
+
+ CreateQueryPool();
+}
+
+VulkanTimerNode::~VulkanTimerNode() { Cleanup(); }
+
+void VulkanTimerNode::CreateQueryPool() {
+ VkQueryPoolCreateInfo query_pool_info{};
+ query_pool_info.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
+ query_pool_info.queryType = VK_QUERY_TYPE_TIMESTAMP;
+ query_pool_info.queryCount = 2;
+
+ VkResult res = vkCreateQueryPool(device_, &query_pool_info, nullptr,
&query_pool_);
+ TVM_FFI_ICHECK(res == VK_SUCCESS) << "Failed to create Vulkan query pool.";
+}
+
+void VulkanTimerNode::Start() {
+ stream_->Launch([this](VulkanStreamState* state) {
+ // Reset the query pool before writing timestamps
+ vkCmdResetQueryPool(state->cmd_buffer_, query_pool_, start_query_, 2);
+ vkCmdWriteTimestamp(state->cmd_buffer_, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
query_pool_,
+ start_query_);
+ });
+}
+
+void VulkanTimerNode::Stop() {
+ stream_->Launch([this](VulkanStreamState* state) {
+ vkCmdWriteTimestamp(state->cmd_buffer_,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, query_pool_,
+ end_query_);
+ });
+
+ // Ensure GPU has finished writing timestamps before collecting them
+ stream_->Synchronize();
+ CollectTimestamps();
+}
+
+int64_t VulkanTimerNode::SyncAndGetElapsedNanos() { return duration_; }
+
+void VulkanTimerNode::CollectTimestamps() {
+ uint64_t timestamps[2] = {0};
+
+ VkResult result =
+ vkGetQueryPoolResults(device_, query_pool_, 0, 2, sizeof(timestamps),
timestamps,
+ sizeof(uint64_t), VK_QUERY_RESULT_64_BIT |
VK_QUERY_RESULT_WAIT_BIT);
+
+ TVM_FFI_ICHECK(result == VK_SUCCESS) << "Failed to get Vulkan query pool
results.";
+
+ // Calculate the duration in nanoseconds
+ uint64_t diff = timestamps[1] - timestamps[0];
+ duration_ = static_cast<int64_t>(static_cast<double>(diff) *
timestamp_period_);
+}
+
+void VulkanTimerNode::Cleanup() {
+ if (query_pool_ != VK_NULL_HANDLE) {
+ vkDestroyQueryPool(device_, query_pool_, nullptr);
+ query_pool_ = VK_NULL_HANDLE;
+ }
+}
+
+} // namespace vulkan
+} // namespace runtime
+} // namespace tvm
diff --git a/src/backend/vulkan/runtime/vulkan_timer.h
b/src/backend/vulkan/runtime/vulkan_timer.h
new file mode 100644
index 0000000000..62d737e6b2
--- /dev/null
+++ b/src/backend/vulkan/runtime/vulkan_timer.h
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef TVM_RUNTIME_VULKAN_VULKAN_TIMER_H_
+#define TVM_RUNTIME_VULKAN_VULKAN_TIMER_H_
+
+#include <tvm/runtime/timer.h>
+#include <vulkan/vulkan.h>
+
+#include "vulkan_device.h"
+#include "vulkan_stream.h"
+
+namespace tvm {
+namespace runtime {
+namespace vulkan {
+
+/*!
+ * \brief Timer node for measuring GPU execution time using Vulkan.
+ *
+ * This class uses Vulkan timestamp queries to measure the time taken
+ * by GPU operations between `Start()` and `Stop()` calls.
+ */
+class VulkanTimerNode : public TimerNode {
+ public:
+ /*!
+ * \brief Constructs a VulkanTimerNode for the specified device.
+ * \param dev The TVM device to be used for timing.
+ */
+ explicit VulkanTimerNode(Device dev);
+
+ /*!
+ * \brief Destructor to clean up Vulkan resources.
+ */
+ ~VulkanTimerNode() override;
+
+ /*!
+ * \brief Starts the timer by recording a timestamp.
+ */
+ void Start() override;
+
+ /*!
+ * \brief Stops the timer by recording another timestamp.
+ */
+ void Stop() override;
+
+ /*!
+ * \brief Retrieves the elapsed time in nanoseconds.
+ * \return The elapsed time in nanoseconds between Start and Stop.
+ */
+ int64_t SyncAndGetElapsedNanos() override;
+
+ TVM_FFI_DECLARE_OBJECT_INFO_FINAL("runtime.vulkan.VulkanTimerNode",
VulkanTimerNode, TimerNode);
+
+ private:
+ Device dev_; ///< The TVM device being used.
+ VkDevice device_{VK_NULL_HANDLE}; ///< The Vulkan device handle.
+ VulkanStream* stream_{nullptr}; ///< The Vulkan stream for command
buffer management.
+ VkQueryPool query_pool_{VK_NULL_HANDLE}; ///< The Vulkan query pool for
timestamp queries.
+ double timestamp_period_; ///< The period (in nanoseconds) for each
timestamp tick.
+ uint32_t start_query_ = 0; ///< The index for the start timestamp query.
+ uint32_t end_query_ = 1; ///< The index for the end timestamp query.
+ int64_t duration_ = 0; ///< The measured duration in nanoseconds.
+
+ /*!
+ * \brief Creates a Vulkan query pool for timestamp queries.
+ */
+ void CreateQueryPool();
+
+ /*!
+ * \brief Collects timestamps and calculates the duration.
+ */
+ void CollectTimestamps();
+
+ /*!
+ * \brief Cleans up the Vulkan query pool.
+ */
+ void Cleanup();
+};
+
+} // namespace vulkan
+} // namespace runtime
+} // namespace tvm
+
+#endif // TVM_RUNTIME_VULKAN_VULKAN_TIMER_H_