Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions docs/cub/environments.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
.. _cub-environments:

Execution environments
================================================================================

..
TODO(bgruber): We should generalize the std::execution::* parts into a new document in libcu++ and link to it here

Almost all CUB device-wide algorithms accept an execution environment as their last argument.
Environments are objects responding to queries which return values.
A "query" in this case is usually a (tag) type naming the query, for example `cuda::execution::__get_tuning_t`.

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.

Should probably list the most commonly used ones

Suggested change
A "query" in this case is usually a (tag) type naming the query, for example `cuda::execution::__get_tuning_t`.
A "query" in this case is usually a (tag) type naming the query, for example `cuda::execution::__get_tuning_t`, `cuda::get_stream`, or `cuda::mr::get_memory_resource`.

Environments can also be passed to certain customization point objects (CPOs),
which will extract values from the environment.
For example, the CPO `cuda::get_stream` will extract the stream from the given environment.
The purpose of an environment is to provide properties that govern the execution of an algorithm,
like the used CUDA stream, memory resource, required determinism, requested tuning, etc.
Comment on lines +15 to +16

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.

Personally the explanation that really illuminated environments was @gevtushenko likening them to **kwargs arguments in Python. i.e. some kind of optional, extensible hook that users can attach arbitrary information to which the algorithm interprets as needed.

Perhaps we can snake this in here somewhere?


Some types are already environment themselves.
For example, `cudaStream_t` or `cuda::stream_ref` are environments responding to the `cuda::get_stream` CPO.
Similarly, several memory resource types are environments responding to the `cuda::mr::get_memory_resource` CPO.
Objects of such types can be passed directly to a CUB device-scope algorithm,
and the algorithm will query them for the stream or memory resource to use.
No wrapping of the value is necessary.


Constructing environments
--------------------------------------------------------------------------------

If the type of a value is not queryable directly,
a simple environment can be constructed using `cuda::std::execution::prop` given a value and a query.
For example:

.. code-block:: c++

cudaStream_t stream = ...;
auto env = cuda::std::execution::prop(cuda::get_stream, stream);

Builds an environment `env` that responds to the `cuda::get_stream` query with the value of `stream`.
If environments with more properties are needed,
they can be constructed from other environments using `cuda::std::execution::env`.
For example:

.. code-block:: c++

cudaStream_t stream = ...;
auto stream_env = cuda::std::execution::prop(cuda::get_stream, stream);
cuda::mr::resource_ref<> mr = ...;
auto mr_env = cuda::std::execution::prop{cuda::mr::get_memory_resource, cuda::mr::resource_ref<>{alloc}}
auto env = cuda::std::execution::env(stream_env, mr_env);

Here, `env` is constructed from two other environments, `stream_env` and `mr_env`.
`env` now responds to both the `cuda::get_stream` and `cuda::mr::get_memory_resource` queries.
Notice that wrapping environments with `cuda::std::execution::env` does not nest them.

However, because `cudaStream_t` and `cuda::mr::resource_ref<>` are already environments themselves,
the above can be simplified to:

.. code-block:: c++

cudaStream_t stream = ...;
cuda::mr::resource_ref<> mr = ...;
auto env = cuda::std::execution::env(stream, mr);

CUB also has a few more convenience functions for constructing environments,
like `cuda::execution::tune(...)` or `cuda::execution::require(...)`,
which build environments containing tuning policies and execution requirements, respectively.
These environments can be freely composed with others again:

.. code-block:: c++

cudaStream_t stream = ...;
cuda::mr::resource_ref<> mr = ...;
auto env = cuda::std::execution::env(stream, mr, cuda::execution::tune(...));
auto env2 = cuda::std::execution::env(env, cuda::execution::require(...));

Here, `env` is an environment containing a stream, a memory resource, and tuning policies.
`env2` additionally contains an execution requirement.
A shorter way to define `env2` would be:

.. code-block:: c++

cudaStream_t stream = ...;
cuda::mr::resource_ref<> mr = ...;
auto env2 = cuda::std::execution::env(
stream, mr, cuda::execution::tune(...), cuda::execution::require(...));


Querying environments
--------------------------------------------------------------------------------

There are two ways to get a value out of an environment again.

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.

*four, also cuda::__call_or and cuda::__lazy_call_or. Actually this is a good point to bring this up, we should really unify all of these, I myself don't actually know which is better.

Using the query (tag), we can query an environment directly using `cuda::std::execution::__query_or`:

.. code-block:: c++

auto env = cuda::std::execution::env(stream, mr); // from above example
cudaStream_t fallback_stream = ...;
cudaStream_t stream = cuda::std::execution::__query_or(env, cuda::get_stream, fallback_stream);

`__query_or` will look for a value in the environment responding to the `cuda::get_stream` query.
If no such value is found, it will return the fallback value, `fallback_stream`, instead.

..
TODO(bgruber) Does cuda::std::execution::__query_or(cudaStream_t{}, cuda::get_stream, fallback_stream); work?

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.

It does not.

auto r = cuda::std::execution::__query_or(cudaStream_t{}, cuda::get_stream, 1.0f);

static_assert(same_as<decltype(r), float>);

But cuda::get_stream(cudaStream_t{}) works. cuda::__call_or() also works, returning the cudaStream_t instead of the float.


...
TODO(bgruber): Should we mention `__query_result_or_t`?

Alternatively, we can use the CPO `cuda::get_stream` to query an environment for a stream:

.. code-block:: c++

auto env = cuda::std::execution::env(stream, mr); // from above example
cudaStream_t stream = cuda::get_stream(env);

If `env` would not contain any value responding to the `cuda::get_stream` query, the above would not compile.
Using a CPO to query an environment is preferred over using `__query_or` in general.

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 statement seems counterintuitive. The environments are usually optional, so it's not known (unless you check) whether an env can satisfy a query, so __query_or seems like the safest general method no?


CUB currently supports the following CPOs for querying environments:

- `cuda::get_stream`
- `cuda::mr::get_memory_resource`
- `cuda::execution::__get_requirements_t` (internal)
- `cuda::execution::__get_tuning_t` (internal)

While combining environments with `cuda::std::execution::env` does not nest them,
environments can contain values which are environments themselves,
and thus respond to queries again.
Execution requirements and tuning policy selectors are examples of values which are environments themselves.
For example:

.. code-block:: c++

auto env = cuda::std::execution::env(cuda::execution::require(cuda::execution::determinism::run_to_run));
auto requirements = cuda::__get_requirements_t(env);
auto determinism = cuda::__get_requirements_t(requirements);
static_assert(is_same_v<decltype(determinism), cuda::execution::determinism::run_to_run>):

..
TODO(bgruber): Should we mention `__queryable_with`?

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.

Yes, I think so. It would round out the discussion by documenting how to check whether you can query without doing it.

TODO(bgruber): Should we mention `__call_or`?

Structure of the CUB execution environment
--------------------------------------------------------------------------------

In CUB, all device wide algorithms with an environment parameter `env` expect that:

- If the environment contains a stream, it can be queried using `cuda::get_stream(env)`.
- If the environment contains a memory resource, it can be queried using `cuda::mr::get_memory_resource(env)`.
- If the environment contains execution requirements,
they can be queried using `auto requirements = cuda::execution::__get_requirements_t(env)`.
The value `requirements` is another environment, which can be queried for requirements,
e.g. the requested determinism using `cuda::execution::__get_determinism_t`.
- If the environment contains tuning policy selectors,
they can be queried using `auto tunings = cuda::execution::__get_tuning_t(env)`.
The value `tunings` is another environment, which can be queried for tuning policy selectors using
`cuda::execution::__query_or` and the type of a tuning policy

See also the documentation on `determinism <cub-determinism>`_ and tuning <cub-policy-selectors>`_.


Properties of environments

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.

IMO this section should go first. The most important thing for maintainers is how to pass envs around and what they should not do with them, so knowing the following upfront is the most useful information:

  1. Environment queries should all be immutable so we should always pass by const &.
  2. Algorithms do not need to concern themselves with env lifetimes.

Also the following questions come to mind here:

  1. Are environments required to be idempotent? Is it OK to query an env multiple times for the same information or should we cache results?
  2. What is the general policy for handling environments that contain properties you do not handle? Should you static_assert()? Let it pass?

--------------------------------------------------------------------------------

Since any object (of any type) can potentially be an environment,
we cannot describe the properties of environments in general.
However, in CUB we assume that environments are lightweight objects,
containing reference/pointer like values like stream or memory resource handles.
CUB therefore assumes in many places that an environment is copyable and movable.
We are currently changing this to pass environments between functions by `const&`,
to not rely on these properties.
Since CUB only queries environments, it does not mutate them, store them,
extend their lifetime in any way beyond a CUB API call,
or end their lifetime prematurely.
If an environment manages the lifetime of a contained resource,
and CUB queries and uses this resource for the execution of an algorithm,
it is the responsibility of the user to ensure the lifetime of the resource
exceeds the duration of the asynchronous CUB algorithm execution.
CUB strives for non-owning environments.
1 change: 1 addition & 0 deletions docs/cub/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ CUB
warp_wide
block_wide
device_wide
environments

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

important: Do not expose the environments page as user-facing guidance until its internal APIs have an explicit support contract. The page directs readers to __query_or, __get_requirements_t, and __get_tuning_t; either document these as unstable implementation details or provide public replacements before adding this navigation entry. As per path instructions, documentation changes must address technical accuracy and API/version consistency.

Source: Path instructions


🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

important: Fix the non-compiling C++ example in docs/cub/environments.rst before exposing it. The prop{...} declaration for mr_env is missing a terminating semicolon, violating the buildable-examples requirement.

Source: Path instructions

determinism
benchmarking
tuning
Expand Down
Loading