This is an automated email from the ASF dual-hosted git repository.

marin-ma pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new 5f71f0525e [VL] Add concurrency control to GpuLock (#12090)
5f71f0525e is described below

commit 5f71f0525ed61ae8854477e9d834adeb32ecd73e
Author: Rong Ma <[email protected]>
AuthorDate: Tue Jun 23 20:16:33 2026 +0100

    [VL] Add concurrency control to GpuLock (#12090)
---
 .../org/apache/gluten/config/VeloxConfig.scala     |   6 +
 cpp/velox/compute/VeloxBackend.cc                  |   2 +
 cpp/velox/config/VeloxConfig.h                     |   4 +
 cpp/velox/cudf/GpuLock.cc                          |  70 +++++++-----
 cpp/velox/cudf/GpuLock.h                           |  12 +-
 cpp/velox/tests/CMakeLists.txt                     |  37 +++----
 cpp/velox/tests/GpuLockTest.cc                     | 123 +++++++++++++++++++++
 docs/velox-configuration.md                        |   1 +
 8 files changed, 204 insertions(+), 51 deletions(-)

diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/config/VeloxConfig.scala 
b/backends-velox/src/main/scala/org/apache/gluten/config/VeloxConfig.scala
index 3a1ce682b8..1f87ec000a 100644
--- a/backends-velox/src/main/scala/org/apache/gluten/config/VeloxConfig.scala
+++ b/backends-velox/src/main/scala/org/apache/gluten/config/VeloxConfig.scala
@@ -758,6 +758,12 @@ object VeloxConfig extends ConfigRegistry {
       .booleanConf
       .createWithDefault(true)
 
+  val CUDF_CONCURRENT_GPU_TASKS =
+    
buildStaticConf("spark.gluten.sql.columnar.backend.velox.cudf.concurrentGpuTasks")
+      .doc("The number of concurrent GPU tasks to run.")
+      .intConf
+      .createWithDefault(1)
+
   val CUDF_BATCH_SIZE =
     buildConf("spark.gluten.sql.columnar.backend.velox.cudf.batchSize")
       .doc("Cudf input batch size after shuffle reader")
diff --git a/cpp/velox/compute/VeloxBackend.cc 
b/cpp/velox/compute/VeloxBackend.cc
index 902674b23b..aa77d0ffe7 100644
--- a/cpp/velox/compute/VeloxBackend.cc
+++ b/cpp/velox/compute/VeloxBackend.cc
@@ -30,6 +30,7 @@
 #include "utils/qat/QatCodec.h"
 #endif
 #ifdef GLUTEN_ENABLE_GPU
+#include "cudf/GpuLock.h"
 #include "operators/plannodes/CudfVectorStream.h"
 #include "velox/experimental/cudf/CudfConfig.h"
 #include "velox/experimental/cudf/connectors/hive/CudfHiveConnector.h"
@@ -193,6 +194,7 @@ void VeloxBackend::init(
 
 #ifdef GLUTEN_ENABLE_GPU
   if (backendConf_->get<bool>(kCudfEnabled, kCudfEnabledDefault)) {
+    
configureGpuTaskConcurrency(backendConf_->get<uint32_t>(kCudfConcurrentGpuTasks,
 kCudfConcurrentGpuTasksDefault));
     std::unordered_map<std::string, std::string> options = {
         {velox::cudf_velox::CudfConfig::kCudfEnabled, "true"},
         {velox::cudf_velox::CudfConfig::kCudfDebugEnabled, 
backendConf_->get(kDebugCudf, kDebugCudfDefault)},
diff --git a/cpp/velox/config/VeloxConfig.h b/cpp/velox/config/VeloxConfig.h
index b88cd505ad..d88c436193 100644
--- a/cpp/velox/config/VeloxConfig.h
+++ b/cpp/velox/config/VeloxConfig.h
@@ -211,6 +211,10 @@ const std::string kCudfMemoryResourceDefault =
 const std::string kCudfMemoryPercent = 
"spark.gluten.sql.columnar.backend.velox.cudf.memoryPercent";
 const std::string kCudfMemoryPercentDefault = "50";
 
+// Maximum number of concurrent tasks allowed to execute GPU work.
+const std::string kCudfConcurrentGpuTasks = 
"spark.gluten.sql.columnar.backend.velox.cudf.concurrentGpuTasks";
+const uint32_t kCudfConcurrentGpuTasksDefault = 1;
+
 /// Preferred size of batches in bytes to be returned by operators.
 const std::string kVeloxPreferredBatchBytes = 
"spark.gluten.sql.columnar.backend.velox.preferredBatchBytes";
 
diff --git a/cpp/velox/cudf/GpuLock.cc b/cpp/velox/cudf/GpuLock.cc
index f7caaf57f5..e03ab8ac2f 100644
--- a/cpp/velox/cudf/GpuLock.cc
+++ b/cpp/velox/cudf/GpuLock.cc
@@ -19,16 +19,18 @@
 #include <glog/logging.h>
 #include <condition_variable>
 #include <mutex>
-#include <optional>
 #include <stdexcept>
 
 namespace gluten {
 
 namespace {
+thread_local bool gThreadGpuHolder = false;
+
 struct GpuLockState {
   std::mutex gGpuMutex;
   std::condition_variable gGpuCv;
-  std::optional<std::thread::id> gGpuOwner;
+  size_t maxConcurrentTasks{1};
+  size_t activeTasks{0};
 };
 
 GpuLockState& getGpuLockState() {
@@ -37,51 +39,63 @@ GpuLockState& getGpuLockState() {
 }
 } // namespace
 
+void configureGpuTaskConcurrency(size_t maxConcurrentTasks) {
+  if (maxConcurrentTasks <= 0) {
+    throw std::invalid_argument("configureGpuTaskConcurrency() requires 
maxConcurrentTasks > 0");
+  }
+
+  std::lock_guard<std::mutex> lock(getGpuLockState().gGpuMutex);
+  getGpuLockState().maxConcurrentTasks = maxConcurrentTasks;
+  getGpuLockState().gGpuCv.notify_all();
+}
+
 void lockGpu() {
-  std::thread::id tid = std::this_thread::get_id();
-  std::unique_lock<std::mutex> lock(getGpuLockState().gGpuMutex);
-  if (getGpuLockState().gGpuOwner == tid) {
-    // Reentrant call from the same thread — do nothing
+  if (gThreadGpuHolder) {
     return;
   }
 
-  // Wait until the GPU lock becomes available
-  getGpuLockState().gGpuCv.wait(lock, [] { return 
!getGpuLockState().gGpuOwner.has_value(); });
+  std::unique_lock<std::mutex> lock(getGpuLockState().gGpuMutex);
+  getGpuLockState().gGpuCv.wait(
+      lock, [] { return getGpuLockState().activeTasks < 
getGpuLockState().maxConcurrentTasks; });
 
-  // Acquire ownership
-  getGpuLockState().gGpuOwner = tid;
+  ++getGpuLockState().activeTasks;
+  gThreadGpuHolder = true;
 }
 
 bool tryLockGpu() {
-  std::thread::id tid = std::this_thread::get_id();
-  std::unique_lock<std::mutex> lock(getGpuLockState().gGpuMutex);
-  if (getGpuLockState().gGpuOwner == tid) {
+  if (gThreadGpuHolder) {
     return true;
   }
-  if (getGpuLockState().gGpuOwner.has_value()) {
-    return false;
+
+  std::unique_lock<std::mutex> lock(getGpuLockState().gGpuMutex);
+
+  // Check if a permit is available without blocking
+  if (getGpuLockState().activeTasks < getGpuLockState().maxConcurrentTasks) {
+    ++getGpuLockState().activeTasks;
+    gThreadGpuHolder = true;
+    return true;
   }
-  getGpuLockState().gGpuOwner = tid;
-  return true;
+
+  return false;
 }
 
 void unlockGpu() {
-  std::thread::id tid = std::this_thread::get_id();
-  std::unique_lock<std::mutex> lock(getGpuLockState().gGpuMutex);
-  if (!getGpuLockState().gGpuOwner.has_value()) {
-    LOG(INFO) << "unlockGpu() called when no thread holds the lock!";
+  if (!gThreadGpuHolder) {
+    LOG(INFO) << "unlockGpu() called by non-owner thread!" << std::endl;
     return;
   }
 
-  if (getGpuLockState().gGpuOwner != tid) {
-    throw std::runtime_error("unlockGpu() called by other thread!");
-  }
+  gThreadGpuHolder = false;
 
-  // Release ownership
-  getGpuLockState().gGpuOwner = std::nullopt;
+  {
+    std::lock_guard<std::mutex> lock(getGpuLockState().gGpuMutex);
+    if (getGpuLockState().activeTasks == 0) {
+      throw std::runtime_error("unlockGpu() called with no active GPU tasks!");
+    }
+
+    --getGpuLockState().activeTasks;
+  }
 
-  // Notify one waiting thread
-  lock.unlock();
   getGpuLockState().gGpuCv.notify_one();
 }
 
diff --git a/cpp/velox/cudf/GpuLock.h b/cpp/velox/cudf/GpuLock.h
index bbf55491a7..0873055419 100644
--- a/cpp/velox/cudf/GpuLock.h
+++ b/cpp/velox/cudf/GpuLock.h
@@ -17,13 +17,23 @@
 
 #pragma once
 
+#include <cstddef>
 #include <thread>
 
 namespace gluten {
 
-/// Acquire the GPU lock, blocking until available. Reentrant for the same 
thread.
+/// Configure the maximum number of concurrent GPU tasks.
+/// Must be greater than 0.
+void configureGpuTaskConcurrency(size_t maxConcurrentTasks);
+
+/// Acquire a GPU execution permit (reentrant within the same thread).
 void lockGpu();
+
+/// Try to acquire a GPU execution permit without blocking (reentrant within 
the same thread).
+/// Returns true if the permit was acquired, false otherwise.
 bool tryLockGpu();
+
+/// Release a GPU execution permit held by the current thread.
 void unlockGpu();
 
 } // namespace gluten
diff --git a/cpp/velox/tests/CMakeLists.txt b/cpp/velox/tests/CMakeLists.txt
index 0f5e634b9d..59408cd233 100644
--- a/cpp/velox/tests/CMakeLists.txt
+++ b/cpp/velox/tests/CMakeLists.txt
@@ -37,14 +37,20 @@ function(add_velox_test TEST_EXEC)
 endfunction()
 
 if(ENABLE_GPU)
-  function(import_library TARGET_NAME LIB_PATH)
-    if(NOT EXISTS ${LIB_PATH})
-      message(FATAL_ERROR "Library does not exist: ${LIB_PATH}")
-    endif()
-    add_library(${TARGET_NAME} STATIC IMPORTED)
-    set_target_properties(${TARGET_NAME} PROPERTIES IMPORTED_LOCATION
-                                                    ${LIB_PATH})
-  endfunction()
+  import_library(
+    facebook::velox::velox_cudf_expression
+    
${VELOX_BUILD_PATH}/velox/experimental/cudf/expression/libvelox_cudf_expression.a
+  )
+  import_library(
+    facebook::velox::velox_cudf_exec
+    ${VELOX_BUILD_PATH}/velox/experimental/cudf/exec/libvelox_cudf_exec.a)
+  import_library(
+    facebook::velox::velox_cudf_vector
+    ${VELOX_BUILD_PATH}/velox/experimental/cudf/vector/libvelox_cudf_vector.a)
+  import_library(
+    facebook::velox::velox_cudf_hive_connector
+    
${VELOX_BUILD_PATH}/velox/experimental/cudf/connectors/hive/libvelox_cudf_hive_connector.a
+  )
 
   function(add_velox_gpu_test TEST_EXEC)
     set(options)
@@ -61,20 +67,6 @@ if(ENABLE_GPU)
     add_executable(${TEST_EXEC} ${SOURCES} ${VELOX_TEST_COMMON_SRCS})
     target_include_directories(${TEST_EXEC} PRIVATE ${CMAKE_SOURCE_DIR}/velox
                                                     ${CMAKE_SOURCE_DIR}/src)
-    import_library(
-      facebook::velox::velox_cudf_expression
-      
${VELOX_BUILD_PATH}/velox/experimental/cudf/expression/libvelox_cudf_expression.a
-    )
-    import_library(
-      facebook::velox::velox_cudf_exec
-      ${VELOX_BUILD_PATH}/velox/experimental/cudf/exec/libvelox_cudf_exec.a)
-    import_library(
-      facebook::velox::velox_cudf_vector
-      
${VELOX_BUILD_PATH}/velox/experimental/cudf/vector/libvelox_cudf_vector.a)
-    import_library(
-      facebook::velox::velox_cudf_hive_connector
-      
${VELOX_BUILD_PATH}/velox/experimental/cudf/connectors/hive/libvelox_cudf_hive_connector.a
-    )
     target_compile_definitions(
       ${TEST_EXEC} PUBLIC LIBCUDACXX_ENABLE_EXPERIMENTAL_MEMORY_RESOURCE)
     target_link_libraries(
@@ -94,6 +86,7 @@ if(ENABLE_GPU)
 
   add_velox_gpu_test(velox_gpu_shuffle_writer_test SOURCES
                      VeloxGpuShuffleWriterTest.cc)
+  add_velox_gpu_test(velox_gpu_lock_test SOURCES GpuLockTest.cc)
 endif()
 
 set(VELOX_TEST_COMMON_SRCS JsonToProtoConverter.cc FilePathGenerator.cc)
diff --git a/cpp/velox/tests/GpuLockTest.cc b/cpp/velox/tests/GpuLockTest.cc
new file mode 100644
index 0000000000..53a67b7bf9
--- /dev/null
+++ b/cpp/velox/tests/GpuLockTest.cc
@@ -0,0 +1,123 @@
+/*
+ * 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 "cudf/GpuLock.h"
+
+#include <atomic>
+#include <chrono>
+#include <future>
+#include <stdexcept>
+#include <thread>
+
+#include <gtest/gtest.h>
+
+namespace gluten {
+
+TEST(GpuLockTest, configureRejectsZeroConcurrency) {
+  configureGpuTaskConcurrency(1);
+  EXPECT_THROW(configureGpuTaskConcurrency(0), std::invalid_argument);
+}
+
+TEST(GpuLockTest, sameThreadLockIsReentrant) {
+  configureGpuTaskConcurrency(1);
+  lockGpu();
+  EXPECT_TRUE(tryLockGpu());
+
+  unlockGpu();
+  EXPECT_TRUE(tryLockGpu());
+
+  unlockGpu();
+}
+
+TEST(GpuLockTest, tryLockFailsWhilePermitHeldByAnotherThread) {
+  configureGpuTaskConcurrency(1);
+  lockGpu();
+
+  auto acquired = std::async(std::launch::async, [] { return tryLockGpu(); });
+  EXPECT_FALSE(acquired.get());
+
+  unlockGpu();
+}
+
+TEST(GpuLockTest, lockBlocksUntilPermitIsReleased) {
+  configureGpuTaskConcurrency(1);
+  lockGpu();
+
+  std::promise<void> workerStarted;
+  auto workerStartedFuture = workerStarted.get_future();
+  std::atomic<bool> workerAcquired{false};
+
+  std::thread worker([&] {
+    workerStarted.set_value();
+    EXPECT_FALSE(tryLockGpu());
+    lockGpu();
+    workerAcquired.store(true);
+    unlockGpu();
+  });
+
+  workerStartedFuture.wait();
+  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  EXPECT_FALSE(workerAcquired.load());
+
+  unlockGpu();
+  worker.join();
+
+  EXPECT_TRUE(workerAcquired.load());
+}
+
+TEST(GpuLockTest, allowsUpToConfiguredConcurrentTasks) {
+  configureGpuTaskConcurrency(2);
+
+  std::promise<void> firstLocked;
+  std::promise<void> secondLocked;
+  auto firstLockedFuture = firstLocked.get_future();
+  auto secondLockedFuture = secondLocked.get_future();
+  std::promise<void> releaseWorkers;
+  auto releaseWorkersFuture = releaseWorkers.get_future().share();
+  std::atomic<int> acquiredCount{0};
+
+  std::thread first([&] {
+    lockGpu();
+    EXPECT_TRUE(tryLockGpu());
+    acquiredCount.fetch_add(1);
+    firstLocked.set_value();
+    releaseWorkersFuture.wait();
+    unlockGpu();
+  });
+
+  std::thread second([&] {
+    lockGpu();
+    EXPECT_TRUE(tryLockGpu());
+    acquiredCount.fetch_add(1);
+    secondLocked.set_value();
+    releaseWorkersFuture.wait();
+    unlockGpu();
+  });
+
+  firstLockedFuture.wait();
+  secondLockedFuture.wait();
+  EXPECT_EQ(acquiredCount.load(), 2);
+
+  auto thirdAcquire = std::async(std::launch::async, [] { return tryLockGpu(); 
});
+  EXPECT_FALSE(thirdAcquire.get());
+
+  releaseWorkers.set_value();
+  first.join();
+  second.join();
+}
+
+} // namespace gluten
diff --git a/docs/velox-configuration.md b/docs/velox-configuration.md
index 7f8918d40d..b0e1afdaa1 100644
--- a/docs/velox-configuration.md
+++ b/docs/velox-configuration.md
@@ -20,6 +20,7 @@ nav_order: 16
 | spark.gluten.sql.columnar.backend.velox.cachePrefetchMinPct                  
    | ⚓ Static      | 0                 | Set prefetch cache min pct for velox 
file scan                                                                       
                                                                                
                                                                                
                                                                                
               [...]
 | spark.gluten.sql.columnar.backend.velox.checkUsageLeak                       
    | ⚓ Static      | true              | Enable check memory usage leak.       
                                                                                
                                                                                
                                                                                
                                                                                
              [...]
 | spark.gluten.sql.columnar.backend.velox.cudf.batchSize                       
    | 🔄 Dynamic    | 2147483647        | Cudf input batch size after shuffle 
reader                                                                          
                                                                                
                                                                                
                                                                                
                 [...]
+| spark.gluten.sql.columnar.backend.velox.cudf.concurrentGpuTasks              
    | ⚓ Static      | 1                 | The number of concurrent GPU tasks to 
run.                                                                            
                                                                                
                                                                                
                                                                                
              [...]
 | spark.gluten.sql.columnar.backend.velox.cudf.enableTableScan                 
    | ⚓ Static      | false             | Enable cudf table scan                
                                                                                
                                                                                
                                                                                
                                                                                
              [...]
 | spark.gluten.sql.columnar.backend.velox.cudf.enableValidation                
    | ⚓ Static      | true              | Heuristics you can apply to validate 
a cuDF/GPU plan and only offload when the entire stage can be fully and 
profitably executed on GPU                                                      
                                                                                
                                                                                
                       [...]
 | spark.gluten.sql.columnar.backend.velox.cudf.memoryPercent                   
    | ⚓ Static      | 50                | The initial percent of GPU memory to 
allocate for memory resource for one thread.                                    
                                                                                
                                                                                
                                                                                
               [...]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to