Skip to content

Avoid blocking memory reservations in actor coroutines - #23892

Merged
rapids-bot[bot] merged 1 commit into
NVIDIA:mainfrom
madsbk:avoid-blocking-reservations-in-actor-coroutines
Sep 1, 2026
Merged

Avoid blocking memory reservations in actor coroutines#23892
rapids-bot[bot] merged 1 commit into
NVIDIA:mainfrom
madsbk:avoid-blocking-reservations-in-actor-coroutines

Conversation

@madsbk

@madsbk madsbk commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Every actor on a rank shares one event loop, so an actor that reserves device memory synchronously stalls all the others while it spills. This moves every such reservation that runs inside an actor coroutine off the blocking path. They take their memory from reserve_memory() now, so a request that cannot be satisfied queues alongside the other actors' and is served by priority instead of spilling on the spot. That gives these sites memory backpressure as well as an unblocked loop, since an actor waiting on a reservation lets the ones that can release memory run first.

Unspilling table chunks

TableChunk.make_available_and_spill() spills synchronously. Twelve call sites move to the awaitable make_table_chunks_available_or_wait(), which suspends until a reservation is granted so the other actors can run and release memory meanwhile.

AllGatherManager.Inserter.insert and _unpack_remote_partition become coroutines, updating eight call sites across join.py, repartition.py, sort.py, utils.py, ordering.py and the AllGather tests. _unpack_remote_partition also takes the context rather than a buffer resource. Neither is public API.

Behavior change

The unspill calls previously passed allow_overbooking=True. They now fall through to the allow_overbooking_by_default configuration option, matching every existing make_table_chunks_available_or_wait() call site. That option ships as true, so under the default the only difference is that these sites wait for memory before overbooking rather than overbooking immediately. With the option set to false they can raise where previously they could not.

@madsbk madsbk self-assigned this Aug 31, 2026
@madsbk madsbk added improvement Improvement / enhancement to an existing function breaking Breaking change labels Aug 31, 2026
@github-actions github-actions Bot added Python Affects Python cuDF API. cudf-polars Issues specific to cudf-polars labels Aug 31, 2026
@madsbk
madsbk force-pushed the avoid-blocking-reservations-in-actor-coroutines branch from 140c1e7 to 5826e82 Compare August 31, 2026 10:51
@madsbk
madsbk marked this pull request as ready for review August 31, 2026 11:57
@madsbk
madsbk requested a review from a team as a code owner August 31, 2026 11:57
@madsbk
madsbk requested a review from nirandaperera August 31, 2026 11:57
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Aug 31, 2026
@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Performance

    • Improved streaming data movement and aggregation by coordinating memory availability before processing chunks.
    • Reduced unnecessary spilling, copying, and temporary memory allocation across joins, shuffles, sorting, unions, fanout, repartitioning, and output operations.
    • Skipped unnecessary memory work for empty chunks.
  • Reliability

    • Improved handling of memory pressure during remote processing and file output.
    • Enhanced asynchronous processing so operations wait safely when resources are temporarily unavailable.
    • Improved fanout memory placement across device, pinned-host, and host memory.

Walkthrough

The streaming actor graph now uses asynchronous chunk availability and explicit memory reservations. AllGather, shuffle, ordering, join, fanout, sink, origin-stamp, and union paths use the updated coordination flow.

Changes

Streaming memory coordination

Layer / File(s) Summary
Asynchronous AllGather reservation
python/cudf_polars/.../collectives/allgather.py
AllGather insertion is asynchronous. Packing and unpacking reserve estimated memory before processing.
AllGather caller updates
python/cudf_polars/.../collectives/sort.py, python/cudf_polars/.../collectives/join.py, python/cudf_polars/.../repartition.py, python/cudf_polars/.../utils.py, python/cudf_polars/tests/streaming/test_allgather.py
All AllGather insertion callers and the test now await insert.
Collective shuffle and ordering memory accounting
python/cudf_polars/.../collectives/ordering.py, python/cudf_polars/.../collectives/shuffle.py, python/cudf_polars/.../collectives/sort.py
Shuffle and ordering operations now await availability, reserve temporary memory, and pass reservations into packing and unpacking. Empty ordering chunks skip reservations.
Actor-graph memory paths
python/cudf_polars/.../io.py, python/cudf_polars/.../join.py, python/cudf_polars/.../nodes.py, python/cudf_polars/.../over.py, python/cudf_polars/.../union.py
Sinks, joins, fanout, origin stamping, and union relay paths use explicit reservation and memory-delta tracking instead of the previous spill-based overbooking paths.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: 🟡 Moderate · up to a3aeb

The PR replaces blocking memory reservations with asynchronous backpressure across streaming operations, but expanded hash-key expressions can exceed the reserved memory, and some collective paths consume data before a reservation wait that may fail or be cancelled. Under memory pressure this could cause failures or strand collective state, so the change needs targeted fixes or explicit owner acceptance before merge.

Suggested reviewers: tomaugspurger, nirandaperera, rjzamora

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 92.31% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 26 functions across 12 files.
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.
Title check ✅ Passed The title clearly and concisely describes the main change: replacing blocking memory reservations in actor coroutines with non-blocking behavior.
Description check ✅ Passed The description accurately explains the reservation changes, affected call sites, coroutine updates, and behavior change for overbooking.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

coderabbitai[bot]

This comment was marked as resolved.

madsbk added a commit to madsbk/cudf that referenced this pull request Aug 31, 2026

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

Breaking change
Two functions become coroutines

These aren't part of the public cudf-polars API, so if you were basing the "breaking" label off those two I think we can remove it.

Comment on lines +116 to +122
# Representation change: the packed input is consumed as the
# unpacked table is produced, at roughly the same size.
reservation = await reserve_memory(
self.context,
unpack_and_concat_cost(partitions),
net_memory_delta=0,
)

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.

Can you explain this reservation? IIUC, we're making a change to ensure that we have room for both partitions (already in memory) and the unpack_and_concat form.

But the comment says that "the packed input is consumed as the unpacked table is produces". So are we freeing from partitions as the unpacked form is produced or not?

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.

Updated the comment, the same size was refering to the net_memory_delta argument. Fixed in: ccac165

@madsbk

madsbk commented Aug 31, 2026

Copy link
Copy Markdown
Contributor Author

Breaking change
Two functions become coroutines

These aren't part of the public cudf-polars API, so if you were basing the "breaking" label off those two I think we can remove it.

It was, removed

@madsbk madsbk added non-breaking Non-breaking change and removed breaking Breaking change labels Aug 31, 2026
@madsbk
madsbk requested a review from TomAugspurger August 31, 2026 14:25
madsbk added a commit to madsbk/cudf that referenced this pull request Aug 31, 2026
@madsbk
madsbk force-pushed the avoid-blocking-reservations-in-actor-coroutines branch from afdcf5f to a3aeb96 Compare September 1, 2026 12:03
madsbk added a commit to madsbk/cudf that referenced this pull request Sep 1, 2026
coderabbitai[bot]

This comment was marked as resolved.

reserve_extra=0,
net_memory_delta=0,
)
self._manager.allgather.insert(

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 cannot mark the whole block, because GitHub, but could we reserve and consume the packing allocation here as well? make_table_chunks_available_or_wait(..., reserve_extra=0) consumes its reservation just to make the chunk available, but chunk.into_packed_data() calls cudf::pack() for non-PackedData chunks. The local table_chunk API explicitly documents that this allocation is not reservation-tracked, so it can still synchronously spill/block the shared actor event loop. I think this should route packing through a reservation-aware path and cover an unpacked chunk under memory pressure.

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.

Done in #23894, kept separate because it is a breaking C++ and Cython change.

@madsbk

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@madsbk
madsbk requested a review from pentschev September 1, 2026 12:28
@NVIDIA NVIDIA deleted a comment from coderabbitai Bot Sep 1, 2026
@madsbk

madsbk commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the quick reviews!

@madsbk

madsbk commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

/merge

@rapids-bot
rapids-bot Bot merged commit f75e6e8 into NVIDIA:main Sep 1, 2026
122 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in cuDF Python Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cudf-polars Issues specific to cudf-polars improvement Improvement / enhancement to an existing function non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants