Skip to content

Fix Distinct expression lowering - #22970

Merged
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
rjzamora:fix-unique-expression-dynamic-distinct
Jun 29, 2026
Merged

Fix Distinct expression lowering#22970
rapids-bot[bot] merged 11 commits into
NVIDIA:mainfrom
rjzamora:fix-unique-expression-dynamic-distinct

Conversation

@rjzamora

Copy link
Copy Markdown
Contributor

Expression-level unique() was bypassing the dynamic Distinct lowering path by calling the static lower_distinct tree reduction directly. This updates the shared Distinct lowering helper so select(pl.col(...).unique()) can use dynamic planning and avoid static repartition/allgather paths that may overflow for large cardinalities.

Adds a small regression test covering dynamic expression unique() lowering.

@rjzamora rjzamora self-assigned this Jun 24, 2026
@rjzamora rjzamora added the bug Something isn't working label Jun 24, 2026
@rjzamora
rjzamora requested a review from a team as a code owner June 24, 2026 20:02
@rjzamora rjzamora added 2 - In Progress Currently a work in progress non-breaking Non-breaking change labels Jun 24, 2026
@rjzamora
rjzamora requested a review from mroeschke June 24, 2026 20:02
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels Jun 24, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python Jun 24, 2026
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 49cdaab7-0439-45b2-8138-58487ee78cd1

📥 Commits

Reviewing files that changed from the base of the PR and between 7f44158 and 042b9b4.

📒 Files selected for processing (2)
  • python/cudf_polars/cudf_polars/streaming/actor_graph/groupby.py
  • python/cudf_polars/cudf_polars/streaming/actor_graph/join.py

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved streaming distinct/unique lowering so dynamic planning uses the expected distinct execution path, with consistent distinct-key handling across static and dynamic modes.
    • Ensures correct behavior for ordering and slice-based scenarios.
    • Adjusted streaming groupby strategy selection by clamping small output counts to reduce suboptimal reduce choices.
  • Tests

    • Added a GPU regression test for streaming unique under dynamic planning, validating results and confirming the physical plan contains DISTINCT and excludes REPARTITION.

Walkthrough

Streaming distinct lowering now separates shared key selection and static reduction from dynamic-planning dispatch. unique() builds the distinct IR explicitly, join filter task setup is split into routing and channel wiring, and groupby strategy selection now clamps the non-tree output count.

Changes

Streaming distinct lowering and unique regression

Layer / File(s) Summary
Distinct helper extraction
python/cudf_polars/cudf_polars/streaming/distinct.py
_distinct_keys centralizes distinct-key expression construction, and _lower_distinct_static contains the partition-wise unique plus Repartition reduction path.
Dynamic-planning dispatch
python/cudf_polars/cudf_polars/streaming/distinct.py
lower_distinct now reconstructs Distinct with PartitionInfo(count=..., partitioned_on=_distinct_keys(ir)) when dynamic planning is enabled, otherwise it calls the static helper, and lower_ir_node.register(Distinct) delegates the final lowering through that wrapper after ordering and complex-slice checks.
Unique decomposition and regression
python/cudf_polars/cudf_polars/streaming/expressions.py, python/cudf_polars/tests/streaming/test_unique.py
_decompose_unique now stores the Distinct IR in a local variable before lowering it, and the new streaming test checks the physical plan for DISTINCT without REPARTITION under dynamic planning.

Streaming actor-graph wiring

Layer / File(s) Summary
Join filter wiring
python/cudf_polars/cudf_polars/streaming/actor_graph/join.py
make_filter_tasks first selects passthrough/build/apply routing values, then allocates channels and assigns ch_left, ch_right, and bloom_apply_output in a separate step.
Groupby strategy selection
python/cudf_polars/cudf_polars/streaming/actor_graph/groupby.py
_choose_strategy clamps the non-tree output_count to at least 2 before choosing between tree and shuffle reduction paths.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change to Distinct lowering.
Description check ✅ Passed The description matches the changeset and explains the dynamic Distinct lowering fix and regression test.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@python/cudf_polars/tests/streaming/test_unique.py`:
- Around line 57-73: The dynamic-planning unique test only checks the physical
plan and does not verify correctness of `unique()` results. In
`test_unique_select_dynamic_planning_uses_dynamic_distinct`, add a
result-equivalence assertion for `q` against the CPU Polars output, using the
same `GPUEngine` setup and allowing `check_row_order=False` if needed. Keep the
existing `explain_query` assertions for `DISTINCT` and `REPARTITION`, but make
sure the test also validates GPU output matches Polars semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 66bb2b07-731d-4dae-bc1d-b468fba53d7e

📥 Commits

Reviewing files that changed from the base of the PR and between f4f67e7 and fcd6b11.

📒 Files selected for processing (3)
  • python/cudf_polars/cudf_polars/streaming/distinct.py
  • python/cudf_polars/cudf_polars/streaming/expressions.py
  • python/cudf_polars/tests/streaming/test_unique.py

Comment thread python/cudf_polars/tests/streaming/test_unique.py Outdated

@pentschev pentschev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I've tested this, and alone it exposes a bug that @rjzamora discovered is luckily being resolved by #22995 . The bug causes a hang in NDSH Q22, in Rick's words about the issue:

When we go down the shuffle path, we are already partitioned on the right side of a left join. The current bloom-filter logic will create filtering channels, but then bail on the filtering because we are already partitioned on the keys. Then we end up waiting on the wrong channels.

Combining this change with #22995 resolves both the original issue (being fixed by this bug) and the hang. The results for SF30K on 8xNVL72 is 4.50s lukewarm, 3.13s hot.

Since, originally, Q22 didn't work anyway, I don't think it's necessary to wait for #22995 and we can merge it anytime now.

# query DAG.
return ch_left, ch_right, [], []

bloom_build_output: Channel[BloomFilterChunk] = context.create_channel()

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@pentschev - Thanks for the review! Just a note that these changes "fix" the hang when this branch is used in the absence of #22995 - Hopefully you don't mind the conflicts this creates in that PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My apologies, I had indeed forgotten to pull the latest changes. However, the current state with latest 042b9b4 is actually back to the original problem, q22 fails:

OverflowError: CUDF failure at: /tmp/conda-bld-output/bld/rattler-build_libcudf/work/cpp/src/copying/concatenate.cu:476: Total number of concatenated rows exceeds the column size limit

The previous state I was trying was the latest commit I had locally, which was 19ed80e, and merging that on top of #22997 , that had worked as expected, and all queries passed (without the need for changes to the queries themselves).

With the above being said I want to ask whether the changes coming after 19ed80e are really necessary, or are they fixed by a combination of 19ed80e + #22997 (which includes changes also from #22995 and #22996), WDYT? Once again, I have already verified original changes from this PR + #22997 has everything in a good state, but the same is not true with the current in 042b9b4, which brings back the original issue to Q22. For the sake of simplicity (rerunning everything at scale is time-consuming) I would propose instead merging the changes here only up to and including 19ed80e and then #22995, #22996 and #22997, which I have already confirmed to get us to the state we want to be in.

Let me know what you think. For now I'm changing my approval to block the PR from an accidental merge until we are sure of next steps.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the current state with latest 042b9b4 is actually back to the original problem, q22 fails

Okay - I don't quite understand why that might be the case yet, but that's good to know.

With the above being said I want to ask whether the changes coming after 19ed80e are really necessary

The changes were meant to avoid a hang between 22970 and 22995 being merged. However, I was assuming you would just ignore/replace any changes made to this file.

I definitely don't understand why we would be back to the int-overflow issue with this change.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like something messed up with my image build and I probably ran an incorrect version. Indeed, after rebuilding I can confirm everything works with this PR now. I'm very sorry for the confusion and added work on verifications.

# query DAG.
return ch_left, ch_right, [], []

bloom_build_output: Channel[BloomFilterChunk] = context.create_channel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

My apologies, I had indeed forgotten to pull the latest changes. However, the current state with latest 042b9b4 is actually back to the original problem, q22 fails:

OverflowError: CUDF failure at: /tmp/conda-bld-output/bld/rattler-build_libcudf/work/cpp/src/copying/concatenate.cu:476: Total number of concatenated rows exceeds the column size limit

The previous state I was trying was the latest commit I had locally, which was 19ed80e, and merging that on top of #22997 , that had worked as expected, and all queries passed (without the need for changes to the queries themselves).

With the above being said I want to ask whether the changes coming after 19ed80e are really necessary, or are they fixed by a combination of 19ed80e + #22997 (which includes changes also from #22995 and #22996), WDYT? Once again, I have already verified original changes from this PR + #22997 has everything in a good state, but the same is not true with the current in 042b9b4, which brings back the original issue to Q22. For the sake of simplicity (rerunning everything at scale is time-consuming) I would propose instead merging the changes here only up to and including 19ed80e and then #22995, #22996 and #22997, which I have already confirmed to get us to the state we want to be in.

Let me know what you think. For now I'm changing my approval to block the PR from an accidental merge until we are sure of next steps.

@pentschev pentschev left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

After another review, it was a mistake with the build of my image, I don't know what happened that caused it to build with the wrong change. I can now confirm again this is indeed fixing the original issue and there are no blockers, we are good to merge.

Once again, thanks @rjzamora for working on this fix!

# query DAG.
return ch_left, ch_right, [], []

bloom_build_output: Channel[BloomFilterChunk] = context.create_channel()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It seems like something messed up with my image build and I probably ran an incorrect version. Indeed, after rebuilding I can confirm everything works with this PR now. I'm very sorry for the confusion and added work on verifications.

@pentschev

Copy link
Copy Markdown
Contributor

/merge

@rapids-bot
rapids-bot Bot merged commit b314d96 into NVIDIA:main Jun 29, 2026
107 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python Jun 29, 2026
@rjzamora
rjzamora deleted the fix-unique-expression-dynamic-distinct branch June 29, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

2 - In Progress Currently a work in progress bug Something isn't working cudf-polars Issues specific to cudf-polars non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants