Skip to content

Replace internal Thrust sequential system with libcuc++#9666

Open
charan-003 wants to merge 4 commits into
NVIDIA:mainfrom
charan-003:replace-internal-thrust-system
Open

Replace internal Thrust sequential system with libcuc++#9666
charan-003 wants to merge 4 commits into
NVIDIA:mainfrom
charan-003:replace-internal-thrust-system

Conversation

@charan-003

Copy link
Copy Markdown
Contributor

Replace Thrust sequential backend algorithms with libcu++ equivalents using wrapped_function for proxy reference handling.
Add test to validate implicit type conversions and proxy reference unwrapping.

Closes #5157

@charan-003 charan-003 requested a review from a team as a code owner July 1, 2026 13:42
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Jul 1, 2026
@copy-pr-bot

copy-pr-bot Bot commented Jul 1, 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 Jul 1, 2026
@charan-003 charan-003 force-pushed the replace-internal-thrust-system branch from 40cf3c3 to f007483 Compare July 1, 2026 13:44
@charan-003

Copy link
Copy Markdown
Contributor Author

@bernhardmgruber
some files like count.h , equal.h have no implementation, they follow the generic backend. do you want me to add explicit sequential implementations there too and wrap predicates with wrapped_function where needed, or leave them?

I'm assuming we should, since libcu++ already has those algorithms

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note: CodeRabbit is enabled on this repository as a convenience for maintainers
and contributors. Use your best judgment when considering its review comments and
suggestions — a suggested change may be inadequate, unnecessary, or safe to ignore.
Contributors are not expected to address every comment. Human reviews are what
ultimately matter for merging.

Replaced Thrust’s sequential backend algorithm implementations with delegations to libcu++/cuda::std equivalents, reducing the amount of handwritten fallback logic while preserving Thrust’s public API.

Key updates:

  • Switched sequential implementations for algorithms including find_if, for_each, for_each_n, reduce, scan, merge, partition, remove_if, unique, binary_search, copy_backward, set_*, min/max/minmax_element, and adjacent_difference to cuda::std calls.
  • Wrapped predicates and binary operations with thrust::detail::wrapped_function to correctly handle proxy references and implicit type conversions.
  • Added new Catch2 tests covering wrapped_function behavior across unary, binary, and predicate-based algorithms, including float-to-double conversion and proxy-unwrapping scenarios.
  • Removed now-unnecessary manual iterator loops and related helper logic from the sequential backend.

Walkthrough

Sequential Thrust algorithm implementations (find_if, binary_search, extrema, for_each, copy_backward, adjacent_difference, reduce, scan, remove, unique, partition, merge, set_operations) are rewritten to delegate to corresponding ::cuda::std algorithms, wrapping predicates/operators via wrapped_function. A new Catch2 test file validates this behavior.

Changes

Sequential algorithms delegated to libcu++

Layer / File(s) Summary
Search/extrema delegation
thrust/thrust/system/detail/sequential/find.h, binary_search.h, extrema.h
find_if, lower_bound/upper_bound, min_element/max_element/minmax_element now call ::cuda::std equivalents with wrapped comparators instead of manual loops.
for_each/copy_backward/adjacent_difference delegation
thrust/thrust/system/detail/sequential/for_each.h, copy_backward.h, adjacent_difference.h
for_each, for_each_n, copy_backward, and adjacent_difference call ::cuda::std implementations, replacing manual iteration.
reduce/scan delegation
thrust/thrust/system/detail/sequential/reduce.h, scan.h
reduce uses ::cuda::std::accumulate; inclusive_scan/exclusive_scan overloads use ::cuda::std::{inclusive,exclusive}_scan.
remove/unique/partition delegation
thrust/thrust/system/detail/sequential/remove.h, unique.h, partition.h
remove_if/remove_copy_if, unique/unique_copy/unique_count, and partition/stable_partition_copy delegate to ::cuda::std with wrapped predicates.
merge/set_operations delegation
thrust/thrust/system/detail/sequential/merge.h, set_operations.h
merge and set_difference/set_intersection/set_symmetric_difference/set_union delegate to ::cuda::std, removing custom merge loops and thrust::copy tail handling.
wrapped_function regression tests
thrust/testing/catch2_test_wrapped_function.cu
New Catch2 tests exercise all updated algorithms with functors requiring const double& to verify proxy-reference/conversion handling.

Assessment against linked issues

Objective Addressed Explanation
Replace internal Thrust sequential algorithm system with libcu++ implementations (#5157)
Maintain correctness via test coverage of libcu++-backed algorithms (#5157)

Suggested reviewers: griwes, elstehle, davebayer


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: 7

🧹 Nitpick comments (2)
thrust/thrust/system/detail/sequential/merge.h (1)

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

suggestion: thrust/detail/copy.h looks unused now that the merge loop delegates to ::cuda::std::merge instead of calling thrust::copy for trailing elements. merge_by_key never used it either (hand-written loop). Confirm no other symbol in this file needs it, then drop the include.

diff
-#include <thrust/detail/copy.h>
 `#include` <thrust/detail/function.h>

As per coding guidelines, "Remove unneeded headers."

Also applies to: 44-46

Source: Coding guidelines

thrust/thrust/system/detail/sequential/for_each.h (1)

33-36: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: make both wrapped functor locals const.

Neither wrapped_f is reassigned, and these headers require unmodified locals to be declared const. As per coding guidelines, "All variables that are not modified must be declared const."

Also applies to: 43-45

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d33296bf-e63e-4157-95ae-74a03aea673b

📥 Commits

Reviewing files that changed from the base of the PR and between 5205c8a and 40cf3c3.

📒 Files selected for processing (14)
  • thrust/testing/catch2_test_wrapped_function.cu
  • thrust/thrust/system/detail/sequential/adjacent_difference.h
  • thrust/thrust/system/detail/sequential/binary_search.h
  • thrust/thrust/system/detail/sequential/copy_backward.h
  • thrust/thrust/system/detail/sequential/extrema.h
  • thrust/thrust/system/detail/sequential/find.h
  • thrust/thrust/system/detail/sequential/for_each.h
  • thrust/thrust/system/detail/sequential/merge.h
  • thrust/thrust/system/detail/sequential/partition.h
  • thrust/thrust/system/detail/sequential/reduce.h
  • thrust/thrust/system/detail/sequential/remove.h
  • thrust/thrust/system/detail/sequential/scan.h
  • thrust/thrust/system/detail/sequential/set_operations.h
  • thrust/thrust/system/detail/sequential/unique.h

Comment on lines +1 to +7
// Test that sequential algorithms correctly handle predicates
// that require implicit type conversions (e.g., float → const double&).
// This validates that wrapped_function properly handles the conversion chain
// when user-provided callables expect a different type than the iterator's value_type.
// When the sequential backend is invoked from device code (CDP) with device_vector
// iterators, wrapped_function also unwraps proxy references (device_reference<T> → T&)
// before the implicit conversion occurs.

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 | 🟠 Major | 🏗️ Heavy lift

important: These tests do not exercise proxy references. Every case uses thrust::host_vector<float>, so dereference stays float& and the suite only covers implicit float -> const double& conversion. A regression in wrapped_function/raw_reference_cast for device_reference or another proxy iterator would still pass here, despite the file comments and test names claiming proxy-reference coverage. Add at least one real proxy-reference case, or narrow the comments and names to conversion-only coverage. As per path instructions, thrust/**/* review should focus on iterator/system interactions and regressions. Based on PR objectives, this suite is supposed to validate proxy-reference unwrapping.

Also applies to: 93-379

Source: Path instructions

Comment on lines +1 to +25
// Test that sequential algorithms correctly handle predicates
// that require implicit type conversions (e.g., float → const double&).
// This validates that wrapped_function properly handles the conversion chain
// when user-provided callables expect a different type than the iterator's value_type.
// When the sequential backend is invoked from device code (CDP) with device_vector
// iterators, wrapped_function also unwraps proxy references (device_reference<T> → T&)
// before the implicit conversion occurs.

#include <thrust/adjacent_difference.h>
#include <thrust/binary_search.h>
#include <thrust/count.h>
#include <thrust/extrema.h>
#include <thrust/find.h>
#include <thrust/for_each.h>
#include <thrust/host_vector.h>
#include <thrust/logical.h>
#include <thrust/merge.h>
#include <thrust/partition.h>
#include <thrust/reduce.h>
#include <thrust/remove.h>
#include <thrust/scan.h>
#include <thrust/set_operations.h>
#include <thrust/unique.h>

#include "catch2_test_helper.h"

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

important: Add the standard SPDX/license header to this new .cu file. CCCL requires the correct license text on source files, and this file currently starts with test comments instead. As per coding guidelines, "All headers, and also source files, must have the correct license text."

Source: Coding guidelines

Comment on lines +93 to +379
TEST_CASE("SequentialInclusiveScanProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> input{1.0f, 2.0f, 3.0f, 4.0f};
thrust::host_vector<float> output(4);

thrust::inclusive_scan(thrust::seq, input.begin(), input.end(), output.begin(), double_plus{});
CHECK(output[0] == 1.0f);
CHECK(output[1] == 3.0f);
CHECK(output[2] == 6.0f);
CHECK(output[3] == 10.0f);
}

TEST_CASE("SequentialExclusiveScanProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> input{1.0f, 2.0f, 3.0f, 4.0f};
thrust::host_vector<float> output(4);

thrust::exclusive_scan(thrust::seq, input.begin(), input.end(), output.begin(), 0.0f, double_plus{});
CHECK(output[0] == 0.0f);
CHECK(output[1] == 1.0f);
CHECK(output[2] == 3.0f);
CHECK(output[3] == 6.0f);
}

TEST_CASE("SequentialStablePartitionCopyProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> input{1.0f, 2.0f, 3.0f, 4.0f, 5.0f};
thrust::host_vector<float> out_true(5);
thrust::host_vector<float> out_false(5);

auto result = thrust::stable_partition_copy(
thrust::seq, input.begin(), input.end(), out_true.begin(), out_false.begin(), double_greater_than_two{});
auto n_true = result.first - out_true.begin();
auto n_false = result.second - out_false.begin();
CHECK(n_true == 3);
CHECK(n_false == 2);
CHECK(out_true[0] == 3.0f);
CHECK(out_true[1] == 4.0f);
CHECK(out_true[2] == 5.0f);
CHECK(out_false[0] == 1.0f);
CHECK(out_false[1] == 2.0f);
}

TEST_CASE("SequentialPartitionProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> vec{1.0f, 3.0f, 2.0f, 5.0f, 4.0f};

auto mid = thrust::partition(thrust::seq, vec.begin(), vec.end(), double_greater_than_two{});
CHECK(mid - vec.begin() == 3);
// all elements in [begin, mid) satisfy pred; none in [mid, end) do
for (auto it = vec.begin(); it != mid; ++it)
{
CHECK(*it > 2.0f);
}
for (auto it = mid; it != vec.end(); ++it)
{
CHECK(*it <= 2.0f);
}
}

TEST_CASE("SequentialInclusiveScanWithInitProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> input{1.0f, 2.0f, 3.0f, 4.0f};
thrust::host_vector<float> output(4);

thrust::inclusive_scan(thrust::seq, input.begin(), input.end(), output.begin(), 10.0f, double_plus{});
CHECK(output[0] == 11.0f); // 10 + 1
CHECK(output[1] == 13.0f); // 11 + 2
CHECK(output[2] == 16.0f); // 13 + 3
CHECK(output[3] == 20.0f); // 16 + 4
}

TEST_CASE("SequentialCountIfProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> vec{1.0f, 2.0f, 3.0f, 4.0f, 5.0f};

auto n = thrust::count_if(thrust::seq, vec.begin(), vec.end(), double_greater_than_two{});
CHECK(n == 3);
}

TEST_CASE("SequentialAllOfProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> vec{3.0f, 4.0f, 5.0f};

CHECK(thrust::all_of(thrust::seq, vec.begin(), vec.end(), double_greater_than_two{}));
}

TEST_CASE("SequentialAnyOfProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> vec{1.0f, 2.0f, 3.0f};

CHECK(thrust::any_of(thrust::seq, vec.begin(), vec.end(), double_greater_than_two{}));
}

TEST_CASE("SequentialNoneOfProxyReference", "[sequential][proxy_reference]")
{
thrust::host_vector<float> vec{1.0f, 2.0f};

CHECK(thrust::none_of(thrust::seq, vec.begin(), vec.end(), double_greater_than_two{}));
}

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

suggestion: Mark immutable locals const throughout this file. result, new_end, mid, n, n_true, n_false, and similar locals are never reassigned, so the new tests miss the repo's immutable-local rule. As per coding guidelines, "All variables that are not modified must be declared const, including cast results, function return values, and loop-invariant computations."

Source: Coding guidelines

Comment on lines +38 to +40
// wrap binary_op to handle proxy references
thrust::detail::wrapped_function<BinaryFunction, InputType> wrapped_op{binary_op};
return ::cuda::std::adjacent_difference(first, last, result, wrapped_op);

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the changed file and the wrapped_function definition.
git ls-files thrust/thrust/system/detail/sequential/adjacent_difference.h thrust/thrust/detail/function.h
echo "---- adjacent_difference.h ----"
nl -ba thrust/thrust/system/detail/sequential/adjacent_difference.h | sed -n '1,120p'
echo "---- function.h (wrapped_function) ----"
rg -n "wrapped_function|operator\\(" thrust/thrust/detail/function.h
nl -ba thrust/thrust/detail/function.h | sed -n '1,220p'

Repository: NVIDIA/cccl

Length of output: 314


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "---- adjacent_difference.h ----"
awk 'NR>=1 && NR<=120 { printf "%4d  %s\n", NR, $0 }' thrust/thrust/system/detail/sequential/adjacent_difference.h

echo "---- function.h (wrapped_function) ----"
rg -n "wrapped_function|operator\\(" thrust/thrust/detail/function.h
awk 'NR>=1 && NR<=240 { printf "%4d  %s\n", NR, $0 }' thrust/thrust/detail/function.h

Repository: NVIDIA/cccl

Length of output: 3318


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect libcudacxx adjacent_difference semantics to compare result typing.
rg -n "adjacent_difference" libcudacxx/include/cuda/std -g '*.h' -g '*.cuh' -g '*.hpp'

Repository: NVIDIA/cccl

Length of output: 1917


🏁 Script executed:

#!/bin/bash
set -euo pipefail

awk 'NR>=1 && NR<=120 { printf "%4d  %s\n", NR, $0 }' libcudacxx/include/cuda/std/__numeric/adjacent_difference.h

Repository: NVIDIA/cccl

Length of output: 3111


important: wrapped_function<BinaryFunction, InputType> forces every binary_op result through InputType before it reaches ::cuda::std::adjacent_difference. That changes semantics for valid heterogeneous cases like InputType=int with OutputIterator=double* and a double-returning op, where the fractional part is lost. Keep the wrapper/result type aligned with the callable/output contract instead of InputType.

Source: Path instructions

Comment on lines 39 to +41
// wrap comp
thrust::detail::wrapped_function<StrictWeakOrdering, bool> wrapped_comp{comp};

using difference_type = thrust::detail::it_difference_t<ForwardIterator>;

difference_type len = ::cuda::std::distance(first, last);

while (len > 0)
{
difference_type half = len >> 1;
ForwardIterator middle = first;

::cuda::std::advance(middle, half);

if (wrapped_comp(*middle, val))
{
first = middle;
++first;
len = len - half - 1;
}
else
{
len = half;
}
}

return first;
return ::cuda::std::lower_bound(first, last, val, wrapped_comp);

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

suggestion: Make the wrapped comparators const in both helpers. Neither wrapped_comp is reassigned, so the current form violates the repo rule for immutable locals. As per coding guidelines, "All variables that are not modified must be declared const, including cast results, function return values, and loop-invariant computations."

Also applies to: 53-55

Source: Coding guidelines

Comment on lines 35 to +37
// wrap comp
thrust::detail::wrapped_function<BinaryPredicate, bool> wrapped_comp{comp};

ForwardIterator imin = first;

for (; first != last; ++first)
{
if (wrapped_comp(*first, *imin))
{
imin = first;
}
}

return imin;
return ::cuda::std::min_element(first, last, wrapped_comp);

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

suggestion: Make the wrapped comparators const in these helpers. Each wrapped_comp is immutable after construction, so this misses the repo's const-for-immutable-locals rule. As per coding guidelines, "All variables that are not modified must be declared const, including cast results, function return values, and loop-invariant computations."

Also applies to: 45-47, 55-57

Source: Coding guidelines

Comment on lines 32 to +34
// wrap pred
thrust::detail::wrapped_function<Predicate, bool> wrapped_pred{pred};

while (first != last)
{
if (wrapped_pred(*first))
{
return first;
}

++first;
}

// return first so zip_iterator works correctly
return first;
return ::cuda::std::find_if(first, last, wrapped_pred);

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.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

suggestion: Make wrapped_pred const. It is never mutated in this helper, and the repo style requires immutable locals to be declared const. As per coding guidelines, "All variables that are not modified must be declared const, including cast results, function return values, and loop-invariant computations."

Source: Coding guidelines

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.

The internal Thrust sequential system should be replaced by libcu++

1 participant