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
330
cccl_upstream/docs/cudax/stf/custom_data_interface.rst
Normal file
@@ -0,0 +1,330 @@
|
||||
.. _stf_custom_data_interface:
|
||||
|
||||
CUDASTF offers an extensible API that allows users to implement their
|
||||
own data interface.
|
||||
|
||||
Let us for example go through the different steps to implement a data
|
||||
interface for a very simple simple implementation of a matrix class.
|
||||
|
||||
For the sake of simplicity, we here only consider the CUDA stream
|
||||
backend, but adding support for the CUDA graph backend simply require
|
||||
some extra steps which use the CUDA graph API.
|
||||
|
||||
Implementation of the ``matrix`` class
|
||||
======================================
|
||||
|
||||
For the sake of simplicity, we consider a very simple representation of
|
||||
matrix, only defined by the dimensions m and n, and by the base address
|
||||
of the matrix which we assume to be contiguous.
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename T>
|
||||
class matrix {
|
||||
public:
|
||||
matrix(size_t m, size_t n, T* base) : m(m), n(n), base(base) {}
|
||||
__host__ __device__ T& operator()(size_t i, size_t j) { return base[i + j * m]; }
|
||||
__host__ __device__ const T& operator()(size_t i, size_t j) const { return base[i + j * m]; }
|
||||
size_t m, n;
|
||||
T* base;
|
||||
};
|
||||
|
||||
Defining the shape of a matrix
|
||||
==============================
|
||||
|
||||
The first step consists in defining what is the *shape* of a matrix. The
|
||||
shape of a matrix should be a class that defines all parameters which
|
||||
are the same for all data instances, ``m`` and ``n``. On the other hand,
|
||||
the base address should not be part of this shape class, because each
|
||||
data instance will have its own base address.
|
||||
|
||||
To define what is the shape of a matrix, we need to specialize the
|
||||
``cudastf::shape_of`` trait class.
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename T>
|
||||
class cudastf::shape_of<matrix<T>> {
|
||||
public:
|
||||
/**
|
||||
* @brief The default constructor.
|
||||
*
|
||||
* All `shape_of` specializations must define this constructor.
|
||||
*/
|
||||
shape_of() = default;
|
||||
|
||||
explicit shape_of(size_t m, size_t n) : m(m), n(n) {}
|
||||
|
||||
/**
|
||||
* @name Copies a shape.
|
||||
*
|
||||
* All `shape_of` specializations must define this constructor.
|
||||
*/
|
||||
shape_of(const shape_of&) = default;
|
||||
|
||||
/**
|
||||
* @brief Extracts the shape from a matrix
|
||||
*
|
||||
* @param M matrix to get the shape from
|
||||
*
|
||||
* All `shape_of` specializations must define this constructor.
|
||||
*/
|
||||
shape_of(const matrix<T>& M) : shape_of<matrix<T>>(M.m, M.n) {}
|
||||
|
||||
/// Mandatory method : defined the total number of elements in the shape
|
||||
size_t size() const { return m * n; }
|
||||
|
||||
size_t m;
|
||||
size_t n;
|
||||
};
|
||||
|
||||
We here see that ``shape_of<matrix<T>>`` contains two ``size_t`` fields
|
||||
``m`` and ``n``.
|
||||
|
||||
In addition, we need to define a default constructor and a copy
|
||||
constructors.
|
||||
|
||||
To implement the ``.shape()`` member of the ``logical_data`` class, we
|
||||
need to define a constructor which takes a const reference to a matrix.
|
||||
|
||||
Finally, if the ``ctx.parallel_for`` construct is needed, we must define
|
||||
a ``size_t size() const`` method which computes the total number of
|
||||
elements in a shape.
|
||||
|
||||
Hash of a matrix
|
||||
================
|
||||
|
||||
For internal needs, such as using (unordered) maps of data instances,
|
||||
CUDASTF need to have specialized forms of the ``std::hash`` trait class.
|
||||
|
||||
The ``()`` operator of this class should compute a unique identifier
|
||||
associated to the description of the data instance. This typically means
|
||||
computing a hash of the matrix sizes, and of the base address. Note that
|
||||
this hash *does not* depend on the actual content of the matrix.
|
||||
|
||||
In code snippet, we are using the ``cudastf::hash_combine`` helper which
|
||||
updates a hash value with another value. This function is available from
|
||||
the ``cudastf/utility/hash.h`` header.
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename T>
|
||||
struct std::hash<matrix<T>> {
|
||||
std::size_t operator()(matrix<T> const& m) const noexcept {
|
||||
// Combine hashes from the base address and sizes
|
||||
return cudastf::hash_all(m.m, m.n, m.base);
|
||||
}
|
||||
};
|
||||
|
||||
Defining a data interface
|
||||
=========================
|
||||
|
||||
We can now implement the actual data interface for a matrix class, which
|
||||
defines the basic operations that CUDASTF need to perform on a matrix.
|
||||
|
||||
The ``matrix_stream_interface`` class inherits from the
|
||||
``data_interface`` class, but to implement a data interface using APIs
|
||||
based on CUDA streams, ``matrix_stream_interface`` inherits from
|
||||
``stream_data_interface_simple<matrix<T>>`` which contains pure virtual
|
||||
functions that need to be implemented.
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename T>
|
||||
class matrix_stream_interface : public stream_data_interface_simple<matrix<T>> {
|
||||
public:
|
||||
using base = stream_data_interface_simple<matrix<T>>;
|
||||
using base::shape_t;
|
||||
|
||||
/// Initialize from an existing matrix
|
||||
matrix_stream_interface(matrix<T> m) : base(std::move(m)) {}
|
||||
|
||||
/// Initialize from a shape of matrix
|
||||
matrix_stream_interface(shape_t s) : base(s) {}
|
||||
|
||||
/// Copy the content of an instance to another instance
|
||||
///
|
||||
/// This implementation assumes that we have registered memory if one of the data place is the host
|
||||
void stream_data_copy(const data_place& dst_memory_node, instance_id_t dst_instance_id,
|
||||
const data_place& src_memory_node, instance_id_t src_instance_id, cudaStream_t stream) override {
|
||||
assert(src_memory_node != dst_memory_node);
|
||||
|
||||
cudaMemcpyKind kind = cudaMemcpyDeviceToDevice;
|
||||
if (src_memory_node == data_place::host) {
|
||||
kind = cudaMemcpyHostToDevice;
|
||||
}
|
||||
|
||||
if (dst_memory_node == data_place::host) {
|
||||
kind = cudaMemcpyDeviceToHost;
|
||||
}
|
||||
|
||||
const matrix<T>& src_instance = this->instance(src_instance_id);
|
||||
const matrix<T>& dst_instance = this->instance(dst_instance_id);
|
||||
|
||||
size_t sz = src_instance.m * src_instance.n * sizeof(T);
|
||||
|
||||
cuda_safe_call(cudaMemcpyAsync((void*) dst_instance.base, (void*) src_instance.base, sz, kind, stream));
|
||||
}
|
||||
|
||||
/// allocate an instance on a specific data place
|
||||
///
|
||||
/// setting *s to a negative value informs CUDASTF that the allocation
|
||||
/// failed, and that a memory reclaiming mechanism need to be performed.
|
||||
void stream_data_allocate(backend_ctx_untyped& ctx, const data_place& memory_node, instance_id_t instance_id, ssize_t& s,
|
||||
void** extra_args, cudaStream_t stream) override {
|
||||
matrix<T>& instance = this->instance(instance_id);
|
||||
size_t sz = instance.m * instance.n * sizeof(T);
|
||||
|
||||
T* base_ptr;
|
||||
|
||||
if (memory_node == data_place::host) {
|
||||
// Fallback to a synchronous method as there is no asynchronous host allocation API
|
||||
cuda_safe_call(cudaStreamSynchronize(stream));
|
||||
cuda_safe_call(cudaHostAlloc(&base_ptr, sz, cudaHostAllocMapped));
|
||||
} else {
|
||||
cuda_safe_call(cudaMallocAsync(&base_ptr, sz, stream));
|
||||
}
|
||||
|
||||
// By filling a positive number, we notify that the allocation was successful
|
||||
*s = sz;
|
||||
|
||||
instance.base = base_ptr;
|
||||
}
|
||||
|
||||
/// deallocate an instance
|
||||
void stream_data_deallocate(backend_ctx_untyped& ctx, const data_place& memory_node, instance_id_t instance_id, void* extra_args,
|
||||
cudaStream_t stream) override {
|
||||
matrix<T>& instance = this->instance(instance_id);
|
||||
if (memory_node == data_place::host) {
|
||||
// Fallback to a synchronous method as there is no asynchronous host deallocation API
|
||||
cuda_safe_call(cudaStreamSynchronize(stream));
|
||||
cuda_safe_call(cudaFreeHost(instance.base));
|
||||
} else {
|
||||
cuda_safe_call(cudaFreeAsync(instance.base, stream));
|
||||
}
|
||||
}
|
||||
|
||||
/// Register the host memory associated to an instance of matrix
|
||||
///
|
||||
/// Note that this pin_host_memory method is not mandatory, but then it is
|
||||
/// the responsibility of the user to only passed memory that is already
|
||||
/// registered, and the allocation method on the host must allocate
|
||||
/// registered memory too. Otherwise, copy methods need to be synchronous.
|
||||
bool pin_host_memory(instance_id_t instance_id) override {
|
||||
matrix<T>& instance = this->instance(instance_id);
|
||||
if (!instance.base) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cuda_safe_call(pin_memory(instance.base, instance.m * instance.n * sizeof(T)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Unregister memory pinned by pin_host_memory
|
||||
void unpin_host_memory(instance_id_t instance_id) override {
|
||||
matrix<T>& instance = this->instance(instance_id);
|
||||
unpin_memory(instance.base);
|
||||
}
|
||||
};
|
||||
|
||||
``matrix_stream_interface`` must meet the following requirements so that
|
||||
they can be used in the CUDA stream backend : - It must provide
|
||||
constructors which take either a matrix, or a shape of matrix as
|
||||
arguments. - It must implement the ``stream_data_copy``,
|
||||
``stream_data_allocate`` and ``stream_data_deallocate`` virtual methods,
|
||||
which respectively define how to copy an instance into another instance,
|
||||
how to allocate an instance, and how to deallocate an instance. - It may
|
||||
implement the ``pin_host_memory`` and ``unpin_host_memory`` virtual
|
||||
methods which respectively register and unregister the memory associated
|
||||
to an instance allocated on the host. These two methods are not
|
||||
mandatory, but it is the responsibility of the user to either only pass
|
||||
and allocate registered host buffers, or to ensure that the copy method
|
||||
does not require such memory pinning. Similarly, accessing an instance
|
||||
located in host memory from a device typically requires to access
|
||||
registered memory.
|
||||
|
||||
Associating a data interface with the CUDA stream backend
|
||||
=========================================================
|
||||
|
||||
To ensure that we can initialize a logical data from a matrix, or from
|
||||
the shape of a matrix with ``stream_ctx::logical_data``, we then need to
|
||||
specialize the ``cudastf::streamed_interface_of`` trait class.
|
||||
|
||||
The resulting class must simply define a type named ``type`` which is
|
||||
the type of the data interface for the CUDA stream backend.
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename T>
|
||||
class cudastf::streamed_interface_of<matrix<T>> {
|
||||
public:
|
||||
using type = matrix_stream_interface<T>;
|
||||
};
|
||||
|
||||
Once we have defined this trait class, it is for example possible to
|
||||
initialize a logical data from a matrix, or from a matrix shape :
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::vector<int> v(m * n, 0);
|
||||
matrix M(m, n, &v[0]);
|
||||
|
||||
// Initialize from a matrix
|
||||
auto lM = ctx.logical_data(M);
|
||||
|
||||
// Initialize from a shape
|
||||
auto lM2 = ctx.logical_data(shape_of<matrix<int>>(m, n));
|
||||
|
||||
Example of code using the ``matrix`` data interface
|
||||
===================================================
|
||||
|
||||
We can now use the ``matrix`` class in CUDASTF, and access it from
|
||||
tasks. In this code, we first initialize a matrix on the host, we then
|
||||
apply a task which will update its content on the current device. We
|
||||
finally check that the content is correct, by the means of the
|
||||
write-back mechanism that automatically updates the reference data
|
||||
instance of a logical data when calling ``ctx.sync()``.
|
||||
|
||||
.. code:: c++
|
||||
|
||||
template <typename T>
|
||||
__global__ void kernel(matrix<T> M) {
|
||||
int tid_x = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int nthreads_x = gridDim.x * blockDim.x;
|
||||
|
||||
int tid_y = blockIdx.y * blockDim.y + threadIdx.y;
|
||||
int nthreads_y = gridDim.y * blockDim.y;
|
||||
|
||||
for (int x = tid_x; x < M.m; x += nthreads_x)
|
||||
for (int y = tid_y; y < M.n; y += nthreads_y) {
|
||||
M(x, y) += -x + 7 * y;
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
stream_ctx ctx;
|
||||
|
||||
const size_t m = 8;
|
||||
const size_t n = 10;
|
||||
std::vector<int> v(m * n);
|
||||
|
||||
for (size_t j = 0; j < n; j++)
|
||||
for (size_t i = 0; i < m; i++) {
|
||||
v[i + j * m] = 17 * i + 23 * j;
|
||||
}
|
||||
|
||||
matrix<int> M(m, n, &v[0]);
|
||||
|
||||
auto lM = ctx.logical_data(M);
|
||||
|
||||
// M(i,j) += -i + 7*i
|
||||
ctx.task(lM.rw())->*[](cudaStream_t s, auto dM) { kernel<<<dim3(8, 8), dim3(8, 8), 0, s>>>(dM); };
|
||||
|
||||
ctx.sync();
|
||||
|
||||
for (size_t j = 0; j < n; j++)
|
||||
for (size_t i = 0; i < m; i++) {
|
||||
assert(v[i + j * m] == (17 * i + 23 * j) + (-i + 7*i));
|
||||
}
|
||||
}
|
||||
5
cccl_upstream/docs/cudax/stf/images/dag-sections-0.dot
Normal file
@@ -0,0 +1,5 @@
|
||||
digraph {
|
||||
"NODE_23" [style="filled" fillcolor="red" label="task fence"]
|
||||
"NODE_3" -> "NODE_23"
|
||||
"NODE_3" [style="filled" fillcolor="white" label="foo"]
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/dag-sections-0.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
14
cccl_upstream/docs/cudax/stf/images/dag-sections-1.dot
Normal file
@@ -0,0 +1,14 @@
|
||||
digraph {
|
||||
"NODE_23" [style="filled" fillcolor="red" label="task fence"]
|
||||
subgraph cluster_section_1 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="foo"
|
||||
"NODE_13"
|
||||
"NODE_3"
|
||||
} // end subgraph cluster_section_1
|
||||
"NODE_13" -> "NODE_23"
|
||||
"NODE_3" -> "NODE_13"
|
||||
"NODE_13" [style="filled" fillcolor="white" label="bar"]
|
||||
"NODE_3" [style="filled" fillcolor="white" label="bar"]
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/dag-sections-1.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
36
cccl_upstream/docs/cudax/stf/images/dag-sections-2.dot
Normal file
@@ -0,0 +1,36 @@
|
||||
digraph {
|
||||
"NODE_23" [style="filled" fillcolor="red" label="task fence"]
|
||||
subgraph cluster_section_1 {
|
||||
subgraph cluster_section_2 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="bar"
|
||||
"NODE_9"
|
||||
"NODE_5"
|
||||
"NODE_3"
|
||||
} // end subgraph cluster_section_2
|
||||
subgraph cluster_section_5 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="bar"
|
||||
"NODE_19"
|
||||
"NODE_15"
|
||||
"NODE_13"
|
||||
} // end subgraph cluster_section_5
|
||||
color=black;
|
||||
style=dashed
|
||||
label="foo"
|
||||
} // end subgraph cluster_section_1
|
||||
"NODE_19" -> "NODE_23"
|
||||
"NODE_3" -> "NODE_5"
|
||||
"NODE_5" -> "NODE_9"
|
||||
"NODE_15" -> "NODE_19"
|
||||
"NODE_9" -> "NODE_13"
|
||||
"NODE_13" -> "NODE_15"
|
||||
"NODE_19" [style="filled" fillcolor="white" label="baz"]
|
||||
"NODE_15" [style="filled" fillcolor="white" label="baz"]
|
||||
"NODE_13" [style="filled" fillcolor="white" label="t1\nA(read)(0) \nB(rw)(0) "]
|
||||
"NODE_9" [style="filled" fillcolor="white" label="baz"]
|
||||
"NODE_5" [style="filled" fillcolor="white" label="baz"]
|
||||
"NODE_3" [style="filled" fillcolor="white" label="t1\nA(read)(0) \nB(rw)(0) "]
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/dag-sections-2.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
69
cccl_upstream/docs/cudax/stf/images/dag-sections.dot
Normal file
@@ -0,0 +1,69 @@
|
||||
digraph {
|
||||
"NODE_23" [style="filled" fillcolor="red" label="task fence"]
|
||||
subgraph cluster_section_1 {
|
||||
subgraph cluster_section_2 {
|
||||
subgraph cluster_section_3 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="baz"
|
||||
"NODE_7"
|
||||
"NODE_5"
|
||||
} // end subgraph cluster_section_3
|
||||
subgraph cluster_section_4 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="baz"
|
||||
"NODE_11"
|
||||
"NODE_9"
|
||||
} // end subgraph cluster_section_4
|
||||
color=black;
|
||||
style=dashed
|
||||
label="bar"
|
||||
"NODE_3"
|
||||
} // end subgraph cluster_section_2
|
||||
subgraph cluster_section_5 {
|
||||
subgraph cluster_section_6 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="baz"
|
||||
"NODE_17"
|
||||
"NODE_15"
|
||||
} // end subgraph cluster_section_6
|
||||
subgraph cluster_section_7 {
|
||||
color=black;
|
||||
style=dashed
|
||||
label="baz"
|
||||
"NODE_21"
|
||||
"NODE_19"
|
||||
} // end subgraph cluster_section_7
|
||||
color=black;
|
||||
style=dashed
|
||||
label="bar"
|
||||
"NODE_13"
|
||||
} // end subgraph cluster_section_5
|
||||
color=black;
|
||||
style=dashed
|
||||
label="foo"
|
||||
} // end subgraph cluster_section_1
|
||||
"NODE_15" -> "NODE_17"
|
||||
"NODE_9" -> "NODE_11"
|
||||
"NODE_11" -> "NODE_15"
|
||||
"NODE_13" -> "NODE_17"
|
||||
"NODE_19" -> "NODE_21"
|
||||
"NODE_11" -> "NODE_13"
|
||||
"NODE_7" -> "NODE_9"
|
||||
"NODE_5" -> "NODE_7"
|
||||
"NODE_17" -> "NODE_19"
|
||||
"NODE_3" -> "NODE_7"
|
||||
"NODE_21" -> "NODE_23"
|
||||
"NODE_21" [style="filled" fillcolor="white" label="t3\nA(rw)(0) \nB(read)(0) \nC(read)(0) "]
|
||||
"NODE_19" [style="filled" fillcolor="white" label="t2\nA(read)(0) \nC(rw)(0) "]
|
||||
"NODE_17" [style="filled" fillcolor="white" label="t3\nA(rw)(0) \nB(read)(0) \nC(read)(0) "]
|
||||
"NODE_15" [style="filled" fillcolor="white" label="t2\nA(read)(0) \nC(rw)(0) "]
|
||||
"NODE_13" [style="filled" fillcolor="white" label="t1\nA(read)(0) \nB(rw)(0) "]
|
||||
"NODE_11" [style="filled" fillcolor="white" label="t3\nA(rw)(0) \nB(read)(0) \nC(read)(0) "]
|
||||
"NODE_9" [style="filled" fillcolor="white" label="t2\nA(read)(0) \nC(rw)(0) "]
|
||||
"NODE_7" [style="filled" fillcolor="white" label="t3\nA(rw)(0) \nB(read)(0) \nC(read)(0) "]
|
||||
"NODE_5" [style="filled" fillcolor="white" label="t2\nA(read)(0) \nC(rw)(0) "]
|
||||
"NODE_3" [style="filled" fillcolor="white" label="t1\nA(read)(0) \nB(rw)(0) "]
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/dag-sections.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 11 KiB |
BIN
cccl_upstream/docs/cudax/stf/images/dot-output-axpy-events.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
BIN
cccl_upstream/docs/cudax/stf/images/dot-output-axpy.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
cccl_upstream/docs/cudax/stf/images/dot-output-heat.png
Normal file
|
After Width: | Height: | Size: 113 KiB |
10
cccl_upstream/docs/cudax/stf/images/graph_01.dot
Normal file
@@ -0,0 +1,10 @@
|
||||
digraph {
|
||||
T_1 [label="T1\nX(rw)"];
|
||||
T_2 [label="T2\nX(read)\nY(rw)"];
|
||||
T_3 [label="T3\nX(read)\nZ(rw)"];
|
||||
T_4 [label="T4\nY(read)\nZ(rw)"];
|
||||
T_1 -> T_2;
|
||||
T_1 -> T_3;
|
||||
T_2 -> T_4;
|
||||
T_3 -> T_4;
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/graph_01.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
16
cccl_upstream/docs/cudax/stf/images/graph_02.dot
Normal file
@@ -0,0 +1,16 @@
|
||||
digraph {
|
||||
subgraph cluster_0 {
|
||||
label="device 0";
|
||||
T_1 [label="T_1(A^W)"];
|
||||
T_2 [label="T_2(A^R, B^W)"];
|
||||
}
|
||||
subgraph cluster_1 {
|
||||
label="device 1";
|
||||
T_3 [label="T_3(A^R, C^W)"];
|
||||
T_4 [label="T_4(B^R, C^R, D^W)"];
|
||||
}
|
||||
T_1 -> T_2 [label="A"];
|
||||
T_1 -> T_3 [label="A"];
|
||||
T_2 -> T_4 [label="B"];
|
||||
T_3 -> T_4 [label ="C"];
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/graph_02.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
cccl_upstream/docs/cudax/stf/images/ncu-ui.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
26
cccl_upstream/docs/cudax/stf/images/task-sequence-user.dot
Normal file
@@ -0,0 +1,26 @@
|
||||
digraph {
|
||||
compound=true;
|
||||
subgraph cluster_0 {
|
||||
label="T1";
|
||||
K1 [label="K1"];
|
||||
K2 [label="K2"];
|
||||
}
|
||||
subgraph cluster_1 {
|
||||
label="T2";
|
||||
K3 [label="K3"];
|
||||
}
|
||||
subgraph cluster_2 {
|
||||
label="T3";
|
||||
K4 [label="K4"];
|
||||
}
|
||||
subgraph cluster_3 {
|
||||
label="T4";
|
||||
cb [label="callback"];
|
||||
}
|
||||
|
||||
K1 -> K2;
|
||||
K2 -> K3 [ltail=cluster_0,lhead=cluster_1,minlen=2];
|
||||
K2 -> K4 [ltail=cluster_0,lhead=cluster_2,minlen=2];
|
||||
K3 -> cb [ltail=cluster_1,lhead=cluster_3,minlen=2];
|
||||
K4 -> cb [ltail=cluster_2,lhead=cluster_3,minlen=2];
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/task-sequence-user.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
68
cccl_upstream/docs/cudax/stf/images/task-sequence.dot
Normal file
@@ -0,0 +1,68 @@
|
||||
digraph {
|
||||
compound=true;
|
||||
subgraph cluster_00 {
|
||||
label="";
|
||||
AA [label="Allocate A"];
|
||||
}
|
||||
|
||||
subgraph cluster_01 {
|
||||
label="";
|
||||
CA [label="Copy A H->D"];
|
||||
}
|
||||
|
||||
subgraph cluster_10 {
|
||||
label="";
|
||||
AB [label="Allocate B"];
|
||||
}
|
||||
|
||||
subgraph cluster_11 {
|
||||
label="";
|
||||
CB [label="Copy B H->D"];
|
||||
}
|
||||
|
||||
subgraph cluster_0 {
|
||||
label="T1";
|
||||
K1 [label="K1"];
|
||||
K2 [label="K2"];
|
||||
}
|
||||
|
||||
CA -> K1 [ltail=cluster_01,lhead=cluster_0,minlen=2];
|
||||
AA -> CA [ltail=cluster_00,lhead=cluster_01,minlen=2];
|
||||
|
||||
CB -> K1 [ltail=cluster_11,lhead=cluster_0,minlen=2];
|
||||
AB -> CB [ltail=cluster_10,lhead=cluster_11,minlen=2];
|
||||
|
||||
subgraph cluster_1 {
|
||||
label="T2";
|
||||
K3 [label="K3"];
|
||||
}
|
||||
subgraph cluster_2 {
|
||||
label="T3";
|
||||
K4 [label="K4"];
|
||||
}
|
||||
|
||||
K1 -> K2;
|
||||
K2 -> K3 [ltail=cluster_0,lhead=cluster_1,minlen=2];
|
||||
K2 -> K4 [ltail=cluster_0,lhead=cluster_2,minlen=2];
|
||||
|
||||
subgraph cluster_02 {
|
||||
label="";
|
||||
CA2 [label="Copy A D->A"];
|
||||
}
|
||||
|
||||
subgraph cluster_12 {
|
||||
label="";
|
||||
CB2 [label="Copy B D->A"];
|
||||
}
|
||||
|
||||
subgraph cluster_3 {
|
||||
label="T4";
|
||||
cb [label="callback"];
|
||||
}
|
||||
|
||||
K3 -> CA2 [ltail=cluster_1,lhead=cluster_02,minlen=2];
|
||||
K4 -> CB2 [ltail=cluster_2,lhead=cluster_12,minlen=2];
|
||||
|
||||
CA2 -> cb [ltail=cluster_02,lhead=cluster_3,minlen=2]
|
||||
CB2 -> cb [ltail=cluster_12,lhead=cluster_3,minlen=2]
|
||||
}
|
||||
BIN
cccl_upstream/docs/cudax/stf/images/task-sequence.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
114
cccl_upstream/docs/cudax/stf/lower_level_api.rst
Normal file
@@ -0,0 +1,114 @@
|
||||
.. _stf_lower_level_api:
|
||||
|
||||
Lower-level API
|
||||
===============
|
||||
|
||||
In some situations, the use of ``operator->*()`` on the object returned
|
||||
by ``ctx.task()`` (where ``ctx`` is a stream or graph context) may not
|
||||
be suitable, for example when the number of parameters is not known
|
||||
statically. To address such situations, CUDASTF provides a lower-level
|
||||
interface for creating tasks, which is described below.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
#include "cudastf/stf.h"
|
||||
#include "cudastf/__stf/stream/stream_ctx.h"
|
||||
|
||||
using namespace cudastf;
|
||||
|
||||
template <typename T>
|
||||
__global__ void axpy(int n, T a, T* x, T* y) {
|
||||
int tid = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
int nthreads = gridDim.x * blockDim.x;
|
||||
|
||||
for (int ind = tid; ind < n; ind += nthreads) {
|
||||
y[ind] += a * x[ind];
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
stream_ctx ctx;
|
||||
|
||||
const size_t N = 16;
|
||||
double X[N], Y[N];
|
||||
|
||||
for (size_t ind = 0; ind < N; ind++) {
|
||||
X[ind] = sin(double(ind));
|
||||
Y[ind] = cos(double(ind));
|
||||
}
|
||||
|
||||
auto lX = ctx.logical_data(X);
|
||||
auto lY = ctx.logical_data(Y);
|
||||
|
||||
double alpha = 3.14;
|
||||
|
||||
/* Compute Y = Y + alpha X */
|
||||
auto t = ctx.task(lX.read(), lY.rw());
|
||||
t.start();
|
||||
slice<double> sX = t.get<0>();
|
||||
slice<double> sY = t.get<1>();
|
||||
axpy<<<16, 128, 0, t.get_stream()>>>(sX.size(), alpha, sX.data_handle(), sY.data_handle());
|
||||
t.end();
|
||||
|
||||
ctx.sync();
|
||||
}
|
||||
|
||||
The ``ctx.task()`` call returns a task object. This object provides
|
||||
access to the local description of the data associated with the task and
|
||||
a CUDA stream that can be used to submit work asynchronously. The
|
||||
beginning of the task body and its end are delimited by the ``.start()``
|
||||
and ``.end()`` calls. Failing to call either of these methods or calling
|
||||
them more than once or in the wrong order results in undefined behavior.
|
||||
|
||||
Asynchrony is achieved by using the CUDA stream, which provides a
|
||||
mechanism to submit work on the execution place (here, implicitly the
|
||||
current CUDA device). CUDA ensures that all kernels synchronized with
|
||||
this CUDA stream will only be executed once all prerequisites have been
|
||||
fulfilled (e.g., preceding tasks, data transfers, etc.). In addition,
|
||||
CUDASTF performs all the necessary synchronization so that future tasks
|
||||
will be properly synchronized with the operations enqueued in the CUDA
|
||||
stream associated with this task after calling ``.start()`` and before
|
||||
calling ``.end()``.
|
||||
|
||||
Compatibility with CUDA graphs
|
||||
==============================
|
||||
|
||||
Similarly to the CUDA stream backend with a context of type
|
||||
``stream_ctx``, the CUDA graph backend ``graph_ctx`` also provides a
|
||||
low-level interface.
|
||||
|
||||
.. code:: cpp
|
||||
|
||||
graph_ctx ctx;
|
||||
|
||||
double X[1024], Y[1024];
|
||||
auto lX = ctx.logical_data(X);
|
||||
auto lY = ctx.logical_data(Y);
|
||||
|
||||
for (int k = 0; k < 10; k++) {
|
||||
graph_task t = ctx.task();
|
||||
t.add_deps(handle_X.rw());
|
||||
t.start();
|
||||
cudaGraphNode_t n;
|
||||
cuda_safe_call(cudaGraphAddEmptyNode(&n, t.get_graph(), nullptr, 0));
|
||||
t.end();
|
||||
}
|
||||
|
||||
graph_task t2 = ctx.task();
|
||||
t2.add_deps(lX.read(), lY.rw());
|
||||
t2.start();
|
||||
cudaGraphNode_t n2;
|
||||
cuda_safe_call(cudaGraphAddEmptyNode(&n2, t2.get_graph(), nullptr, 0));
|
||||
t2.end();
|
||||
|
||||
ctx.sync();
|
||||
|
||||
A task in the CUDA graph backend corresponds to a *child graph*
|
||||
automatically inserted into the CUDA graph associated to a ``graph_ctx``
|
||||
context. The example above creates 10 tasks that modify logical data
|
||||
``lX``, followed by a task that reads ``lX`` and modifies ``lY``. The
|
||||
code illustrates how one can add dependencies to a task by using the
|
||||
``add_deps`` method.
|
||||
|
||||
Similarly to the CUDA stream backend, a task is outlined by a pair of
|
||||
calls to the ``start()``/``end()`` member functions.
|
||||