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 6dee1fa824 [VL] Fix empty cuDF broadcast join results by streaming the 
build side (#12812)
6dee1fa824 is described below

commit 6dee1fa8244387eee3e2258885d72390d0f0be68
Author: Reema <[email protected]>
AuthorDate: Wed Aug 19 10:16:41 2026 +0200

    [VL] Fix empty cuDF broadcast join results by streaming the build side 
(#12812)
    
    With cuDF enabled, broadcast hash joins run as CudfHashJoin, which builds
    its own GPU hash table from the build-side value stream and cannot consume
    the prebuilt CPU OpaqueHashTable created by the build-once-per-executor
    optimization. Feeding it Iterator.empty silently produced empty join
    results.
    
    Skip the prebuilt-table path and stream the broadcast relation batches
    when cuDF is enabled, the same path shuffle joins use on GPU. Skipping the
    CPU cache build also keeps hybrid mode correct: a CPU-fallback join finds
    no cached table and builds from the stream as usual.
    
    Adds a CudfTest-tagged regression suite. The tag is excluded in CI, which
    has no GPU test runners; run on a GPU host with -DtagsToExclude=None.
---
 .github/workflows/velox_backend_ansi.yml           |  8 +--
 .github/workflows/velox_backend_enhanced.yml       |  4 +-
 .github/workflows/velox_backend_x86.yml            | 12 ++--
 backends-velox/pom.xml                             |  2 +-
 .../execution/VeloxBroadcastBuildSideRDD.scala     | 10 ++-
 .../test/java/org/apache/gluten/tags/CudfTest.java | 29 ++++++++
 .../gluten/execution/CudfBroadcastJoinSuite.scala  | 79 ++++++++++++++++++++++
 7 files changed, 130 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/velox_backend_ansi.yml 
b/.github/workflows/velox_backend_ansi.yml
index 5f4bb1d524..2ed44123ce 100644
--- a/.github/workflows/velox_backend_ansi.yml
+++ b/.github/workflows/velox_backend_ansi.yml
@@ -172,7 +172,7 @@ jobs:
           java -version
           $MVN_CMD clean test -Pspark-4.1 -Pscala-2.13 -Pjava-17 
-Pbackends-velox \
           -DargLine="-Dspark.test.home=/opt/shims/spark41/spark_home/ 
-Dspark.gluten.sql.ansiFallback.enabled=false" \
-          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest
 \
+          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest
 \
           2>&1 | tee backends-velox-test-output.log
       - name: "Parse test results"
         if: always()
@@ -241,7 +241,7 @@ jobs:
           $MVN_CMD clean test -Pspark-4.1 -Pscala-2.13 -Pjava-17 
-Pbackends-velox -Pspark-ut \
           -DwildcardSuites='org.apache.spark.' -Dtest=none 
-DfailIfNoTests=false \
           -DargLine="-Dspark.test.home=/opt/shims/spark41/spark_home/ 
-Dspark.gluten.sql.ansiFallback.enabled=false" \
-          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest
 \
+          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest
 \
           2>&1 | tee spark-ut-test-output.log
       - name: "Parse test results"
         if: always()
@@ -318,7 +318,7 @@ jobs:
           java -version
           $MVN_CMD clean test -Pspark-4.0 -Pscala-2.13 -Pjava-17 
-Pbackends-velox \
           -DargLine="-Dspark.test.home=/opt/shims/spark40/spark_home/ 
-Dspark.gluten.sql.ansiFallback.enabled=false" \
-          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest
 \
+          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest
 \
           2>&1 | tee backends-velox-spark40-test-output.log
       - name: "Parse test results"
         if: always()
@@ -387,7 +387,7 @@ jobs:
           $MVN_CMD clean test -Pspark-4.0 -Pscala-2.13 -Pjava-17 
-Pbackends-velox -Pspark-ut \
           -DwildcardSuites='org.apache.spark.' -Dtest=none 
-DfailIfNoTests=false \
           -DargLine="-Dspark.test.home=/opt/shims/spark40/spark_home/ 
-Dspark.gluten.sql.ansiFallback.enabled=false" \
-          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest
 \
+          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest
 \
           2>&1 | tee spark-ut-spark40-test-output.log
       - name: "Parse test results"
         if: always()
diff --git a/.github/workflows/velox_backend_enhanced.yml 
b/.github/workflows/velox_backend_enhanced.yml
index ac0b1bb18c..e9493a3ee0 100644
--- a/.github/workflows/velox_backend_enhanced.yml
+++ b/.github/workflows/velox_backend_enhanced.yml
@@ -160,7 +160,7 @@ jobs:
           export SPARK_HOME=/opt/shims/spark35/spark_home/
           ls -l $SPARK_HOME
           $MVN_CMD clean test -Pspark-3.5 -Pjava-17 -Pbackends-velox -Piceberg 
-Pdelta -Phudi \
-          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.SkipTest
 \
+          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest
 \
           -DargLine="-Dspark.test.home=$SPARK_HOME"
       - name: Upload test report
         if: always()
@@ -264,7 +264,7 @@ jobs:
           export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
           export PATH=$JAVA_HOME/bin:$PATH
           java -version
-          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.SkipTest"
+          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest"
           if [ "${{ matrix.group }}" = "1" ]; then
             $MVN_CMD clean test -Pspark-4.0 -Pscala-2.13 -Pjava-17 
-Pbackends-velox -Piceberg -Pdelta \
             -Pspark-ut 
-DargLine="-Dspark.test.home=/opt/shims/spark40/spark_home/" 
-DtagsToExclude="$TAGS_EXCLUDE" \
diff --git a/.github/workflows/velox_backend_x86.yml 
b/.github/workflows/velox_backend_x86.yml
index 951f55cc9f..4a2c600efe 100644
--- a/.github/workflows/velox_backend_x86.yml
+++ b/.github/workflows/velox_backend_x86.yml
@@ -821,7 +821,7 @@ jobs:
           java -version
           $MVN_CMD clean test -Pspark-3.3 -Pjava-17 -Pbackends-velox -Piceberg 
-Pdelta -Phudi -Ppaimon -Pspark-ut \
           -DargLine="-Dspark.test.home=/opt/shims/spark33/spark_home/" \
-          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest
+          
-DtagsToExclude=org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest
       - name: Upload test report
         if: always()
         uses: actions/upload-artifact@v7
@@ -926,7 +926,7 @@ jobs:
           export PATH=$JAVA_HOME/bin:$PATH
           java -version
           export SPARK_HOME=/opt/shims/spark34/spark_home/
-          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest"
+          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest"
           if [ "${{ matrix.group }}" = "1" ]; then
             $MVN_CMD clean test -Pspark-3.4 -Pjava-17 -Pbackends-velox 
-Piceberg -Piceberg-test -Pdelta -Phudi -Ppaimon -Pspark-ut \
             -DtagsToExclude="$TAGS_EXCLUDE" 
-DargLine="-Dspark.test.home=$SPARK_HOME" \
@@ -1050,7 +1050,7 @@ jobs:
           export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
           export PATH=$JAVA_HOME/bin:$PATH
           java -version
-          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest"
+          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest"
           if [ "${{ matrix.group }}" = "1" ]; then
             $MVN_CMD clean test -Pspark-3.5 -Pjava-17 -Pbackends-velox 
-Piceberg -Pdelta -Phudi -Ppaimon -Pspark-ut \
             -DargLine="-Dspark.test.home=/opt/shims/spark35/spark_home/" 
-DtagsToExclude="$TAGS_EXCLUDE" \
@@ -1122,7 +1122,7 @@ jobs:
           export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
           export PATH=$JAVA_HOME/bin:$PATH
           java -version
-          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest"
+          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest"
           if [ "${{ matrix.group }}" = "1" ]; then
             $MVN_CMD clean test -Pspark-3.5 -Pscala-2.13 -Pjava-17 
-Pbackends-velox -Piceberg -Pdelta -Pspark-ut \
             
-DargLine="-Dspark.test.home=/opt/shims/spark35-scala-2.13/spark_home/" 
-DtagsToExclude="$TAGS_EXCLUDE" \
@@ -1426,7 +1426,7 @@ jobs:
           export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
           export PATH=$JAVA_HOME/bin:$PATH
           java -version
-          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest"
+          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest"
           if [ "${{ matrix.group }}" = "1" ]; then
             $MVN_CMD clean test -Pspark-4.0 -Pscala-2.13 -Pjava-17 
-Pbackends-velox -Pdelta -Ppaimon -Pspark-ut \
             -DargLine="-Dspark.test.home=/opt/shims/spark40/spark_home/" 
-DtagsToExclude="$TAGS_EXCLUDE" \
@@ -1551,7 +1551,7 @@ jobs:
           export JAVA_HOME=/usr/lib/jvm/java-17-openjdk
           export PATH=$JAVA_HOME/bin:$PATH
           java -version
-          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.SkipTest"
+          
TAGS_EXCLUDE="org.apache.spark.tags.ExtendedSQLTest,org.apache.spark.tags.SlowHiveTest,org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.gluten.tags.SkipTest"
           if [ "${{ matrix.group }}" = "1" ]; then
             # Group 1: streaming + gluten utils + top-level spark tests (~55 
classes)
             $MVN_CMD clean test -Pspark-4.1 -Pscala-2.13 -Pjava-17 
-Pbackends-velox -Pspark-ut -Pdelta \
diff --git a/backends-velox/pom.xml b/backends-velox/pom.xml
index 8f833020bb..acdc199c17 100644
--- a/backends-velox/pom.xml
+++ b/backends-velox/pom.xml
@@ -374,7 +374,7 @@
         <activeByDefault>true</activeByDefault>
       </activation>
       <properties>
-        
<tagsToExclude>org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.spark.tags.SkipTest</tagsToExclude>
+        
<tagsToExclude>org.apache.gluten.tags.UDFTest,org.apache.gluten.tags.EnhancedFeaturesTest,org.apache.gluten.tags.CudfTest,org.apache.spark.tags.SkipTest</tagsToExclude>
       </properties>
     </profile>
     <profile>
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastBuildSideRDD.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastBuildSideRDD.scala
index 2d4b157056..074eae17df 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastBuildSideRDD.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastBuildSideRDD.scala
@@ -16,6 +16,7 @@
  */
 package org.apache.gluten.execution
 
+import org.apache.gluten.config.GlutenConfig
 import org.apache.gluten.iterator.Iterators
 
 import org.apache.spark.{broadcast, SparkContext}
@@ -38,7 +39,14 @@ case class VeloxBroadcastBuildSideRDD(
       case unsafe: UnsafeColumnarBuildSideRelation =>
         unsafe.isOffload
     }
-    val output = if (isBNL || !offload) {
+    // With cuDF enabled the hash join runs as CudfHashJoin, which builds its 
own GPU
+    // hash table from the build-side value stream and cannot consume the 
prebuilt CPU
+    // OpaqueHashTable. Feeding Iterator.empty here silently produces empty 
join
+    // results, so stream the broadcast batches instead (the same path shuffle 
joins
+    // use on GPU). Skipping the CPU cache build also keeps hybrid mode 
correct:
+    // VeloxBroadcastBuildSideCache.get finds no table, so the HashJoinNode 
carries no
+    // reusable table and a CPU-fallback join builds from this stream as usual.
+    val output = if (isBNL || !offload || GlutenConfig.get.enableColumnarCudf) 
{
       val relation = broadcasted.value.asReadOnlyCopy()
       Iterators
         .wrap(relation.deserialized)
diff --git a/backends-velox/src/test/java/org/apache/gluten/tags/CudfTest.java 
b/backends-velox/src/test/java/org/apache/gluten/tags/CudfTest.java
new file mode 100644
index 0000000000..5874be95e0
--- /dev/null
+++ b/backends-velox/src/test/java/org/apache/gluten/tags/CudfTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+package org.apache.gluten.tags;
+
+import org.scalatest.TagAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@TagAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface CudfTest {}
diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/execution/CudfBroadcastJoinSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/execution/CudfBroadcastJoinSuite.scala
new file mode 100644
index 0000000000..62a9a64e3b
--- /dev/null
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/execution/CudfBroadcastJoinSuite.scala
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+package org.apache.gluten.execution
+
+import org.apache.gluten.tags.CudfTest
+
+import org.apache.spark.SparkConf
+
+/**
+ * Regression tests for GLUTEN-12471: broadcast hash joins on the cuDF (GPU) 
backend silently
+ * returned empty results because CudfHashJoin built its hash table from the 
empty build-side
+ * iterator instead of the prebuilt CPU table.
+ *
+ * These tests require GPU hardware and a cuDF-enabled build, so they are 
tagged with CudfTest and
+ * excluded from regular CI runs. Run on a GPU host:
+ *
+ * mvn test -Pbackends-velox -DtagsToExclude=None \
+ * -DwildcardSuites=org.apache.gluten.execution.CudfBroadcastJoinSuite
+ */
+@CudfTest
+class CudfBroadcastJoinSuite extends VeloxWholeStageTransformerSuite {
+
+  override protected val resourcePath: String = "/tpch-data-parquet"
+  override protected val fileFormat: String = "parquet"
+
+  override protected def sparkConf: SparkConf = {
+    super.sparkConf
+      .set("spark.gluten.sql.columnar.cudf", "true")
+      .set("spark.gluten.sql.columnar.backend.velox.cudf.allowCpuFallback", 
"false")
+      // Force the broadcast path: the small side (orders filter) fits easily.
+      .set("spark.sql.autoBroadcastJoinThreshold", "10MB")
+      .set("spark.shuffle.manager", 
"org.apache.spark.shuffle.sort.ColumnarShuffleManager")
+      .set("spark.memory.offHeap.enabled", "true")
+      .set("spark.memory.offHeap.size", "4g")
+  }
+
+  override def beforeAll(): Unit = {
+    super.beforeAll()
+    createTPCHNotNullTables()
+  }
+
+  test("GLUTEN-12471: cuDF broadcast hash join returns non-empty, correct 
results") {
+    val query =
+      """
+        |SELECT l.l_orderkey, o.o_orderdate, l.l_extendedprice
+        |FROM lineitem l
+        |JOIN orders o ON l.l_orderkey = o.o_orderkey
+        |WHERE o.o_orderdate < date '1995-01-01'
+        |""".stripMargin
+
+    // runQueryAndCompare executes on Gluten AND vanilla Spark and compares
+    // results -- this is the core regression check: before the fix, the Gluten
+    // side returned 0 rows and the comparison failed.
+    runQueryAndCompare(query) {
+      df =>
+        val plan = df.queryExecution.executedPlan
+        // The join must actually be offloaded as a broadcast hash join, not
+        // demoted or fallen back.
+        val bhj = collect(plan) { case j: BroadcastHashJoinExecTransformer => 
j }
+        assert(bhj.nonEmpty, s"expected an offloaded broadcast hash join, 
got:\n$plan")
+        assert(df.count() > 0, "broadcast join must not return empty results 
(GLUTEN-12471)")
+    }
+  }
+
+}


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

Reply via email to