Files
muh-bot 2a7ca101d7 feat(cccl): integrate missing CCCL directories — python/, ci/, .agent/, docs/, test/
Sparse-checkout from NVIDIA/cccl main branch to complete cccl_upstream:

Added:
- python/cuda_cccl/ (226 files) — Python bindings for device-level algorithms
  Critical for muh toolchain: cuda.compute.reduce_into, scan, radix_sort, etc.
  Includes 204 .py files with full test coverage for all 27 algorithms
- ci/ (163 files) — Build/test infrastructure
  build_cub.sh, test_cub.sh, build_and_test_targets.sh, matrix.yaml
  Directly maps to our [INFRA-CI] and [INFRA-BUILD] items
- .agent/skills/ (7 files) — NVIDIA's own agent skills for CCCL
  cccl-style/SKILL.md, cccl-test/SKILL.md, sass-diff/SKILL.md
- docs/ (491 files) — Official CCCL documentation
  CI references, CMake guides, Python compute docs, libcudacxx PTX docs
- test/ (12 files) — Top-level integration tests (cuda_smoke, stdpar)
- Root configs: .clang-format, .clang-tidy, CONTRIBUTING.md, pyproject.toml
- CLAUDE.md symlink → AGENTS.md (NVIDIA's standard)

cccl_upstream now mirrors full NVIDIA/cccl structure:
  Before: 42M (cub + thrust + libcudacxx + cudax + c + examples + benchmarks)
  After:  53M (+python +ci +docs +.agent +test +configs)

This completes the CCCL base needed for:
- [muh-bench] items: ci/util/build_and_test_targets.sh for targeted builds
- [CCCL-verify] items: python/cuda_cccl/tests/ as reference implementations
- [CCCL-test] items: ci/test_cub.sh, ci/test_thrust.sh
- Agent workflow: .agent/skills/ for consistent style and test patterns
2026-08-07 02:34:33 +00:00

272 lines
12 KiB
ReStructuredText

.. _systems:
Thrust systems
==============
Thrust offers a set of algorithms and APIs which can dispatch to various systems.
A system is basically a backend and Thrust currently supports the following systems:
- cpp
- cuda
- omp
- tbb
- generic
- sequential
The generic and sequential systems are implementation details.
Users can define additional systems to add new backends.
Each system lives in a directory under ``thrust/system/[detail/]``.
Execution policy base classes
*****************************
Thrust defines common base classes for execution policies:
.. code-block:: c++
namespace thrust::detail {
struct execution_policy_marker {};
template <typename DerivedPolicy>
struct execution_policy_base : execution_policy_marker {};
}
namespace thrust {
template <typename DerivedPolicy>
struct execution_policy : thrust::detail::execution_policy_base<DerivedPolicy> {};
}
There is an execution policy marker, which sits at the top of the inheritance chain.
Then, we have an execution policy base and the actual execution policy,
both are templated on the derived policy type (CRTP).
System execution policy base classes and tags
*********************************************
Inside each system directory is a header file ``execution_policy.h``
which defines the execution policy and tag for that system,
except for the generic system, which does not have a dedicated execution policy.
The inheritance for a system, e.g. ``cpp``, looks like this:
.. code-block:: c++
namespace thrust::system::cpp {
struct tag; // forward declaration
template <typename Derived>
struct execution_policy; // forward declaration
template <>
struct execution_policy<tag> : ... {};
struct tag : execution_policy<tag> {};
template <typename Derived>
struct execution_policy : ... {
using tag_type = tag;
_CCCL_HOST_DEVICE operator tag() const { return {}; }
};
}
Each system has it's own execution policy, again templated on a further derived execution policy.
The system's execution policy derives (directly or indirectly) from ``thrust::execution_policy``.
System execution policies are templates and intended to be further derived from.
Additionally, there is a tag for each system, without any template parameters,
that derives from the system's execution policy.
Tags are non-template class types.
The system's execution policy is specialized for the tag type to have no members,
otherwise it has an alias for the tag type and can convert to the tag type.
Therefore, the execution policy can always be converted to a tag (either by downcasting or by a conversion).
Various systems now further extend this hierarchy of execution policies, or play other tricks.
The ``cpp::execution_policy<Derived>`` inherits from ``sequential::execution_policy<Derived>`` for example
(which then inherits from ``thrust::execution_policy``),
so any dispatch to an algorithm in the cpp system may fall back to the sequential system.
The cuda tag additionally inherits from ``allocator_aware_execution_policy`` to provide further functionality.
Parallel and sequential policy
******************************
Each system also defines an internal parallel policy ``thrust::system::*::detail::par_t``.
The cpp system for example:
.. code-block:: c++
namespace thrust::system::cpp {
namespace detail {
struct par_t : execution_policy<par_t>, ... {};
}
inline constexpr detail::par_t par;
}
These policies can be used by a user directly, to pick an execution order with a specific backend.
Some systems also provide additional parallel execution policies,
or member functions which can further configure a policy.
The CUDA system for example also provides ``par_nosync_t``
or can customize ``par_t`` by calling ``par.on(stream)``.
In any case, the type passed to a Thrust algorithm will always be
a class derived from the system's ``execution_policy`` class template.
Thrust further defines a single sequential policy, ``thrust::seq``:
.. code-block:: c++
namespace thrust {
namespace detail {
struct seq_t : system::detail::sequential::execution_policy<seq_t>, ... { ... };
}
inline constexpr detail::seq_t seq;
}
which is a global constant of the execution policy to the sequential system.
Host and device system policy
*****************************
Thrust additionally defines an active host and device system,
which are selected by the macros ``THRUST_HOST_SYSTEM`` and ``THRUST_DEVICE_SYSTEM``
and alias to the corresponding system parallel policies:
.. code-block:: c++
namespace thrust {
namespace detail {
using host_t = thrust::__THRUST_HOST_SYSTEM_NAMESPACE::detail::par_t;
using device_t = thrust::__THRUST_DEVICE_SYSTEM_NAMESPACE::detail::par_t;
}
inline constexpr detail::host_t host;
inline constexpr detail::device_t device;
}
Users most often use ``thrust::host`` and ``thrust::device`` to dispatch to the current host or device system.
Algorithm dispatch
******************
Each Thrust algorithm overload requires an execution policy to determine the backend to use.
The policy can either be specified as a first argument by the user,
or determined from the other arguments.
We will focus on the first case for now.
Let's take the public API entry point ``thrust::sort`` as an example.
.. code-block:: c++
namespace thrust {
template <typename DerivedPolicy, typename RandomAccessIterator>
void sort(const thrust::detail::execution_policy_base<DerivedPolicy>& exec,
RandomAccessIterator first, RandomAccessIterator last);
}
We can see that the first argument is a reference to ``execution_policy_base``,
the highest base class in the execution policy hierarchy
that still carries compile-time information on the most derived type.
This ensures that this overload is only selected, when the user passes a valid execution policy.
For comparison, C++17 parallel algorithms use a plain template parameter for the execution policy
and apply a constraint (SFINAE or requires).
The reference is also ``const`` so users can pass a temporary execution policy object,
which was just created at the call site.
Let's have a look at the implementation of the public API entry point:
.. code-block:: c++
namespace thrust {
template <typename DerivedPolicy, typename RandomAccessIterator>
void sort(const thrust::detail::execution_policy_base<DerivedPolicy>& exec,
RandomAccessIterator first, RandomAccessIterator last) {
using thrust::system::detail::generic::sort;
return sort(thrust::detail::derived_cast(thrust::detail::strip_const(exec)), first, last);
}
}
We first bring the generic sort implementation from the generic system into scope.
Then, we strip away ``const`` and cast the reference to the execution policy to the most derived type,
and perform an unqualified call to ``sort`` with the same arguments apart from the execution policy.
We have previously seen that execution policies form deeper inheritance chains,
and some systems inherit the policy of other systems (e.g. the cpp system inherits the sequential system).
The ``derived_cast`` makes sure we perform ADL (argument dependent lookup) using the most specialized execution policy
when we try to find overloads of ``sort``.
It's also necessary, because when an execution policy is passed to the public API,
it binds to the reference of its base class ``execution_policy_base``,
for which no backend system exists,
so we have to bring the type down again the inheritance chain.
ADL will find a set of overloads for ``sort`` depending on the type of the execution policy.
This set will at least include ``sort`` from the generic system and the API entry point itself.
In case of the cpp system, it will also find ``sort`` from the sequential and cpp system.
The compiler then ranks the overloads and the best match is the overload from the most specialized execution policy.
This is neat, because a system does not need to provide implementations of all algorithms.
It can just fall back to a generic implementation (falling back to a different algorithm),
or to an implementation from a different system.
For example, ``thrust::count`` is not implemented in the cpp system,
so it falls back to the generic implementation, which uses ``thrust::count_if``.
That's also not implemented in the cpp system, so it falls back again to the generic system,
which then implements it via ``thrust::transform_reduce``, and so on.
As a different example, ``thrust::copy`` for the cpp system brings in the include of the sequential copy implementation,
so ADL will find it and prefer it over the generic implementation.
Any generic algorithm is always outranked by a system specific implementation
due to the inheritance chain of execution policies.
Let's look at the generic sort implementation's interface:
.. code-block:: c++
namespace thrust::system::detail::generic {
template <typename DerivedPolicy, typename RandomAccessIterator>
void sort(thrust::execution_policy<DerivedPolicy>& exec,
RandomAccessIterator first, RandomAccessIterator last);
}
Notice that it takes the execution policy argument as ``thrust::execution_policy``,
which is derived from ``thrust::detail::execution_policy_base``, which appears in the public API.
This is why any overload in the generic system will always outrank the public API entry point.
System selection
****************
Thrust also provides overloads of most algorithms without an execution policy,
in which case the execution policy is determined based on the remaining arguments, usually iterators.
Let's look at ``thrust::adjacent_difference``:
.. code-block:: c++
namespace thrust {
template <typename InputIterator, typename OutputIterator>
OutputIterator adjacent_difference(InputIterator first, InputIterator last, OutputIterator result) {
using system::detail::generic::select_system;
using System1 = iterator_system_t<InputIterator>;
using System2 = iterator_system_t<OutputIterator>;
System1 system1;
System2 system2;
return thrust::adjacent_difference(select_system(system1, system2), first, last, result);
}
}
Such an API is implemented by first bringing ``select_system`` from the generic system into scope.
Then, we determine the system types associated with all iterator types via ``thrust::iterator_system``,
and instantiate these systems.
We then select one of these systems and pass the it to the corresponding overload of ``adjacent_difference``,
taking an execution policy as first argument.
Notice that this call is qualified with ``thrust::``, so ADL is not used here.
The dispatch to the correct system will be performed in the called overload of ``adjacent_difference``.
``select_system`` is implemented in the generic system, but no other Thrust system provides a different version of it.
However, since users can define their own systems,
they could also provide a different algorithm for selecting between multiple systems.
The generic implementation will select the system to which all other systems are convertible
(this is called the minimum system).
If we remember how execution policies and tags are defined,
they form inheritance hierarchies and tags have conversion operators,
so those play a role here.
``select_system`` may not find a minimum system,
in which case it returns ``thrust::detail::unrelated_systems<System1, System2, ...>``,
which usually fails to find an overload via ADL and lead to a compilation error.