Skip to content

[FEA]: Add runtime-width group merge-sort primitive for segmented sort - #11030

Open
roro2006 wants to merge 1 commit into
NVIDIA:mainfrom
roro2006:fea/group-merge-sort-runtime-width
Open

[FEA]: Add runtime-width group merge-sort primitive for segmented sort#11030
roro2006 wants to merge 1 commit into
NVIDIA:mainfrom
roro2006:fea/group-merge-sort-runtime-width

Conversation

@roro2006

Copy link
Copy Markdown

Adds a reusable runtime-width group merge-sort primitive (cub::detail::GroupMergeSort and cub::detail::call_group_merge_runtime)
supporting:
- Sub-warp (< 32), warp (== 32), and multi-warp (> 32) group widths selected at runtime.
- Non-interfering boundary-safe synchronization using sub-warp masks and hardware named barriers (bar.sync).
- Runtime valid_items clamping with early termination of redundant merge passes.
- Full support for keys-only and key-value pairs across arbitrary Strict Weak Ordering comparators.
- Complete Catch2 unit test coverage in cub/test/catch2_test_group_merge_sort.cu.

*I'm not the most experienced, just saw the issue and thought it would be interesting, so any advice would be greatly appreciated

@roro2006
roro2006 requested a review from a team as a code owner August 27, 2026 01:16
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Aug 27, 2026
@copy-pr-bot

copy-pr-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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.

@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Review in CCCL Aug 27, 2026
@roro2006
roro2006 force-pushed the fea/group-merge-sort-runtime-width branch from 1c36eeb to fdf5478 Compare August 27, 2026 01:22
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

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
  • 🔍 Trigger review
📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added stable segmented merge sorting for sub-warp, warp, and multi-warp groups.
    • Supports key-only and key-value sorting, custom comparators, duplicate keys, partial tiles, and optional sentinel handling.
    • Added runtime configuration for group widths and sorting modes.
    • Supports ascending or descending ordering while preserving the relative order of equal keys.
  • Tests

    • Added comprehensive coverage for group sizes, sorting modes, partial inputs, duplicate keys, descending order, sentinel handling, and runtime dispatch.

Walkthrough

Changes

suggestion: The PR adds a runtime-width, stable segmented merge-sort primitive for keys and key-value pairs. It supports partial tiles, sentinels, custom comparators, shared storage, and runtime dispatch.

Group merge sort

Layer / File(s) Summary
Sort API and merge implementation
cub/cub/detail/group_merge_sort.cuh
Adds GroupMergeSort, storage and constructors, sorting overloads, partial-tile handling, merge rounds, and stable sorting.
Runtime dispatch interfaces
cub/cub/detail/group_merge_sort.cuh
Adds call_group_merge_runtime overloads for temporary storage and raw shared-memory pointers.
CUDA test harness and references
cub/test/catch2_test_group_merge_sort.cu
Adds delegates, CUDA kernels, host references, and parameterized group configurations.
Sorting behavior validation
cub/test/catch2_test_group_merge_sort.cu
Tests full and partial tiles, sentinel modes, descending order, duplicate keys, key-value sorting, runtime dispatch, and static temporary storage.

Suggested reviewers: srinivasyadav18


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

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

Actionable comments posted: 6

🧹 Nitpick comments (3)
cub/cub/detail/group_merge_sort.cuh (3)

107-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: rename the _Unroll template parameter.

An identifier that starts with an underscore followed by an uppercase letter is reserved for the implementation in every scope. The project style also requires PascalCase for template parameter names. Rename to Unroll and update the uses at lines 257, 316, 517, 530, 612.

The private helpers Sync, StoreKeys, ExchangeItems, SortPartialTile, and MergeRounds are internal to cub::detail, so snake_case applies to them; only the public Sort/StableSort surface keeps PascalCase.

As per coding guidelines: "Use PascalCase for template parameter names" and "Use snake_case for all other symbols, except that the CUB public API uses PascalCase."

Source: Coding guidelines


229-237: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: mark the accessors [[nodiscard]] and noexcept.

Both functions return a value and have no side effects.

As per coding guidelines: "Most functions with a non-void return type should use [[nodiscard]]" and "Functions that do not throw exceptions must be marked noexcept."

Proposed change
-  _CCCL_DEVICE _CCCL_FORCEINLINE unsigned int get_member_tid() const
+  [[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE unsigned int get_member_tid() const noexcept
   {
     return member_tid;
   }
 
-  _CCCL_DEVICE _CCCL_FORCEINLINE unsigned int get_group_id() const
+  [[nodiscard]] _CCCL_DEVICE _CCCL_FORCEINLINE unsigned int get_group_id() const noexcept
   {
     return group_id;
   }

Source: Coding guidelines


16-19: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: remove cub/util_ptx.cuh and cuda/std/__utility/swap.h; group_merge_sort.cuh uses neither header directly. MergePath is already declared by the included cub/block/block_merge_sort.cuh.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f06af7a5-c990-49a1-af2f-adcff877c701

📥 Commits

Reviewing files that changed from the base of the PR and between f047516 and 1c36eeb.

📒 Files selected for processing (2)
  • cub/cub/detail/group_merge_sort.cuh
  • cub/test/catch2_test_group_merge_sort.cu

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

Comment thread cub/cub/detail/group_merge_sort.cuh Outdated
Comment thread cub/cub/detail/group_merge_sort.cuh
Comment thread cub/test/catch2_test_group_merge_sort.cu Outdated
Comment thread cub/test/catch2_test_group_merge_sort.cu
Comment thread cub/test/catch2_test_group_merge_sort.cu Outdated
@roro2006
roro2006 force-pushed the fea/group-merge-sort-runtime-width branch from fdf5478 to 794ac94 Compare August 27, 2026 01:37

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
cub/cub/detail/group_merge_sort.cuh (1)

451-460: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

important: barrier ids still collide when a CTA hosts 16 groups.

1u + (group_id % 15u) maps group_id 0 and group_id 15 to barrier 1. This path runs only for threads_per_group > 32, and 1024 CTA threads with threads_per_group == 64 produce exactly 16 groups. Both groups then execute bar.sync 1, 64, so 128 threads arrive at one barrier that expects 64 arrivals. The barrier releases mixed groups, which breaks the shared-memory ordering in merge_rounds and can also hang the CTA.

Either derive the id from an assert-checked group count, or document and enforce a maximum of 15 multi-warp groups per CTA.

Proposed change
     else
     {
       // Named barrier 0 is reserved for __syncthreads(); use ids 1..15.
+      // Distinct groups must map to distinct ids, so a CTA may host at most 15 multi-warp groups.
+      _CCCL_ASSERT(group_id < 15u, "GroupMergeSort supports at most 15 multi-warp groups per CTA");
       const unsigned int barrier_id = 1u + (group_id % 15u);
🧹 Nitpick comments (2)
cub/test/catch2_test_group_merge_sort.cu (2)

130-223: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: Four pair delegates are never used, so the key-value paths they cover stay untested.

Only group_sort_pairs_partial_tile_t is launched. group_sort_pairs_full_tile_t, group_sort_pairs_partial_tile_no_sentinel_t, group_sort_pairs_descending_t, and group_sort_call_helper_pairs_t are dead code. The PR states that key-value runtime dispatch is covered; no test exercises the key-value call_group_merge_runtime overload, the full-tile pair Sort, the no-sentinel pair Sort, or descending pair ordering.

Add pair tests that reuse group_merge_sort_pairs_kernel with these delegates, or remove the unused delegates.

Also applies to: 647-695


736-764: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: The TempStorage test does not check the tail beyond valid_items.

The test truncates both vectors to valid_items before comparison, so it does not assert anything about positions >= valid_items. The no-sentinel Sort overload only guarantees the first valid_items positions, so the truncation is correct. State that guarantee in a comment so a later reader does not restore a full-range comparison.

Also consider parameterizing GROUP_THREADS and valid_items (including 0 and TILE_ITEMS) instead of the single hardcoded 180, to cover the boundary cases of the partial-tile path.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: b02482d5-fb29-4250-ace8-1c91403b0853

📥 Commits

Reviewing files that changed from the base of the PR and between 1c36eeb and 794ac94.

📒 Files selected for processing (2)
  • cub/cub/detail/group_merge_sort.cuh
  • cub/test/catch2_test_group_merge_sort.cu

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

Comment thread cub/cub/detail/group_merge_sort.cuh Outdated
Comment on lines +377 to +419
template <typename CompareOp>
_CCCL_DEVICE _CCCL_FORCEINLINE void Sort(KeyT (&keys)[ITEMS_PER_THREAD], CompareOp compare_op)
{
Sort(keys, compare_op, default_group_threads);
}

template <typename CompareOp>
_CCCL_DEVICE _CCCL_FORCEINLINE void Sort(KeyT (&keys)[ITEMS_PER_THREAD], CompareOp compare_op, int valid_items)
{
Sort(keys, compare_op, default_group_threads, valid_items);
}

template <typename CompareOp>
_CCCL_DEVICE _CCCL_FORCEINLINE void
Sort(KeyT (&keys)[ITEMS_PER_THREAD], CompareOp compare_op, int valid_items, KeyT oob_default)
{
Sort(keys, compare_op, default_group_threads, valid_items, oob_default);
}

template <typename CompareOp>
_CCCL_DEVICE _CCCL_FORCEINLINE void
Sort(KeyT (&keys)[ITEMS_PER_THREAD], ValueT (&items)[ITEMS_PER_THREAD], CompareOp compare_op)
{
Sort(keys, items, compare_op, default_group_threads);
}

template <typename CompareOp>
_CCCL_DEVICE _CCCL_FORCEINLINE void
Sort(KeyT (&keys)[ITEMS_PER_THREAD], ValueT (&items)[ITEMS_PER_THREAD], CompareOp compare_op, int valid_items)
{
Sort(keys, items, compare_op, default_group_threads, valid_items);
}

template <typename CompareOp>
_CCCL_DEVICE _CCCL_FORCEINLINE void
Sort(KeyT (&keys)[ITEMS_PER_THREAD],
ValueT (&items)[ITEMS_PER_THREAD],
CompareOp compare_op,
int valid_items,
KeyT oob_default)
{
Sort(keys, items, compare_op, default_group_threads, valid_items, oob_default);
}

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.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# List all Sort declarations in the header to confirm the colliding signatures.
fd -t f 'group_merge_sort.cuh' cub | xargs -r rg -n -A4 'void[[:space:]]*$|Sort\(' -g '*.cuh'

Repository: NVIDIA/cccl

Length of output: 7582


critical: Rename or remove the default-width Sort overloads because they redeclare existing members. The overload at line 384 matches line 259, and the key-value overload at line 405 matches line 322. When KeyT is int, the sentinel overloads also match lines 281 and 341. C++ cannot overload on parameter names, so the class template is ill-formed. Rename all conflicting entry points; changing only one overload is insufficient.

Comment thread cub/cub/detail/group_merge_sort.cuh Outdated
@roro2006
roro2006 force-pushed the fea/group-merge-sort-runtime-width branch from 794ac94 to 95b100c Compare August 27, 2026 01:55

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
cub/cub/detail/group_merge_sort.cuh (1)

370-412: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

critical: the convenience overloads still redeclare existing members, so the class template does not compile.

  • Line 377 Sort(KeyT (&keys)[ITEMS_PER_THREAD], CompareOp, int valid_items) has the same signature as line 248 Sort(KeyT (&keys)[ITEMS_PER_THREAD], CompareOp, int threads_per_group).
  • Line 398 has the same signature as line 313.
  • Line 384 has the same signature as line 272 when KeyT is int, and line 405 matches line 333 in the same case.

Parameter names do not participate in overloading. Rename these entry points (for example SortDefaultWidth) or remove them.

🧹 Nitpick comments (2)
cub/cub/detail/group_merge_sort.cuh (2)

640-644: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: align the template parameter order of the dispatch helpers.

The keys-only helpers use <KeyT, ITEMS_PER_THREAD, MAX_GROUP_THREADS, ValueT, CompareOp>. The key-value helpers use <KeyT, ValueT, ITEMS_PER_THREAD, MAX_GROUP_THREADS, CompareOp>. A caller that sets MAX_GROUP_THREADS explicitly must remember two different orders. Use one order for all overloads.

Also applies to: 765-769


152-159: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: apply the required function annotations.

The guidelines require _CCCL_DEVICE_API (or another _CCCL_*_API macro) instead of bare _CCCL_DEVICE _CCCL_FORCEINLINE, and require noexcept on functions that do not throw. The members here, including sync at line 428 and the Sort overloads, use _CCCL_DEVICE _CCCL_FORCEINLINE and omit noexcept; only the two getters at lines 225 and 230 are marked noexcept.

As per coding guidelines: "Functions must be marked with _CCCL_HOST_API, _CCCL_DEVICE_API, _CCCL_HOST_DEVICE_API, _CCCL_TILE_API, or _CCCL_API" and "Functions that do not throw exceptions must be marked noexcept".

Also applies to: 428-428

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 6bb18625-9429-47c2-ac84-7360a8974acd

📥 Commits

Reviewing files that changed from the base of the PR and between 794ac94 and 95b100c.

📒 Files selected for processing (2)
  • cub/cub/detail/group_merge_sort.cuh
  • cub/test/catch2_test_group_merge_sort.cu

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

Comment thread cub/cub/detail/group_merge_sort.cuh
@roro2006
roro2006 force-pushed the fea/group-merge-sort-runtime-width branch from 95b100c to 63ec31d Compare August 27, 2026 02:05

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 170ecd76-6a54-4d37-b4e4-e8d58043716a

📥 Commits

Reviewing files that changed from the base of the PR and between 95b100c and 63ec31d.

📒 Files selected for processing (1)
  • cub/cub/detail/group_merge_sort.cuh

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

Comment thread cub/cub/detail/group_merge_sort.cuh
@roro2006
roro2006 force-pushed the fea/group-merge-sort-runtime-width branch from 63ec31d to 7993b3d Compare August 27, 2026 02:14

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
cub/cub/detail/group_merge_sort.cuh (2)

271-282: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

suggestion: document the barrier requirement before temporary storage is reused.

The last shared-memory read of a Sort call happens inside serial_merge, and no group synchronization follows it. A thread can return from Sort while other group members still read keys_shared. A caller that reuses temp_storage (or the raw shared slice) directly after Sort returns then races.

Add the requirement to the class doc: call the same group synchronization used by the group width before reusing the storage.


444-455: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

suggestion: Do not use __syncthreads() as the multi-warp fallback in GroupMergeSort::sync.

merge_rounds<true> stops when size >= valid_items, so groups with different valid_items values can call sync a different number of times. The CTA-wide fallback can then deadlock when one group reaches a barrier that another group no longer reaches.

Use the named barrier on supported targets. Reject multi-warp groups on targets that cannot provide it.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 5e1d28da-a5c1-4120-b3c0-0d494a14323f

📥 Commits

Reviewing files that changed from the base of the PR and between 63ec31d and 7993b3d.

📒 Files selected for processing (1)
  • cub/cub/detail/group_merge_sort.cuh

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

Comment on lines +309 to +333
/**
* @brief Sorts the first valid_items keys across threads_per_group threads using an out-of-bounds default sentinel.
*
* @tparam CompareOp Functor type having member `bool operator()(KeyT lhs, KeyT rhs)` (Strict Weak Ordering).
* @param[in,out] keys Keys array held by this thread.
* @param[in] compare_op Comparison function object returning true if lhs < rhs.
* @param[in] threads_per_group Number of cooperating threads in this group (must be a power of two).
* @param[in] valid_items Total number of valid keys in this group segment.
* @param[in] oob_default Sentinel value ordered after any valid key in the segment.
*/
template <typename CompareOp>
_CCCL_DEVICE_API _CCCL_FORCEINLINE void
Sort(KeyT (&keys)[ITEMS_PER_THREAD],
CompareOp compare_op,
int threads_per_group,
int valid_items,
KeyT oob_default) noexcept
{
static_assert(KEYS_ONLY, "Keys-only Sort requires ValueT == NullType; use the key-value overload instead");
_CCCL_ASSERT(threads_per_group > 0, "threads_per_group must be greater than 0");
_CCCL_ASSERT(::cuda::is_power_of_two(threads_per_group), "threads_per_group must be a power of two");
_CCCL_ASSERT(valid_items >= 0, "valid_items must be non-negative");
ValueT items[ITEMS_PER_THREAD];
sort_partial_tile<true>(keys, items, compare_op, threads_per_group, valid_items, oob_default);
}

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

suggestion: document that tile positions at or after valid_items are unspecified.

sort_partial_tile applies the oob_default padding only inside if (ITEMS_PER_THREAD * member_tid < valid_items). A thread whose whole item range is out of bounds keeps its caller-supplied keys. merge_rounds<true> clamps every run to valid_items, so those keys never reach a valid output position, but they also are not replaced by oob_default. In addition, threads that over-consume a clamped run write duplicated keys into output slots at or after valid_items.

A caller that migrates from BlockMergeSort::Sort(keys, compare_op, valid_items, oob_default) can expect the tail of the tile to hold oob_default. State the actual guarantee in the doc block of this overload and of the key-value sentinel overload at lines 387-412.

NVIDIA#10944)

Address CodeRabbit review suggestions:
- Enforce strict <= 15 multi-warp groups per CTA with bar.sync (1 + group_id) and assertions.
- Eliminate unsafe __syncthreads() fallback in sync() on unsupported targets to avoid CTA deadlock.
- Validate threads_per_group > 0, <= MAX_GROUP_THREADS, and power-of-two in constructors and dispatch helpers.
- Document that tile output positions at or after valid_items are unspecified.
- Document storage lifetime and synchronization requirements prior to TempStorage reuse.
- Remove redundant convenience Sort overloads to eliminate signature redeclaration conflicts.
- Align template parameter ordering across keys-only and key-value dispatch helpers.
- Apply _CCCL_DEVICE_API and noexcept annotations to all methods according to guidelines.
- Add test coverage for all key-value pair delegates and boundary conditions.
- Apply clang-format and verify all pre-commit formatting checks pass.
@roro2006
roro2006 force-pushed the fea/group-merge-sort-runtime-width branch from 7993b3d to f54953b Compare August 27, 2026 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

1 participant