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_

Reply via email to