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
297 lines
15 KiB
ReStructuredText
297 lines
15 KiB
ReStructuredText
.. _libcudacxx-extended-api-execution-model:
|
||
|
||
Execution model
|
||
===============
|
||
|
||
CUDA C++ aims to provide `parallel forward progress [intro.progress.9] <https://eel.is/c++draft/intro.progress#9>`__
|
||
for all device threads of execution, facilitating the parallelization of pre-existing C++ applications with CUDA C++.
|
||
|
||
.. dropdown:: `[intro.progress] <https://eel.is/c++draft/intro.progress>`__
|
||
|
||
- `[intro.progress.7] <https://eel.is/c++draft/intro.progress#7>`__: For a thread of execution
|
||
providing `concurrent forward progress guarantees <https://eel.is/c++draft/intro.progress#def:concurrent_forward_progress_guarantees>`__,
|
||
the implementation ensures that the thread will eventually make progress for as long as it has not terminated.
|
||
|
||
[Note 5: This applies regardless of whether or not other threads of execution (if any) have been or are making progress.
|
||
To eventually fulfill this requirement means that this will happen in an unspecified but finite amount of time. — end note]
|
||
|
||
- `[intro.progress.9] <https://eel.is/c++draft/intro.progress>`__: For a thread of execution providing
|
||
`parallel forward progress guarantees <https://eel.is/c++draft/intro.progress#9>`__, the implementation is not required to ensure that
|
||
the thread will eventually make progress if it has not yet executed any execution step; once this thread has executed a step,
|
||
it provides concurrent forward progress guarantees.
|
||
|
||
[Note 6: This does not specify a requirement for when to start this thread of execution, which will typically be specified by the entity
|
||
that creates this thread of execution. For example, a thread of execution that provides concurrent forward progress guarantees and executes
|
||
tasks from a set of tasks in an arbitrary order, one after the other, satisfies the requirements of parallel forward progress for these
|
||
tasks. — end note]
|
||
|
||
|
||
The CUDA C++ Programming Language is an extension of the C++ Programming Language.
|
||
This section documents the modifications and extensions to the `[intro.progress] <https://eel.is/c++draft/intro.progress>`__ section of the current `ISO International Standard ISO/IEC 14882 – Programming Language C++ <https://eel.is/c++draft/>`__ draft.
|
||
Modified sections are called out explicitly and their diff is shown in **bold**.
|
||
All other sections are additions.
|
||
|
||
.. _libcudacxx-extended-api-execution-model-host-threads:
|
||
|
||
Host threads
|
||
------------
|
||
|
||
The forward progress provided by threads of execution created by the host implementation to
|
||
execute `main <https://en.cppreference.com/w/cpp/language/main_function>`__, `std::thread <https://en.cppreference.com/w/cpp/thread/thread>`__,
|
||
and `std::jthread <https://en.cppreference.com/w/cpp/thread/jthread>`__ is implementation-defined behavior of the host
|
||
implementation `[intro.progress] <https://eel.is/c++draft/intro.progress>`__.
|
||
General-purpose host implementations should provide concurrent forward progress.
|
||
|
||
If the host implementation provides `concurrent forward progress [intro.progress.7] <https://eel.is/c++draft/intro.progress#7>`__,
|
||
then CUDA C++ provides `parallel forward progress [intro.progress.9] <https://eel.is/c++draft/intro.progress#9>`__ for device threads.
|
||
|
||
|
||
.. _libcudacxx-extended-api-execution-model-device-threads:
|
||
|
||
Device threads
|
||
--------------
|
||
|
||
Once a device thread makes progress:
|
||
|
||
- If it is part of a `Cooperative Grid <https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__EXECUTION.html#group__CUDART__EXECUTION_1g504b94170f83285c71031be6d5d15f73>`__,
|
||
all device threads in its grid shall eventually make progress.
|
||
- Otherwise, all device threads in its `thread-block cluster <https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#thread-block-clusters>`__
|
||
shall eventually make progress.
|
||
|
||
[Note: Threads in other thread-block clusters are not guaranteed to eventually make progress. - end note.]
|
||
|
||
[Note: This implies that all device threads within its thread block shall eventually make progress. - end note.]
|
||
|
||
|
||
Modify `[intro.progress.1] <https://eel.is/c++draft/intro.progress>`__ as follows (modifications in **bold**):
|
||
|
||
The implementation may assume that any **host** thread will eventually do one of the following:
|
||
|
||
1. terminate,
|
||
2. invoke the function `std::this_thread::yield <https://en.cppreference.com/w/cpp/thread/yield>`__ (`[thread.thread.this] <http://eel.is/c++draft/thread.thread.this>`__),
|
||
3. make a call to a library I/O function,
|
||
4. perform an access through a volatile glvalue,
|
||
5. perform a synchronization operation or an atomic operation, or
|
||
6. continue execution of a trivial infinite loop (`[stmt.iter.general] <http://eel.is/c++draft/stmt.iter.general>`__).
|
||
|
||
**The implementation may assume that any device thread will eventually do one of the following:**
|
||
|
||
1. **terminate**,
|
||
2. **make a call to a library I/O function**,
|
||
3. **perform an access through a volatile glvalue except if the designated object has automatic storage duration, or**
|
||
4. **perform a synchronization operation or an atomic read operation except if the designated object has automatic storage duration.**
|
||
|
||
[Note: Some current limitations of device threads relative to host threads
|
||
are implementation defects known to us, that we may fix over time.
|
||
Examples include the undefined behavior that arises from device threads
|
||
that eventually only perform volatile or atomic operations
|
||
on automatic storage duration objects.
|
||
However, other limitations of device threads relative to host threads
|
||
are intentional choices. They enable performance optimizations
|
||
that would not be possible if device threads followed the C++ Standard strictly.
|
||
For example, providing forward progress to programs
|
||
that eventually only perform atomic writes or fences
|
||
would degrade overall performance for little practical benefit. - end note.]
|
||
|
||
.. dropdown:: Examples of forward progress guarantee differences between host and device threads due to modifications to `[intro.progress.1] <https://eel.is/c++draft/intro.progress#1>`__.
|
||
|
||
The following examples refer to the itemized sub-clauses of the implementation assumptions for host and device threads above
|
||
using "host.threads.<id>" and "device.threads.<id>", respectively.
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Device.0
|
||
// Outcome: grid eventually terminates per device.threads.4 because the atomic object does not have automatic storage duration.
|
||
__global__ void ex0(cuda::atomic_ref<int, cuda::thread_scope_device> atom) {
|
||
if (threadIdx.x == 0) {
|
||
while(atom.load(cuda::memory_order_relaxed) == 0);
|
||
} else if (threadIdx.x == 1) {
|
||
atom.store(1, cuda::memory_order_relaxed);
|
||
}
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Device.1
|
||
// Allowed outcome: No thread makes progress because device threads don't support host.threads.2.
|
||
__global__ void ex1() {
|
||
while(true) cuda::std::this_thread::yield();
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Device.2
|
||
// Allowed outcome: No thread makes progress because device threads don't support host.threads.4
|
||
// for objects with automatic storage duration (see exception in device.threads.3).
|
||
__global__ void ex2() {
|
||
volatile bool True = true;
|
||
while(True);
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Device.3
|
||
// Allowed outcome: No thread makes progress because device threads don't support host.threads.5
|
||
// for objects with automatic storage duration (see exception in device.threads.4).
|
||
__global__ void ex3() {
|
||
cuda::atomic<bool, cuda::thread_scope_thread> True = true;
|
||
while(True.load());
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Device.4
|
||
// Allowed outcome: No thread makes progress because device threads don't support host.thread.6.
|
||
__global void ex4() {
|
||
while(true) { /* empty */ }
|
||
}
|
||
|
||
.. _libcudacxx-extended-api-execution-model-cuda-apis:
|
||
|
||
CUDA APIs
|
||
---------
|
||
|
||
A CUDA API call shall eventually either return or ensure at least one device thread makes progress.
|
||
|
||
CUDA query functions (e.g. `cudaStreamQuery <https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__STREAM.html#group__CUDART__STREAM_1g2021adeb17905c7ec2a3c1bf125c5435>`__,
|
||
`cudaEventQuery <https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__EVENT.html#group__CUDART__EVENT_1g2bf738909b4a059023537eaa29d8a5b7>`__, etc.) shall not consistently
|
||
return ``cudaErrorNotReady`` without a device thread making progress.
|
||
|
||
[Note: The device thread need not be "related" to the API call, e.g., an API operating on one stream or process may ensure progress of a device thread on another stream or process. - end note.]
|
||
|
||
[Note: A simple but not sufficient method to test a program for CUDA API Forward Progress conformance is to run them with following environment variables set: ``CUDA_DEVICE_MAX_CONNECTIONS=1 CUDA_LAUNCH_BLOCKING=1``, and then check that the program still terminates.
|
||
If it does not, the program has a bug.
|
||
This method is not sufficient because it does not catch all Forward Progress bugs, but it does catch many such bugs. - end note.]
|
||
|
||
.. dropdown:: Examples of CUDA API forward progress guarantees.
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.API.1
|
||
// Outcome: if no other device threads (e.g., from other processes) are making progress,
|
||
// this program terminates and returns cudaSuccess.
|
||
// Rationale: CUDA guarantees that if the device is empty:
|
||
// - `cudaDeviceSynchronize` eventually ensures that at least one device-thread makes progress, which implies that eventually `hello_world` grid and one of its device-threads start.
|
||
// - All thread-block threads eventually start (due to "if a device thread makes progress, all other threads in its thread-block cluster eventually make progress").
|
||
// - Once all threads in thread-block arrive at `__syncthreads` barrier, all waiting threads are unblocked.
|
||
// - Therefore all device threads eventually exit the `hello_world`` grid.
|
||
// - And `cudaDeviceSynchronize`` eventually unblocks.
|
||
__global__ void hello_world() { __syncthreads(); }
|
||
int main() {
|
||
hello_world<<<1,2>>>();
|
||
return (int)cudaDeviceSynchronize();
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.API.2
|
||
// Allowed outcome: eventually, no thread makes progress.
|
||
// Rationale: the `cudaDeviceSynchronize` API below is only called if a device thread eventually makes progress and sets the flag.
|
||
// However, CUDA only guarantees that `producer` device thread eventually starts if the synchronization API is called.
|
||
// Therefore, the host thread may never be unblocked from the flag spin-loop.
|
||
cuda::atomic<int, cuda::thread_scope_system> flag = 0;
|
||
__global__ void producer() { flag.store(1); }
|
||
int main() {
|
||
cudaHostRegister(&flag, sizeof(flag));
|
||
producer<<<1,1>>>();
|
||
while (flag.load() == 0);
|
||
return cudaDeviceSynchronize();
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.API.3
|
||
// Allowed outcome: eventually, no thread makes progress.
|
||
// Rationale: same as Example.Model.API.2, with the addition that a single CUDA query API call does not guarantee
|
||
// the device thread eventually starts, only repeated CUDA query API calls do (see Execution.Model.API.4).
|
||
cuda::atomic<int, cuda::thread_scope_system> flag = 0;
|
||
__global__ void producer() { flag.store(1); }
|
||
int main() {
|
||
cudaHostRegister(&flag, sizeof(flag));
|
||
producer<<<1,1>>>();
|
||
(void)cudaStreamQuery(0);
|
||
while (flag.load() == 0);
|
||
return cudaDeviceSynchronize();
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.API.4
|
||
// Outcome: terminates.
|
||
// Rationale: same as Execution.Model.API.3, but this example repeatedly calls
|
||
// a CUDA query API in within the flag spin-loop, which guarantees that the device thread
|
||
// eventually makes progress.
|
||
cuda::atomic<int, cuda::thread_scope_system> flag = 0;
|
||
__global__ void producer() { flag.store(1); }
|
||
int main() {
|
||
cudaHostRegister(&flag, sizeof(flag));
|
||
producer<<<1,1>>>();
|
||
while (flag.load() == 0) {
|
||
(void)cudaStreamQuery(0);
|
||
}
|
||
return cudaDeviceSynchronize();
|
||
}
|
||
|
||
.. _libcudacxx-extended-api-execution-model-cuda-dependencies:
|
||
|
||
Dependencies
|
||
~~~~~~~~~~~~
|
||
|
||
A device thread shall not start until all its dependencies have completed.
|
||
|
||
[Note: Dependencies that prevent device threads from starting to make progress can be created, for example, via `CUDA Stream Commands <https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#streams>`__ .
|
||
These may include dependencies on the completion of, among others, `CUDA Events <https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#events>`__ and `CUDA Kernels <https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#kernels>`__ . - end note.]
|
||
|
||
.. dropdown:: Examples of CUDA API forward progress guarantees due to dependencies
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Stream.0
|
||
// Allowed outcome: eventually, no thread makes progress.
|
||
// Rationale: while CUDA guarantees that one device thread makes progress, since there
|
||
// is no dependency between `first` and `second`, it does not guarantee which thread,
|
||
// and therefore it could always pick the device thread from `second`, which then never
|
||
// unblocks from the spin-loop.
|
||
// That is, `second` may starve `first`.
|
||
cuda::atomic<int, cuda::thread_scope_system> flag = 0;
|
||
__global__ void first() { flag.store(1, cuda::memory_order_relaxed); }
|
||
__global__ void second() { while(flag.load(cuda::memory_order_relaxed) == 0) {} }
|
||
int main() {
|
||
cudaHostRegister(&flag, sizeof(flag));
|
||
cudaStream_t s0, s1;
|
||
cudaStreamCreate(&s0);
|
||
cudaStreamCreate(&s1);
|
||
first<<<1,1,0,s0>>>();
|
||
second<<<1,1,0,s1>>>();
|
||
return cudaDeviceSynchronize();
|
||
}
|
||
|
||
.. code-block:: cuda
|
||
:linenos:
|
||
|
||
// Example: Execution.Model.Stream.1
|
||
// Outcome: terminates.
|
||
// Rationale: same as Execution.Model.Stream.0, but this example has a stream dependency
|
||
// between first and second, which requires CUDA to run the grids in order.
|
||
cuda::atomic<int, cuda::thread_scope_system> flag = 0;
|
||
__global__ void first() { flag.store(1, cuda::memory_order_relaxed); }
|
||
__global__ void second() { while(flag.load(cuda::memory_order_relaxed) == 0) {} }
|
||
int main() {
|
||
cudaHostRegister(&flag, sizeof(flag));
|
||
cudaStream_t s0;
|
||
cudaStreamCreate(&s0);
|
||
first<<<1,1,0,s0>>>();
|
||
second<<<1,1,0,s0>>>();
|
||
return cudaDeviceSynchronize();
|
||
}
|