Skip to content

Fix Spark 4.2 collect_set float buffer conversion for mixed aggs - #15455

Merged
firestarman merged 2 commits into
NVIDIA:release/26.08from
firestarman:fix/collect-set-float-nan-spark420
Jul 31, 2026
Merged

Fix Spark 4.2 collect_set float buffer conversion for mixed aggs#15455
firestarman merged 2 commits into
NVIDIA:release/26.08from
firestarman:fix/collect-set-float-nan-spark420

Conversation

@firestarman

Copy link
Copy Markdown
Collaborator

Fixes #15454.

Description

  • Convert CollectSet float/double aggregation buffers between GPU logical values and Spark 4.2 CPU normalized bit-pattern keys during mixed hashAgg stages, so NaN and signed-zero uniqueness matches pure CPU.
  • Add collectSetCpuBufferElementType shim (bit-keyed on Spark 4.2, identity earlier) and CollectSet-specific GPU↔CPU buffer converters used by GpuOverrides.
  • Validate with Spark 4.2.0 / Scala 2.13 and DATAGEN_SEED=1785353212: 352 passed including the previously failing test_hash_groupby_collect_partial_replace_fallback / test_hash_groupby_collect_partial_replace_with_distinct_fallback Float cases; also mvn -f scala2.13/pom.xml -Dbuildver=420 -Dcuda.version=cuda13 -DskipTests -pl sql-plugin verify.

Checklists

Documentation

  • Updated for new or modified user-facing features or behaviors
  • 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.)
  • Not required

Performance

  • Tests ran and results are added in the PR description
  • Issue filed with a link in the PR description
  • Not required

@greptile-apps

greptile-apps Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes Spark 4.2 mixed CPU/GPU collect_set aggregation-buffer conversion.

  • Adds CollectSet-specific converters between logical float/double values and Spark’s normalized integer/long bit-pattern keys.
  • Adds version-specific CPU buffer element-type shims, preserving identity behavior before Spark 4.2.
  • Wires the converters into the CollectSet GPU override.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
sql-plugin/src/main/scala/com/nvidia/spark/rapids/GpuOverrides.scala Selects the new CollectSet-specific buffer converters at mixed CPU/GPU aggregation boundaries.
sql-plugin/src/main/scala/org/apache/spark/sql/rapids/aggregate/aggregateFunctions.scala Implements Spark-compatible float/double normalization and bidirectional aggregate-buffer conversion.
sql-plugin/src/main/spark330/scala/com/nvidia/spark/rapids/shims/TypeUtilsShims.scala Preserves the pre-Spark-4.2 identity buffer representation.
sql-plugin/src/main/spark330db/scala/com/nvidia/spark/rapids/shims/TypeUtilsShims.scala Preserves identity buffer representation across the listed pre-4.2 and Databricks shims.
sql-plugin/src/main/spark420/scala/com/nvidia/spark/rapids/shims/TypeUtilsShims.scala Maps top-level float and double CollectSet CPU buffers to Spark 4.2 integer and long bit-key representations.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  CPU["Spark CPU CollectSet buffer<br/>normalized Int/Long bit keys"]
  C2G["CPU-to-GPU converter<br/>bits to Float/Double"]
  GPU["GPU CollectSet buffer<br/>logical Float/Double values"]
  G2C["GPU-to-CPU converter<br/>normalize values to bits"]
  CPU --> C2G --> GPU
  GPU --> G2C --> CPU
Loading

Reviews (3): Last reviewed commit: "Trigger CI for PR #15455" | Re-trigger Greptile

Spark 4.2 stores CollectSet float/double buffers as normalized bit
patterns, so convert GPU logical values when crossing CPU/GPU stages.

Fixes NVIDIA#15454.

Signed-off-by: Firestarman <firestarmanllc@gmail.com>
@firestarman
firestarman force-pushed the fix/collect-set-float-nan-spark420 branch from b3ae0bd to fc8daac Compare July 30, 2026 08:14
@firestarman
firestarman requested a review from a team as a code owner July 30, 2026 08:14
@firestarman
firestarman changed the base branch from main to release/26.08 July 30, 2026 08:14
@firestarman
firestarman requested a review from a team July 30, 2026 08:20
@firestarman

Copy link
Copy Markdown
Collaborator Author

build

@sameerz sameerz added the bug Something isn't working label Jul 30, 2026
Comment on lines +2179 to +2181
elementType match {
case FloatType =>
while (i < n) {

@ttnghia ttnghia Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop is running on the CPU and can be a bottleneck, since it iterates over every row and convert one row at a time. The buffer conversion like this is also seen in several other places as well. We should move all these conversion into running on the GPU. I'm not blocking this fix, but we should do the follow up work for migrate all buffer conversion into jni code with native GPU kernels.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #15463.

@firestarman firestarman changed the title Fix Spark 4.2 collect_set float buffer conversion for mixed aggs Fix Spark 4.2 collect_set float buffer conversion for mixed aggs[skip-ci] Jul 31, 2026
Signed-off-by: Firestarman <firestarmanllc@gmail.com>
@firestarman

Copy link
Copy Markdown
Collaborator Author

build

@firestarman

firestarman commented Jul 31, 2026

Copy link
Copy Markdown
Collaborator Author

premerge has already passed, but some required jobs are pending, so submit an empty commit to re-trigger them with skip-ci.
It should be [skip ci] not [skip-ci], so premerge has been launched again.

@firestarman firestarman changed the title Fix Spark 4.2 collect_set float buffer conversion for mixed aggs[skip-ci] Fix Spark 4.2 collect_set float buffer conversion for mixed aggs Jul 31, 2026
@firestarman
firestarman merged commit 6f42817 into NVIDIA:release/26.08 Jul 31, 2026
57 checks passed
@firestarman
firestarman deleted the fix/collect-set-float-nan-spark420 branch July 31, 2026 05:41
firestarman added a commit that referenced this pull request Aug 13, 2026
#15546)

Contributes to #15463.

related to [this
comment](#15463 (comment))

### Description

- Normalize NaN / `-0.0` and store Spark 4.2 CollectSet float/double agg
buffers as Int/Long bit keys inside `GpuCollectSet`, so GPU uniqueness
matches CPU without host-side per-row float↔bits converters.
- Reuse the generic Collect buffer converters for mixed CPU/GPU hashAgg
stages, because GPU and CPU buffer layouts now match on Spark 4.2+.
- Keep pre-4.2 shims on the previous Float/Double buffer path via
`TypeUtilsShims.collectSetCpuBufferElementType`.
- Opt Spark 4.2 float/double `GpuCollectSet` out of
`GpuUnboundedToUnboundedAggWindowExec` (bit-key `inputProjection` is
incompatible with that shortcut) and keep the window path on regular
`GpuWindowExec` with `GpuNormalizeNaNAndZero`.
- Reuse cuDF `normalizeNANsAndZeros()` instead of a custom NaN/`-0.0`
normalize helper.
- Validated locally on Spark 4.2.0 / Scala 2.13 / CUDA 13 with
`DATAGEN_SEED=1785353212`: new/updated ITs for mixed-stage Float/Double
`RESPECT NULLS`, deterministic `+0`/`-0`/NaN/inf edges, empty typed
reduction, and fully-unbounded Float/Double windows (`14 passed`); also
`mvn -f scala2.13/pom.xml -Dbuildver=420 -Dcuda.version=cuda13
-DskipTests -pl sql-plugin,dist,integration_tests -am package` and `mvn
-Dbuildver=330 -Dcuda.version=cuda13 -DskipTests -pl sql-plugin -am
package`.

This follows the direction discussed on #15463 (normalize inside
`collect_set` rather than expanding host-side converters).

### Performance

Operator-level microbench (not NDS) comparing current `main` (#15455
host CollectSet float↔bits converters) vs this PR.

- Hardware: NVIDIA RTX 5880 Ada Generation
- Spark 4.2.0 / Scala 2.13 / CUDA 13 /
`spark.rapids.memory.gpu.allocSize=8192m`
- Data includes ~2% NaN and ~2% `-0.0` in float/double columns
- Method: 1 warmup + 3 iters, report median wall time
- Timed SQL (equivalent to the DataFrame microbench):

```sql
SELECT SUM(sf) AS sum_f, SUM(sd) AS sum_d
FROM (
  SELECT
    k,
    SIZE(COLLECT_SET(f)) AS sf,
    SIZE(COLLECT_SET(d)) AS sd
  FROM (
    SELECT
      CAST(id % ${num_groups} AS INT) AS k,
      CASE
        WHEN (id % 50) = 0 THEN CAST('NaN' AS FLOAT)
        WHEN (id % 50) = 1 THEN CAST(-0.0 AS FLOAT)
        ELSE CAST(CAST((id % 997) AS FLOAT) / 10.0 AS FLOAT)
      END AS f,
      CASE
        WHEN (id % 50) = 2 THEN CAST('NaN' AS DOUBLE)
        WHEN (id % 50) = 3 THEN CAST(-0.0 AS DOUBLE)
        ELSE CAST(CAST((id % 1009) AS DOUBLE) / 10.0 AS DOUBLE)
      END AS d
    FROM range(0, ${num_rows})
  )
  GROUP BY k
)
```

#### Suite C: 200,000,000 rows / 500,000 groups (mixed modes ~12–13s)

| Case | main median (s) | PR median (s) | Speedup (main/PR) | Notes |
|------|----------------:|--------------:|------------------:|-------|
| `pure_gpu` | 3.160 | 3.235 | 0.98x | pure GPU (short at this scale) |
| `mixed_partial_gpu` | 12.443 | 11.553 | 1.08x | GPU partial + CPU
final |
| `mixed_final_gpu` | 13.133 | 12.935 | 1.02x | CPU partial + GPU final
|

Checksums matched (`sum_f=sum_d=192020000`).

#### Suite D: 1,000,000,000 rows / 1,000,000 groups (pure GPU ~15s)

| Case | main median (s) | PR median (s) | Speedup (main/PR) | Notes |
|------|----------------:|--------------:|------------------:|-------|
| `pure_gpu` | 15.725 | 15.436 | 1.02x | pure GPU |

Checksums matched (`sum_f=957160000`, `sum_d=960040000`).

**Takeaway:** no meaningful wall-time regression vs `main` at
multi-second scale. Mixed `partial` lean (~1.08x) is consistent with
avoiding host float↔bits conversion on GPU buffer boundaries; pure GPU
is within noise (~±2%).


### Checklists

Documentation
- [ ] Updated for new or modified user-facing features or behaviors
- [x] No user-facing change

Testing
- [x] 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.)
- [ ] Not required

Performance
- [x] Tests ran and results are added in the PR description
- [ ] Issue filed with a link in the PR description
- [ ] Not required

---------

Signed-off-by: Firestarman <firestarmanllc@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Spark 4.2 collect_set float/double CPU-GPU buffer mismatch in mixed hashAgg stages

5 participants