[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples

变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
  保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
  async_reduce, custom_temporary_allocation, explicit_cuda_stream,
  global_device_vector, range_view, unwrap_pointer, wrap_pointer, device

结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
  27/27 tuning headers, 78 benchmarks, 243 tests,
  60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
muh-bot
2026-08-03 12:39:26 +00:00
parent a2a5dd8f00
commit 24ef6a91b5
5439 changed files with 0 additions and 719516 deletions

View File

@@ -1,70 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief This example illustrates how to introduce CUDASTF contexts within existing stream-synchronized library calls
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// B += alpha*A;
__global__ void axpy(double alpha, const double* d_ptrA, double* d_ptrB, size_t N)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < N; i += nthreads)
{
d_ptrB[i] += alpha * d_ptrA[i];
}
}
int main()
{
double *d_ptrA, *d_ptrB;
const size_t N = 128 * 1024;
const size_t NITER = 128;
// User allocated memory
cuda_safe_call(cudaMalloc(&d_ptrA, N * sizeof(double)));
cuda_safe_call(cudaMalloc(&d_ptrB, N * sizeof(double)));
cudaStream_t stream;
cuda_safe_call(cudaStreamCreate(&stream));
async_resources_handle handle;
for (size_t i = 0; i < NITER; i++)
{
stream_ctx ctx(stream, handle);
auto lA = ctx.logical_data(make_slice(d_ptrA, N), data_place::current_device());
auto lB = ctx.logical_data(make_slice(d_ptrB, N), data_place::current_device());
ctx.parallel_for(lA.shape(), lA.write())->*[] __device__(size_t i, auto a) {
a(i) = sin(double(i));
};
ctx.parallel_for(lB.shape(), lB.write())->*[] __device__(size_t i, auto b) {
b(i) = cos(double(i));
};
ctx.task(lA.read(), lB.rw())->*[=](cudaStream_t s, auto a, auto b) {
axpy<<<128, 32, 0, s>>>(3.0, a.data_handle(), b.data_handle(), N);
};
// Note that this is non-blocking because we have creating the stream_ctx
// relative to a user-provided CUDA stream
ctx.finalize();
axpy<<<128, 32, 0, stream>>>(2.0, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
}

View File

@@ -1,250 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/experimental/__stf/utility/nvtx.cuh>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void initA(double* d_ptrA, size_t N)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t nthreads = blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += nthreads)
{
d_ptrA[i] = sin((double) i);
}
}
__global__ void initB(double* d_ptrB, size_t N)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t nthreads = blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += nthreads)
{
d_ptrB[i] = cos((double) i);
}
}
// B += alpha*A;
__global__ void axpy(double alpha, const double* d_ptrA, double* d_ptrB, size_t N)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < N; i += nthreads)
{
d_ptrB[i] += alpha * d_ptrA[i];
}
}
__global__ void empty_kernel()
{
// no-op
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
void ref_lib_call(cudaStream_t stream, double* d_ptrA, double* d_ptrB, size_t N)
{
initA<<<128, 32, 0, stream>>>(d_ptrA, N);
initB<<<128, 32, 0, stream>>>(d_ptrB, N);
axpy<<<128, 32, 0, stream>>>(3.0, d_ptrA, d_ptrB, N);
empty_kernel<<<16, 8, 0, stream>>>();
}
void lib_call(cudaStream_t stream, double* d_ptrA, double* d_ptrB, size_t N)
{
stream_ctx ctx(stream);
auto lA = ctx.logical_data(make_slice(d_ptrA, N), data_place::current_device());
auto lB = ctx.logical_data(make_slice(d_ptrB, N), data_place::current_device());
ctx.task(lA.write())->*[=](cudaStream_t s, auto a) {
initA<<<128, 32, 0, s>>>(a.data_handle(), N);
};
ctx.task(lB.write())->*[=](cudaStream_t s, auto b) {
initB<<<128, 32, 0, s>>>(b.data_handle(), N);
};
ctx.task(lA.read(), lB.rw())->*[=](cudaStream_t s, auto a, auto b) {
axpy<<<128, 32, 0, s>>>(3.0, a.data_handle(), b.data_handle(), N);
};
ctx.task()->*[](cudaStream_t s) {
empty_kernel<<<16, 8, 0, s>>>();
};
// Note that this is non-blocking because we have creating the stream_ctx
// relative to a user-provided CUDA stream
ctx.finalize();
}
void lib_call_with_handle(async_resources_handle& handle, cudaStream_t stream, double* d_ptrA, double* d_ptrB, size_t N)
{
stream_ctx ctx(stream, handle);
auto lA = ctx.logical_data(make_slice(d_ptrA, N), data_place::current_device());
auto lB = ctx.logical_data(make_slice(d_ptrB, N), data_place::current_device());
ctx.task(lA.write())->*[=](cudaStream_t s, auto a) {
initA<<<128, 32, 0, s>>>(a.data_handle(), N);
};
ctx.task(lB.write())->*[=](cudaStream_t s, auto b) {
initB<<<128, 32, 0, s>>>(b.data_handle(), N);
};
ctx.task(lA.read(), lB.rw())->*[=](cudaStream_t s, auto a, auto b) {
axpy<<<128, 32, 0, s>>>(3.0, a.data_handle(), b.data_handle(), N);
};
ctx.task()->*[](cudaStream_t s) {
empty_kernel<<<16, 8, 0, s>>>();
};
// Note that this is non-blocking because we have creating the stream_ctx
// relative to a user-provided CUDA stream
ctx.finalize();
}
template <typename Ctx_t>
void lib_call_generic(async_resources_handle& handle, cudaStream_t stream, double* d_ptrA, double* d_ptrB, size_t N)
{
Ctx_t ctx(stream, handle);
auto lA = ctx.logical_data(make_slice(d_ptrA, N), data_place::current_device());
auto lB = ctx.logical_data(make_slice(d_ptrB, N), data_place::current_device());
ctx.task(lA.write())->*[=](cudaStream_t s, auto a) {
initA<<<128, 32, 0, s>>>(a.data_handle(), N);
};
ctx.task(lB.write())->*[=](cudaStream_t s, auto b) {
initB<<<128, 32, 0, s>>>(b.data_handle(), N);
};
ctx.task(lA.read(), lB.rw())->*[=](cudaStream_t s, auto a, auto b) {
axpy<<<128, 32, 0, s>>>(3.0, a.data_handle(), b.data_handle(), N);
};
ctx.task()->*[](cudaStream_t s) {
empty_kernel<<<16, 8, 0, s>>>();
};
ctx.submit();
}
template <typename Ctx_t>
void lib_call_token(async_resources_handle& handle, cudaStream_t stream, double* d_ptrA, double* d_ptrB, size_t N)
{
Ctx_t ctx(stream, handle);
auto lA = ctx.token();
auto lB = ctx.token();
ctx.task(lA.write())->*[=](cudaStream_t s) {
initA<<<128, 32, 0, s>>>(d_ptrA, N);
};
ctx.task(lB.write())->*[=](cudaStream_t s) {
initB<<<128, 32, 0, s>>>(d_ptrB, N);
};
ctx.task(lA.read(), lB.rw())->*[=](cudaStream_t s) {
axpy<<<128, 32, 0, s>>>(3.0, d_ptrA, d_ptrB, N);
};
ctx.task()->*[](cudaStream_t s) {
empty_kernel<<<16, 8, 0, s>>>();
};
ctx.submit();
}
int main()
{
double *d_ptrA, *d_ptrB;
const size_t N = 128 * 1024;
const size_t NITER = 128;
// User allocated memory
cuda_safe_call(cudaMalloc(&d_ptrA, N * sizeof(double)));
cuda_safe_call(cudaMalloc(&d_ptrB, N * sizeof(double)));
cudaStream_t stream;
cuda_safe_call(cudaStreamCreate(&stream));
nvtx_range r_warmup("warmup");
for (size_t i = 0; i < NITER; i++)
{
ref_lib_call(stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_warmup.end();
nvtx_range r_ref("ref");
for (size_t i = 0; i < NITER; i++)
{
ref_lib_call(stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_ref.end();
nvtx_range r_local("local stf");
for (size_t i = 0; i < NITER; i++)
{
lib_call(stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_local.end();
nvtx_range r_local_handle("local stf handle");
async_resources_handle handle;
for (size_t i = 0; i < NITER; i++)
{
lib_call_with_handle(handle, stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_local_handle.end();
nvtx_range r_generic_graph_handle("generic graph handle");
for (size_t i = 0; i < NITER; i++)
{
lib_call_generic<graph_ctx>(handle, stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_generic_graph_handle.end();
nvtx_range r_generic_stream_handle("generic stream handle");
for (size_t i = 0; i < NITER; i++)
{
lib_call_generic<stream_ctx>(handle, stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_generic_stream_handle.end();
nvtx_range r_generic_context_handle("generic context handle");
for (size_t i = 0; i < NITER; i++)
{
lib_call_generic<context>(handle, stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_generic_context_handle.end();
nvtx_range r_token("logical token");
for (size_t i = 0; i < NITER; i++)
{
lib_call_token<context>(handle, stream, d_ptrA, d_ptrB, N);
}
cuda_safe_call(cudaStreamSynchronize(stream));
r_token.end();
}

View File

@@ -1,429 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Exercise building an STF context on a caller-provided stream that is
* currently in CUDA graph capture mode.
*
* The flow is:
*
* 1. Create an explicit CUDA stream.
* 2. Start a capture on it with ``cudaStreamCaptureModeRelaxed``.
* 3. Construct a ``stream_ctx`` bound to that captured stream and submit a
* small fork-join DAG of tasks (plus an unrelated empty epilogue):
*
* ctx.task(lA.write()) // initA on one pool stream
* ctx.task(lB.write()) // initB on another pool stream
* ctx.task(lA.read(), lB.rw()) // axpy joining the two branches
* ctx.task() // empty task, no token deps
*
* 4. ``ctx.finalize()`` and ``cudaStreamEndCapture`` produce a CUDA graph.
* 5. The graph is instantiated, launched several times on a clean replay
* stream, and the resulting arrays are validated on the host.
*
* The captured graph is also walked with the CUDA runtime API to confirm the
* two ``init`` kernel nodes are mutually independent, i.e. STF's fork/join
* through its internal stream pool actually produced multi-stream concurrency
* rather than a serialized single-stream chain.
*
* A final negative case verifies that constructing ``stream_ctx(user_stream,
* handle)`` with a caller-provided ``async_resources_handle`` while
* ``user_stream`` is capturing is rejected early with a precise diagnostic.
*/
#include <cuda/experimental/stf.cuh>
#include <cmath>
#include <unordered_set>
#include <vector>
using namespace cuda::experimental::stf;
__global__ void initA(double* d_ptrA, size_t N)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t nthreads = blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += nthreads)
{
d_ptrA[i] = sin(i);
}
}
__global__ void initB(double* d_ptrB, size_t N)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t nthreads = blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += nthreads)
{
d_ptrB[i] = cos(i);
}
}
// B += alpha * A
__global__ void axpy(double alpha, const double* d_ptrA, double* d_ptrB, size_t N)
{
size_t tid = blockIdx.x * blockDim.x + threadIdx.x;
size_t nthreads = blockDim.x * gridDim.x;
for (size_t i = tid; i < N; i += nthreads)
{
d_ptrB[i] += alpha * d_ptrA[i];
}
}
__global__ void empty_kernel()
{
// no-op: acts as the unrelated empty-task epilogue (no token deps).
}
/**
* @brief Control path: same fork-join DAG, built with plain CUDA API calls --
* two side streams forked from the captured main stream via events,
* then joined back on the main stream.
*
* Establishes a baseline that the fork-join pattern itself (fork + join via
* events, with non-blocking side streams) is legal inside a Relaxed-mode
* capture, independently of STF.
*/
void submit_fork_join_manual(cudaStream_t main, double* d_ptrA, double* d_ptrB, size_t N)
{
// Fresh side streams created inside the capture so they have no prior
// (uncaptured) work of their own. Use the non-blocking flag to mirror how
// STF creates its own pool streams.
cudaStream_t sA;
cudaStream_t sB;
cuda_safe_call(cudaStreamCreateWithFlags(&sA, cudaStreamNonBlocking));
cuda_safe_call(cudaStreamCreateWithFlags(&sB, cudaStreamNonBlocking));
// Start event on the captured main stream.
cudaEvent_t e_start;
cuda_safe_call(cudaEventCreateWithFlags(&e_start, cudaEventDisableTiming));
cuda_safe_call(cudaEventRecord(e_start, main));
// Fork: bring sA and sB into the capture.
cuda_safe_call(cudaStreamWaitEvent(sA, e_start, 0));
cuda_safe_call(cudaStreamWaitEvent(sB, e_start, 0));
// Run the two independent init branches on sA / sB.
initA<<<128, 32, 0, sA>>>(d_ptrA, N);
initB<<<128, 32, 0, sB>>>(d_ptrB, N);
// Join both side streams back on ``main`` for the axpy combine step.
cudaEvent_t e_A;
cudaEvent_t e_B;
cuda_safe_call(cudaEventCreateWithFlags(&e_A, cudaEventDisableTiming));
cuda_safe_call(cudaEventCreateWithFlags(&e_B, cudaEventDisableTiming));
cuda_safe_call(cudaEventRecord(e_A, sA));
cuda_safe_call(cudaEventRecord(e_B, sB));
cuda_safe_call(cudaStreamWaitEvent(main, e_A, 0));
cuda_safe_call(cudaStreamWaitEvent(main, e_B, 0));
axpy<<<128, 32, 0, main>>>(3.0, d_ptrA, d_ptrB, N);
empty_kernel<<<16, 8, 0, main>>>();
// Destroy events (safe at any point -- cleanup).
cuda_safe_call(cudaEventDestroy(e_start));
cuda_safe_call(cudaEventDestroy(e_A));
cuda_safe_call(cudaEventDestroy(e_B));
// Side-stream destruction is deferred: they are now part of the capture
// and must outlive it.
cuda_safe_call(cudaStreamDestroy(sA));
cuda_safe_call(cudaStreamDestroy(sB));
}
/**
* @brief Submit the token-based fork-join DAG inside an already-started capture.
*
* Must be called while ``stream`` is in ``StreamCaptureStatusActive``.
*/
void submit_fork_join_token(cudaStream_t stream, double* d_ptrA, double* d_ptrB, size_t N)
{
stream_ctx ctx(stream);
auto lA = ctx.token();
auto lB = ctx.token();
ctx.task(lA.write())->*[=](cudaStream_t s) {
initA<<<128, 32, 0, s>>>(d_ptrA, N);
};
ctx.task(lB.write())->*[=](cudaStream_t s) {
initB<<<128, 32, 0, s>>>(d_ptrB, N);
};
ctx.task(lA.read(), lB.rw())->*[=](cudaStream_t s) {
axpy<<<128, 32, 0, s>>>(3.0, d_ptrA, d_ptrB, N);
};
ctx.task()->*[](cudaStream_t s) {
empty_kernel<<<16, 8, 0, s>>>();
};
// ``finalize()`` is non-blocking when the context is bound to a
// user-provided stream: all work is now enqueued on ``stream`` (or on
// pool streams that have been folded into ``stream``'s capture).
ctx.finalize();
}
/**
* @brief Compute the transitive-dependency closure of ``root`` in a CUDA graph.
*
* Walks predecessors via ``cudaGraphNodeGetDependencies`` and returns every
* node reachable from ``root`` (excluding ``root`` itself). Used to assert
* that two kernel nodes are mutually independent in the captured graph.
*/
static std::unordered_set<cudaGraphNode_t> transitive_dependencies(cudaGraphNode_t root)
{
std::unordered_set<cudaGraphNode_t> visited;
std::vector<cudaGraphNode_t> stack;
stack.push_back(root);
while (!stack.empty())
{
cudaGraphNode_t n = stack.back();
stack.pop_back();
size_t ndeps = 0;
#if _CCCL_CTK_AT_LEAST(13, 0)
cuda_safe_call(cudaGraphNodeGetDependencies(n, nullptr, nullptr, &ndeps));
#else
cuda_safe_call(cudaGraphNodeGetDependencies(n, nullptr, &ndeps));
#endif
if (ndeps == 0)
{
continue;
}
std::vector<cudaGraphNode_t> deps(ndeps);
#if _CCCL_CTK_AT_LEAST(13, 0)
// CTK 13+: dependency edges may carry cudaGraphEdgeData. Querying into
// pDependencies with edgeData == nullptr returns cudaErrorLossyQuery when
// any edge has non-default data (e.g. port annotations from capture).
std::vector<cudaGraphEdgeData> edge_data(ndeps);
cuda_safe_call(cudaGraphNodeGetDependencies(n, deps.data(), edge_data.data(), &ndeps));
#else
cuda_safe_call(cudaGraphNodeGetDependencies(n, deps.data(), &ndeps));
#endif
for (cudaGraphNode_t d : deps)
{
if (visited.insert(d).second)
{
stack.push_back(d);
}
}
}
visited.erase(root);
return visited;
}
/**
* @brief Walk the captured graph and assert that the ``initA``/``initB``
* branches are mutually independent (i.e. really parallel, not
* serialized by a hidden edge STF inserted).
*
* We identify the three kernel nodes by launch-dimension signature: the two
* ``init`` kernels and the ``axpy`` kernel all use ``<<<128, 32>>>`` while the
* empty epilogue uses ``<<<16, 8>>>`` and is ignored here. The combiner
* (``axpy``) is the kernel node that transitively depends on both of the
* others; the remaining two are the independent ``init`` branches.
*/
static void assert_inits_are_parallel(cudaGraph_t graph)
{
size_t nnodes = 0;
cuda_safe_call(cudaGraphGetNodes(graph, nullptr, &nnodes));
std::vector<cudaGraphNode_t> nodes(nnodes);
cuda_safe_call(cudaGraphGetNodes(graph, nodes.data(), &nnodes));
// Collect kernel nodes whose grid/block signature matches the three
// fork-join kernels (initA / initB / axpy all launch <<<128, 32>>>).
std::vector<cudaGraphNode_t> fork_join_kernels;
for (cudaGraphNode_t n : nodes)
{
cudaGraphNodeType t;
cuda_safe_call(cudaGraphNodeGetType(n, &t));
if (t != cudaGraphNodeTypeKernel)
{
continue;
}
cudaKernelNodeParams p = {};
cuda_safe_call(cudaGraphKernelNodeGetParams(n, &p));
if (p.gridDim.x == 128 && p.blockDim.x == 32)
{
fork_join_kernels.push_back(n);
}
}
EXPECT(fork_join_kernels.size() == 3,
"Expected exactly 3 fork-join kernel nodes (initA, initB, axpy), got ",
fork_join_kernels.size());
// Find the combiner: the single kernel whose transitive predecessors
// contain the other two fork-join kernels.
int combiner_idx = -1;
for (int i = 0; i < 3; ++i)
{
auto deps = transitive_dependencies(fork_join_kernels[i]);
int hits = 0;
for (int j = 0; j < 3; ++j)
{
if (j != i && deps.count(fork_join_kernels[j]) > 0)
{
++hits;
}
}
if (hits == 2)
{
EXPECT(combiner_idx == -1, "More than one combiner kernel found; DAG is not a fork-join");
combiner_idx = i;
}
}
EXPECT(combiner_idx != -1, "No combiner kernel found; init A and init B must both flow into axpy");
// The two remaining kernels are the init branches. They must be mutually
// independent -- neither reachable from the other via predecessor edges.
std::vector<cudaGraphNode_t> inits;
for (int i = 0; i < 3; ++i)
{
if (i != combiner_idx)
{
inits.push_back(fork_join_kernels[i]);
}
}
EXPECT(inits.size() == 2);
auto deps_a = transitive_dependencies(inits[0]);
auto deps_b = transitive_dependencies(inits[1]);
EXPECT(deps_a.count(inits[1]) == 0,
"init branch A transitively depends on init branch B -- STF serialized the fork-join");
EXPECT(deps_b.count(inits[0]) == 0,
"init branch B transitively depends on init branch A -- STF serialized the fork-join");
}
/**
* @brief Run ``submit``, under Relaxed-mode capture on a caller-owned stream.
* Harvest the captured graph, replay it ``NREPLAYS`` times on a clean
* replay stream, and validate the resulting arrays on the host.
*/
template <typename Submit>
void run_fork_join_under_capture(const char* label, Submit&& submit)
{
const size_t N = 128 * 1024;
double* d_ptrA = nullptr;
double* d_ptrB = nullptr;
cuda_safe_call(cudaMalloc(&d_ptrA, N * sizeof(double)));
cuda_safe_call(cudaMalloc(&d_ptrB, N * sizeof(double)));
// Zero the buffers so a failure to run the captured graph would produce a
// host-side mismatch rather than coincidentally-correct data.
cuda_safe_call(cudaMemset(d_ptrA, 0, N * sizeof(double)));
cuda_safe_call(cudaMemset(d_ptrB, 0, N * sizeof(double)));
// Caller-owned stream, started in Relaxed capture mode.
cudaStream_t capture_stream;
cuda_safe_call(cudaStreamCreate(&capture_stream));
cuda_safe_call(cudaStreamBeginCapture(capture_stream, cudaStreamCaptureModeRelaxed));
submit(capture_stream, d_ptrA, d_ptrB, N);
cudaGraph_t graph = nullptr;
cuda_safe_call(cudaStreamEndCapture(capture_stream, &graph));
EXPECT(graph != nullptr, "cudaStreamEndCapture returned a null graph");
// Captured DAG check: initA and initB must be mutually independent.
assert_inits_are_parallel(graph);
// Instantiate and replay the captured graph several times on a clean replay
// stream.
cudaGraphExec_t graph_exec = nullptr;
cuda_safe_call(cudaGraphInstantiate(&graph_exec, graph, nullptr, nullptr, 0));
cudaStream_t replay_stream;
cuda_safe_call(cudaStreamCreate(&replay_stream));
const int NREPLAYS = 4;
for (int r = 0; r < NREPLAYS; ++r)
{
cuda_safe_call(cudaGraphLaunch(graph_exec, replay_stream));
}
cuda_safe_call(cudaStreamSynchronize(replay_stream));
// Validate results on the host.
// A[i] = sin(i)
// B[i] = cos(i) + 3*sin(i)
std::vector<double> h_A(N, 0.0);
std::vector<double> h_B(N, 0.0);
cuda_safe_call(cudaMemcpy(h_A.data(), d_ptrA, N * sizeof(double), cudaMemcpyDeviceToHost));
cuda_safe_call(cudaMemcpy(h_B.data(), d_ptrB, N * sizeof(double), cudaMemcpyDeviceToHost));
double max_err_A = 0.0;
double max_err_B = 0.0;
for (size_t i = 0; i < N; ++i)
{
double ref_A = sin((double) i);
double ref_B = cos((double) i) + 3.0 * sin((double) i);
max_err_A = std::fmax(max_err_A, std::fabs(h_A[i] - ref_A));
max_err_B = std::fmax(max_err_B, std::fabs(h_B[i] - ref_B));
}
EXPECT(max_err_A < 1e-10, "[", label, "] A mismatch: max|A - sin(i)| = ", max_err_A);
EXPECT(max_err_B < 1e-10, "[", label, "] B mismatch: max|B - (cos(i) + 3 sin(i))| = ", max_err_B);
cuda_safe_call(cudaGraphExecDestroy(graph_exec));
cuda_safe_call(cudaGraphDestroy(graph));
cuda_safe_call(cudaStreamDestroy(replay_stream));
cuda_safe_call(cudaStreamDestroy(capture_stream));
cuda_safe_call(cudaFree(d_ptrA));
cuda_safe_call(cudaFree(d_ptrB));
}
int main()
{
// Control path: plain CUDA API fork-join inside a Relaxed-mode capture.
run_fork_join_under_capture("manual", submit_fork_join_manual);
// STF path: token-based fork-join submitted through ``stream_ctx(user_stream)``.
// The context is constructed without an explicit ``async_resources_handle``
// so it gets a fresh, empty stream pool that STF is free to fold into the
// on-going capture. This is the supported in-capture configuration.
run_fork_join_under_capture("stf_token", submit_fork_join_token);
// Negative case: ``stream_ctx(user_stream, handle)`` with a user-provided
// handle while ``user_stream`` is capturing must be rejected early, before
// any pool streams are touched.
{
cudaStream_t capture_stream = nullptr;
cuda_safe_call(cudaStreamCreate(&capture_stream));
cuda_safe_call(cudaStreamBeginCapture(capture_stream, cudaStreamCaptureModeRelaxed));
bool threw = false;
try
{
async_resources_handle h; // user-provided handle (non-null)
stream_ctx ctx(capture_stream, mv(h));
}
catch (const ::std::exception&)
{
threw = true;
}
EXPECT(threw,
"stream_ctx(user_stream, handle) should have aborted because "
"user_stream is in a capture and a user-provided handle was passed.");
// Discard the capture we started -- if we reached here, no work was
// actually enqueued on ``capture_stream`` after the failed construction.
cudaGraph_t unused = nullptr;
cuda_safe_call(cudaStreamEndCapture(capture_stream, &unused));
if (unused)
{
cuda_safe_call(cudaGraphDestroy(unused));
}
cuda_safe_call(cudaStreamDestroy(capture_stream));
}
return 0;
}

View File

@@ -1,320 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
//! \file
//! \brief Test that ctx_t::logical_data_t works correctly when ctx_t is a template parameter
//!
//! This test ensures that the logical_data_t type alias defined in context classes
//! (stream_ctx, graph_ctx, stackable_ctx, etc.) works correctly in generic code where
//! the context type is a template parameter. This is essential for writing context-agnostic
//! library functions and algorithms.
#include <cuda/std/type_traits>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// Helper kernel for testing
template <typename T>
__global__ void scale_kernel(size_t n, T factor, T* data)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (size_t i = tid; i < n; i += nthreads)
{
data[i] *= factor;
}
}
// Test 1: Compile-time type trait tests
// Verify that ctx_t::logical_data_t<T> is a valid type for different context types
void test_compile_time_logical_data_t_exists()
{
// Test that logical_data_t is defined for stream_ctx
using stream_logical_data_int = stream_ctx::logical_data_t<slice<int>>;
using stream_logical_data_double = stream_ctx::logical_data_t<slice<double>>;
static_assert(::std::is_same_v<stream_logical_data_int, logical_data<slice<int>>>,
"stream_ctx::logical_data_t<slice<int>> should be logical_data<slice<int>>");
static_assert(::std::is_same_v<stream_logical_data_double, logical_data<slice<double>>>,
"stream_ctx::logical_data_t<slice<double>> should be logical_data<slice<double>>");
// Test that logical_data_t is defined for graph_ctx
using graph_logical_data_int = graph_ctx::logical_data_t<slice<int>>;
using graph_logical_data_double = graph_ctx::logical_data_t<slice<double>>;
static_assert(::std::is_same_v<graph_logical_data_int, logical_data<slice<int>>>,
"graph_ctx::logical_data_t<slice<int>> should be logical_data<slice<int>>");
static_assert(::std::is_same_v<graph_logical_data_double, logical_data<slice<double>>>,
"graph_ctx::logical_data_t<slice<double>> should be logical_data<slice<double>>");
// Test that logical_data_t is defined for stackable_ctx
using stackable_logical_data_int = stackable_ctx::logical_data_t<slice<int>>;
using stackable_logical_data_double = stackable_ctx::logical_data_t<slice<double>>;
static_assert(::std::is_same_v<stackable_logical_data_int, stackable_logical_data<slice<int>>>,
"stackable_ctx::logical_data_t<slice<int>> should be stackable_logical_data<slice<int>>");
static_assert(::std::is_same_v<stackable_logical_data_double, stackable_logical_data<slice<double>>>,
"stackable_ctx::logical_data_t<slice<double>> should be stackable_logical_data<slice<double>>");
}
// Test 2: Generic function that uses ctx_t::logical_data_t as a template parameter
// This demonstrates the main use case: writing context-agnostic code
template <typename Ctx>
typename Ctx::template logical_data_t<slice<int>> create_and_initialize_data(Ctx& ctx, size_t n, int init_value)
{
// Create logical data using the context-specific logical_data_t type
auto data = ctx.logical_data(shape_of<slice<int>>(n));
data.set_symbol("initialized_data");
// Initialize the data
ctx.parallel_for(data.shape(), data.write())->*[init_value] __device__(size_t i, auto d) {
d(i) = init_value + static_cast<int>(i);
};
return data;
}
// Test 3: Generic function that takes and returns ctx_t::logical_data_t
// Note: Using auto&& for the data parameter to avoid template deduction issues with dependent types
template <typename Ctx, typename LogicalData, typename Factor>
void scale_data(Ctx& ctx, LogicalData&& data, Factor factor)
{
ctx.task(data.rw())->*[factor](cudaStream_t s, auto d) {
scale_kernel<<<16, 128, 0, s>>>(d.size(), factor, d.data_handle());
};
}
// Test 4: Generic class that stores ctx_t::logical_data_t
template <typename Ctx>
class GenericDataHolder
{
public:
using int_data_t = typename Ctx::template logical_data_t<slice<int>>;
using double_data_t = typename Ctx::template logical_data_t<slice<double>>;
GenericDataHolder(Ctx& ctx, size_t n)
: int_data_(ctx.logical_data(shape_of<slice<int>>(n)))
, double_data_(ctx.logical_data(shape_of<slice<double>>(n)))
{
int_data_.set_symbol("holder_int_data");
double_data_.set_symbol("holder_double_data");
}
int_data_t& get_int_data()
{
return int_data_;
}
double_data_t& get_double_data()
{
return double_data_;
}
private:
int_data_t int_data_;
double_data_t double_data_;
};
// Test 5: Generic function using auto with ctx_t::logical_data_t
template <typename Ctx>
void test_auto_deduction(Ctx& ctx)
{
// Create data using the generic function
auto data = create_and_initialize_data(ctx, 100, 42);
// Verify the type is correct
using expected_type = typename Ctx::template logical_data_t<slice<int>>;
static_assert(::std::is_same_v<decltype(data), expected_type>,
"Auto-deduced type should match ctx_t::logical_data_t<slice<int>>");
// Use the data
ctx.host_launch(data.read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == 42 + static_cast<int>(i), "Expected ", 42 + i, " but got ", d(i), " at index ", i);
}
};
}
// Main test function template
template <typename Ctx>
void run_tests()
{
Ctx ctx;
// Test 1: Basic creation and usage with template logical_data_t
{
auto data = create_and_initialize_data(ctx, 256, 10);
ctx.host_launch(data.read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == 10 + static_cast<int>(i));
}
};
}
// Test 2: Using scale_data generic function
{
auto data = create_and_initialize_data(ctx, 128, 5);
scale_data(ctx, data, 3);
ctx.host_launch(data.read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == (5 + static_cast<int>(i)) * 3);
}
};
}
// Test 3: Using GenericDataHolder class
{
GenericDataHolder<Ctx> holder(ctx, 64);
// Initialize int data
ctx.parallel_for(holder.get_int_data().shape(), holder.get_int_data().write())->*[] __device__(size_t i, auto d) {
d(i) = static_cast<int>(i * 2);
};
// Initialize double data
ctx.parallel_for(holder.get_double_data().shape(), holder.get_double_data().write())
->*[] __device__(size_t i, auto d) {
d(i) = static_cast<double>(i) * 1.5;
};
// Verify int data
ctx.host_launch(holder.get_int_data().read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == static_cast<int>(i * 2));
}
};
// Verify double data
ctx.host_launch(holder.get_double_data().read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(fabs(d(i) - static_cast<double>(i) * 1.5) < 1e-9);
}
};
}
// Test 4: Auto deduction
test_auto_deduction(ctx);
// Test 5: Multiple operations with logical_data_t
{
typename Ctx::template logical_data_t<slice<int>> data1 = ctx.logical_data(shape_of<slice<int>>(32));
typename Ctx::template logical_data_t<slice<int>> data2 = ctx.logical_data(shape_of<slice<int>>(32));
data1.set_symbol("data1");
data2.set_symbol("data2");
// Initialize both
ctx.parallel_for(data1.shape(), data1.write())->*[] __device__(size_t i, auto d) {
d(i) = static_cast<int>(i);
};
ctx.parallel_for(data2.shape(), data2.write())->*[] __device__(size_t i, auto d) {
d(i) = static_cast<int>(i * 10);
};
// Combine them
ctx.parallel_for(data1.shape(), data1.rw(), data2.read())->*[] __device__(size_t i, auto d1, auto d2) {
d1(i) += d2(i);
};
// Verify
ctx.host_launch(data1.read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == static_cast<int>(i + i * 10));
}
};
}
ctx.finalize();
}
// Additional test for stackable_ctx specific features
void run_stackable_tests()
{
stackable_ctx ctx;
// Test nested contexts with logical_data_t
{
auto data = create_and_initialize_data(ctx, 128, 20);
// Enter nested context
{
stackable_ctx::graph_scope_guard scope{ctx};
// Scale in nested context
scale_data(ctx, data, 2);
// Create local data in nested context
typename stackable_ctx::logical_data_t<slice<int>> local_data = ctx.logical_data(shape_of<slice<int>>(64));
local_data.set_symbol("nested_local");
ctx.parallel_for(local_data.shape(), local_data.write())->*[] __device__(size_t i, auto d) {
d(i) = static_cast<int>(i * 5);
};
ctx.host_launch(local_data.read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == static_cast<int>(i * 5));
}
};
}
// Verify data after nested context
ctx.host_launch(data.read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == (20 + static_cast<int>(i)) * 2);
}
};
}
// Test with GenericDataHolder in stackable context
{
GenericDataHolder<stackable_ctx> holder(ctx, 48);
ctx.parallel_for(holder.get_int_data().shape(), holder.get_int_data().write())->*[] __device__(size_t i, auto d) {
d(i) = static_cast<int>(i + 100);
};
ctx.host_launch(holder.get_int_data().read())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
EXPECT(d(i) == static_cast<int>(i + 100));
}
};
}
ctx.finalize();
}
int main()
{
// Run compile-time tests
test_compile_time_logical_data_t_exists();
// Run runtime tests with different context types
run_tests<stream_ctx>();
run_tests<graph_ctx>();
run_tests<stackable_ctx>();
// Run stackable-specific tests
run_stackable_tests();
return 0;
}

View File

@@ -1,88 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment with local context nesting
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx sctx;
int array[1024];
for (size_t i = 0; i < 1024; i++)
{
array[i] = 1 + i * i;
}
auto lC = sctx.logical_data(array);
auto lA = sctx.logical_data(lC.shape());
lA.set_symbol("A");
auto lA2 = sctx.logical_data(shape_of<slice<int>>(1024));
lA2.set_symbol("A2");
sctx.parallel_for(lA.shape(), lA.write())->*[] __device__(size_t i, auto a) {
a(i) = 42 + 2 * i;
};
/* Create nested graph */
{
stackable_ctx::graph_scope_guard scope{sctx};
auto lB = sctx.logical_data(shape_of<slice<int>>(512));
lB.set_symbol("B");
sctx.parallel_for(lB.shape(), lB.write())->*[] __device__(size_t i, auto b) {
b(i) = 17 - 3 * i;
};
sctx.parallel_for(lA2.shape(), lA2.write())->*[] __device__(size_t i, auto a2) {
a2(i) = 5 * i + 4;
};
sctx.parallel_for(lB.shape(), lA.read(), lB.rw())->*[] __device__(size_t i, auto a, auto b) {
b(i) += a(i);
};
sctx.parallel_for(lB.shape(), lB.read(), lC.rw())->*[] __device__(size_t i, auto b, auto c) {
c(i) += b(i);
};
}
sctx.host_launch(lA2.read())->*[](auto a2) {
for (size_t i = 0; i < a2.size(); i++)
{
EXPECT(a2(i) == 5 * i + 4);
}
};
// Do the same check in another graph
{
stackable_ctx::graph_scope_guard scope{sctx};
lA2.push(access_mode::read);
sctx.host_launch(lA2.read())->*[](auto a2) {
for (size_t i = 0; i < a2.size(); i++)
{
EXPECT(a2(i) == 5 * i + 4);
}
};
}
sctx.finalize();
}

View File

@@ -1,69 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment with local context nesting
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx ctx;
int array[1024];
for (size_t i = 0; i < 1024; i++)
{
array[i] = 1 + i * i;
}
auto lA = ctx.logical_data(array).set_symbol("A");
// repeat : {tmp = a; tmp*=2; a+=tmp}
for (size_t iter = 0; iter < 10; iter++)
{
stackable_ctx::graph_scope_guard graph{ctx}; // RAII: automatic push/pop (lock_guard style)
auto tmp = ctx.logical_data(lA.shape()).set_symbol("tmp");
ctx.parallel_for(tmp.shape(), tmp.write(), lA.read())->*[] __device__(size_t i, auto tmp, auto a) {
tmp(i) = a(i);
};
ctx.parallel_for(tmp.shape(), tmp.rw())->*[] __device__(size_t i, auto tmp) {
tmp(i) *= 2;
};
ctx.parallel_for(lA.shape(), tmp.read(), lA.rw())->*[] __device__(size_t i, auto tmp, auto a) {
a(i) += tmp(i);
};
// ctx.pop() is called automatically when 'graph' goes out of scope
}
ctx.finalize();
// Verify the array has been updated correctly by the write-back mechanism
// Each iteration transforms each element: a_new = a_old + 2 * a_old = 3 * a_old
// Starting from array[i] = 1 + i*i, after 10 iterations:
// array[i] = 3^10 * (1 + i*i)
constexpr int pow3_10 = 59049; // 3^10
for (size_t i = 0; i < 1024; i++)
{
int expected = pow3_10 * (1 + static_cast<int>(i * i));
EXPECT(array[i] == expected);
}
}

View File

@@ -1,58 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief Ensure we can use the add_deps mechanism on tasks in stackable contexts.
* This test verifies that dependencies can be added one by one to tasks
* using add_deps() in stackable contexts, with proper automatic data
* pushing and validation.
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// a = b + 1;
template <typename T>
__global__ void add(slice<T> a, slice<T> b)
{
a(0) += b(0);
}
int main()
{
stackable_ctx ctx;
int var1 = 42;
int var2 = 64;
auto lvar1 = ctx.logical_data(make_slice(&var1, 1));
auto lvar2 = ctx.logical_data(make_slice(&var2, 1));
ctx.push();
auto t = ctx.task();
t.add_deps(lvar1.rw());
t.add_deps(lvar2.read());
t->*[&t](cudaStream_t stream) {
auto d1 = t.template get<slice<int>>(0);
auto d2 = t.template get<slice<int>>(1);
add<<<1, 1, 0, stream>>>(d1, d2);
};
ctx.pop();
ctx.host_launch(lvar1.read())->*[]([[maybe_unused]] auto d1) {
_CCCL_ASSERT(d1(0) == (42 + 64), "invalid result");
};
ctx.finalize();
}

View File

@@ -1,66 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment to see if we can read data generated in a nested context
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx sctx;
auto lA = sctx.logical_data(shape_of<slice<int>>(512));
lA.set_symbol("A");
sctx.parallel_for(lA.shape(), lA.write())->*[] __device__(size_t i, auto a) {
a(i) = 42 + 2 * i;
};
/* Start to use a graph */
auto g = sctx.dot_section("foo");
sctx.push();
lA.push(access_mode::read);
auto lB = sctx.logical_data(lA.shape());
lB.set_symbol("B");
auto lC = sctx.logical_data_no_export(lA.shape());
lC.set_symbol("C");
sctx.parallel_for(lB.shape(), lB.write(), lA.read(), lC.write())->*[] __device__(size_t i, auto b, auto a, auto c) {
b(i) = 17 - 3 * a(i);
c(i) = b(i);
};
sctx.parallel_for(lB.shape(), lB.rw())->*[] __device__(size_t i, auto b) {
b(i) *= 2;
};
sctx.pop();
g.end();
/* Access B in a context below the context where it was created */
sctx.host_launch(lB.read())->*[](auto b) {
for (size_t i = 0; i < b.size(); i++)
{
EXPECT(b(i) == 2 * (17 - 3 * (42 + 2 * i)));
}
};
sctx.finalize();
}

View File

@@ -1,77 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment to see if we can read data generated in a nested context
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void kernel(slice<int> b, long long int clock_cnt)
{
long long int start_clock = clock64();
long long int clock_offset = 0;
while (clock_offset < clock_cnt)
{
clock_offset = clock64() - start_clock;
}
size_t n = b.size();
int i = blockIdx.x * blockDim.x + threadIdx.x;
int stride = blockDim.x * gridDim.x;
while (i < n)
{
b(i) = 17 - 3 * i;
i += stride;
}
}
int main()
{
int device;
cudaGetDevice(&device);
// cudaDevAttrClockRate: Peak clock frequency in kilohertz;
int clock_rate;
cudaDeviceGetAttribute(&clock_rate, cudaDevAttrClockRate, device);
double ms = 500;
long long int clock_cnt = (long long int) (ms * clock_rate);
stackable_ctx sctx;
auto lB = sctx.logical_data(shape_of<slice<int>>(1024));
sctx.push();
lB.push(access_mode::write);
lB.set_symbol("B");
sctx.task(lB.write())->*[clock_cnt](cudaStream_t stream, auto b) {
kernel<<<32, 4, 0, stream>>>(b, clock_cnt);
};
sctx.pop();
/* Access B in a context below the context where it was created */
sctx.host_launch(lB.read())->*[](auto b) {
for (size_t i = 0; i < b.size(); i++)
{
EXPECT(b(i) == (17 - 3 * i));
}
};
sctx.finalize();
}

View File

@@ -1,75 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment to see if we can read data generated in a nested context
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx sctx;
auto lA = sctx.logical_data(shape_of<slice<int>>(512));
lA.set_symbol("A");
sctx.parallel_for(lA.shape(), lA.write())->*[] __device__(size_t i, auto a) {
a(i) = 42 + 2 * i;
};
/* Start to use a graph */
auto g = sctx.dot_section("foo");
sctx.push();
auto lB = sctx.logical_data(lA.shape());
lB.set_symbol("B");
auto lA_moved = mv(lA);
sctx.parallel_for(lB.shape(), lB.write(), lA_moved.read())->*[] __device__(size_t i, auto b, auto a) {
b(i) = 17 - 3 * a(i);
};
auto lC = mv(lB);
sctx.parallel_for(lC.shape(), lC.rw())->*[] __device__(size_t i, auto b) {
b(i) *= 2;
};
sctx.parallel_for(lA_moved.shape(), lA_moved.rw())->*[] __device__(size_t i, auto a) {
a(i) *= 3;
};
sctx.pop();
g.end();
/* Access C in a context below the context where it was created */
sctx.host_launch(lC.read())->*[](auto b) {
for (size_t i = 0; i < b.size(); i++)
{
EXPECT(b(i) == 2 * (17 - 3 * (42 + 2 * i)));
}
};
sctx.host_launch(lA_moved.read())->*[](auto a) {
for (size_t i = 0; i < a.size(); i++)
{
EXPECT(a(i) == 3 * (42 + 2 * i));
}
};
sctx.finalize();
}

View File

@@ -1,73 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Simple test demonstrating stackable context with double push before parallel_for
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx sctx;
size_t sz = 1024;
::std::vector<int> data(sz);
// Initialize data
for (size_t i = 0; i < sz; i++)
{
data[i] = static_cast<int>(i);
}
// Create logical data
auto ldata = sctx.logical_data(make_slice(data.data(), sz));
// First scope with first context push and first data push
for (size_t iter = 0; iter < 3; iter++)
{
stackable_ctx::graph_scope_guard scope1{sctx};
ldata.push(access_mode::rw);
// NESTED second scope with second context push and second data push
{
stackable_ctx::graph_scope_guard scope2{sctx};
ldata.push(access_mode::rw);
// Now do the parallel_for operation - double each element
sctx.parallel_for(ldata.shape(), ldata.rw())->*[] __device__(size_t i, auto d) {
d(i) *= 2;
};
sctx.host_launch(ldata.rw())->*[](auto d) {
for (size_t i = 0; i < d.size(); i++)
{
d(i)++;
}
};
}
}
sctx.finalize();
// Verify results - each element goes through 3 iterations of d(i)*=2, d(i)++
for (size_t i = 0; i < sz; i++)
{
int expected = static_cast<int>(((i * 2 + 1) * 2 + 1) * 2 + 1);
_CCCL_ASSERT(data[i] == expected, "invalid result at index");
}
return 0;
}

View File

@@ -1,83 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Ensure we can nest push/pop section to author composable code
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// Z += X*Y
void fma_lib(stackable_ctx& sctx,
stackable_logical_data<slice<int>>& lX,
stackable_logical_data<slice<int>>& lY,
stackable_logical_data<slice<int>>& lZ)
{
stackable_ctx::graph_scope_guard scope{sctx};
lX.push(access_mode::read);
lY.push(access_mode::read);
sctx.parallel_for(lZ.shape(), lZ.rw(), lX.read(), lY.read())->*[] __device__(size_t i, auto z, auto x, auto y) {
z(i) += x(i) * y(i);
};
}
// Z += (XiYi) for all i
void dot_lib(stackable_ctx& sctx,
::std::vector<stackable_logical_data<slice<int>>>& vecx,
::std::vector<stackable_logical_data<slice<int>>>& vecy,
stackable_logical_data<slice<int>>& Z)
{
stackable_ctx::graph_scope_guard scope{sctx};
for (size_t i = 0; i < vecx.size(); i++)
{
// Force read push to stress test nested context handling
vecx[i].push(access_mode::read);
vecy[i].push(access_mode::read);
fma_lib(sctx, vecx[i], vecy[i], Z);
}
}
int main()
{
stackable_ctx sctx;
size_t sz = 4;
::std::vector<int> X(sz), Y(sz);
::std::vector<stackable_logical_data<slice<int>>> vecx, vecy;
int expected = 0;
for (size_t i = 0; i < sz; i++)
{
X[i] = i;
vecx.push_back(sctx.logical_data(make_slice(&X[i], 1)));
Y[i] = (i - 1);
vecy.push_back(sctx.logical_data(make_slice(&Y[i], 1)));
expected += i * (i - 1);
}
int result = 0;
auto lresult = sctx.logical_data(make_slice(&result, 1));
dot_lib(sctx, vecx, vecy, lresult);
sctx.finalize();
_CCCL_ASSERT(result == expected, "invalid result");
}

View File

@@ -1,71 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Simple test demonstrating stackable context with double push before parallel_for
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: repeat_graph_scope is only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
int niter1 = 7;
int niter2 = 3;
int niter3 = 17;
size_t sz = 2;
::std::vector<int> data(sz);
// Initialize data
for (size_t i = 0; i < sz; i++)
{
data[i] = static_cast<int>(i);
}
// Create logical data
auto ldata = ctx.logical_data(make_slice(data.data(), sz));
{
auto r1 = ctx.repeat_graph_scope(niter1);
{
auto r2 = ctx.repeat_graph_scope(niter2);
{
auto r3 = ctx.repeat_graph_scope(niter3);
ctx.parallel_for(ldata.shape(), ldata.rw())->*[] __device__(size_t i, auto d) {
d(i)++;
};
}
}
}
ctx.finalize();
// Verify results - each element is incremented niter1*niter2*niter3 times
for (size_t i = 0; i < sz; i++)
{
int expected = static_cast<int>(i + niter1 * niter2 * niter3);
_CCCL_ASSERT(data[i] == expected, "invalid result at index");
}
return 0;
#endif // !_CCCL_CTK_BELOW(12, 4)
}

View File

@@ -1,93 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Simple test demonstrating stackable context with double push before parallel_for
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: while_graph_scope is only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
size_t sz = 1024;
::std::vector<int> data(sz);
// Initialize data
for (size_t i = 0; i < sz; i++)
{
data[i] = static_cast<int>(i);
}
// Create logical data
auto ldata = ctx.logical_data(make_slice(data.data(), sz));
auto liter1 = ctx.logical_data(shape_of<scalar_view<int>>());
auto liter2 = ctx.logical_data(shape_of<scalar_view<int>>());
int max_iter1 = 2;
int max_iter2 = 3;
// First scope with first context push and first data push
{
// Initialize iteration counter
ctx.parallel_for(box(1), liter1.write())->*[] __device__(size_t, auto iter1) {
*iter1 = 0;
};
auto while_guard_1 = ctx.while_graph_scope();
// NESTED second scope with second context push and second data push
{
ctx.parallel_for(box(1), liter2.write())->*[] __device__(size_t, auto iter2) {
*iter2 = 0;
};
auto while_guard_2 = ctx.while_graph_scope();
// Now do the parallel_for operation - double each element
ctx.parallel_for(ldata.shape(), ldata.rw())->*[] __device__(size_t i, auto d) {
d(i)++;
};
while_guard_2.update_cond(liter2.rw())->*[max_iter2] __device__(auto iter2) {
(*iter2)++;
return (*iter2 < max_iter2); // Continue if not converged and under limit
};
}
while_guard_1.update_cond(liter1.rw())->*[max_iter1] __device__(auto iter1) {
(*iter1)++;
return (*iter1 < max_iter1); // Continue if not converged and under limit
};
}
ctx.finalize();
// Verify results - each element is incremented max_iter1*max_iter2 times
for (size_t i = 0; i < sz; i++)
{
int expected = static_cast<int>(i + max_iter1 * max_iter2);
_CCCL_ASSERT(data[i] == expected, "invalid result at index");
}
return 0;
#endif // !_CCCL_CTK_BELOW(12, 4)
}

View File

@@ -1,71 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Test that the node hierarchy pool grows beyond its initial capacity
* and that freed slots are properly reused across multiple batches.
*/
#include <cuda/experimental/stf.cuh>
#include <thread>
using namespace cuda::experimental::stf;
// The initial node pool size is 16. Using more threads than that forces growth.
static constexpr int NTHREADS = 20;
static constexpr int NBATCHES = 3;
static constexpr size_t N = 1024;
void worker(stackable_ctx sctx, int main_head, stackable_logical_data<slice<int>> ld)
{
sctx.set_head_offset(main_head);
sctx.push();
sctx.parallel_for(ld.shape(), ld.write())->*[=] __device__(size_t k, auto d) {
d(k) = 42;
};
sctx.pop();
}
int main()
{
stackable_ctx sctx;
int main_head = sctx.get_head_offset();
::std::vector<stackable_logical_data<slice<int>>> lds;
for (int i = 0; i < NTHREADS; i++)
{
lds.push_back(sctx.logical_data(shape_of<slice<int>>(N)));
}
// Run multiple batches: the first batch forces growth beyond 16,
// subsequent batches verify that freed slots are reused.
for (int batch = 0; batch < NBATCHES; batch++)
{
::std::vector<::std::thread> threads;
for (int i = 0; i < NTHREADS; i++)
{
threads.emplace_back(worker, sctx, main_head, lds[i]);
}
for (auto& t : threads)
{
t.join();
}
}
sctx.finalize();
return 0;
}

View File

@@ -1,78 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Test set_read_only() on stackable logical data
*
* When data is marked read-only, it should be auto-pushed as read in nested
* contexts, allowing concurrent reads from multiple graph scopes without
* the conservative rw push that would serialize access.
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx ctx;
constexpr size_t N = 1024;
int host_data[N];
for (size_t i = 0; i < N; i++)
{
host_data[i] = static_cast<int>(i * 3 + 7);
}
auto lConst = ctx.logical_data(host_data).set_symbol("const_data");
lConst.set_read_only();
auto lOut = ctx.logical_data(shape_of<slice<int>>(N)).set_symbol("output");
// Use read-only data in multiple sequential graph scopes. Because the data
// is read-only, it is auto-pushed as read (not rw), and the original host
// buffer must remain unchanged after finalize.
{
auto scope = ctx.graph_scope();
ctx.parallel_for(lOut.shape(), lOut.write(), lConst.read())->*[] __device__(size_t i, auto out, auto cst) {
out(i) = cst(i) + 1;
};
}
{
auto scope = ctx.graph_scope();
ctx.parallel_for(lOut.shape(), lOut.rw(), lConst.read())->*[] __device__(size_t i, auto out, auto cst) {
out(i) += cst(i);
};
}
// Third scope: use read-only data alongside the accumulated output
{
auto scope = ctx.graph_scope();
ctx.parallel_for(lOut.shape(), lOut.rw(), lConst.read())->*[] __device__(size_t i, auto out, auto cst) {
out(i) += cst(i) * 2;
};
}
ctx.finalize();
// Verify the read-only host buffer was not modified
for (size_t i = 0; i < N; i++)
{
EXPECT(host_data[i] == static_cast<int>(i * 3 + 7));
}
return 0;
}

View File

@@ -1,51 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief Ensure we can use the same logical data multiple times in the same
* task even with different access modes (which should be combined)
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// a = b + 1;
template <typename T>
__global__ void add(T* a, const T* b)
{
*a = *b + 1;
}
int main()
{
stackable_ctx ctx;
int var = 42;
auto var_handle = ctx.logical_data(make_slice(&var, 1));
ctx.push();
// da and db are for the same variable : we expect it to be equivalent to a RW access
ctx.task(var_handle.write(), var_handle.read())->*[](cudaStream_t stream, auto da, auto db) {
add<<<1, 1, 0, stream>>>(da.data_handle(), db.data_handle());
};
ctx.pop();
// Read that value on the host
ctx.host_launch(var_handle.read())->*[](auto da) {
int result = *da.data_handle();
assert(result == 43);
};
ctx.finalize();
}

View File

@@ -1,89 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment with local context nesting
*
*/
#include <cuda/experimental/stf.cuh>
#include <thread>
using namespace cuda::experimental::stf;
void worker(
stackable_ctx sctx, int main_head, stackable_logical_data<slice<int>> lAi, stackable_logical_data<slice<int>> lB)
{
sctx.set_head_offset(main_head);
sctx.push();
auto lC = sctx.logical_data_no_export(lB.shape());
sctx.parallel_for(lC.shape(), lC.write(), lB.read())->*[] __device__(size_t k, auto c, auto b) {
c(k) = b(k);
};
sctx.parallel_for(lAi.shape(), lAi.write())->*[] __device__(size_t k, auto ai) {
ai(k) = k;
};
sctx.parallel_for(lAi.shape(), lAi.rw(), lC.read())->*[] __device__(size_t k, auto ai, auto c) {
ai(k) += int(sin(cos(cos(10.0 * c(k)))));
};
sctx.pop();
}
int main()
{
const size_t N = 1024000;
stackable_ctx sctx;
int array[N];
for (size_t i = 0; i < N; i++)
{
array[i] = 1 + i * i;
}
auto lB = sctx.logical_data(array);
lB.set_read_only();
int main_head = sctx.get_head_offset();
::std::vector<stackable_logical_data<slice<int>>> lA;
const int NTHREADS = 8;
for (int i = 0; i < NTHREADS; ++i)
{
lA.push_back(sctx.logical_data(shape_of<slice<int>>(N)));
}
for (int k = 0; k < 30; k++)
{
::std::vector<::std::thread> threads;
for (int i = 0; i < NTHREADS; ++i)
{
threads.emplace_back(worker, sctx, main_head, lA[i], lB);
}
for (int i = 0; i < NTHREADS; ++i)
{
threads[i].join();
}
}
sctx.finalize();
}

View File

@@ -1,57 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment with local context and temporary logical data
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
template <typename T>
static __global__ void kernel_set(T* addr, T val)
{
*addr = val;
}
int main()
{
stackable_ctx ctx;
auto lA = ctx.logical_data(shape_of<slice<int>>(1024));
ctx.task(lA.write())->*[](cudaStream_t stream, auto a) {
kernel_set<<<1, 1, 0, stream>>>(a.data_handle(), 42);
};
{
auto scope = ctx.graph_scope();
// Create temporary data in nested context
auto temp = ctx.logical_data(shape_of<slice<int>>(1024));
ctx.parallel_for(lA.shape(), temp.write(), lA.read())->*[] __device__(size_t i, auto temp, auto a) {
// Copy data and modify
temp(i) = a(i) * 2;
};
ctx.parallel_for(lA.shape(), lA.write(), temp.read())->*[] __device__(size_t i, auto a, auto temp) {
// Copy back
a(i) = temp(i) + 1;
};
// temp automatically cleaned up when scope ends
}
ctx.finalize();
}

View File

@@ -1,76 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment to see if we can read data generated in a nested context
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx sctx;
auto lA = sctx.logical_data(shape_of<slice<int>>(512));
lA.set_symbol("A");
sctx.parallel_for(lA.shape(), lA.write())->*[] __device__(size_t i, auto a) {
a(i) = 42 + 2 * i;
};
auto ltoken = sctx.token();
/* Create nested graph */
sctx.push();
lA.push(access_mode::rw);
ltoken.push(access_mode::rw);
auto ltoken2 = sctx.token();
auto lB = sctx.logical_data(lA.shape());
lB.set_symbol("B");
sctx.parallel_for(lB.shape(), lB.write(), lA.read(), ltoken.rw())->*[] __device__(size_t i, auto b, auto a) {
b(i) = 17 - 3 * a(i);
};
auto lC = mv(lB);
sctx.parallel_for(lC.shape(), lC.rw())->*[] __device__(size_t i, auto b) {
b(i) *= 2;
};
sctx.parallel_for(lA.shape(), lA.rw())->*[] __device__(size_t i, auto a) {
a(i) *= 3;
};
sctx.pop();
/* Access C in a context below the context where it was created */
sctx.host_launch(lC.read(), ltoken2.read())->*[](auto b, auto) {
for (size_t i = 0; i < b.size(); i++)
{
EXPECT(b(i) == 2 * (17 - 3 * (42 + 2 * i)));
}
};
sctx.host_launch(lA.read(), ltoken.read())->*[](auto a, auto) {
for (size_t i = 0; i < a.size(); i++)
{
EXPECT(a(i) == 3 * (42 + 2 * i));
}
};
sctx.finalize();
}

View File

@@ -1,100 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Experiment with local context nesting
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx sctx;
int array[1024];
int array2[1024];
int array3[1024];
int array4[1024];
for (size_t i = 0; i < 1024; i++)
{
array[i] = 1 + i * i;
array2[i] = 4 - i;
array3[i] = 19 + 5 * i;
array4[i] = 2 - i * i;
}
auto lA = sctx.logical_data(array).set_symbol("A");
auto lA2 = sctx.logical_data(array2).set_symbol("A2");
auto lA3 = sctx.logical_data(array3).set_symbol("A3");
auto lA4 = sctx.logical_data(array4).set_symbol("A4");
/* Start to use a graph */
sctx.push();
sctx.parallel_for(lA.shape(), lA.rw())->*[] __device__(size_t i, auto a) {
a(i) += 2 * i;
};
// Copy the (stackable) logical data, which is not a deep copy so modifying
// lA2_cpy should also modify lA2.
auto lA2_cpy = lA2;
sctx.parallel_for(lA2_cpy.shape(), lA2_cpy.rw())->*[] __device__(size_t i, auto a2) {
a2(i) += 4 * i;
};
auto lA3_mv = mv(lA3);
sctx.parallel_for(lA3_mv.shape(), lA3_mv.rw())->*[] __device__(size_t i, auto a3) {
a3(i) += i;
};
sctx.parallel_for(lA4.shape(), lA4.rw())->*[] __device__(size_t i, auto a4) {
a4(i) += 4 * i;
};
// force to move to a different place, and probably to allocate another copy on the host
// XXX (it seems that this write is not written back to the copy of array4 on the device when popping before we write
// back ?)
sctx.host_launch(lA4.rw())->*[](auto a4) {
for (size_t i = 0; i < 1024; i++)
{
a4(i) *= 2;
}
};
sctx.pop();
sctx.finalize();
// Ensure the write-back mechanism was effective
for (size_t i = 0; i < 1024; i++)
{
EXPECT(array[i] == (1 + i * i) + 2 * i);
}
for (size_t i = 0; i < 1024; i++)
{
EXPECT(array2[i] == (4 - i) + 4 * i);
}
for (size_t i = 0; i < 1024; i++)
{
EXPECT(array3[i] == (19 + 5 * i) + i);
}
for (size_t i = 0; i < 1024; i++)
{
EXPECT(array4[i] == 2 * (2 - i * i + 4 * i));
}
}

View File

@@ -1,201 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief Ensure back-to-back stream_ctx instances on a caller stream are ordered.
*
* The test submits two stream_ctx instances back-to-back on the same
* caller-provided stream, without an explicit synchronization between them.
* Each context launches independent token chains on STF pool streams. The
* second context writes value 2 into the same buffer written by the first
* context, so observing any value other than 2 means the contexts were not
* chained through the caller stream correctly.
*
* The explicit-sync and shared-handle variants exercise the same shape through
* the two configurations that were already known to be safe.
*/
#include <cuda/experimental/stf.cuh>
#include <algorithm>
#include <vector>
#include <cuda_runtime.h>
using namespace cuda::experimental::stf;
namespace
{
constexpr int N = 1 << 14;
constexpr int CHAIN_COUNT = 8;
constexpr int CHAIN_LEN = 40;
constexpr int OUTER = 5;
constexpr long long BUSY_CYCLES = 5'000'000;
__global__ void slow_set_kernel(int* slice, int n, int value, long long cycles)
{
const int tid = blockIdx.x * blockDim.x + threadIdx.x;
if (tid >= n)
{
return;
}
const long long start = clock64();
while (clock64() - start < cycles)
{
// busy wait
}
slice[tid] = value;
}
void submit_token_chains(stream_ctx& ctx, int* d_arr, int value)
{
std::vector<logical_data<void_interface>> toks;
toks.reserve(CHAIN_COUNT);
for (int k = 0; k < CHAIN_COUNT; ++k)
{
toks.push_back(ctx.token());
}
const int per_chain = N / CHAIN_COUNT;
for (int step = 0; step < CHAIN_LEN; ++step)
{
for (int k = 0; k < CHAIN_COUNT; ++k)
{
int* slice = d_arr + k * per_chain;
ctx.task(toks[k].rw())->*[=](cudaStream_t ts) {
const int threads = 128;
const int blocks = (per_chain + threads - 1) / threads;
slow_set_kernel<<<blocks, threads, 0, ts>>>(slice, per_chain, value, BUSY_CYCLES);
};
}
}
}
bool has_mismatch(const std::vector<int>& values, int expected)
{
return ::std::any_of(values.begin(), values.end(), [=](int x) {
return x != expected;
});
}
void validate_buffer(int* d_arr)
{
std::vector<int> h_arr(N, 0);
cuda_safe_call(cudaMemcpy(h_arr.data(), d_arr, N * sizeof(int), cudaMemcpyDeviceToHost));
EXPECT(!has_mismatch(h_arr, 2));
}
void run_no_handle_no_sync_once()
{
cudaStream_t stream{};
cuda_safe_call(cudaStreamCreate(&stream));
int* d_arr = nullptr;
{
cuda_safe_call(cudaMalloc(&d_arr, N * sizeof(int)));
cuda_safe_call(cudaMemsetAsync(d_arr, 0, N * sizeof(int), stream));
}
{
stream_ctx ctx(stream);
submit_token_chains(ctx, d_arr, 1);
ctx.finalize();
}
{
stream_ctx ctx(stream);
submit_token_chains(ctx, d_arr, 2);
ctx.finalize();
}
cuda_safe_call(cudaStreamSynchronize(stream));
validate_buffer(d_arr);
cuda_safe_call(cudaFree(d_arr));
cuda_safe_call(cudaStreamDestroy(stream));
}
void run_no_handle_sync_once()
{
cudaStream_t stream{};
cuda_safe_call(cudaStreamCreate(&stream));
int* d_arr = nullptr;
{
cuda_safe_call(cudaMalloc(&d_arr, N * sizeof(int)));
cuda_safe_call(cudaMemsetAsync(d_arr, 0, N * sizeof(int), stream));
}
{
stream_ctx ctx(stream);
submit_token_chains(ctx, d_arr, 1);
ctx.finalize();
}
cuda_safe_call(cudaStreamSynchronize(stream));
{
stream_ctx ctx(stream);
submit_token_chains(ctx, d_arr, 2);
ctx.finalize();
}
cuda_safe_call(cudaStreamSynchronize(stream));
validate_buffer(d_arr);
cuda_safe_call(cudaFree(d_arr));
cuda_safe_call(cudaStreamDestroy(stream));
}
void run_shared_handle_no_sync_once()
{
cudaStream_t stream{};
cuda_safe_call(cudaStreamCreate(&stream));
int* d_arr = nullptr;
{
cuda_safe_call(cudaMalloc(&d_arr, N * sizeof(int)));
cuda_safe_call(cudaMemsetAsync(d_arr, 0, N * sizeof(int), stream));
}
async_resources_handle handle;
{
stream_ctx ctx(stream, handle);
submit_token_chains(ctx, d_arr, 1);
ctx.finalize();
}
{
stream_ctx ctx(stream, handle);
submit_token_chains(ctx, d_arr, 2);
ctx.finalize();
}
cuda_safe_call(cudaStreamSynchronize(stream));
validate_buffer(d_arr);
cuda_safe_call(cudaFree(d_arr));
cuda_safe_call(cudaStreamDestroy(stream));
}
template <typename Test>
void repeat(Test test)
{
for (int i = 0; i < OUTER; ++i)
{
test();
}
}
} // namespace
int main()
{
repeat(run_no_handle_no_sync_once);
repeat(run_no_handle_sync_once);
repeat(run_shared_handle_no_sync_once);
}

View File

@@ -1,229 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief Test copy and move semantics for task dependency types
*
* This test verifies that task_dep_untyped, task_dep<T>, and stackable_task_dep<T>
* are properly copyable and movable, which is essential for the STF framework to work
* correctly with deferred operations and stackable contexts.
*/
#include <cuda/std/type_traits>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// Simple kernel for testing - defined outside to avoid device lambda nesting issues
__global__ void double_values_kernel(int* out, const int* in, int n)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
if (idx < n)
{
out[idx] = in[idx] * 2;
}
}
// Helper template to test copy/move operations on any type
template <typename T>
void test_copy_move_semantics(const T& original)
{
// Test copy constructor
{
T copy_constructed(original);
(void) copy_constructed; // Suppress unused variable warning
}
// Test move constructor
{
T temp(original);
T move_constructed(::std::move(temp));
(void) move_constructed; // Suppress unused variable warning
}
// Test copy assignment
{
T temp(original); // Create a temporary to assign to
T copy_assigned(original); // Initialize with copy constructor first
copy_assigned = temp; // Then test copy assignment
(void) copy_assigned; // Suppress unused variable warning
}
// Test move assignment
{
T temp(original);
T move_assigned(original); // Initialize with copy constructor first
move_assigned = ::std::move(temp); // Then test move assignment
(void) move_assigned; // Suppress unused variable warning
}
}
// Compile-time type trait tests
void test_compile_time_type_traits()
{
// Test task_dep_untyped
static_assert(::std::is_copy_constructible_v<task_dep_untyped>, "task_dep_untyped must be copy constructible");
static_assert(::std::is_copy_assignable_v<task_dep_untyped>, "task_dep_untyped must be copy assignable");
static_assert(::std::is_move_constructible_v<task_dep_untyped>, "task_dep_untyped must be move constructible");
static_assert(::std::is_move_assignable_v<task_dep_untyped>, "task_dep_untyped must be move assignable");
// Test task_dep<T> for common types
using int_task_dep = task_dep<slice<int>>;
static_assert(::std::is_copy_constructible_v<int_task_dep>, "task_dep<slice<int>> must be copy constructible");
static_assert(::std::is_copy_assignable_v<int_task_dep>, "task_dep<slice<int>> must be copy assignable");
static_assert(::std::is_move_constructible_v<int_task_dep>, "task_dep<slice<int>> must be move constructible");
static_assert(::std::is_move_assignable_v<int_task_dep>, "task_dep<slice<int>> must be move assignable");
using double_task_dep = task_dep<slice<double>>;
static_assert(::std::is_copy_constructible_v<double_task_dep>, "task_dep<slice<double>> must be copy constructible");
static_assert(::std::is_copy_assignable_v<double_task_dep>, "task_dep<slice<double>> must be copy assignable");
static_assert(::std::is_move_constructible_v<double_task_dep>, "task_dep<slice<double>> must be move constructible");
static_assert(::std::is_move_assignable_v<double_task_dep>, "task_dep<slice<double>> must be move assignable");
// Test stackable_task_dep<T> with proper template parameters
using int_stackable_task_dep = stackable_task_dep<slice<int>, ::std::monostate, false>;
static_assert(::std::is_copy_constructible_v<int_stackable_task_dep>,
"stackable_task_dep<slice<int>> must be copy constructible");
static_assert(::std::is_copy_assignable_v<int_stackable_task_dep>,
"stackable_task_dep<slice<int>> must be copy assignable");
static_assert(::std::is_move_constructible_v<int_stackable_task_dep>,
"stackable_task_dep<slice<int>> must be move constructible");
static_assert(::std::is_move_assignable_v<int_stackable_task_dep>,
"stackable_task_dep<slice<int>> must be move assignable");
using double_stackable_task_dep = stackable_task_dep<slice<double>, ::std::monostate, false>;
static_assert(::std::is_copy_constructible_v<double_stackable_task_dep>,
"stackable_task_dep<slice<double>> must be copy constructible");
static_assert(::std::is_copy_assignable_v<double_stackable_task_dep>,
"stackable_task_dep<slice<double>> must be copy assignable");
static_assert(::std::is_move_constructible_v<double_stackable_task_dep>,
"stackable_task_dep<slice<double>> must be move constructible");
static_assert(::std::is_move_assignable_v<double_stackable_task_dep>,
"stackable_task_dep<slice<double>> must be move assignable");
// Test stackable_logical_data<T>
using int_stackable_logical_data = stackable_logical_data<slice<int>>;
static_assert(::std::is_move_constructible_v<int_stackable_logical_data>,
"stackable_logical_data<slice<int>> must be move constructible");
static_assert(::std::is_move_assignable_v<int_stackable_logical_data>,
"stackable_logical_data<slice<int>> must be move assignable");
}
int main()
{
// Run compile-time tests
test_compile_time_type_traits();
// Create stackable context for runtime tests
stackable_ctx ctx;
const size_t N = 64;
// Create test data
auto ldata_int = ctx.logical_data(shape_of<slice<int>>(N));
auto ldata_double = ctx.logical_data(shape_of<slice<double>>(N));
// Test runtime copy/move semantics for stackable_task_dep
{
auto stackable_read_dep = ldata_int.read();
auto stackable_write_dep = ldata_int.write();
auto stackable_rw_dep = ldata_int.rw();
// Test copy/move operations on stackable task dependencies
test_copy_move_semantics(stackable_read_dep);
test_copy_move_semantics(stackable_write_dep);
test_copy_move_semantics(stackable_rw_dep);
// Test with different data types
auto stackable_double_dep = ldata_double.read();
test_copy_move_semantics(stackable_double_dep);
}
// Test that stackable logical data itself is movable
{
auto ldata_copy = ldata_int; // Copy
auto ldata_moved = mv(ldata_int); // Move using STF's mv() utility
// Verify we can still use the moved-to object
auto dep_from_moved = ldata_moved.read();
test_copy_move_semantics(dep_from_moved);
}
// Test that we can store task dependencies in containers (requires copy/move)
{
auto ldata_container_test = ctx.logical_data(shape_of<slice<float>>(32));
::std::vector<decltype(ldata_container_test.read())> read_deps;
read_deps.push_back(ldata_container_test.read());
read_deps.push_back(ldata_container_test.read());
::std::vector<decltype(ldata_container_test.write())> write_deps;
write_deps.emplace_back(ldata_container_test.write());
// Verify container operations work (copy, move, etc.)
auto read_deps_copy = read_deps; // Copy container
auto write_deps_moved = ::std::move(write_deps); // Move container
(void) read_deps_copy; // Suppress unused variable warnings
(void) write_deps_moved;
}
// Test functional usage with actual tasks to ensure copy/move semantics work in practice
{
auto ltest = ctx.logical_data(shape_of<slice<int>>(16));
// Initialize data
ctx.parallel_for(ltest.shape(), ltest.write())->*[] __device__(size_t i, auto data) {
data(i) = static_cast<int>(i);
};
ctx.push(); // Enter nested context
// Create dependencies that will be copied/moved through deferred operations
auto read_dep = ltest.read();
auto write_dep = ltest.write();
// Use add_deps which relies on copy/move semantics
auto task = ctx.task();
task.add_deps(read_dep); // This should copy/move the dependency
task.add_deps(write_dep); // This should copy/move the dependency
task->*[&task](cudaStream_t stream) {
// Get dependencies by index - this is how add_deps works
auto input = task.template get<slice<int>>(0); // First dependency (read)
auto output = task.template get<slice<int>>(1); // Second dependency (write)
// Simple kernel that doubles the values
int N = input.size();
int block_size = 256;
int grid_size = (N + block_size - 1) / block_size;
// Use the global kernel function to avoid device lambda nesting
double_values_kernel<<<grid_size, block_size, 0, stream>>>(output.data_handle(), input.data_handle(), N);
};
ctx.pop(); // Exit nested context
// Verify result
ctx.host_launch(ltest.read())->*[](auto data) {
for (size_t i = 0; i < data.size(); i++)
{
EXPECT(data(i) == static_cast<int>(i * 2), "Expected ", i * 2, " but got ", data(i), " at index ", i);
}
};
}
ctx.finalize();
return 0;
}

View File

@@ -1,109 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/experimental/stf.cuh>
#include <functional>
#include <thread>
using namespace cuda::experimental::stf;
void worker(
stream_ctx ctx, int id, frozen_logical_data<slice<int>> fAi, frozen_logical_data<slice<int>> fB, ::std::mutex& mutex)
{
cudaStream_t stream = ctx.pick_stream();
auto gctx = graph_ctx(stream);
mutex.lock();
auto dAi = fAi.get(data_place::current_device(), stream);
auto dB = fB.get(data_place::current_device(), stream);
mutex.unlock();
auto g_lAi = gctx.logical_data(dAi, data_place::current_device());
auto g_lB = gctx.logical_data(dB, data_place::current_device());
gctx.parallel_for(g_lAi.shape(), g_lAi.rw(), g_lB.read())->*[id] __device__(size_t j, auto ai, auto b) {
ai(j) += id + b(j);
};
gctx.finalize();
}
int main()
{
::std::mutex mutex;
stream_ctx ctx;
const int N = 128000;
const int NTHREADS = 8;
// A vector of per-thread vectors
::std::vector<::std::vector<int>> A(NTHREADS);
::std::vector<logical_data<slice<int>>> lA(NTHREADS);
for (int i = 0; i < NTHREADS; i++)
{
A[i].resize(N);
for (int j = 0; j < N; j++)
{
A[i][j] = (i + j);
}
lA[i] = ctx.logical_data(A[i].data(), {N}).set_symbol("A_" + ::std::to_string(i));
}
::std::vector<int> B(N);
logical_data<slice<int>> lB;
for (int j = 0; j < N; j++)
{
B[j] = (17 * j + 3);
}
lB = ctx.logical_data(B.data(), {N}).set_symbol("B");
::std::vector<frozen_logical_data<slice<int>>> fA(NTHREADS);
for (int i = 0; i < NTHREADS; i++)
{
fA[i] = ctx.freeze(lA[i], access_mode::rw, data_place::current_device());
fA[i].set_automatic_unfreeze(true);
}
auto fB = ctx.freeze(lB, access_mode::read, data_place::current_device());
fB.set_automatic_unfreeze(true);
::std::vector<::std::thread> threads;
for (int i = 0; i < NTHREADS; ++i)
{
threads.emplace_back(worker, ctx, i, fA[i], fB, ::std::ref(mutex));
}
cudaStream_t stream = ctx.pick_stream();
for (int i = 0; i < NTHREADS; ++i)
{
threads[i].join();
fA[i].unfreeze(stream);
}
fB.unfreeze(stream);
for (int i = 0; i < NTHREADS; ++i)
{
ctx.host_launch(lA[i].read())->*[i](auto ai) {
for (size_t j = 0; j < N; j++)
{
EXPECT(ai(j) == (i + j) + (i + (17 * j + 3)));
}
};
}
ctx.finalize();
}