Normalize float/double inside GpuCollectSet for Spark 4.2 [databricks] - #15546
Conversation
Mirror Spark's CollectSet bit-key buffer so NaN/-0.0 uniqueness matches CPU without host-side per-row float↔bits converters on mixed agg stages. Signed-off-by: Firestarman <firestarmanllc@gmail.com>
Greptile SummaryThe PR changes Spark 4.2 float/double
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
Input[Float or Double input] --> Normalize[Normalize NaN and signed zero]
Normalize --> Bits[Bit-cast to INT or LONG keys]
Bits --> Aggregate[GPU collect_set update or merge]
CPU[CPU partial buffer] <--> Convert[Generic buffer converters]
Convert <--> Aggregate
Aggregate --> Buffer[INT or LONG array buffer]
Buffer --> Values[Bit-cast keys back to Float or Double]
Values --> Result[CollectSet result array]
Normalize --> Window[Regular GpuWindowExec rolling collect_set]
Reviews (3): Last reviewed commit: "Address Spark 4.2 CollectSet review: win..." | Re-trigger Greptile |
|
build |
jihoonson
left a comment
There was a problem hiding this comment.
This is a performance improvement. Please evaluate performance and report the result.
|
@jihoonson Thanks for the review. I ran a local micro-benchmark (operator-level) comparing current Summary (median, 1 warmup + 3 iters):
Details and tables are in the updated PR description. Please take another look. |
wjxiz1992
left a comment
There was a problem hiding this comment.
Requesting changes for the Spark 4.2 fully-unbounded window correctness issue described inline. The other inline comments cover nullable mixed-stage coverage, deterministic floating-point edge coverage, and reuse of fused normalization.
PR description follow-up: link the tracking issue with Contributes to #15463 or Closes #15463.
The existing performance request appears addressed by the benchmark section. I did not run a local GPU runtime test; the blocking finding is based on exact-head source tracing through the existing integration-test path.
| val ignoreNulls = TypeUtilsShims.collectSetIgnoreNulls(c) | ||
| val bufferElementType = | ||
| TypeUtilsShims.collectSetCpuBufferElementType(c.child.dataType) | ||
| aggBuffer.copy(dataType = ArrayType(bufferElementType, !ignoreNulls))( |
There was a problem hiding this comment.
Could you add a Spark 4.2 mixed CPU/GPU test for Float/Double collect_set(... RESPECT NULLS) here? This buffer schema derives containsNull from ignoreNulls, but the mixed-stage tests only exercise default IGNORE NULLS; the existing RESPECT NULLS case uses integral input and does not cross a CPU/GPU aggregate boundary. Please cover both CPU-to-GPU and GPU-to-CPU conversion with mixed-null and all-null groups.
|
|
||
| private lazy val useNormalizedBitKeys: Boolean = bufferElementType != child.dataType | ||
|
|
||
| override lazy val inputProjection: Seq[Expression] = { |
There was a problem hiding this comment.
Blocking: for Spark 4.2 Float/Double, this becomes GpuCollectSetNormalizedBitKey(child), while GpuCollectSet still opts into GpuUnboundedToUnboundedAggWindowExec. That executor selects every marker implementation, but its stage builder accepts only GpuBoundReference/GpuLiteral, so the existing fully-unbounded Float/Double window IT reaches IllegalStateException("Unexpected expression"). It also exposes aggregate result types directly and never applies evaluateExpression, so simply accepting this unary projection would still omit GpuCollectSetBitKeysToValues. Please either guard this path back to regular GpuWindowExec or add both projection and finalizer support, with a Spark 4.2 regression.
| keyType | ||
| } | ||
|
|
||
| override def doColumnar(input: GpuColumnVector): ColumnVector = { |
There was a problem hiding this comment.
Could you add deterministic Spark 4.2 Float/Double regression data for this normalization and round trip? The current RepeatSeqGen(FloatGen/DoubleGen) data samples before repeating, so it does not guarantee that +0/-0 and distinct NaN payloads land in the same group. Please include +0, -0, multiple NaN payloads, infinities, null, both mixed replacement directions, and an empty typed Float/Double aggregation.
| s"CollectSet floating normalize expects FLOAT32/FLOAT64, found $dtype") | ||
| // Canonicalize NaN payloads first (Spark FLOAT_NORMALIZER / DOUBLE_NORMALIZER), then | ||
| // reuse HashUtils for signed-zero normalization (-0.0 -> +0.0). | ||
| val withCanonNan = withResource(cv.isNan) { isNan => |
There was a problem hiding this comment.
Could this reuse the existing GpuNormalizeNaNAndZero / cuDF normalizeNANsAndZeros() path? The current sequence materializes isNan + ifElse, followed by another equality mask + ifElse in HashUtils.normalizeInput, for every hash/window input batch. The existing native operation performs the same NaN/signed-zero normalization in one transform and avoids duplicate normalization logic and temporaries.
Opt Spark 4.2 float/double CollectSet out of the unbounded group-by window shortcut, reuse normalizeNANsAndZeros, and add mixed-stage RESPECT NULLS / edge-case plus unbounded window regressions. Signed-off-by: Firestarman <firestarmanllc@gmail.com>
|
@wjxiz1992 Thanks for the detailed review. Addressed in ef366d0:
PR description updated with |
|
build |
Please take the perspective of readers/reviewers into account. Seeing @wjxiz1992's comments (such as #15546 (comment)) unanswered and unresolved can make readers and reviewers easily think that those comments are left unaddressed. Now I know that those comments are addressed, but need to scroll up and down to map your answers and his comments. Please leave your answer directly to his comment. |
### Description We as humans make mistakes. The most common mistake in the dev process we have been making recently is the missing performance evaluation result for performance-related changes. #15546 is a good example. Even though the PR is tagged as "performance", performance evaluation is missing. Rather, the PR checklist is marked as performance test "Not required". We have been encouraging both PR authors and reviewers to be more mindful of this mistake. And yet, this issue has been recurring. This PR attempts to reduce our mistakes with the help of AI review bots. This PR adds a new rule. It is intentionally a new rule instead of modifying existing ones to make it more clear when it should apply. ### Checklists Documentation - [ ] Updated for new or modified user-facing features or behaviors - [x] No user-facing change Testing - [ ] Added or modified tests to cover new code paths - [ ] Covered by existing tests (Please provide the names of the existing tests in the PR description.) - [x] Not required Performance - [ ] Tests ran and results are added in the PR description - [ ] Issue filed with a link in the PR description - [x] Not required --------- Signed-off-by: Jihoon Son <ghoonson@gmail.com>
Contributes to #15463.
related to this comment
Description
-0.0and store Spark 4.2 CollectSet float/double agg buffers as Int/Long bit keys insideGpuCollectSet, so GPU uniqueness matches CPU without host-side per-row float↔bits converters.TypeUtilsShims.collectSetCpuBufferElementType.GpuCollectSetout ofGpuUnboundedToUnboundedAggWindowExec(bit-keyinputProjectionis incompatible with that shortcut) and keep the window path on regularGpuWindowExecwithGpuNormalizeNaNAndZero.normalizeNANsAndZeros()instead of a custom NaN/-0.0normalize helper.DATAGEN_SEED=1785353212: new/updated ITs for mixed-stage Float/DoubleRESPECT NULLS, deterministic+0/-0/NaN/inf edges, empty typed reduction, and fully-unbounded Float/Double windows (14 passed); alsomvn -f scala2.13/pom.xml -Dbuildver=420 -Dcuda.version=cuda13 -DskipTests -pl sql-plugin,dist,integration_tests -am packageandmvn -Dbuildver=330 -Dcuda.version=cuda13 -DskipTests -pl sql-plugin -am package.This follows the direction discussed on #15463 (normalize inside
collect_setrather than expanding host-side converters).Performance
Operator-level microbench (not NDS) comparing current
main(#15455 host CollectSet float↔bits converters) vs this PR.spark.rapids.memory.gpu.allocSize=8192m-0.0in float/double columnsSuite C: 200,000,000 rows / 500,000 groups (mixed modes ~12–13s)
pure_gpumixed_partial_gpumixed_final_gpuChecksums matched (
sum_f=sum_d=192020000).Suite D: 1,000,000,000 rows / 1,000,000 groups (pure GPU ~15s)
pure_gpuChecksums matched (
sum_f=957160000,sum_d=960040000).Takeaway: no meaningful wall-time regression vs
mainat multi-second scale. Mixedpartiallean (~1.08x) is consistent with avoiding host float↔bits conversion on GPU buffer boundaries; pure GPU is within noise (~±2%).Checklists
Documentation
Testing
(Please provide the names of the existing tests in the PR description.)
Performance