Skip to content

Commit 6e300e7

Browse files
authored
[auto-merge] release/26.08 to main [skip ci] [bot] (NVIDIA#15483)
auto-merge triggered by github actions on `release/26.08` to create a PR keeping `main` up-to-date. If this PR is unable to be merged due to conflicts, it will remain open until manually fix.
2 parents 8cf5362 + 53ed8d0 commit 6e300e7

6 files changed

Lines changed: 299 additions & 18 deletions

File tree

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2026, NVIDIA CORPORATION.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.nvidia.spark.rapids
18+
19+
import scala.util.Try
20+
21+
import org.apache.spark.SparkConf
22+
import org.apache.spark.sql.internal.SQLConf
23+
24+
object RowBasedShuffleChecksumConf {
25+
val ChecksumEnabledKey = "spark.sql.shuffle.orderIndependentChecksum.enabled"
26+
val ChecksumMismatchFullRetryKey =
27+
"spark.sql.shuffle.orderIndependentChecksum.enableFullRetryOnMismatch"
28+
29+
// SQLConf takes priority over SparkConf when explicitly set (e.g. SET command mid-session).
30+
// SparkConf is checked next for values set at session start or via --conf.
31+
// If neither is explicitly set, we fall back to Spark's registered config default via
32+
// SQLConf.getConfString: on Spark 4.2+ both keys default to true (checksums on by
33+
// default), so RAPIDS will fall back to SortShuffleManager unless the user explicitly
34+
// disables them (set both keys to false). On Spark < 4.2 these keys default to false
35+
// or are not registered in SQLConf, so GPU shuffle proceeds normally.
36+
def isEnabled(sqlConf: SQLConf, sparkConf: SparkConf): Boolean = {
37+
getBoolean(sqlConf, sparkConf, ChecksumEnabledKey) ||
38+
getBoolean(sqlConf, sparkConf, ChecksumMismatchFullRetryKey)
39+
}
40+
41+
private def getBoolean(sqlConf: SQLConf, sparkConf: SparkConf, key: String): Boolean = {
42+
if (sqlConf.contains(key)) {
43+
sqlConf.getConfString(key).toBoolean
44+
} else if (sparkConf.contains(key)) {
45+
sparkConf.getBoolean(key, false)
46+
} else {
47+
Try(sqlConf.getConfString(key)).map(_.toBoolean).getOrElse(false)
48+
}
49+
}
50+
}

sql-plugin/src/main/scala/org/apache/spark/sql/rapids/GpuShuffleDependency.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020-2022, NVIDIA CORPORATION.
2+
* Copyright (c) 2020-2026, NVIDIA CORPORATION.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,5 +40,10 @@ class GpuShuffleDependency[K: ClassTag, V: ClassTag, C: ClassTag](
4040
extends ShuffleDependency[K, V, C](rdd, partitioner, serializer, keyOrdering,
4141
aggregator, mapSideCombine, shuffleWriterProcessor) {
4242

43+
// Set by RapidsShuffleInternalManagerBase.registerShuffle to record the
44+
// registration-time checksum fallback decision so writer and reader use
45+
// the same path even if SQLConf changes between calls.
46+
var checksumFallback: Boolean = false
47+
4348
override def toString: String = "GPU Shuffle Dependency"
4449
}

sql-plugin/src/main/scala/org/apache/spark/sql/rapids/GpuShuffleEnv.scala

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ package org.apache.spark.sql.rapids
1919
import java.util.Locale
2020

2121
import com.nvidia.spark.rapids._
22+
import com.nvidia.spark.rapids.RowBasedShuffleChecksumConf
2223
import com.nvidia.spark.rapids.shims.ShuffleManagerShimUtils
2324

2425
import org.apache.spark.{SparkConf, SparkEnv}
2526
import org.apache.spark.internal.Logging
27+
import org.apache.spark.sql.internal.SQLConf
2628

2729
class GpuShuffleEnv(rapidsConf: RapidsConf) extends Logging {
2830
private var shuffleCatalog: ShuffleBufferCatalog = _
@@ -144,10 +146,7 @@ object GpuShuffleEnv extends Logging {
144146
// which is for IO-level corruption diagnosis and IS supported by RAPIDS shuffle.
145147
def isRowBasedChecksumEnabled: Boolean = {
146148
val conf = SparkEnv.get.conf
147-
conf.getBoolean(
148-
"spark.sql.shuffle.orderIndependentChecksum.enabled", false) ||
149-
conf.getBoolean(
150-
"spark.sql.shuffle.orderIndependentChecksum.enableFullRetryOnMismatch", false)
149+
RowBasedShuffleChecksumConf.isEnabled(SQLConf.get, conf)
151150
}
152151

153152
//

sql-plugin/src/main/scala/org/apache/spark/sql/rapids/RapidsShuffleInternalManagerBase.scala

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1736,7 +1736,9 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
17361736
// NOTE: this can be null in the driver side.
17371737
protected lazy val env = SparkEnv.get
17381738
protected lazy val blockManager = env.blockManager
1739-
protected lazy val shouldFallThroughOnEverything = {
1739+
// Stable reasons to always fall back to SortShuffleManager, evaluated once at
1740+
// first shuffle registration.
1741+
protected lazy val shouldAlwaysFallBack = {
17401742
val fallThroughReasons = new ListBuffer[String]()
17411743
if (!rapidsConf.isMultiThreadedShuffleManagerMode) {
17421744
if (GpuShuffleEnv.isExternalShuffleEnabled) {
@@ -1749,19 +1751,28 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
17491751
if (rapidsConf.isSqlExplainOnlyEnabled) {
17501752
fallThroughReasons += "Plugin is in explain only mode"
17511753
}
1752-
if (GpuShuffleEnv.isRowBasedChecksumEnabled) {
1753-
fallThroughReasons += "Detected order-independent checksum enabled " +
1754-
"(spark.sql.shuffle.orderIndependentChecksum.enabled or " +
1755-
"enableFullRetryOnMismatch). " +
1756-
"This Spark 4.1+ feature is not yet supported by Spark-Rapids."
1757-
}
17581754
if (fallThroughReasons.nonEmpty) {
17591755
logWarning(s"Rapids Shuffle Plugin is falling back to SortShuffleManager " +
17601756
s"because: ${fallThroughReasons.mkString(", ")}")
17611757
}
17621758
fallThroughReasons.nonEmpty
17631759
}
17641760

1761+
private val rowBasedChecksumFallbackLogged = new AtomicBoolean(false)
1762+
1763+
private def shouldFallThroughForShuffle: Boolean = {
1764+
val rowBasedChecksumFallback = GpuShuffleEnv.isRowBasedChecksumEnabled
1765+
if (rowBasedChecksumFallback) {
1766+
if (rowBasedChecksumFallbackLogged.compareAndSet(false, true)) {
1767+
logWarning("Rapids Shuffle Plugin is falling back to SortShuffleManager because: " +
1768+
"Detected order-independent checksum enabled " +
1769+
"(spark.sql.shuffle.orderIndependentChecksum.enabled or enableFullRetryOnMismatch). " +
1770+
"This Spark 4.1+ feature is not yet supported by Spark-Rapids.")
1771+
}
1772+
}
1773+
shouldAlwaysFallBack || rowBasedChecksumFallback
1774+
}
1775+
17651776
private lazy val localBlockManagerId = blockManager.blockManagerId
17661777

17671778
// Used to prevent stopping multiple times RAPIDS Shuffle Manager internals.
@@ -1776,7 +1787,7 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
17761787
"RapidsShuffleManager is configured"))
17771788

17781789
protected lazy val resolver =
1779-
if (shouldFallThroughOnEverything) {
1790+
if (shouldAlwaysFallBack) {
17801791
wrapped.shuffleBlockResolver
17811792
} else if (rapidsConf.isMultiThreadedShuffleManagerMode) {
17821793
// MULTITHREADED mode: use GpuShuffleBlockResolver
@@ -1848,11 +1859,13 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
18481859
val orig = wrapped.registerShuffle(shuffleId, dependency)
18491860

18501861
dependency match {
1851-
case _ if shouldFallThroughOnEverything ||
1852-
rapidsConf.isMultiThreadedShuffleManagerMode => orig
18531862
case gpuDependency: GpuShuffleDependency[K, V, C] if gpuDependency.useGPUShuffle =>
1854-
new GpuShuffleHandle(orig,
1855-
dependency.asInstanceOf[GpuShuffleDependency[K, V, V]])
1863+
val gpuDep = gpuDependency.asInstanceOf[GpuShuffleDependency[K, V, V]]
1864+
gpuDep.checksumFallback = shouldFallThroughForShuffle
1865+
if (rapidsConf.isMultiThreadedShuffleManagerMode) orig
1866+
else new GpuShuffleHandle(orig, gpuDep)
1867+
case _ if shouldAlwaysFallBack ||
1868+
rapidsConf.isMultiThreadedShuffleManagerMode => orig
18561869
case _ => orig
18571870
}
18581871
}
@@ -1900,6 +1913,8 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
19001913
context: TaskContext,
19011914
metricsReporter: ShuffleWriteMetricsReporter): ShuffleWriter[K, V] = {
19021915
handle match {
1916+
case gpu: GpuShuffleHandle[_, _] if gpu.dependency.checksumFallback =>
1917+
wrapped.getWriter(gpu.wrapped, mapId, context, metricsReporter)
19031918
case gpu: GpuShuffleHandle[_, _] =>
19041919
registerGpuShuffle(handle.shuffleId)
19051920
new RapidsCachingWriter(
@@ -1914,6 +1929,7 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
19141929
handle.dependency match {
19151930
case gpuDep: GpuShuffleDependency[_, _, _]
19161931
if gpuDep.useMultiThreadedShuffle &&
1932+
!gpuDep.checksumFallback &&
19171933
rapidsConf.shuffleMultiThreadedWriterThreads > 0 =>
19181934
// use the threaded writer if the number of threads specified is 1 or above,
19191935
// with 0 threads we fallback to the Spark-provided writer.
@@ -1957,6 +1973,9 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
19571973
context: TaskContext,
19581974
metrics: ShuffleReadMetricsReporter): ShuffleReader[K, C] = {
19591975
handle match {
1976+
case gpuHandle: GpuShuffleHandle[_, _] if gpuHandle.dependency.checksumFallback =>
1977+
ShuffleManagerShims.getReader(wrapped, gpuHandle.wrapped, startMapIndex, endMapIndex,
1978+
startPartition, endPartition, context, metrics)
19601979
case gpuHandle: GpuShuffleHandle[_, _] =>
19611980
logInfo(s"Asking map output tracker for dependency ${gpuHandle.dependency}, " +
19621981
s"map output sizes for: ${gpuHandle.shuffleId}, parts=$startPartition-$endPartition")
@@ -1995,7 +2014,8 @@ class RapidsShuffleInternalManagerBase(conf: SparkConf, val isDriver: Boolean)
19952014
// would need to be made to deal with missing metrics, for example, for a regular
19962015
// Exchange node.
19972016
baseHandle.dependency match {
1998-
case gpuDep: GpuShuffleDependency[K, C, C] if gpuDep.useMultiThreadedShuffle =>
2017+
case gpuDep: GpuShuffleDependency[K, C, C]
2018+
if gpuDep.useMultiThreadedShuffle && !gpuDep.checksumFallback =>
19992019
// We want to use batch fetch in the non-push shuffle case. Spark
20002020
// checks for a config to see if batch fetch is enabled (this check), and
20012021
// it also checks when getting (potentially merged) map status from

sql-plugin/src/test/spark420/scala/com/nvidia/spark/rapids/shims/spark420/SparkShimsSuite.scala

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,13 @@ package com.nvidia.spark.rapids.shims.spark420
2222
import com.nvidia.spark.rapids._
2323
import org.scalatest.funsuite.AnyFunSuite
2424

25+
import org.apache.spark.SparkConf
26+
import org.apache.spark.sql.internal.SQLConf
27+
2528
class SparkShimsSuite extends AnyFunSuite with FQSuiteName {
29+
private val checksumEnabledKey = RowBasedShuffleChecksumConf.ChecksumEnabledKey
30+
private val fullRetryKey = RowBasedShuffleChecksumConf.ChecksumMismatchFullRetryKey
31+
2632
test("spark shims version") {
2733
assert(ShimLoader.getShimVersion === SparkShimVersion(4, 2, 0))
2834
}
@@ -32,4 +38,30 @@ class SparkShimsSuite extends AnyFunSuite with FQSuiteName {
3238
classOf[com.nvidia.spark.rapids.spark420.RapidsShuffleManager].getCanonicalName)
3339
}
3440

41+
test("row-based shuffle checksum defaults to enabled") {
42+
val sqlConf = new SQLConf()
43+
assert(sqlConf.shuffleOrderIndependentChecksumEnabled)
44+
assert(sqlConf.shuffleChecksumMismatchFullRetryEnabled)
45+
assert(RowBasedShuffleChecksumConf.isEnabled(sqlConf, new SparkConf(false)))
46+
}
47+
48+
test("row-based shuffle checksum uses SparkConf when SQLConf is unset") {
49+
val sparkConf = new SparkConf(false)
50+
.set(checksumEnabledKey, "false")
51+
.set(fullRetryKey, "false")
52+
53+
assert(!RowBasedShuffleChecksumConf.isEnabled(new SQLConf(), sparkConf))
54+
}
55+
56+
test("row-based shuffle checksum uses SQLConf when present") {
57+
val sqlConf = new SQLConf()
58+
sqlConf.setConfString(checksumEnabledKey, "false")
59+
sqlConf.setConfString(fullRetryKey, "false")
60+
val sparkConf = new SparkConf(false)
61+
.set(checksumEnabledKey, "true")
62+
.set(fullRetryKey, "true")
63+
64+
assert(!RowBasedShuffleChecksumConf.isEnabled(sqlConf, sparkConf))
65+
}
66+
3567
}

0 commit comments

Comments
 (0)