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 f9c1152f11 [VL] Deserialize broadcast build side per the consuming
stage's cuDF tag (#12838)
f9c1152f11 is described below
commit f9c1152f1180c864673aec4bbf94bf7dc7df8849
Author: Reema <[email protected]>
AuthorDate: Mon Aug 31 11:46:03 2026 +0300
[VL] Deserialize broadcast build side per the consuming stage's cuDF tag
(#12838)
---
.../gluten/execution/HashJoinExecTransformer.scala | 6 ++--
.../execution/VeloxBroadcastBuildSideRDD.scala | 14 ++++++++--
...loxBroadcastNestedLoopJoinExecTransformer.scala | 8 +++++-
.../sql/execution/ColumnarBuildSideRelation.scala | 18 ++++++++++--
.../unsafe/UnsafeColumnarBuildSideRelation.scala | 15 ++++++++--
.../gluten/execution/CudfBroadcastJoinSuite.scala | 32 ++++++++++++++++++++--
6 files changed, 80 insertions(+), 13 deletions(-)
diff --git
a/backends-velox/src/main/scala/org/apache/gluten/execution/HashJoinExecTransformer.scala
b/backends-velox/src/main/scala/org/apache/gluten/execution/HashJoinExecTransformer.scala
index 2c1c976e91..705f938a20 100644
---
a/backends-velox/src/main/scala/org/apache/gluten/execution/HashJoinExecTransformer.scala
+++
b/backends-velox/src/main/scala/org/apache/gluten/execution/HashJoinExecTransformer.scala
@@ -221,7 +221,7 @@ case class BroadcastHashJoinExecTransformer(
} else {
logInfo(s"Using executor-side broadcast hash table build for
$buildBroadcastTableId")
}
- VeloxBroadcastBuildSideRDD(sparkContext, broadcast, context)
+ VeloxBroadcastBuildSideRDD(sparkContext, broadcast, context,
cudfEnabled = offloadCuda)
case unsafe: UnsafeColumnarBuildSideRelation =>
joinParamsForMetrics.foreach(_.usesDriverSideSerializedHashTable =
false)
@@ -235,7 +235,7 @@ case class BroadcastHashJoinExecTransformer(
} else {
logInfo(s"Using executor-side broadcast hash table build for
$buildBroadcastTableId")
}
- VeloxBroadcastBuildSideRDD(sparkContext, broadcast, context)
+ VeloxBroadcastBuildSideRDD(sparkContext, broadcast, context,
cudfEnabled = offloadCuda)
case other =>
joinParamsForMetrics.foreach(_.usesDriverSideSerializedHashTable =
false)
@@ -243,7 +243,7 @@ case class BroadcastHashJoinExecTransformer(
logWarning(
s"Unknown broadcast relation type: ${other.getClass.getName}, " +
"using executor-side build")
- VeloxBroadcastBuildSideRDD(sparkContext, broadcast, context)
+ VeloxBroadcastBuildSideRDD(sparkContext, broadcast, context,
cudfEnabled = offloadCuda)
}
// FIXME: Do we have to make build side a RDD?
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 074eae17df..1c5fcbb70e 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
@@ -29,7 +29,8 @@ case class VeloxBroadcastBuildSideRDD(
@transient private val sc: SparkContext,
broadcasted: broadcast.Broadcast[BuildSideRelation],
broadcastContext: BroadcastHashJoinContext,
- isBNL: Boolean = false)
+ isBNL: Boolean = false,
+ cudfEnabled: Boolean = false)
extends BroadcastBuildSideRDD(sc, broadcasted) {
override def genBroadcastBuildSideIterator(): Iterator[ColumnarBatch] = {
@@ -48,8 +49,17 @@ case class VeloxBroadcastBuildSideRDD(
// 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()
+ // cudfEnabled is the consuming stage's own tag
(TransformSupport#offloadCuda).
+ val batches = relation match {
+ case columnar: ColumnarBuildSideRelation =>
+ columnar.deserialized(cudfEnabled)
+ case unsafe: UnsafeColumnarBuildSideRelation =>
+ unsafe.deserialized(cudfEnabled)
+ case other =>
+ other.deserialized
+ }
Iterators
- .wrap(relation.deserialized)
+ .wrap(batches)
.recyclePayload(batch => batch.close())
.create()
} else {
diff --git
a/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastNestedLoopJoinExecTransformer.scala
b/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastNestedLoopJoinExecTransformer.scala
index 6e0aaa27c6..7ba3edbd8e 100644
---
a/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastNestedLoopJoinExecTransformer.scala
+++
b/backends-velox/src/main/scala/org/apache/gluten/execution/VeloxBroadcastNestedLoopJoinExecTransformer.scala
@@ -45,7 +45,13 @@ case class VeloxBroadcastNestedLoopJoinExecTransformer(
override def columnarInputRDDs: Seq[RDD[ColumnarBatch]] = {
val streamedRDD = getColumnarInputRDDs(streamedPlan)
val broadcast = buildPlan.executeBroadcast[BuildSideRelation]()
- val broadcastRDD = VeloxBroadcastBuildSideRDD(sparkContext, broadcast,
null, true)
+ val broadcastRDD =
+ VeloxBroadcastBuildSideRDD(
+ sparkContext,
+ broadcast,
+ null,
+ isBNL = true,
+ cudfEnabled = offloadCuda)
// FIXME: Do we have to make build side a RDD?
streamedRDD :+ broadcastRDD
}
diff --git
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/ColumnarBuildSideRelation.scala
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/ColumnarBuildSideRelation.scala
index 7374cf0b04..dc1be02d74 100644
---
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/ColumnarBuildSideRelation.scala
+++
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/ColumnarBuildSideRelation.scala
@@ -18,6 +18,7 @@ package org.apache.spark.sql.execution
import org.apache.gluten.backendsapi.BackendsApiManager
import org.apache.gluten.columnarbatch.ColumnarBatches
+import org.apache.gluten.config.GlutenConfig
import org.apache.gluten.execution.BroadcastHashJoinContext
import org.apache.gluten.expression.ConverterUtils
import org.apache.gluten.iterator.Iterators
@@ -40,6 +41,8 @@ import org.apache.spark.util.KnownSizeEstimation
import org.apache.arrow.c.ArrowSchema
+import java.util.Collections
+
import scala.collection.JavaConverters._
import scala.collection.JavaConverters.asScalaIteratorConverter
import scala.collection.mutable.{ArrayBuffer, Map}
@@ -110,9 +113,20 @@ case class ColumnarBuildSideRelation(
}
}
- override def deserialized: Iterator[ColumnarBatch] = {
+ /** Host-resident deserialization, for CPU consumers. */
+ override def deserialized: Iterator[ColumnarBatch] =
deserialized(cudfEnabled = false)
+
+ /**
+ * Residency follows the consuming stage: a cuDF-offloaded stage sources
from CudfValueStream and
+ * needs device batches, a non-offloaded stage from RowVectorStream and
needs host batches.
+ */
+ def deserialized(cudfEnabled: Boolean): Iterator[ColumnarBatch] = {
val runtime =
- Runtimes.contextInstance(BackendsApiManager.getBackendName,
"BuildSideRelation#deserialized")
+ Runtimes.contextInstance(
+ BackendsApiManager.getBackendName,
+ "BuildSideRelation#deserialized",
+ Collections.singletonMap(GlutenConfig.COLUMNAR_CUDF_ENABLED.key,
cudfEnabled.toString)
+ )
val jniWrapper = ColumnarBatchSerializerJniWrapper.create(runtime)
val serializeHandle: Long = {
val allocator = ArrowBufferAllocators.contextInstance()
diff --git
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
index d0129e887c..1c2adbc57f 100644
---
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
+++
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/unsafe/UnsafeColumnarBuildSideRelation.scala
@@ -18,6 +18,7 @@ package org.apache.spark.sql.execution.unsafe
import org.apache.gluten.backendsapi.BackendsApiManager
import org.apache.gluten.columnarbatch.ColumnarBatches
+import org.apache.gluten.config.GlutenConfig
import org.apache.gluten.execution.BroadcastHashJoinContext
import org.apache.gluten.expression.ConverterUtils
import org.apache.gluten.iterator.Iterators
@@ -45,6 +46,7 @@ import com.esotericsoftware.kryo.io.{Input, Output}
import org.apache.arrow.c.ArrowSchema
import java.io.{Externalizable, ObjectInput, ObjectOutput}
+import java.util.Collections
import scala.collection.JavaConverters._
import scala.collection.JavaConverters.asScalaIteratorConverter
@@ -372,11 +374,20 @@ class UnsafeColumnarBuildSideRelation(
}
}
- override def deserialized: Iterator[ColumnarBatch] = {
+ /** Host-resident deserialization, for CPU consumers. */
+ override def deserialized: Iterator[ColumnarBatch] =
deserialized(cudfEnabled = false)
+
+ /**
+ * Residency follows the consuming stage: a cuDF-offloaded stage sources
from CudfValueStream and
+ * needs device batches, a non-offloaded stage from RowVectorStream and
needs host batches.
+ */
+ def deserialized(cudfEnabled: Boolean): Iterator[ColumnarBatch] = {
val runtime =
Runtimes.contextInstance(
BackendsApiManager.getBackendName,
- "UnsafeBuildSideRelation#deserialize")
+ "UnsafeBuildSideRelation#deserialize",
+ Collections.singletonMap(GlutenConfig.COLUMNAR_CUDF_ENABLED.key,
cudfEnabled.toString)
+ )
val jniWrapper = ColumnarBatchSerializerJniWrapper.create(runtime)
val serializerHandle: Long = {
val allocator = ArrowBufferAllocators.contextInstance()
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
index 62a9a64e3b..c31f707252 100644
---
a/backends-velox/src/test/scala/org/apache/gluten/execution/CudfBroadcastJoinSuite.scala
+++
b/backends-velox/src/test/scala/org/apache/gluten/execution/CudfBroadcastJoinSuite.scala
@@ -21,7 +21,7 @@ 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
+ * Regression tests for GLUTEN-12812: 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.
*
@@ -53,7 +53,7 @@ class CudfBroadcastJoinSuite extends
VeloxWholeStageTransformerSuite {
createTPCHNotNullTables()
}
- test("GLUTEN-12471: cuDF broadcast hash join returns non-empty, correct
results") {
+ test("GLUTEN-12812: cuDF broadcast hash join returns non-empty, correct
results") {
val query =
"""
|SELECT l.l_orderkey, o.o_orderdate, l.l_extendedprice
@@ -72,7 +72,33 @@ class CudfBroadcastJoinSuite extends
VeloxWholeStageTransformerSuite {
// 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)")
+ assert(df.count() > 0, "broadcast join must not return empty results
(GLUTEN-12812)")
+ }
+ }
+
+ test("GLUTEN-12838: broadcast build side follows the consuming stage's cuDF
tag") {
+ // We need a broadcast that lands in a CPU stage. NOT IN gives us one for
free: it
+ // becomes a null-aware anti join, which Spark can only run as a
broadcast, and the
+ // stage reading it has a table scan, so cuDF never claims it.
+ val query =
+ """
+ |SELECT p_partkey, p_brand
+ |FROM part
+ |WHERE p_partkey NOT IN (
+ | SELECT ps_partkey FROM partsupp WHERE ps_availqty < 10
+ |)
+ |""".stripMargin
+
+ runQueryAndCompare(query) {
+ df =>
+ val plan = df.queryExecution.executedPlan
+ val bhj = collect(plan) { case j: BroadcastHashJoinExecTransformer =>
j }
+ assert(bhj.nonEmpty, s"expected a broadcast hash join for the anti
join, got:\n$plan")
+ // The point of the test: the join must stay off cuDF, so the build
side has to
+ // arrive on the host.
+ assert(
+ bhj.forall(!_.offloadCuda),
+ s"expected the broadcast hash join to be un-tagged for cuDF,
got:\n$plan")
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]