Skip to content

Support empty segments in segmented bitmask reductions - #23689

Open
vuule wants to merge 15 commits into
NVIDIA:mainfrom
vuule:segmented-bitmask-empty-segments
Open

Support empty segments in segmented bitmask reductions#23689
vuule wants to merge 15 commits into
NVIDIA:mainfrom
vuule:segmented-bitmask-empty-segments

Conversation

@vuule

@vuule vuule commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Description

segmented_offset_bitmask_binop seeded each segment's reduction by loading the segment's first mask, sources[segment_start], before knowing whether the segment contains any masks. An empty segment therefore had no defined result and read a mask it does not own; for a trailing empty segment, that read is past the end of the mask array.

With this PR, seeding is done with the identity of the binary operator. An empty segment then means the identity, which for bitwise AND is an all-valid mask with a null count of zero; this is now documented on the public segmented_bitmask_and overloads.

The kernel also indexed segment_offsets and destinations before the segment_id >= num_segments guard, which the excess warps of the last block do whenever the segment count is not a multiple of the warps per block; those loads now happen after the guard.

Segment offsets are additionally validated as non-decreasing and within the mask array, since a decreasing pair leaves a segment's bounds meaningless. The bitmask benchmark's segment size generator was producing exactly that: it truncated draws from an unbounded normal distribution into size_type, so a small expected_masks_per_segment gave negative sizes and an illegal memory access. It now clamps at zero, keeping empty segments.

Checklist

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

@copy-pr-bot

copy-pr-bot Bot commented Aug 17, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@github-actions github-actions Bot added the libcudf Affects libcudf (C++/CUDA) code. label Aug 17, 2026
@vuule
vuule force-pushed the segmented-bitmask-empty-segments branch from 7b0eb55 to 76d938b Compare August 17, 2026 22:40
@vuule vuule changed the title Define empty segments in segmented bitmask reductions and fix the benchmark's segment generator Support empty segments in segmented bitmask reductions Aug 17, 2026
@vuule
vuule marked this pull request as ready for review August 17, 2026 22:52
@vuule
vuule requested a review from a team as a code owner August 17, 2026 22:52
@coderabbitai

coderabbitai Bot commented Aug 17, 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: e0dafa01-dbf9-47c7-aaef-e4d0630e604f

📥 Commits

Reviewing files that changed from the base of the PR and between 400bf84 and 46413e2.

📒 Files selected for processing (3)
  • cpp/include/cudf/detail/null_mask.cuh
  • cpp/include/cudf/null_mask.hpp
  • cpp/tests/bitmask/bitmask_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • cpp/tests/bitmask/bitmask_tests.cpp
  • cpp/include/cudf/null_mask.hpp
  • cpp/include/cudf/detail/null_mask.cuh

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved segmented bitmask AND operations to correctly handle leading, trailing, and interior empty segments.
    • Ensured empty segments produce all-valid masks with zero null counts.
    • Added validation for decreasing or out-of-range segment offsets.
  • Documentation

    • Documented empty-segment behavior and offset validation errors.
  • Tests

    • Added coverage for empty segments, identity masks, null counts, combined masks, and invalid offsets.

Walkthrough

Changes

The segmented bitmask API now accepts identity values, supports empty segments, and validates offsets. Bitwise-AND uses an all-bits-set identity. Tests cover empty segments and invalid offsets. The benchmark now generates non-negative segment sizes.

Segmented bitmask operations

Layer / File(s) Summary
Identity-aware kernel and validation
cpp/include/cudf/detail/null_mask.cuh
Segmented bitmask declarations and launches now carry an identity value. Offset validation occurs before allocation, and destination words start with the identity.
Bitwise-AND identity wiring and contract
cpp/src/bitmask/null_mask.cu, cpp/include/cudf/null_mask.hpp
Both segmented bitwise-AND overloads provide an all-bits-set identity. Documentation defines empty-segment results and invalid-offset errors.
Empty-segment and offset validation tests
cpp/tests/bitmask/bitmask_tests.cpp
Tests cover leading, interior, and trailing empty segments, identity masks, null counts, decreasing offsets, and out-of-range offsets.
Benchmark segment generation
cpp/benchmarks/bitmask/bitmask_and.cpp
The benchmark uses Poisson-distributed non-negative segment sizes and adds the required headers.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 46413

The change defines correct results for empty segments and prevents out-of-bounds indexing; no actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: igorpeshansky, mhaseeb123

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 clearly and concisely describes the main change: support for empty segments in segmented bitmask reductions.
Description check ✅ Passed The description directly explains the empty-segment fix, identity initialization, validation changes, benchmark update, tests, and documentation.
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.

@vuule
vuule marked this pull request as draft August 17, 2026 23:01
Comment thread cpp/benchmarks/bitmask/bitmask_and.cpp Outdated
Comment thread cpp/include/cudf/detail/null_mask.cuh Outdated
Comment thread cpp/include/cudf/detail/null_mask.cuh Outdated
…ity seed

Seeding each segment's reduction with the identity of the binary operator, instead of
loading the segment's first mask, gives an empty segment a well defined result (an
all-valid destination mask, null count zero) and removes the read of sources[segment_start]
that a segment with no masks would otherwise perform out of bounds.

Also validate that segment offsets are non-decreasing and within the mask array, and stop
the bitmask benchmark's segment size generator from drawing negative sizes, which produced
non-monotonic offsets and an illegal memory access for a small expected_masks_per_segment.
@vuule
vuule force-pushed the segmented-bitmask-empty-segments branch from 76d938b to 9b5cc7a Compare August 17, 2026 23:52
@vuule vuule added bug Something isn't working non-breaking Non-breaking change labels Aug 18, 2026
auto const segment_id = cudf::detail::grid_1d::global_thread_id() / warp.size();
auto const segment_id = cudf::detail::grid_1d::global_thread_id() / warp.size();

if (segment_id >= num_segments) { return; }

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.

exit before reading from segment_offsets

@vuule

vuule commented Aug 18, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 400bf84

@vuule
vuule requested a review from bdice August 18, 2026 17:40
@vuule
vuule marked this pull request as ready for review August 18, 2026 17:40
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

Comment thread cpp/include/cudf/detail/null_mask.cuh Outdated
CUDF_EXPECTS(segment_offsets.size() >= 2,
"At least one segment needs to be passed for bitwise operations");
CUDF_EXPECTS(std::is_sorted(segment_offsets.begin(), segment_offsets.end()),
"Segment offsets must be non-decreasing");

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.

This seems expensive and not a check we normally do (i.e. other segmented APIs).
Could we just check in the kernel and just return if segment_start > segment_end ?

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.

This is a linear check, same as the two all_ofs above. Turns out it's not expensive, even compared to those:

segments masks API (µs) is_sorted (µs) both all_of (µs) is_sorted share (%)
100 420 227.5 0.054 0.30 0.024
1,000 3,993 1,883.8 0.448 2.71 0.024
10,000 39,862 18,560 4.38 27.0 0.024
100,000 400,186 188,860 43.9 273.9 0.023

In addition, AFAICT, checking in the kernel would require some mechanism to return this information to the host, which is likely to be more expensive than is_sorted.

vuule added 2 commits August 18, 2026 12:15
Match the convention of the neighboring host-side index checks in this header
(validate_segmented_indices) and of slice/contiguous_split: a decreasing pair of
offsets throws std::invalid_argument and an offset outside the mask array throws
std::out_of_range, instead of a bare cudf::logic_error.
@vuule
vuule requested a review from davidwendt August 18, 2026 19:49
@vuule

vuule commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 50ebdd5

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

Labels

bug Something isn't working libcudf Affects libcudf (C++/CUDA) code. non-breaking Non-breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants