[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:
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user