Skip to content

Add LocalRepartitioner utility - #22439

Merged
rapids-bot[bot] merged 9 commits into
NVIDIA:mainfrom
rjzamora:local-shuffle-api
May 13, 2026
Merged

Add LocalRepartitioner utility#22439
rapids-bot[bot] merged 9 commits into
NVIDIA:mainfrom
rjzamora:local-shuffle-api

Conversation

@rjzamora

@rjzamora rjzamora commented May 9, 2026

Copy link
Copy Markdown
Contributor

Description

Typical Usge*

  1. Wrap the ShuffleManager in a LocalRepartitioner after insertion is finished (and before extracting chunks).
  2. Call LocalRepartitioner.repartition_by_hash or repartition_by_index to dictate how the local partition(s) should be re-partitioned locally.
  3. Use LocalRepartitioner.local_partitions() and LocalRepartitioner.extract_chunk() in the same way we do with ShuffleManager

Checklist

  • I am familiar with the Contributing Guidelines.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@rjzamora rjzamora self-assigned this May 9, 2026
@rjzamora
rjzamora requested a review from a team as a code owner May 9, 2026 13:20
@rjzamora rjzamora added the feature request New feature or request label May 9, 2026
@rjzamora
rjzamora requested a review from TomAugspurger May 9, 2026 13:20
@rjzamora rjzamora added 2 - In Progress Currently a work in progress non-breaking Non-breaking change labels May 9, 2026
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels May 9, 2026
@GPUtester GPUtester moved this to In Progress in cuDF Python May 9, 2026
)
)

def insert_index(self, chunk: TableChunk, partition_map: TableChunk) -> None:

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.

@Matt711 - I think we need something like this for the reverse shuffle? We can record the local chunk index ([0,N)) in a column, and then use that column as the index for the reverse shuffle? (see also repartition_by_index)

@coderabbitai

coderabbitai Bot commented May 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added index-based partitioning for inserts, local repartitioning with hash- and index-based routing, and new extraction helpers for efficient shuffle data access
  • Refactor

    • Consolidated stream-ordering utilities to simplify safe, ordered GPU stream usage and related execution context logic
  • Tests

    • Added distributed tests validating hash- and index-based local repartitioning and end-to-end routing correctness

Walkthrough

This PR extends the RapidsMPF shuffle implementation by adding index-based partition insertion, refactoring extraction APIs, and introducing LocalRepartitioner for local repartitioning workflows. The implementation uses precomputed partition-maps and pylibcudf partitioning to reorder rows, stores communicator state on ShuffleManager, and integrates the new APIs into global shuffle emission. Two SPMD-distributed tests validate hash and index-based local repartitioning.

Changes

LocalRepartitioner and Shuffle Extraction Refactoring

Layer / File(s) Summary
Module Dependencies
python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
Imports extended for single-rank communicator construction, unconditional Context import, pylibcudf.partitioning, and typing/TYPE_CHECKING updates.
CUDA Stream Helper & IR Usage
python/cudf_polars/cudf_polars/utils/cuda_stream.py, python/cudf_polars/cudf_polars/dsl/ir.py
Added stream_ordered_after context manager and refactored IRExecutionContext.stream_ordered_after to use it; updated related imports and typing.
Index-Based Insertion API
python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
ShuffleManager.Inserter.insert_index() added to accept a precomputed single-column partition map and use pylibcudf.partitioning.partition for row reordering and split offset computation.
Manager State and Extraction
python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
ShuffleManager.__init__ stores comm and collective_id; new extract_chunk(partition_id, stream) unpacks and concatenates partition data; new extract_pieces(partition_id) returns raw packed items.
LocalRepartitioner Class
python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
New LocalRepartitioner wraps completed ShuffleManager with a single-rank communicator/context and supports repartition_by_hash and repartition_by_index with optional partition-column dropping.
Global Shuffle Integration
python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
_global_shuffle() updated to iterate over shuffle.local_partitions() and use refactored extract_chunk(partition_id, stream) when building output message payloads.
Test Infrastructure
python/cudf_polars/tests/experimental/test_shuffler.py
Imports added for ShuffleManager, LocalRepartitioner, SPMD allgather utilities, and cudf_polars DataFrame/DataType types.
LocalRepartitioner Test Coverage
python/cudf_polars/tests/experimental/test_shuffler.py
Two parametrized SPMD tests (test_local_repartitioner_hash and test_local_repartitioner_index) validate hash and index-based local partitioning with routing consistency checks and global aggregation assertions.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% 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 clearly summarizes the main addition of a new LocalRepartitioner utility class, which is the primary focus of the changeset.
Description check ✅ Passed The description is related to the changeset, explaining the purpose and typical usage of the new LocalRepartitioner utility and its integration with ShuffleManager.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@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.

🧹 Nitpick comments (2)
python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py (1)

304-322: 💤 Low value

Consider adding bounds validation for partition_col.

If partition_col is out of range (>= number of columns), cols[partition_col] will raise an IndexError. While this might be acceptable as a programming error, explicit validation with a clearer error message could improve debuggability.

💡 Optional: Add bounds check
 async with self._local_shuffle.inserting() as inserter:
     for table in self._iter_chunks(stream):
         cols = table.columns()
+        if partition_col >= len(cols):
+            raise ValueError(
+                f"partition_col {partition_col} out of range for table with {len(cols)} columns"
+            )
         payload = plc.Table(
🤖 Prompt for 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.

In `@python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py`
around lines 304 - 322, Validate partition_col before indexing into cols to
avoid an IndexError and provide a clearer error message: check that
partition_col is an int and 0 <= partition_col < len(cols) (where cols comes
from each table returned by self._iter_chunks(stream)) and raise a ValueError
with a descriptive message if it is out of range; perform this check once per
table before building partition_map and payload (i.e., before using
cols[partition_col] in the inserter.insert_index call that uses
TableChunk.from_pylibcudf_table) so failures indicate the invalid partition_col
and the table column count.
python/cudf_polars/tests/experimental/test_shuffler.py (1)

145-272: 💤 Low value

Consider adding edge case tests for empty and single-row inputs.

The current tests validate the happy path with 12 rows. Per coding guidelines, consider adding tests for:

  • Empty DataFrame (0 rows)
  • Single-row input
  • local_count greater than row count

These can be added as a follow-up if time permits.

🤖 Prompt for 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.

In `@python/cudf_polars/tests/experimental/test_shuffler.py` around lines 145 -
272, Add three edge-case variants for both test_local_repartitioner_hash and
test_local_repartitioner_index: run each test with an empty DataFrame (0 rows),
a single-row DataFrame (1 row), and with local_count greater than the number of
rows to ensure partitioning logic handles small inputs; reuse the existing test
bodies but change the input pl.DataFrame construction and parametrize or add new
tests that call the same logic (referencing test_local_repartitioner_hash,
test_local_repartitioner_index, LocalRepartitioner, repartition_by_hash,
repartition_by_index, and the allgather_polars_dataframe validation) so you
assert preserved routing and global row counts for these edge cases.
🤖 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.

Nitpick comments:
In
`@python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py`:
- Around line 304-322: Validate partition_col before indexing into cols to avoid
an IndexError and provide a clearer error message: check that partition_col is
an int and 0 <= partition_col < len(cols) (where cols comes from each table
returned by self._iter_chunks(stream)) and raise a ValueError with a descriptive
message if it is out of range; perform this check once per table before building
partition_map and payload (i.e., before using cols[partition_col] in the
inserter.insert_index call that uses TableChunk.from_pylibcudf_table) so
failures indicate the invalid partition_col and the table column count.

In `@python/cudf_polars/tests/experimental/test_shuffler.py`:
- Around line 145-272: Add three edge-case variants for both
test_local_repartitioner_hash and test_local_repartitioner_index: run each test
with an empty DataFrame (0 rows), a single-row DataFrame (1 row), and with
local_count greater than the number of rows to ensure partitioning logic handles
small inputs; reuse the existing test bodies but change the input pl.DataFrame
construction and parametrize or add new tests that call the same logic
(referencing test_local_repartitioner_hash, test_local_repartitioner_index,
LocalRepartitioner, repartition_by_hash, repartition_by_index, and the
allgather_polars_dataframe validation) so you assert preserved routing and
global row counts for these edge cases.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b8b4b9b4-1533-4925-b0c8-e08de2463de8

📥 Commits

Reviewing files that changed from the base of the PR and between 4534447 and dd168dc.

📒 Files selected for processing (2)
  • python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
  • python/cudf_polars/tests/experimental/test_shuffler.py

Comment thread python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py Outdated
Comment thread python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py Outdated

@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.

🧹 Nitpick comments (1)
python/cudf_polars/cudf_polars/utils/cuda_stream.py (1)

67-109: ⚡ Quick win

Document or enforce owner lifetimes in this helper.

stream_ordered_after only retains CudaStreamLike objects, so it does not itself guarantee the deallocation behavior described in the docstring. A caller can still drop the owning dataframe/buffer before finally runs, enqueueing its stream-ordered free before the reverse join. Either keep the upstream owners alive in this utility, or narrow the contract so callers must keep those owners alive until the context exits.

💡 Minimal fix
-    Get a joined CUDA stream with safe stream ordering for deallocation of inputs.
+    Get a joined CUDA stream ordered after the given upstream streams.
@@
-    upstreams
-        The streams being provided to stream-ordered operations.
+    upstreams
+        The streams being provided to stream-ordered operations. Callers must
+        keep the owning objects alive until the context exits; this helper only
+        establishes stream dependencies.
@@
-    This context manager provides two useful guarantees when working with
-    objects holding references to stream-ordered objects:
+    This context manager provides stream-ordering guarantees. It does not keep
+    the upstream-owning Python objects alive.
As per coding guidelines, `python/**/*.{py,pyx}`: Use-after-free scenarios in device memory handling - Prevent access to freed device memory resources.
🤖 Prompt for 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.

In `@python/cudf_polars/cudf_polars/utils/cuda_stream.py` around lines 67 - 109,
stream_ordered_after currently only holds CudaStreamLike objects so callers can
drop the actual owner objects (e.g., DataFrame/buffer) before the context exits,
causing their stream-ordered free to be enqueued and leading to use-after-free;
fix by adding an explicit mechanism to retain owner lifetimes: modify
stream_ordered_after to accept an optional upstream_owners: Sequence[Any] (or
rename keep_alive), store that sequence in a local variable referenced for the
duration of the context so Python keeps those owners alive, and then proceed to
yield the downstream stream and call
join_cuda_streams(get_joined_cuda_stream(...)) in the finally block as before;
update the docstring to state that callers must pass owners or that the helper
will retain them until exit and reference the function names
stream_ordered_after, get_joined_cuda_stream, and join_cuda_streams.
🤖 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.

Nitpick comments:
In `@python/cudf_polars/cudf_polars/utils/cuda_stream.py`:
- Around line 67-109: stream_ordered_after currently only holds CudaStreamLike
objects so callers can drop the actual owner objects (e.g., DataFrame/buffer)
before the context exits, causing their stream-ordered free to be enqueued and
leading to use-after-free; fix by adding an explicit mechanism to retain owner
lifetimes: modify stream_ordered_after to accept an optional upstream_owners:
Sequence[Any] (or rename keep_alive), store that sequence in a local variable
referenced for the duration of the context so Python keeps those owners alive,
and then proceed to yield the downstream stream and call
join_cuda_streams(get_joined_cuda_stream(...)) in the finally block as before;
update the docstring to state that callers must pass owners or that the helper
will retain them until exit and reference the function names
stream_ordered_after, get_joined_cuda_stream, and join_cuda_streams.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 61aa97ae-5c5a-40d6-80a7-14e4b4cc0105

📥 Commits

Reviewing files that changed from the base of the PR and between dd168dc and 96e50b4.

📒 Files selected for processing (3)
  • python/cudf_polars/cudf_polars/dsl/ir.py
  • python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py
  • python/cudf_polars/cudf_polars/utils/cuda_stream.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • python/cudf_polars/cudf_polars/experimental/rapidsmpf/collectives/shuffle.py

@Matt711 Matt711 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Addressed my nits. Thanks, @rjzamora, I don't really have anything else

@Matt711

Matt711 commented May 13, 2026

Copy link
Copy Markdown
Member

/merge

@rapids-bot
rapids-bot Bot merged commit 56e1a4a into NVIDIA:main May 13, 2026
90 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in cuDF Python May 13, 2026
@rjzamora
rjzamora deleted the local-shuffle-api branch May 13, 2026 16:41
@rjzamora rjzamora added 5 - Ready to Merge Testing and reviews complete, ready to merge and removed 2 - In Progress Currently a work in progress labels May 13, 2026
rapids-bot Bot pushed a commit that referenced this pull request May 18, 2026
`over()` is, at its heart, a grouped aggregation followed by a broadcast back to the shape of the input. For each group `g` defined by the partition-by keys, evaluate the expression, then map the result back to every row that belongs to `g`.

```python
import polars as pl

df = pl.LazyFrame(
    {
        "x": [1, 2, 3, 4, 5, 6],
        "g": [1, None, 1, None, 2, 1],
    }
)
print(df.select(pl.col("x").sum().over("g")).collect())
```
```
shape: (6, 1)
┌─────┐
│ x   │
│ --- │
│ i64 │
╞═════╡
│ 10  │
│ 6   │
│ 10  │
│ 6   │
│ 5   │
│ 10  │
└─────┘
```


Polars represents this with a `WindowMapping` enum. This PR adds support for the `group_to_rows` mapping in the RapidsMPF streaming executor (the variant where the output has the same number of rows as the input and each row receives the value computed for its group). The entry point is a new `over_actor` that selects one of three execution strategies at runtime based on the incoming channel metadata and expression shape.

### The `over_actor`: three strategies

**1. Chunkwise (already partitioned)**

If the incoming channel metadata shows the data is already hash-partitioned on the `over()` keys (or any prefix of them; being partitioned on `('a',)` is sufficient for `over('a', 'b')`, since every group is contained within one rank), the window function is trivially correct on each chunk in isolation. We evaluate chunkwise with no coordination at all.

**2. Scalar aggregations: AllGather + broadcast**

When every `GroupedWindow` in the expression is a scalar aggregation (`sum`, `mean`, `count`, etc.), we exploit the fact that these are decomposable: each worker computes partial aggregates chunkwise, an AllGather collects all workers' partial results, a single reduction produces the global aggregate per group, and then each original chunk has those results broadcast back into its row positions via a hash join on the partition keys.

**3. Non-scalar aggregations: forward-shuffle + return-shuffle**

Functions like `rank` are not decomposable; they require every row in the group to be visible at once. We hash-shuffle by the partition keys so that all rows belonging to group `g` land in the same rank for evaluation. The challenge is then twofold: putting rows back in the right order, *and* getting them back to the rank that owns the corresponding output chunk in the first place. Output channels are rank-local, so only the rank that received an input chunk is wired up to emit it, and the hash shuffle scatters rows by group with no regard for where they originated. We need an explicit return trip.

### Preserving full order

A lot of the implementation exists purely to put output chunks back in the same sequence-number order as the input. Getting this right across both strategies is where most of the complexity lives.

**Scalar aggregation path.** We can't produce any output until the global aggregate is known, so we buffer incoming chunks while simultaneously computing partial aggregates over them. Once the AllGather + final reduction completes, we iterate over the buffer and evaluate each chunk against the global aggregate, emitting results with their original sequence numbers. Order preservation falls out naturally: the buffer is in receive order and we never reorder it.

**Non-scalar shuffle path.** Each row is stamped with three pieces of origin metadata before it enters the forward shuffle: an `origin_rank` (which rank ingested it), a `chunk_index` (a rank-local 0-based counter, *not* the upstream message sequence number, which can collide when the input is the output of a prior shuffle), and a `position` within that input chunk. After the forward shuffle, each rank holds a mix of rows from every origin, but each row knows where it came from. We evaluate the window function on each local forward partition (so `rank` sees every row in the group), then route the results through a *return* shuffle keyed on `origin_rank`. The return shuffle uses `num_partitions = nranks` and `PartitionAssignment.CONTIGUOUS`, so partition `i` lives on rank `i`, and every row goes back to the rank that originally received it. Each rank then sorts the returned rows by `(chunk_index, position)`, splits at chunk-index transitions, drops the stamp columns, and emits one output chunk per input chunk in input order.

To avoid buffering every input chunk just to size the forward shuffle, the actor samples a small number of chunks up front (`_choose_modulus`), AllGathers a size estimate, picks the modulus, and then replays the sampled chunks back through a fresh channel via `replay_buffered_channel`. The forward-insert phase reads from that replay channel and streams rows into the shuffle as they arrive, never holding more than the shuffle's own internal buffering.

- Closes #22047
- Closes #22235
- Depends on #22439
- Contributes to #21749 and #22032

Authors:
  - Matthew Murray (https://github.com/Matt711)
  - Richard (Rick) Zamora (https://github.com/rjzamora)

Approvers:
  - Richard (Rick) Zamora (https://github.com/rjzamora)
  - Lawrence Mitchell (https://github.com/wence-)

URL: #22191
madsbk pushed a commit to madsbk/cudf that referenced this pull request May 19, 2026
`over()` is, at its heart, a grouped aggregation followed by a broadcast back to the shape of the input. For each group `g` defined by the partition-by keys, evaluate the expression, then map the result back to every row that belongs to `g`.

```python
import polars as pl

df = pl.LazyFrame(
    {
        "x": [1, 2, 3, 4, 5, 6],
        "g": [1, None, 1, None, 2, 1],
    }
)
print(df.select(pl.col("x").sum().over("g")).collect())
```
```
shape: (6, 1)
┌─────┐
│ x   │
│ --- │
│ i64 │
╞═════╡
│ 10  │
│ 6   │
│ 10  │
│ 6   │
│ 5   │
│ 10  │
└─────┘
```


Polars represents this with a `WindowMapping` enum. This PR adds support for the `group_to_rows` mapping in the RapidsMPF streaming executor (the variant where the output has the same number of rows as the input and each row receives the value computed for its group). The entry point is a new `over_actor` that selects one of three execution strategies at runtime based on the incoming channel metadata and expression shape.

### The `over_actor`: three strategies

**1. Chunkwise (already partitioned)**

If the incoming channel metadata shows the data is already hash-partitioned on the `over()` keys (or any prefix of them; being partitioned on `('a',)` is sufficient for `over('a', 'b')`, since every group is contained within one rank), the window function is trivially correct on each chunk in isolation. We evaluate chunkwise with no coordination at all.

**2. Scalar aggregations: AllGather + broadcast**

When every `GroupedWindow` in the expression is a scalar aggregation (`sum`, `mean`, `count`, etc.), we exploit the fact that these are decomposable: each worker computes partial aggregates chunkwise, an AllGather collects all workers' partial results, a single reduction produces the global aggregate per group, and then each original chunk has those results broadcast back into its row positions via a hash join on the partition keys.

**3. Non-scalar aggregations: forward-shuffle + return-shuffle**

Functions like `rank` are not decomposable; they require every row in the group to be visible at once. We hash-shuffle by the partition keys so that all rows belonging to group `g` land in the same rank for evaluation. The challenge is then twofold: putting rows back in the right order, *and* getting them back to the rank that owns the corresponding output chunk in the first place. Output channels are rank-local, so only the rank that received an input chunk is wired up to emit it, and the hash shuffle scatters rows by group with no regard for where they originated. We need an explicit return trip.

### Preserving full order

A lot of the implementation exists purely to put output chunks back in the same sequence-number order as the input. Getting this right across both strategies is where most of the complexity lives.

**Scalar aggregation path.** We can't produce any output until the global aggregate is known, so we buffer incoming chunks while simultaneously computing partial aggregates over them. Once the AllGather + final reduction completes, we iterate over the buffer and evaluate each chunk against the global aggregate, emitting results with their original sequence numbers. Order preservation falls out naturally: the buffer is in receive order and we never reorder it.

**Non-scalar shuffle path.** Each row is stamped with three pieces of origin metadata before it enters the forward shuffle: an `origin_rank` (which rank ingested it), a `chunk_index` (a rank-local 0-based counter, *not* the upstream message sequence number, which can collide when the input is the output of a prior shuffle), and a `position` within that input chunk. After the forward shuffle, each rank holds a mix of rows from every origin, but each row knows where it came from. We evaluate the window function on each local forward partition (so `rank` sees every row in the group), then route the results through a *return* shuffle keyed on `origin_rank`. The return shuffle uses `num_partitions = nranks` and `PartitionAssignment.CONTIGUOUS`, so partition `i` lives on rank `i`, and every row goes back to the rank that originally received it. Each rank then sorts the returned rows by `(chunk_index, position)`, splits at chunk-index transitions, drops the stamp columns, and emits one output chunk per input chunk in input order.

To avoid buffering every input chunk just to size the forward shuffle, the actor samples a small number of chunks up front (`_choose_modulus`), AllGathers a size estimate, picks the modulus, and then replays the sampled chunks back through a fresh channel via `replay_buffered_channel`. The forward-insert phase reads from that replay channel and streams rows into the shuffle as they arrive, never holding more than the shuffle's own internal buffering.

- Closes NVIDIA#22047
- Closes NVIDIA#22235
- Depends on NVIDIA#22439
- Contributes to NVIDIA#21749 and NVIDIA#22032

Authors:
  - Matthew Murray (https://github.com/Matt711)
  - Richard (Rick) Zamora (https://github.com/rjzamora)

Approvers:
  - Richard (Rick) Zamora (https://github.com/rjzamora)
  - Lawrence Mitchell (https://github.com/wence-)

URL: NVIDIA#22191
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

5 - Ready to Merge Testing and reviews complete, ready to merge cudf-polars Issues specific to cudf-polars feature request New feature or request non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants