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 c1f72ca95e [GLUTEN-11524][VL] Fix ColumnarAQEShuffleReadExec (#12691)
c1f72ca95e is described below

commit c1f72ca95e45ccc1785b0df9d3f2fc3954979703
Author: Rong Ma <[email protected]>
AuthorDate: Wed Aug 5 15:11:02 2026 +0100

    [GLUTEN-11524][VL] Fix ColumnarAQEShuffleReadExec (#12691)
---
 .../org/apache/gluten/config/VeloxConfig.scala     |  2 +-
 ...AppendBatchResizeForShuffleInputAndOutput.scala |  4 +-
 .../sql/execution/AdjustStageExecutionMode.scala   | 16 ++---
 .../gluten/execution/MiscOperatorSuite.scala       | 45 ++++++++++++-
 .../gluten/execution/StageExecutionModeSuite.scala | 29 +++++----
 docs/velox-configuration.md                        |  2 +-
 .../adaptive/ColumnarAQEShuffleReadExec.scala      | 76 +++++++++++-----------
 7 files changed, 111 insertions(+), 63 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 b742e3a5a0..fade4402cf 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
@@ -822,7 +822,7 @@ object VeloxConfig extends ConfigRegistry {
       .createWithDefault(false)
 
   val CUDF_ENABLE_VALIDATION =
-    
buildStaticConf("spark.gluten.sql.columnar.backend.velox.cudf.enableValidation")
+    buildConf("spark.gluten.sql.columnar.backend.velox.cudf.enableValidation")
       .doc(
         "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")
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/extension/AppendBatchResizeForShuffleInputAndOutput.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/extension/AppendBatchResizeForShuffleInputAndOutput.scala
index 872ab1db25..fbed0f5f5c 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/extension/AppendBatchResizeForShuffleInputAndOutput.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/extension/AppendBatchResizeForShuffleInputAndOutput.scala
@@ -31,7 +31,9 @@ case class 
AppendBatchResizeForShuffleInputAndOutput(isAdaptiveContext: Boolean)
   extends Rule[SparkPlan] {
   override def apply(plan: SparkPlan): SparkPlan = {
     val resizeBatchesShuffleInputEnabled = 
VeloxConfig.get.veloxResizeBatchesShuffleInput
-    val resizeBatchesShuffleOutputEnabled = 
VeloxConfig.get.veloxResizeBatchesShuffleOutput
+    // TODO: Move cudf resize batches into shuffle reader.
+    val resizeBatchesShuffleOutputEnabled =
+      VeloxConfig.get.veloxResizeBatchesShuffleOutput || 
VeloxConfig.get.enableColumnarCudf
     if (!resizeBatchesShuffleInputEnabled && 
!resizeBatchesShuffleOutputEnabled) {
       return plan
     }
diff --git 
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/AdjustStageExecutionMode.scala
 
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/AdjustStageExecutionMode.scala
index adadc67df7..adddc630eb 100644
--- 
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/AdjustStageExecutionMode.scala
+++ 
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/AdjustStageExecutionMode.scala
@@ -85,21 +85,21 @@ object AdjustStageExecutionMode extends Logging {
       // TODO: support BroadcastQueryStageExec.
       case aqeShuffleRead @ AQEShuffleReadExec(s @ ShuffleQueryStageExec(_, _, 
_), _)
           if s.shuffle.isInstanceOf[ColumnarShuffleExchangeExec] =>
-        ColumnarAQEShuffleReadExec(
-          Left(aqeShuffleRead),
-          stageExecutionMode)
+        ColumnarAQEShuffleReadExec(aqeShuffleRead, stageExecutionMode)
       case queryStageExec: ShuffleQueryStageExec
           if queryStageExec.shuffle.isInstanceOf[ColumnarShuffleExchangeExec] 
=>
-        ColumnarAQEShuffleReadExec(
-          Right(queryStageExec),
-          stageExecutionMode)
+        ColumnarAQEShuffleReadExec(queryStageExec, stageExecutionMode)
       case shuffle: ColumnarShuffleExchangeExec =>
         shuffle
           .copy(mapperStageMode = Some(stageExecutionMode))
           .withNewChildren(Seq(adjustExecutionMode(shuffle.child, 
stageExecutionMode)))
-      case resizeBatches: VeloxResizeBatchesExec =>
+      case r: VeloxResizeBatchesExec
+          // TODO: This should be removed after merging resize into native 
shuffle read.
+          // Only change the execution mode for shuffle reader.
+          if r.child.isInstanceOf[ShuffleQueryStageExec] ||
+            r.child.isInstanceOf[AQEShuffleReadExec] =>
         VeloxResizeBatchesExec(
-          adjustExecutionMode(resizeBatches.child, stageExecutionMode),
+          adjustExecutionMode(r.child, stageExecutionMode),
           Some(stageExecutionMode))
       case _ =>
         plan.withNewChildren(plan.children.map(adjustExecutionMode(_, 
stageExecutionMode)))
diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
index 6fad06dc0b..6cac6ccb1c 100644
--- 
a/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/execution/MiscOperatorSuite.scala
@@ -23,7 +23,7 @@ import org.apache.spark.SparkConf
 import org.apache.spark.shuffle.GlutenShuffleUtils
 import org.apache.spark.sql.{DataFrame, Row}
 import org.apache.spark.sql.execution._
-import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanHelper, 
AQEShuffleReadExec, ShuffleQueryStageExec}
+import org.apache.spark.sql.execution.adaptive.{AdaptiveSparkPlanHelper, 
AQEShuffleReadExec, ColumnarAQEShuffleReadExec, ShuffleQueryStageExec}
 import org.apache.spark.sql.execution.joins.BaseJoinExec
 import org.apache.spark.sql.execution.window.WindowExec
 import org.apache.spark.sql.functions._
@@ -2199,6 +2199,49 @@ class MiscOperatorSuite extends 
VeloxWholeStageTransformerSuite with AdaptiveSpa
       })
   }
 
+  test("Check VeloxResizeBatches is added in ShuffleRead when cuDF is 
enabled") {
+    Seq(true, false).foreach(
+      coalesceEnabled => {
+        withSQLConf(
+          GlutenConfig.COLUMNAR_CUDF_ENABLED.key -> "true",
+          VeloxConfig.CUDF_ENABLE_VALIDATION.key -> "false",
+          VeloxConfig.COLUMNAR_VELOX_RESIZE_BATCHES_SHUFFLE_OUTPUT.key -> 
"false",
+          SQLConf.SHUFFLE_PARTITIONS.key -> "10",
+          SQLConf.COALESCE_PARTITIONS_ENABLED.key -> coalesceEnabled.toString
+        ) {
+          runQueryAndCompare(
+            "SELECT l_orderkey, count(1) from lineitem group by 
l_orderkey".stripMargin) {
+            df =>
+              val executedPlan = getExecutedPlan(df)
+              if (coalesceEnabled) {
+                // 
VeloxResizeBatches(AQEShuffleRead(ShuffleQueryStage(ColumnarShuffleExchange)))
+                assert(executedPlan.sliding(4).exists {
+                  case Seq(
+                        _: ColumnarShuffleExchangeExec,
+                        _: ShuffleQueryStageExec,
+                        ColumnarAQEShuffleReadExec(AQEShuffleReadExec(_, _), 
_),
+                        _: VeloxResizeBatchesExec
+                      ) =>
+                    true
+                  case _ => false
+                })
+              } else {
+                // 
VeloxResizeBatches(ShuffleQueryStage(ColumnarShuffleExchange))
+                assert(executedPlan.sliding(4).exists {
+                  case Seq(
+                        _: ColumnarShuffleExchangeExec,
+                        _: ShuffleQueryStageExec,
+                        ColumnarAQEShuffleReadExec(ShuffleQueryStageExec(_, _, 
_), _),
+                        _: VeloxResizeBatchesExec) =>
+                    true
+                  case _ => false
+                })
+              }
+          }
+        }
+      })
+  }
+
   test("RowToVeloxColumnar preferredBatchBytes") {
     Seq("1", "80", "100000000").foreach(
       preferredBatchBytes => {
diff --git 
a/backends-velox/src/test/scala/org/apache/gluten/execution/StageExecutionModeSuite.scala
 
b/backends-velox/src/test/scala/org/apache/gluten/execution/StageExecutionModeSuite.scala
index 50458d43b6..6efaeae753 100644
--- 
a/backends-velox/src/test/scala/org/apache/gluten/execution/StageExecutionModeSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/gluten/execution/StageExecutionModeSuite.scala
@@ -21,7 +21,8 @@ import org.apache.gluten.config.{GlutenConfig, VeloxConfig}
 import org.apache.spark.SparkConf
 import org.apache.spark.sql.Row
 import org.apache.spark.sql.execution.ColumnarShuffleExchangeExec
-import org.apache.spark.sql.execution.adaptive.{ColumnarAQEShuffleReadExec, 
ShuffleQueryStageExec}
+import org.apache.spark.sql.execution.adaptive.{AQEShuffleReadExec, 
ColumnarAQEShuffleReadExec, ShuffleQueryStageExec}
+import org.apache.spark.sql.execution.exchange.ShuffleExchangeExec
 import org.apache.spark.sql.internal.SQLConf
 
 class StageExecutionModeSuite extends VeloxWholeStageTransformerSuite {
@@ -108,25 +109,27 @@ class StageExecutionModeSuite extends 
VeloxWholeStageTransformerSuite {
 
       shuffleReaders.foreach {
         reader =>
+          val canonicalized = reader.canonicalized
+          // canonicalized plan before applying query stage optimizer rules.
+          
assert(canonicalized.children.forall(_.isInstanceOf[ShuffleExchangeExec]))
           assert(
             reader.executionMode == MockGPUStageMode,
             s"Expected GPU AQE shuffle reader, but got 
${reader.executionMode}")
       }
 
-      val shuffleStages = plan.collect {
-        case stage: ShuffleQueryStageExec => stage
+      val shuffleStages: Seq[ShuffleQueryStageExec] = 
shuffleReaders.map(_.delegate).map {
+        case a: AQEShuffleReadExec =>
+          assert(a.child.isInstanceOf[ShuffleQueryStageExec])
+          a.child.asInstanceOf[ShuffleQueryStageExec]
+        case s: ShuffleQueryStageExec => s
+        case _ =>
+          throw new IllegalArgumentException("Unexpected child of 
ColumnarAQEShuffleReadExec")
       }
 
-      val exchanges = shuffleStages.flatMap {
-        _.plan.collect {
-          case exchange: ColumnarShuffleExchangeExec => exchange
-        }
-      }
-
-      assert(exchanges.nonEmpty)
-
-      exchanges.foreach {
-        exchange =>
+      shuffleStages.foreach {
+        shuffleStage =>
+          
assert(shuffleStage.shuffle.isInstanceOf[ColumnarShuffleExchangeExec])
+          val exchange = 
shuffleStage.shuffle.asInstanceOf[ColumnarShuffleExchangeExec]
           assert(
             !exchange.mapperStageMode.contains(MockGPUStageMode),
             s"Expected CPU mapper stage, but got ${exchange.mapperStageMode}")
diff --git a/docs/velox-configuration.md b/docs/velox-configuration.md
index 9ef1b3ba4b..8f80bb2d38 100644
--- a/docs/velox-configuration.md
+++ b/docs/velox-configuration.md
@@ -23,7 +23,7 @@ nav_order: 16
 | 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.enableValidation                
    | 🔄 Dynamic    | 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.                                    
                                                                                
                                                                                
                                                                                
               [...]
 | spark.gluten.sql.columnar.backend.velox.cudf.memoryResource                  
    | âš“ Static      | async             | GPU RMM memory resource.              
                                                                                
                                                                                
                                                                                
                                                                                
              [...]
 | spark.gluten.sql.columnar.backend.velox.cudf.shuffleMaxPrefetchBytes         
    | 🔄 Dynamic    | 1028MB            | Maximum bytes to prefetch in CPU 
memory during GPU shuffle read while waiting for GPU available.                 
                                                                                
                                                                                
                                                                                
                    [...]
diff --git 
a/gluten-substrait/src/main/scala/org/apache/spark/sql/execution/adaptive/ColumnarAQEShuffleReadExec.scala
 
b/gluten-substrait/src/main/scala/org/apache/spark/sql/execution/adaptive/ColumnarAQEShuffleReadExec.scala
index 28c3ce2dfb..02f7517cc6 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/spark/sql/execution/adaptive/ColumnarAQEShuffleReadExec.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/spark/sql/execution/adaptive/ColumnarAQEShuffleReadExec.scala
@@ -31,49 +31,59 @@ import org.apache.spark.sql.vectorized.ColumnarBatch
  * ShuffleQueryStageExec if executionMode is set by the planner.
  *
  * @param delegate
- *   The AQEShuffleReadExec or ShuffleQueryStageExec.
+ *   AQEShuffleReadExec, ShuffleQueryStageExec, or (during canonicalization) 
ShuffleExchange.
  * @param executionMode
  *   The execution mode of the current AQE stage.
  */
 case class ColumnarAQEShuffleReadExec(
-    delegate: Either[AQEShuffleReadExec, ShuffleQueryStageExec],
+    delegate: SparkPlan,
     executionMode: StageExecutionMode) extends UnaryExecNode {
 
   override def nodeName: String = 
s"ColumnarAQEShuffleRead(${executionMode.name})"
 
-  private val isAQEShuffleRead = delegate.isLeft
-
-  private val aqeReader: AQEShuffleReadExec = {
-    if (isAQEShuffleRead) {
-      delegate.left.get
-    } else {
-      // Wrap ShuffleQueryStageExe with dummy PartitionSpecs.
-      val queryStageExec = delegate.right.get
-      // Create CoalescedPartitionSpec for each partition.
-      val partitionSpecs =
-        Array.tabulate(queryStageExec.shuffle.numPartitions)(i => 
CoalescedPartitionSpec(i, i + 1))
-      AQEShuffleReadExec(queryStageExec, partitionSpecs)
-    }
-  }
-
   override def supportsColumnar: Boolean = true
 
-  override def child: SparkPlan = aqeReader.child
+  override def child: SparkPlan = delegate match {
+    case AQEShuffleReadExec(c, _) => c
+    case _ => delegate
+  }
 
-  override def output: Seq[Attribute] = aqeReader.child.output
+  override def output: Seq[Attribute] = delegate.output
 
-  override lazy val outputPartitioning: Partitioning = 
aqeReader.outputPartitioning
+  override lazy val outputPartitioning: Partitioning = 
delegate.outputPartitioning
 
-  override def stringArgs: Iterator[Any] = aqeReader.stringArgs
+  override protected def stringArgs: Iterator[Any] = {
+    delegate match {
+      case a: AQEShuffleReadExec => a.stringArgs
+      case _ => super.stringArgs
+    }
+  }
 
-  @transient override lazy val metrics: Map[String, SQLMetric] = 
aqeReader.metrics
+  override protected def withNewChildInternal(newChild: SparkPlan): 
ColumnarAQEShuffleReadExec = {
+    delegate match {
+      case a: AQEShuffleReadExec => copy(delegate = 
a.withNewChildren(Seq(newChild)))
+      case _ => copy(delegate = newChild)
+    }
+  }
 
-  private def isCoalescedSpec(spec: ShufflePartitionSpec) = {
-    val method = 
classOf[AQEShuffleReadExec].getDeclaredMethod("isCoalescedSpec")
-    method.setAccessible(true)
-    method.invoke(aqeReader, spec).asInstanceOf[Boolean]
+  private lazy val aqeReader: AQEShuffleReadExec = {
+    delegate match {
+      case a: AQEShuffleReadExec => a
+      case s: ShuffleQueryStageExec =>
+        // Wrap ShuffleQueryStageExe with dummy PartitionSpecs by creating 
CoalescedPartitionSpec
+        // for each partition.
+        val partitionSpecs =
+          Array.tabulate(s.shuffle.numPartitions)(i => 
CoalescedPartitionSpec(i, i + 1))
+        AQEShuffleReadExec(s, partitionSpecs)
+      case _ =>
+        // The child is Exchange during canonicalization.
+        throw new IllegalStateException(
+          s"Cannot get aqeReader from delegate node ${delegate.nodeName}.")
+    }
   }
 
+  @transient override lazy val metrics: Map[String, SQLMetric] = 
aqeReader.metrics
+
   private def shuffleStage = {
     val method = classOf[AQEShuffleReadExec].getDeclaredMethod("shuffleStage")
     method.setAccessible(true)
@@ -89,7 +99,8 @@ case class ColumnarAQEShuffleReadExec(
   private lazy val shuffleRDD: RDD[_] = {
     shuffleStage match {
       case Some(stage) =>
-        if (isAQEShuffleRead) {
+        // Only send driver metrics if it's a wrapper for AQEShuffleRead.
+        if (delegate.isInstanceOf[AQEShuffleReadExec]) {
           sendDriverMetrics()
         }
         stage.shuffle match {
@@ -108,15 +119,4 @@ case class ColumnarAQEShuffleReadExec(
   override protected def doExecuteColumnar(): RDD[ColumnarBatch] = {
     shuffleRDD.asInstanceOf[RDD[ColumnarBatch]]
   }
-
-  override protected def withNewChildInternal(newChild: SparkPlan): 
ColumnarAQEShuffleReadExec = {
-    if (isAQEShuffleRead) {
-      copy(delegate =
-        
Left(delegate.left.get.withNewChildren(Seq(newChild)).asInstanceOf[AQEShuffleReadExec]))
-    } else {
-      copy(delegate =
-        Right(
-          
delegate.right.get.withNewChildren(Seq(newChild)).asInstanceOf[ShuffleQueryStageExec]))
-    }
-  }
 }


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

Reply via email to