Skip to content

Let callers reserve partitioning memory - #23833

Open
madsbk wants to merge 22 commits into
NVIDIA:mainfrom
madsbk:let-callers-reserve-partitioning-memory
Open

Let callers reserve partitioning memory#23833
madsbk wants to merge 22 commits into
NVIDIA:mainfrom
madsbk:let-callers-reserve-partitioning-memory

Conversation

@madsbk

@madsbk madsbk commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

partition_and_pack(), split_and_pack() and unpack_and_concat() can now take a caller-provided MemoryReservation& instead of reserving and spilling internally. That lets a caller reserve before it starts, so the point where it might block is explicit rather than buried inside the call.

partition_and_pack_cost(), split_and_pack_cost() and unpack_and_concat_cost() return the peak device memory each function needs. All six are bound in cudf_streaming.partition_utils, where the reservation is an optional trailing argument, so existing callers are unaffected.

Nothing calls the new overloads yet beyond the tests. cudf-polars picks them up in the follow-up #23834, which gives the shuffle memory backpressure on both the insert and the extract side.

Notes

Only the unspill share of unpack_and_concat_cost() is exact, since the buffer resource consumes it while moving each partition. The rest is an estimate, because libcudf allocates against BufferResource::device_mr() and never sees the reservation. split_and_pack_cost() in particular under-reports for more than one partition, since contiguous_split() aligns every column buffer of every partition. That was true of the internal reservations before this change too.

unpack_and_concat_cost() has an overload taking a vector of pointers, so Cython can compute the cost without moving the partitions out of their Python owners.

@madsbk madsbk self-assigned this Aug 26, 2026
@madsbk madsbk added improvement Improvement / enhancement to an existing function non-breaking Non-breaking change labels Aug 26, 2026
@github-actions github-actions Bot added libcudf Affects libcudf (C++/CUDA) code. Python Affects Python cuDF API. labels Aug 26, 2026
@madsbk
madsbk marked this pull request as ready for review August 26, 2026 14:50
@madsbk
madsbk requested review from a team as code owners August 26, 2026 14:50
@madsbk
madsbk requested a review from quasiben August 26, 2026 14:50
@NVIDIA NVIDIA deleted a comment from copy-pr-bot Bot Aug 26, 2026
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • No new commits to review - use @coderabbitai full review for a full pass
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added memory-cost estimates for partitioning, splitting, and unpacking operations.
    • Added optional memory reservations for predictable device-memory usage.
    • Added support for specifying packed data sizes during reservation-aware partitioning and splitting.
    • Exposed cost-estimation helpers through the Python package.
  • Bug Fixes

    • Improved reservation validation, accounting, and reuse after insufficient-memory attempts.
    • Cost estimates no longer consume partition data.
    • Improved handling of empty tables and consumed partitions.
  • Tests

    • Added coverage for reservations, round trips, and error handling.

Walkthrough

The change adds cost-estimation APIs and caller-provided memory reservations for partitioning, splitting, and unpacking in C++ and Python. It updates stream types, reservation handling, bindings, public declarations, exports, and regression tests.

Changes

Partition memory APIs

Layer / File(s) Summary
C++ partition and split reservation flow
cpp/libcudf_streaming/include/cudf_streaming/partition_utils.hpp, cpp/libcudfudf_streaming/src/partition_utils.cpp
Adds cost estimation, optional packed sizes, caller-provided reservations, and cuda::stream_ref support for partitioning and splitting.
C++ unpack and concatenate reservation flow
cpp/libcudf_streaming/include/cudf_streaming/partition_utils.hpp, cpp/libcudf_streaming/src/partition_utils.cpp
Adds unpack-cost overloads, packed-input validation, and separate reservation accounting for unspilling and concatenation.
Python API bindings and public declarations
python/cudf_streaming/cudf_streaming/partition_utils.*, python/cudf_streaming/cudf_streaming/__init__.py
Exposes cost functions and optional MemoryReservation parameters through Cython bindings, stubs, declarations, stream conversions, and package exports.
Reservation and cost-estimation validation
cpp/libcudf_streaming/tests/test_partition.cpp, python/cudf_streaming/cudf_streaming/tests/test_integration_partition.py
Tests reserved round trips, invalid reservations, empty tables, spill and unspill accounting, reusable cost estimation, generators, and consumed partitions.

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

Merge Risk: 🔴 Critical · up to 7e352

The change adds caller-managed memory reservations, but the current implementation still has binding declarations inconsistent with the C++ APIs, which can block builds, and failed reservations may consume caller-owned partition data so the operation cannot be retried safely. These issues should be fixed before merging.

Suggested reviewers: quasiben, nirandaperera, vyasr, bdice

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.90% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 6 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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 accurately identifies the central change: callers can provide reservations for partitioning memory. It does not mention splitting, unpacking, or cost-estimation APIs, but those are supportin…
Description check ✅ Passed The description clearly explains caller-provided memory reservations, cost-estimation APIs, Python bindings, compatibility, and test coverage. It is directly related to the changeset.
Full details: Docstring Coverage

Explanation

Docstring coverage is 12.90% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 31 functions across 6 files. (1 skipped: 1 unsupported.)

Full details: Title check

Explanation

The title accurately identifies the central change: callers can provide reservations for partitioning memory. It does not mention splitting, unpacking, or cost-estimation APIs, but those are supporting aspects of the changeset.

  • Fix all pre-merge checks with AI
✨ 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.

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

Very small suggestions

Comment thread cpp/libcudf_streaming/include/cudf_streaming/partition_utils.hpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
coderabbitai[bot]

This comment was marked as resolved.

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

some suggestions

Comment thread cpp/libcudf_streaming/include/cudf_streaming/partition_utils.hpp Outdated
Comment thread cpp/libcudf_streaming/include/cudf_streaming/partition_utils.hpp Outdated
Comment thread cpp/libcudf_streaming/include/cudf_streaming/partition_utils.hpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
@madsbk
madsbk requested a review from nirandaperera August 27, 2026 06:53
coderabbitai[bot]

This comment was marked as resolved.

Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment on lines +103 to +109
std::unordered_map<rapidsmpf::shuffler::PartID, rapidsmpf::PackedData> split_and_pack_impl(
cudf::table_view const& table,
std::vector<cudf::size_type> const& splits,
rmm::cuda_stream_view stream,
rapidsmpf::BufferResource* br,
rapidsmpf::AllowOverbooking allow_overbooking,
rapidsmpf::MemoryReservation* reservation);

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 think we can request a mandatory reservation in every *_impl methods.

Suggested change
std::unordered_map<rapidsmpf::shuffler::PartID, rapidsmpf::PackedData> split_and_pack_impl(
cudf::table_view const& table,
std::vector<cudf::size_type> const& splits,
rmm::cuda_stream_view stream,
rapidsmpf::BufferResource* br,
rapidsmpf::AllowOverbooking allow_overbooking,
rapidsmpf::MemoryReservation* reservation);
std::unordered_map<rapidsmpf::shuffler::PartID, rapidsmpf::PackedData> split_and_pack_impl(
cudf::table_view const& table,
std::vector<cudf::size_type> const& splits,
rmm::cuda_stream_view stream,
rapidsmpf::MemoryReservation& reservation);

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 signature is nicer, br and allow_overbooking both fall out since MemoryReservation carries br(). But it moves the reserving up into the public non-reservation overload, and that costs the callers who use it today, which is everything: the benchmarks, the tests and cudf-polars. They would pay an extra packed_size() sync, and hold twice the peak claim, since partition_and_pack() currently reserves the reorder and the pack in sequence rather than together.

@nirandaperera nirandaperera Aug 27, 2026

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.

No I meant in the, *_impl methods only. We have,

auto partition_and_pack_impl(..., res); // anon ns

auto partition_and_pack(..., br, ...){ // original API
// create res
// call impl
}

auto partition_and_pack(..., res, ...) { // new API 
// call impl
}

Maybe that way, we dont really need an impl as well?

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.

Ah, I see what you mean. I was hesitant because it would worsen the repeated cudf::packed_size() calls @wence- mentions. But both are handled now: it does what you suggest, and an optional packed_bytes argument lets the size be computed once and passed down rather than recomputed in each phase.

Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
coderabbitai[bot]

This comment was marked as resolved.

Comment thread cpp/libcudf_streaming/src/partition_utils.cpp Outdated
rapids-bot Bot pushed a commit to rapidsai/rapidsmpf that referenced this pull request Aug 27, 2026
`MemoryReservation::split(size)` reduces a reservation by `size` and returns a new reservation of that size on the same buffer resource and memory type. The total reserved by the buffer resource is unchanged, the bytes only move between the two reservations.

This lets a caller scope part of a reservation to the allocation it covers. The returned reservation releases its bytes when it goes out of scope, so the reservation is never counted on top of memory that has already been allocated, and there is no size argument at the release point to disagree with what was allocated.

The alternative is `BufferResource::release(reservation, size)`, which works but leaves the caller to pick the release point and to repeat the size. `split()` also composes with a caller-provided reservation: a function can carve off what it needs without caring whether the caller supplied the reservation or it made one itself.

Adopted by cudf's `partition_and_pack()` and friends, which take a caller-provided reservation and split off one sub-reservation per allocation phase: NVIDIA/cudf#23833

Authors:
  - Mads R. B. Kristensen (https://github.com/madsbk)

Approvers:
  - Niranda Perera (https://github.com/nirandaperera)

URL: #1177
@madsbk
madsbk requested a review from nirandaperera August 28, 2026 07:11
coderabbitai[bot]

This comment was marked as resolved.

coderabbitai[bot]

This comment was marked as resolved.

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

@madsbk Last change requests! Promise :inno

#include <cudf/partitioning.hpp>
#include <cudf/table/table.hpp>

#include <cuda/stream_ref>

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.

stream_ref header is deprecated 😞

Suggested change
#include <cuda/stream_ref>
#include <cuda/stream>

Comment on lines +73 to +75
if (!packed_bytes.has_value()) {
packed_bytes = table.num_rows() == 0 ? 0 : cudf::packed_size(table, stream, temp_mr);
}

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.

nit. empty tables are handled by packed_size.
https://github.com/NVIDIA/cudf/blob/main/cpp/src/copying/contiguous_split.cu#L2216

Suggested change
if (!packed_bytes.has_value()) {
packed_bytes = table.num_rows() == 0 ? 0 : cudf::packed_size(table, stream, temp_mr);
}
if (!packed_bytes.has_value()) {
packed_bytes = cudf::packed_size(table, stream, temp_mr);
}

Comment on lines +185 to +194
rapidsmpf::BufferResource* br,
rapidsmpf::MemoryReservation& reservation,
std::optional<std::size_t> packed_bytes)
{
RAPIDSMPF_EXPECTS(reservation.mem_type() == rapidsmpf::MemoryType::DEVICE,
"reservation must be for device memory",
std::invalid_argument);
RAPIDSMPF_EXPECTS(reservation.br() == br,
"reservation must belong to the given buffer resource",
std::invalid_argument);

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.

Suggested change
rapidsmpf::BufferResource* br,
rapidsmpf::MemoryReservation& reservation,
std::optional<std::size_t> packed_bytes)
{
RAPIDSMPF_EXPECTS(reservation.mem_type() == rapidsmpf::MemoryType::DEVICE,
"reservation must be for device memory",
std::invalid_argument);
RAPIDSMPF_EXPECTS(reservation.br() == br,
"reservation must belong to the given buffer resource",
std::invalid_argument);
rapidsmpf::MemoryReservation& reservation,
std::optional<std::size_t> packed_bytes)
{
auto br = reservation.br();
RAPIDSMPF_EXPECTS(reservation.mem_type() == rapidsmpf::MemoryType::DEVICE,
"reservation must be for device memory",
std::invalid_argument);

Comment on lines +247 to +256
rapidsmpf::BufferResource* br,
rapidsmpf::MemoryReservation& reservation,
std::optional<std::size_t> packed_bytes)
{
RAPIDSMPF_EXPECTS(reservation.mem_type() == rapidsmpf::MemoryType::DEVICE,
"reservation must be for device memory",
std::invalid_argument);
RAPIDSMPF_EXPECTS(reservation.br() == br,
"reservation must belong to the given buffer resource",
std::invalid_argument);

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.

Suggested change
rapidsmpf::BufferResource* br,
rapidsmpf::MemoryReservation& reservation,
std::optional<std::size_t> packed_bytes)
{
RAPIDSMPF_EXPECTS(reservation.mem_type() == rapidsmpf::MemoryType::DEVICE,
"reservation must be for device memory",
std::invalid_argument);
RAPIDSMPF_EXPECTS(reservation.br() == br,
"reservation must belong to the given buffer resource",
std::invalid_argument);
rapidsmpf::MemoryReservation& reservation,
std::optional<std::size_t> packed_bytes)
{
auto br = reservation.br();
RAPIDSMPF_EXPECTS(reservation.mem_type() == rapidsmpf::MemoryType::DEVICE,
"reservation must be for device memory",
std::invalid_argument);

Comment on lines +307 to +308
rapidsmpf::BufferResource* br,
rapidsmpf::MemoryReservation& reservation)

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.

same here

Comment on lines +328 to +335
// Checked up front so an undersized reservation is caught before the first split
// mutates it, leaving the caller free to reserve more and retry.
check_reservation(reservation, total_size + non_device_size);

// The unspill consumes its reservation as it moves each partition, the concatenation
// only needs its bytes accounted for until it has allocated them.
auto unspill_res = reservation.split(non_device_size);
auto concat_res = reservation.split(total_size);

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 think these are redundant. Splitting the reservation for total_size + non_device_size should cover all these

Suggested change
// Checked up front so an undersized reservation is caught before the first split
// mutates it, leaving the caller free to reserve more and retry.
check_reservation(reservation, total_size + non_device_size);
// The unspill consumes its reservation as it moves each partition, the concatenation
// only needs its bytes accounted for until it has allocated them.
auto unspill_res = reservation.split(non_device_size);
auto concat_res = reservation.split(total_size);
// Checked up front so an undersized reservation is caught before the first split
// mutates it, leaving the caller free to reserve more and retry.
auto scoped_res = reservation.split(total_size + non_device_size);

* Computed here otherwise, which syncs the stream.
* @return A pair of the packed size and the total cost, both in bytes.
*/
[[nodiscard]] std::pair<std::size_t, std::size_t> packed_and_total_size(

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.

Nit. wondering if we really need this 😇

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

improvement Improvement / enhancement to an existing function libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change Python Affects Python cuDF API.

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

3 participants