Skip to content

[BUG] GpuGroupPartitionsExec can silently drop partitions after child partitioning changes #16081

Description

@res-life

Describe the bug

On Spark 5, GpuGroupPartitionsExec can execute planning-time partition groups after its child
has been replaced by a plan with a different partitioning. This can silently omit input
partitions and return wrong results.

SPARK-59289 binds a CPU GroupPartitionsExec's grouping to the childPartitioning it was planned
over. If a later rewrite changes the child's partitioning, Spark drops the keyed partitioning
claim and refuses row or columnar execution. The GPU replacement instead snapshots
groupedPartitions.map(_._2) into GpuGroupPartitionsExecInfo and does not retain or validate the
planned child partitioning.

Steps/Code to reproduce bug

The following regression test can be added to GroupPartitionsExecSuite and run with
buildver=500. It plans groups over three input partitions, replaces the child with a
four-partition GPU leaf, confirms that the CPU operator rejects the stale grouping, and then
executes the same stale groups through the GPU operator.

test("Spark 5 GPU execution must reject stale GroupPartitionsExec grouping") {
  withGpuSparkSession { _ =>
    val attr = AttributeReference("id", IntegerType, nullable = false)()
    val originalChild = spy(LocalTableScanExec(Seq(attr), Nil, None))
    doReturn(KeyedPartitioning(
      Seq(attr),
      Seq(InternalRow(1), InternalRow(1), InternalRow(2))))
      .when(originalChild).outputPartitioning

    val plannedCpu = GroupPartitionsExecTestShim(
      originalChild,
      enableSortedMerge = false)
    assert(plannedCpu.groupedPartitions.map(_._2) == Seq(Seq(0, 1), Seq(2)))

    val changedChild = GroupPartitionsTestGpuLeaf(
      Seq(attr),
      Seq(Seq[Integer](10), Seq[Integer](20), Seq[Integer](30), Seq[Integer](40)),
      Seq.empty)
    val staleCpu = plannedCpu.withNewChildren(Seq(changedChild))
      .asInstanceOf[GroupPartitionsExec]

    val cpuError = intercept[SparkException](staleCpu.executeColumnar())
    assert(cpuError.getMessage.contains(
      "no longer reports the partitioning it was planned over"))

    val staleGpu = GpuGroupPartitionsExec(
      changedChild,
      GpuGroupPartitionsExecInfo(staleCpu, Seq.empty))
    val actual = GpuColumnarToRowExec(staleGpu)
      .executeCollect()
      .map(_.getInt(0))
      .toSeq

    assert(actual == Seq(10, 20, 30, 40))
  }
}

Observed result:

Spark 5 GPU execution must reject stale GroupPartitionsExec grouping *** FAILED ***
ArraySeq(10, 20, 30) did not equal List(10, 20, 30, 40)
Tests: succeeded 17, failed 1

The stale groups are [[0, 1], [2]], so GPU execution never schedules parent partition 3 and
silently drops value 40.

Validation command:

env -u SPARK_HOME mvn -f scala2.13/pom.xml -Dbuildver=500 -Dcuda.version=cuda13 -DwildcardSuites=com.nvidia.spark.rapids.GroupPartitionsExecSuite clean package

Expected behavior

The GPU replacement must not execute partition groups when the current child no longer reports
the partitioning those groups were planned over. It should preserve Spark 5's rejection/fallback
behavior instead of silently reading an incomplete set of parent partitions.

Environment details (please complete the following information)

Additional context

This reproducer explicitly constructs the post-planning child replacement. It proves the GPU
operator returns an incomplete result if that state reaches conversion or execution; it does not
yet establish that a current built-in Spark SQL or AQE rule makes the state reachable in a normal
query. The fix should carry and validate the planned child partitioning at the appropriate GPU
conversion/execution boundary and add a Spark 5 regression test modeled on Spark's SPARK-59289
coverage.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Spark 5.0+Spark 5.0+ issuesbugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions