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

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

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

View File

@@ -1,94 +0,0 @@
if (NOT CCCL_ENABLE_CUDAX)
include(cmake/cudaxAddSubdir.cmake)
return()
endif()
cmake_minimum_required(VERSION 3.21)
project(cudax LANGUAGES CXX CUDA)
option(
cudax_ENABLE_HEADER_TESTING
"Test that CUDA Experimental's public headers compile."
ON
)
option(cudax_ENABLE_TESTING "Build CUDA Experimental's tests." ON)
option(cudax_ENABLE_EXAMPLES "Build CUDA Experimental's examples." ON)
option(cudax_ENABLE_PLACES "Enable standalone Places subproject" ON)
option(cudax_ENABLE_CUDASTF "Enable CUDASTF subproject" ON)
option(
cudax_ENABLE_CUDASTF_CODE_GENERATION
"Enable code generation using STF's parallel_for or launch with CUDA compiler."
ON
)
option(
cudax_ENABLE_CUDASTF_BOUNDSCHECK
"Enable bounds checks for STF targets. Requires debug build."
OFF
)
option(
cudax_ENABLE_CUDASTF_MATHLIBS
"Enable STF tests/examples that use cublas/cusolver."
OFF
)
option(cudax_ENABLE_CUFILE "Enable cuFile in CUDA Experimental" ON)
if (cudax_ENABLE_CUFILE)
if (WIN32)
message(FATAL_ERROR "cuFile is not available on Windows.")
endif()
if (CMAKE_VERSION VERSION_LESS "3.25.0")
message(
FATAL_ERROR
"cuFile is not available before cmake 3.25.0, please, use newer cmake."
)
endif()
cccl_get_cudatoolkit()
if (CUDAToolkit_VERSION VERSION_LESS "12.9.0")
message(FATAL_ERROR "cuFile support requires at least CUDA 12.9.")
endif()
if (NOT TARGET CUDA::cuFile)
message(
FATAL_ERROR
"CUDA::cuFile target required for requested cuFile support was not found."
)
endif()
endif()
if (
cudax_ENABLE_CUDASTF_BOUNDSCHECK
AND NOT CMAKE_BUILD_TYPE MATCHES "Debug"
AND NOT CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo"
)
message(
FATAL_ERROR
"cudax_ENABLE_CUDASTF_BOUNDSCHECK requires a Debug build."
)
endif()
include(cmake/cudaxBuildCompilerTargets.cmake)
if (cudax_ENABLE_PLACES)
include(cmake/cudaxPlacesConfigureTarget.cmake)
endif()
if (cudax_ENABLE_CUDASTF)
include(cmake/cudaxSTFConfigureTarget.cmake)
endif()
if (cudax_ENABLE_HEADER_TESTING)
include(cmake/cudaxHeaderTesting.cmake)
endif()
if (cudax_ENABLE_TESTING)
add_subdirectory(test)
endif()
if (cudax_ENABLE_EXAMPLES)
add_subdirectory(examples)
endif()
if (CCCL_ENABLE_BENCHMARKS)
add_subdirectory(benchmarks)
endif()

View File

@@ -1,222 +0,0 @@
====================================================================================
The CUDA Experimental library is under the Apache License v2.0 with LLVM Exceptions:
====================================================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
---- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.

View File

@@ -1,32 +0,0 @@
## CUDA Experimental: Library for experimental features in CUDA Core Compute Libraries.
CUDA Experimental serves as a distribution channel for features that are considered experimental in the CUDA Core Compute Libraries.
Some of them are still actively designed or developed and their API is evolving.
Some of them are specific to one hardware architecture and are still looking for a generic and forward compatible exposure.
Finally, some of them need to prove useful enough to deserve long term support.
**All APIs available in CUDA Experimental are not considered stable and can change without a notice.** They can also be deprecated or removed on a much faster cadence than in other CCCL libraries.
Features are exposed here for the CUDA C++ community to experiment with and provide feedback on how to shape it to best fit their use cases.
Once we become confident a feature is ready and would be a great permanent addition in CCCL, it will become a part of some other CCCL library with a stable API.
## Installation
CUDA Experimental library is **not** distributed with the CUDA Toolkit like the rest of CCCL. It is only available on the [CCCL GitHub repository](https://github.com/NVIDIA/cccl).
CUDA Experimental compilation requires C++17 standard or newer. Supported compilers are:
CUDA Compilers:
- NVCC 12.3+
NVCC host compilers:
- GCC 7+
- Clang 9+
- MSVC 2019+
Everything in CUDA Experimental is header-only, so cloning and including it in a simple project is as easy as the following:
```bash
git clone https://github.com/NVIDIA/cccl.git
# Note:
nvcc -Icccl/cudax/include main.cu -o main
```
A CMake target `cudax::cudax` is available as part of the CCCL package when `CCCL_ENABLE_UNSTABLE` is set to a truthy value before calling `find_package` or `add_subdirectory`.

View File

@@ -1,69 +0,0 @@
include(${CMAKE_SOURCE_DIR}/benchmarks/cmake/CCCLBenchmarkRegistry.cmake)
cccl_get_nvbench()
cccl_get_nvbench_helper()
set(benches_root "${CMAKE_CURRENT_LIST_DIR}")
function(get_recursive_subdirs subdirs)
set(dirs)
file(
GLOB_RECURSE contents
CONFIGURE_DEPENDS
LIST_DIRECTORIES ON
"${CMAKE_CURRENT_LIST_DIR}/bench/*"
)
foreach (bench_dir IN LISTS contents)
if (IS_DIRECTORY "${bench_dir}")
list(APPEND dirs "${bench_dir}")
endif()
endforeach()
set(${subdirs} "${dirs}" PARENT_SCOPE)
endfunction()
function(add_bench target_name bench_name bench_src)
set(bench_target ${bench_name})
set(${target_name} ${bench_target} PARENT_SCOPE)
cccl_add_executable(${bench_target} SOURCES "${bench_src}")
target_link_libraries(
${bench_target}
PRIVATE #
cccl.nvbench_helper
nvbench::main
)
endfunction()
function(add_bench_dir bench_dir)
file(GLOB bench_srcs CONFIGURE_DEPENDS "${bench_dir}/*.cu")
file(RELATIVE_PATH bench_prefix "${benches_root}" "${bench_dir}")
file(TO_CMAKE_PATH "${bench_prefix}" bench_prefix)
string(REPLACE "/" "." bench_prefix "${bench_prefix}")
foreach (bench_src IN LISTS bench_srcs)
get_filename_component(bench_name "${bench_src}" NAME_WLE)
string(PREPEND bench_name "cudax.${bench_prefix}.")
register_cccl_benchmark("${bench_name}" "")
string(APPEND bench_name ".base")
add_bench(base_bench_target ${bench_name} "${bench_src}")
target_link_libraries(${bench_name} PRIVATE cudax.compiler_interface)
target_compile_options(
${bench_name}
PRIVATE
"$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>"
# cudax.compiler_interface enables assertions for tests/examples; benchmarks should measure release behavior.
"$<$<COMPILE_LANGUAGE:CUDA>:-UCCCL_ENABLE_ASSERTIONS>"
"$<$<COMPILE_LANGUAGE:CXX>:-UCCCL_ENABLE_ASSERTIONS>"
)
endforeach()
endfunction()
get_recursive_subdirs(subdirs)
foreach (subdir IN LISTS subdirs)
add_bench_dir("${subdir}")
endforeach()

View File

@@ -1,331 +0,0 @@
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <thrust/device_vector.h>
#include <cuda/mdspan>
#include <cuda/std/array>
#include <cuda/stream>
#include <cuda/experimental/copy.cuh>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <nvbench/nvbench.cuh>
// GCC -Warray-bounds false positive for high-rank (20+) __raw_tensor instantiations
_CCCL_DIAG_SUPPRESS_GCC("-Warray-bounds")
template <size_t Rank, typename idx_t>
size_t
compute_alloc(size_t offset, const cuda::std::array<idx_t, Rank>& shape, const cuda::std::array<idx_t, Rank>& strides)
{
int64_t max_pos = static_cast<int64_t>(offset);
for (size_t i = 0; i < Rank; ++i)
{
auto delta = static_cast<ptrdiff_t>(shape[i] - 1) * strides[i];
if (delta > 0)
{
max_pos += delta;
}
}
return max_pos + 1;
}
template <typename data_t = int, typename idx_t = int, size_t Rank>
void bench_copy(nvbench::state& state,
size_t src_offset,
const cuda::std::array<idx_t, Rank>& shape,
const cuda::std::array<idx_t, Rank>& src_strides,
size_t dst_offset,
const cuda::std::array<idx_t, Rank>& dst_strides)
{
const auto src_alloc = compute_alloc(src_offset, shape, src_strides);
const auto dst_alloc = compute_alloc(dst_offset, shape, dst_strides);
thrust::device_vector<data_t> d_src(src_alloc);
thrust::device_vector<data_t> d_dst(dst_alloc);
size_t num_items = 1;
for (size_t i = 0; i < Rank; ++i)
{
num_items *= shape[i];
}
state.add_element_count(num_items);
state.add_global_memory_reads<data_t>(num_items);
state.add_global_memory_writes<data_t>(num_items);
using extents_t = cuda::std::dextents<idx_t, Rank>;
using strides_t = cuda::dstrides<idx_t, Rank>;
using mapping_t = cuda::layout_stride_relaxed::mapping<extents_t>;
extents_t ext(shape);
auto src_ptr = thrust::raw_pointer_cast(d_src.data()) + src_offset;
auto dst_ptr = thrust::raw_pointer_cast(d_dst.data()) + dst_offset;
mapping_t src_map(ext, strides_t(src_strides));
mapping_t dst_map(ext, strides_t(dst_strides));
cuda::device_mdspan<data_t, extents_t, cuda::layout_stride_relaxed> src(src_ptr, src_map);
cuda::device_mdspan<data_t, extents_t, cuda::layout_stride_relaxed> dst(dst_ptr, dst_map);
state.exec([&](nvbench::launch& launch) {
cuda::stream_ref stream{launch.get_stream()};
cuda::experimental::copy(src, dst, stream);
});
}
template <typename data_t = int, typename idx_t = int, size_t Rank>
void bench_copy(nvbench::state& state,
size_t offset,
const cuda::std::array<idx_t, Rank>& shape,
const cuda::std::array<idx_t, Rank>& strides)
{
bench_copy<data_t>(state, offset, shape, strides, offset, strides);
}
/***********************************************************************************************************************
* Memcpy benchmarks
**********************************************************************************************************************/
// src: (25, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
// dst: (25, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
void memcpy_layout_0(nvbench::state& state)
{
cuda::std::array<int, 5> shape{25, 70, 90, 80, 80};
cuda::std::array<int, 5> strides{40320000, 576000, 6400, 80, 1};
bench_copy(state, 0, shape, strides);
}
NVBENCH_BENCH(memcpy_layout_0).set_name("contiguous (5D, int, 4GB)");
// src: (25, 80, 70, 80, 90):(40320000, 1, 576000, 80, 6400)
// dst: (25, 80, 70, 80, 90):(40320000, 1, 576000, 80, 6400)
void memcpy_layout_1(nvbench::state& state)
{
cuda::std::array<int, 5> shape{25, 80, 70, 80, 90};
cuda::std::array<int, 5> strides{40320000, 1, 576000, 80, 6400};
bench_copy(state, 0, shape, strides);
}
NVBENCH_BENCH(memcpy_layout_1).set_name("contiguous-perm (5D, int, 4GB)");
// src: (1, 25, 1, 80, 1, 70, 1, 80, 1, 90):(1, 40320000, 1, 1, 1, 576000, 1, 80, 1, 6400)
// dst: (1, 25, 1, 80, 1, 70, 1, 80, 1, 90):(1, 40320000, 1, 1, 1, 576000, 1, 80, 1, 6400)
void memcpy_layout_1b(nvbench::state& state)
{
cuda::std::array<int, 10> shape{1, 25, 1, 80, 1, 70, 1, 80, 1, 90};
cuda::std::array<int, 10> strides{1, 40320000, 1, 1, 1, 576000, 1, 80, 1, 6400};
bench_copy(state, 0, shape, strides);
}
NVBENCH_BENCH(memcpy_layout_1b).set_name("contiguous-1-sized (10D, int, 4GB)");
// src: (25, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
// dst: (25, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
void memcpy_layout_2(nvbench::state& state)
{
cuda::std::array<int, 5> shape{25, 70, 90, 80, 80};
cuda::std::array<int, 5> strides{40320000, 576000, 6400, 80, 1};
bench_copy(state, 1, shape, strides);
}
NVBENCH_BENCH(memcpy_layout_2).set_name("contiguous-not-aligned (5D, int, 4GB)");
// src: (100, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
// dst: (100, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
void memcpy_layout_3(nvbench::state& state)
{
cuda::std::array<int64_t, 5> shape{100, 70, 90, 80, 80};
cuda::std::array<int64_t, 5> strides{40320000, 576000, 6400, 80, 1};
bench_copy<char, int64_t>(state, 0, shape, strides);
}
NVBENCH_BENCH(memcpy_layout_3).set_name("contiguous-small (5D, char, 4GB)");
// src: (100, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
// dst: (100, 70, 90, 80, 80):(40320000, 576000, 6400, 80, 1)
void memcpy_layout_4(nvbench::state& state)
{
cuda::std::array<int64_t, 5> shape{100, 70, 90, 80, 80};
cuda::std::array<int64_t, 5> strides{40320000, 576000, 6400, 80, 1};
bench_copy<char, int64_t>(state, 1, shape, strides);
}
NVBENCH_BENCH(memcpy_layout_4).set_name("contiguous-small-not-aligned (5D, char, 4GB)");
// src: (25, 70, 90, 80, 80):(40320000, 576000, 6400, 80, -1), offset=80
// dst: (25, 70, 90, 80, 80):(40320000, 576000, 6400, 80, -1), offset=80
void memcpy_neg(nvbench::state& state)
{
cuda::std::array<int, 5> shape{25, 70, 90, 80, 80};
cuda::std::array<int, 5> strides{40320000, 576000, 6400, 80, -1};
bench_copy(state, 80, shape, strides);
}
NVBENCH_BENCH(memcpy_neg).set_name("contiguous-negative-stride (5D, int, 4GB)");
// src: (134217600, 32):(128, 1), offset=32
// dst: (134217600, 32):(128, 1), offset=32
// Copies 4GB while allocating 16GB per tensor because of the padded outer stride.
void vectorization(nvbench::state& state)
{
cuda::std::array<int64_t, 2> shape{134217600, 32};
cuda::std::array<int64_t, 2> strides{128, 1};
bench_copy<char, int64_t>(state, 32, shape, strides);
}
NVBENCH_BENCH(vectorization).set_name("vectorization (2D, char, 4GB copy, 16GB alloc)");
// src: (32767, (128 * 1024) / sizeof(int)):(128 * 1024, 1)
// dst: (32767, (128 * 1024) / sizeof(int)):(128 * 1024, 1)
// Copies 4GB while allocating 16GB per tensor because each row is padded to 128K elements.
void block_contiguous(nvbench::state& state)
{
cuda::std::array<int, 2> shape{32767, (128 * 1024) / sizeof(int)};
cuda::std::array<int, 2> strides{128 * 1024, 1};
bench_copy(state, 0, shape, strides);
}
NVBENCH_BENCH(block_contiguous).set_name("block-contiguous (2D, int, 4GB copy, 16GB alloc)");
// (non-vectorizable)
void several_dimensions(nvbench::state& state)
{
cuda::std::array<int, 5> shape{64, 64, 64, 64, 64};
cuda::std::array<int, 5> strides{17043520 + 1, 266304 + 1, 4160 + 1, 64 + 1, 1};
bench_copy(state, 0, shape, strides);
}
NVBENCH_BENCH(several_dimensions).set_name("several_dimensions (5D, int, 4GB)");
void several_dimensions_non_square(nvbench::state& state)
{
cuda::std::array<int, 5> shape{63, 65, 67, 69, 57};
cuda::std::array<int, 5> strides{17433131, 268202, 4003, 58, 1};
bench_copy(state, 0, shape, strides);
}
NVBENCH_BENCH(several_dimensions_non_square).set_name("several_dimensions_non_square (5D, int, 4GB)");
/***********************************************************************************************************************
* Transpose benchmark
**********************************************************************************************************************/
// src: (32768,32768):(1,32768)
// dst: (32768,32768):(32768,1)
void transpose_2D_col_row(nvbench::state& state)
{
cuda::std::array<int, 2> shape{32768, 32768};
cuda::std::array<int, 2> src_strides{1, 32768};
cuda::std::array<int, 2> dst_strides{32768, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_2D_col_row).set_name("transpose_2D_col_row (2D, int, 4GB)");
void transpose_2D_row_col(nvbench::state& state)
{
cuda::std::array<int, 2> shape{32768, 32768};
cuda::std::array<int, 2> src_strides{32768, 1};
cuda::std::array<int, 2> dst_strides{1, 32768};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_2D_row_col).set_name("transpose_2D_row_col (2D, int, 4GB)");
void transpose_2D_char(nvbench::state& state)
{
cuda::std::array<int64_t, 2> shape{65536, 65536};
cuda::std::array<int64_t, 2> src_strides{1, 65536};
cuda::std::array<int64_t, 2> dst_strides{65536, 1};
bench_copy<char, int64_t>(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_2D_char).set_name("transpose_2D_char (2D, char, 4GB)");
void transpose_2D_short(nvbench::state& state)
{
cuda::std::array<int, 2> shape{32760, 32768 * 2};
cuda::std::array<int, 2> src_strides{1, 32760};
cuda::std::array<int, 2> dst_strides{32768 * 2, 1};
bench_copy<short>(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_2D_short).set_name("transpose_2D_short (2D, short, 4GB)");
void transpose_2D_double(nvbench::state& state)
{
cuda::std::array<int64_t, 2> shape{32768, 16384};
cuda::std::array<int64_t, 2> src_strides{1, 32768};
cuda::std::array<int64_t, 2> dst_strides{16384, 1};
bench_copy<double, int64_t>(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_2D_double).set_name("transpose_2D_double (2D, double, 4GB)");
void transpose_2D_odd_both(nvbench::state& state)
{
cuda::std::array<int, 2> shape{32767, 32769};
cuda::std::array<int, 2> src_strides{1, 32767};
cuda::std::array<int, 2> dst_strides{32769, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_2D_odd_both).set_name("transpose_2D_odd_both (2D, int, 4GB)");
void transpose_3D(nvbench::state& state)
{
cuda::std::array<int, 3> shape{1024, 1024, 1024};
cuda::std::array<int, 3> src_strides{1, 1024, 1024 * 1024};
cuda::std::array<int, 3> dst_strides{1024 * 1024, 1024, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_3D).set_name("transpose_3D (3D, int, 4GB)");
void transpose_3D_odd_edges(nvbench::state& state)
{
cuda::std::array<int, 3> shape{1023, 1025, 1024};
cuda::std::array<int, 3> src_strides{1, 1023, 1023 * 1025};
cuda::std::array<int, 3> dst_strides{1025 * 1024, 1024, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_3D_odd_edges).set_name("transpose_3D_odd_edges (3D, int, 4GB)");
void transpose_src_small_15(nvbench::state& state)
{
cuda::std::array<int, 3> shape{15, 2236962, 32};
cuda::std::array<int, 3> src_strides{1, 15 * 32, 15};
cuda::std::array<int, 3> dst_strides{2236962 * 32, 32, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_src_small_15).set_name("transpose_src_small_15 (3D, int, 4GB)");
void transpose_src_small_16(nvbench::state& state)
{
cuda::std::array<int, 3> shape{16, 2097152, 32};
cuda::std::array<int, 3> src_strides{1, 16 * 32, 16};
cuda::std::array<int, 3> dst_strides{2097152 * 32, 32, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_src_small_16).set_name("transpose_src_small_16 (3D, int, 4GB)");
void transpose_src_small_17(nvbench::state& state)
{
cuda::std::array<int, 3> shape{17, 1973790, 32};
cuda::std::array<int, 3> src_strides{1, 17 * 32, 17};
cuda::std::array<int, 3> dst_strides{1973790 * 32, 32, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_src_small_17).set_name("transpose_src_small_17 (3D, int, 4GB)");
void transpose_dst_small_8_padded(nvbench::state& state)
{
cuda::std::array<int64_t, 3> shape{32, 4194304, 8};
cuda::std::array<int64_t, 3> src_strides{1, 32 * 8, 32};
cuda::std::array<int64_t, 3> dst_strides{4194304 * 16, 16, 1};
bench_copy<int, int64_t>(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_dst_small_8_padded).set_name("transpose_dst_small_8_padded (3D, int, 4GB)");
void transpose_dst_small_16_padded(nvbench::state& state)
{
cuda::std::array<int64_t, 3> shape{32, 2097152, 16};
cuda::std::array<int64_t, 3> src_strides{1, 32 * 16, 32};
cuda::std::array<int64_t, 3> dst_strides{2097152 * 32, 32, 1};
bench_copy<int, int64_t>(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_dst_small_16_padded).set_name("transpose_dst_small_16_padded (3D, int, 4GB)");
void transpose_src_small_16_4D(nvbench::state& state)
{
cuda::std::array<int, 4> shape{16, 1024, 2048, 32};
cuda::std::array<int, 4> src_strides{1, 16 * 32, 16 * 32 * 1024, 16};
cuda::std::array<int, 4> dst_strides{1024 * 2048 * 32, 32, 1024 * 32, 1};
bench_copy(state, 0, shape, src_strides, 0, dst_strides);
}
NVBENCH_BENCH(transpose_src_small_16_4D).set_name("transpose_src_small_16_4D (4D, int, 4GB)");

View File

@@ -1,44 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <cuda/std/cstdint>
#include <vector>
#include <nvbench/nvbench.cuh>
#include <nvbench/range.cuh>
namespace cuda::experimental::cuco::benchmark::defaults
{
//! Key types covered by the default CUCO benchmark type axes.
using key_type_range = ::nvbench::type_list<::nvbench::int32_t, ::nvbench::int64_t>;
//! Value types covered by the default CUCO benchmark type axes.
using value_type_range = ::nvbench::type_list<::nvbench::int32_t, ::nvbench::int64_t>;
//! Default number of inputs used when sweeping another benchmark axis.
inline constexpr auto n = ::nvbench::int64_t{100'000'000};
//! Default fixed-capacity map target occupancy.
inline constexpr auto occupancy = 0.5;
//! Default lookup matching rate for contains-style benchmarks.
inline constexpr auto matching_rate = 1.0;
//! Default deterministic seed used by benchmark data generators.
inline constexpr auto seed = ::cuda::std::uint32_t{42};
//! Input-size sweep that remains cacheable for direct comparisons with CUCO benchmarks.
inline const auto n_range_cache = ::std::vector<::nvbench::int64_t>{8'000, 80'000, 800'000, 8'000'000, 80'000'000};
//! Occupancy sweep used by fixed-capacity container benchmarks.
inline const auto occupancy_range = ::nvbench::range(0.1, 0.9, 0.1);
//! Average multiplicity sweep for duplicate-key distributions.
inline const auto multiplicity_range = ::std::vector<double>{1.0, 2.0, 4.0, 8.0, 16.0};
//! Matching-rate sweep used by contains-style benchmarks.
inline const auto matching_rate_range = ::nvbench::range(0.1, 1.0, 0.1);
} // namespace cuda::experimental::cuco::benchmark::defaults

View File

@@ -1,285 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <thrust/execution_policy.h>
#include <thrust/random.h>
#include <thrust/sequence.h>
#include <thrust/shuffle.h>
#include <thrust/transform.h>
#include <cuda/iterator>
#include <cuda/std/cmath>
#include <cuda/std/cstddef>
#include <cuda/std/cstdint>
#include <cuda/std/iterator>
#include <cuda/std/limits>
#include <cuda/std/type_traits>
#include <stdexcept>
#include "defaults.cuh"
#include <nvbench/nvbench.cuh>
namespace cuda::experimental::cuco::benchmark
{
namespace distribution
{
//! Distribution tag for unique keys generated by shuffling the sequence `[0, N)`.
struct unique
{};
//! Distribution tag for uniformly sampled keys with controlled average multiplicity.
struct uniform
{
//! Constructs a uniform distribution tag with the requested average key multiplicity.
//!
//! @param multiplicity Average number of generated keys that map to each unique key value.
//! @throws std::invalid_argument if `multiplicity` is not finite or is less than 1.0.
explicit uniform(double multiplicity)
: multiplicity{multiplicity}
{
if (!cuda::std::isfinite(multiplicity) || multiplicity < 1.0)
{
throw ::std::invalid_argument{"Multiplicity must be finite and at least 1"};
}
}
//! Average number of generated keys that map to each unique key value.
double multiplicity;
};
} // namespace distribution
namespace detail
{
template <typename Key, typename Distribution, typename Rng>
struct generate_uniform_fn
{
__host__ __device__ constexpr generate_uniform_fn(cuda::std::size_t num, Distribution dist, cuda::std::size_t seed)
: num{num}
, dist{dist}
, seed{seed}
{}
__host__ __device__ constexpr Key operator()(cuda::std::size_t idx) const noexcept
{
Rng rng;
rng.seed(seed + idx * 1664525ull + 1013904223ull);
const auto num_unique_keys_unclamped =
static_cast<cuda::std::size_t>(cuda::std::ceil(static_cast<double>(num) / dist.multiplicity));
const auto num_unique_keys =
num_unique_keys_unclamped < cuda::std::size_t{1} ? cuda::std::size_t{1} : num_unique_keys_unclamped;
thrust::uniform_int_distribution<Key> key_dist{Key{0}, static_cast<Key>(num_unique_keys - 1)};
return key_dist(rng);
}
cuda::std::size_t num;
Distribution dist;
cuda::std::size_t seed;
};
template <typename Key, typename Rng>
struct dropout_fn
{
__host__ __device__ constexpr explicit dropout_fn(cuda::std::size_t num)
: num{num}
{}
__host__ __device__ Key operator()(cuda::std::size_t seed) const noexcept
{
Rng rng;
thrust::uniform_int_distribution<Key> dist{static_cast<Key>(num), cuda::std::numeric_limits<Key>::max()};
rng.seed(seed);
return dist(rng);
}
cuda::std::size_t num;
};
template <typename Rng>
struct dropout_pred
{
__host__ __device__ constexpr explicit dropout_pred(double keep_prob)
: keep_prob{keep_prob}
{}
__host__ __device__ bool operator()(cuda::std::size_t seed) const noexcept
{
Rng rng;
thrust::uniform_real_distribution<double> dist{0.0, 1.0};
rng.seed(seed);
return dist(rng) > keep_prob;
}
double keep_prob;
};
} // namespace detail
//! Random key generator used by CUCO benchmarks.
//!
//! The generator defaults to `defaults::seed` to keep benchmark data reproducible across runs.
//!
//! @tparam Rng Pseudo-random number generator type compatible with Thrust random distributions.
template <typename Rng = thrust::default_random_engine>
class key_generator
{
public:
//! Constructs a key generator with the given seed.
//!
//! @param seed Seed used to initialize the generator state.
explicit key_generator(cuda::std::uint32_t seed = defaults::seed)
: rng{seed}
{}
//! Generates keys according to the given distribution using the default device execution policy.
//!
//! @tparam Distribution Distribution tag type.
//! @tparam OutputIt Output iterator type whose value type is the generated key type.
//! @param dist Distribution tag controlling how keys are generated.
//! @param out_begin Beginning of the output key range.
//! @param out_end End of the output key range.
//! @throws std::invalid_argument if `Distribution` is not a supported distribution tag.
template <typename Distribution, typename OutputIt>
void generate(Distribution dist, OutputIt out_begin, OutputIt out_end)
{
generate(dist, out_begin, out_end, thrust::device);
}
//! Generates keys according to the given distribution using the provided execution policy.
//!
//! @tparam Distribution Distribution tag type.
//! @tparam OutputIt Output iterator type whose value type is the generated key type.
//! @tparam ExecPolicy Thrust execution policy type.
//! @param dist Distribution tag controlling how keys are generated.
//! @param out_begin Beginning of the output key range.
//! @param out_end End of the output key range.
//! @param exec_policy Execution policy used for the underlying Thrust algorithms.
//! @throws std::invalid_argument if `Distribution` is not a supported distribution tag.
template <typename Distribution, typename OutputIt, typename ExecPolicy>
void generate(Distribution dist, OutputIt out_begin, OutputIt out_end, ExecPolicy exec_policy)
{
using value_type = typename cuda::std::iterator_traits<OutputIt>::value_type;
if constexpr (cuda::std::is_same_v<Distribution, distribution::unique>)
{
thrust::sequence(exec_policy, out_begin, out_end, value_type{0});
thrust::shuffle(exec_policy, out_begin, out_end, rng);
}
else if constexpr (cuda::std::is_same_v<Distribution, distribution::uniform>)
{
const auto num_keys = static_cast<cuda::std::size_t>(cuda::std::distance(out_begin, out_end));
const auto seed = static_cast<cuda::std::size_t>(rng());
thrust::transform(
exec_policy,
cuda::counting_iterator<cuda::std::size_t>{0},
cuda::counting_iterator<cuda::std::size_t>{num_keys},
out_begin,
detail::generate_uniform_fn<value_type, Distribution, Rng>{num_keys, dist, seed});
}
else
{
throw ::std::invalid_argument{"Unexpected distribution type"};
}
}
//! Drops keys with probability `1 - keep_prob` using the default device execution policy.
//!
//! Replaced keys are sampled from `[N, max_key]`, where `N` is the number of keys in the range.
//!
//! The full range is shuffled afterward, even when all keys are kept.
//!
//! @tparam InOutIt Mutable iterator type whose value type is the key type.
//! @param begin Beginning of the key range to update in place.
//! @param end End of the key range to update in place.
//! @param keep_prob Probability of keeping each original key. Must be in `[0, 1]`.
//! @throws std::invalid_argument if `keep_prob` is outside `[0, 1]`.
template <typename InOutIt>
void dropout(InOutIt begin, InOutIt end, double keep_prob)
{
dropout(begin, end, keep_prob, thrust::device);
}
//! Drops keys with probability `1 - keep_prob` using the provided execution policy.
//!
//! Replaced keys are sampled from `[N, max_key]`, where `N` is the number of keys in the range.
//!
//! The full range is shuffled afterward, even when all keys are kept.
//!
//! @tparam InOutIt Mutable iterator type whose value type is the key type.
//! @tparam ExecPolicy Thrust execution policy type.
//! @param begin Beginning of the key range to update in place.
//! @param end End of the key range to update in place.
//! @param keep_prob Probability of keeping each original key. Must be in `[0, 1]`.
//! @param exec_policy Execution policy used for the underlying Thrust algorithms.
//! @throws std::invalid_argument if `keep_prob` is outside `[0, 1]`.
template <typename InOutIt, typename ExecPolicy>
void dropout(InOutIt begin, InOutIt end, double keep_prob, ExecPolicy exec_policy)
{
using value_type = typename cuda::std::iterator_traits<InOutIt>::value_type;
if (keep_prob < 0.0 || keep_prob > 1.0)
{
throw ::std::invalid_argument{"Probability needs to be between 0 and 1"};
}
if (keep_prob < 1.0)
{
const auto num_keys = static_cast<cuda::std::size_t>(cuda::std::distance(begin, end));
cuda::counting_iterator<cuda::std::size_t> seeds{static_cast<cuda::std::size_t>(rng())};
thrust::transform_if(
exec_policy,
seeds,
seeds + num_keys,
begin,
detail::dropout_fn<value_type, Rng>{num_keys},
detail::dropout_pred<Rng>{keep_prob});
}
thrust::shuffle(exec_policy, begin, end, rng);
}
private:
Rng rng;
};
//! Constructs the requested distribution tag from NVBench axis values.
//!
//! `distribution::uniform` reads the `Multiplicity` axis from `state`.
//!
//! @tparam Distribution Distribution tag type to construct.
//! @param state NVBench state containing distribution-specific axis values.
//! @return Distribution tag initialized from the benchmark state.
//! @throws std::invalid_argument if `Distribution` is not a supported distribution tag.
template <typename Distribution>
Distribution dist_from_state(nvbench::state const& state)
{
if constexpr (cuda::std::is_same_v<Distribution, distribution::unique>)
{
return Distribution{};
}
else if constexpr (cuda::std::is_same_v<Distribution, distribution::uniform>)
{
return Distribution{state.get_float64("Multiplicity")};
}
else
{
throw ::std::invalid_argument{"Unexpected distribution type"};
}
}
} // namespace cuda::experimental::cuco::benchmark
NVBENCH_DECLARE_TYPE_STRINGS(
cuda::experimental::cuco::benchmark::distribution::unique, "UNIQUE", "distribution::unique");
NVBENCH_DECLARE_TYPE_STRINGS(
cuda::experimental::cuco::benchmark::distribution::uniform, "UNIFORM", "distribution::uniform");

View File

@@ -1,110 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <thrust/execution_policy.h>
#include <thrust/transform.h>
#include <cuda/buffer>
#include <cuda/memory_resource>
#include <cuda/std/cstddef>
#include <cuda/std/functional>
#include <cuda/std/utility>
#include <cuda/stream>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <cuda/experimental/__cuco/types.cuh>
#include "../common/defaults.cuh"
#include "../common/key_generator.cuh"
#include <nvbench/nvbench.cuh>
namespace cudax = cuda::experimental;
namespace bench = cudax::cuco::benchmark;
/**
* @brief A benchmark evaluating `cudax::cuco::fixed_capacity_map::contains_async` performance.
*/
template <typename Key, typename Value, typename Dist>
void fixed_capacity_map_contains(nvbench::state& state, nvbench::type_list<Key, Value, Dist>)
{
if constexpr (sizeof(Key) != sizeof(Value))
{
state.skip("Key and Value must have the same size.");
}
else
{
using pair_type = cuda::std::pair<Key, Value>;
using map_type = cudax::cuco::fixed_capacity_map<Key, Value>;
const auto num_keys = state.get_int64("NumInputs");
const auto occupancy = state.get_float64("Occupancy");
const auto matching_rate = state.get_float64("MatchingRate");
const auto size = static_cast<cuda::std::size_t>(static_cast<double>(num_keys) / occupancy);
const auto device = cuda::device_ref{0};
cuda::stream stream{device};
const cuda::device_memory_pool_ref mr = cuda::device_default_memory_pool(device);
const auto exec_policy = thrust::cuda::par_nosync.on(stream.get());
auto keys = cuda::make_device_buffer<Key>(stream, device, num_keys, cuda::no_init);
bench::key_generator gen{};
gen.generate(bench::dist_from_state<Dist>(state), keys.begin(), keys.end(), exec_policy);
auto pairs = cuda::make_device_buffer<pair_type>(stream, device, num_keys, cuda::no_init);
thrust::transform(exec_policy, keys.begin(), keys.end(), pairs.begin(), [] __device__(Key const& key) {
return pair_type{key, Value{}};
});
map_type map{stream, mr, size, cudax::cuco::empty_key(Key{-1}), cudax::cuco::empty_value(Value{-1})};
map.insert(stream, pairs.begin(), pairs.end());
gen.dropout(keys.begin(), keys.end(), matching_rate, exec_policy);
auto result = cuda::make_device_buffer<bool>(stream, device, num_keys, cuda::no_init);
stream.sync();
state.add_element_count(num_keys);
state.exec([&](nvbench::launch& launch) {
map.contains_async({launch.get_stream()}, keys.begin(), keys.end(), result.begin());
});
}
}
NVBENCH_BENCH_TYPES(fixed_capacity_map_contains,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_contains_unique_capacity")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", bench::defaults::n_range_cache)
.add_float64_axis("Occupancy", {bench::defaults::occupancy})
.add_float64_axis("MatchingRate", {bench::defaults::matching_rate});
NVBENCH_BENCH_TYPES(fixed_capacity_map_contains,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_contains_unique_occupancy")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", {bench::defaults::n})
.add_float64_axis("Occupancy", bench::defaults::occupancy_range)
.add_float64_axis("MatchingRate", {bench::defaults::matching_rate});
NVBENCH_BENCH_TYPES(fixed_capacity_map_contains,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_contains_unique_matching_rate")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", {bench::defaults::n})
.add_float64_axis("Occupancy", {bench::defaults::occupancy})
.add_float64_axis("MatchingRate", bench::defaults::matching_rate_range);

View File

@@ -1,108 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <thrust/execution_policy.h>
#include <thrust/transform.h>
#include <cuda/buffer>
#include <cuda/memory_resource>
#include <cuda/std/cstddef>
#include <cuda/std/functional>
#include <cuda/std/utility>
#include <cuda/stream>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <cuda/experimental/__cuco/types.cuh>
#include "../common/defaults.cuh"
#include "../common/key_generator.cuh"
#include <nvbench/nvbench.cuh>
namespace cudax = cuda::experimental;
namespace bench = cudax::cuco::benchmark;
/**
* @brief A benchmark evaluating `cudax::cuco::fixed_capacity_map::find_async` performance.
*/
template <typename Key, typename Value, typename Dist>
void fixed_capacity_map_find(nvbench::state& state, nvbench::type_list<Key, Value, Dist>)
{
if constexpr (sizeof(Key) != sizeof(Value))
{
state.skip("Key and Value must have the same size.");
}
else
{
using pair_type = cuda::std::pair<Key, Value>;
using map_type = cudax::cuco::fixed_capacity_map<Key, Value>;
const auto num_keys = static_cast<::cuda::std::size_t>(state.get_int64("NumInputs"));
const auto occupancy = state.get_float64("Occupancy");
const auto matching_rate = state.get_float64("MatchingRate");
const auto device = cuda::device_ref{0};
cuda::stream stream{device};
const cuda::device_memory_pool_ref mr = cuda::device_default_memory_pool(device);
const auto exec_policy = thrust::cuda::par_nosync.on(stream.get());
auto keys = cuda::make_device_buffer<Key>(stream, device, num_keys, cuda::no_init);
bench::key_generator gen{};
gen.generate(bench::dist_from_state<Dist>(state), keys.begin(), keys.end(), exec_policy);
auto pairs = cuda::make_device_buffer<pair_type>(stream, device, num_keys, cuda::no_init);
thrust::transform(exec_policy, keys.begin(), keys.end(), pairs.begin(), [] __device__(Key const& key) {
return pair_type{key, Value{}};
});
map_type map{stream, mr, num_keys, occupancy, cudax::cuco::empty_key(Key{-1}), cudax::cuco::empty_value(Value{-1})};
map.insert(stream, pairs.begin(), pairs.end());
gen.dropout(keys.begin(), keys.end(), matching_rate, exec_policy);
auto result = cuda::make_device_buffer<Value>(stream, device, num_keys, cuda::no_init);
stream.sync();
state.add_element_count(num_keys);
state.exec([&](nvbench::launch& launch) {
map.find_async({launch.get_stream()}, keys.begin(), keys.end(), result.begin());
});
}
}
NVBENCH_BENCH_TYPES(fixed_capacity_map_find,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_find_unique_capacity")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", bench::defaults::n_range_cache)
.add_float64_axis("Occupancy", {bench::defaults::occupancy})
.add_float64_axis("MatchingRate", {bench::defaults::matching_rate});
NVBENCH_BENCH_TYPES(fixed_capacity_map_find,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_find_unique_occupancy")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", {bench::defaults::n})
.add_float64_axis("Occupancy", bench::defaults::occupancy_range)
.add_float64_axis("MatchingRate", {bench::defaults::matching_rate});
NVBENCH_BENCH_TYPES(fixed_capacity_map_find,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_find_unique_matching_rate")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", {bench::defaults::n})
.add_float64_axis("Occupancy", {bench::defaults::occupancy})
.add_float64_axis("MatchingRate", bench::defaults::matching_rate_range);

View File

@@ -1,105 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <thrust/execution_policy.h>
#include <thrust/transform.h>
#include <cuda/buffer>
#include <cuda/memory_resource>
#include <cuda/std/cstddef>
#include <cuda/std/functional>
#include <cuda/std/utility>
#include <cuda/stream>
#include <cuda/experimental/__cuco/fixed_capacity_map.cuh>
#include <cuda/experimental/__cuco/types.cuh>
#include "../common/defaults.cuh"
#include "../common/key_generator.cuh"
#include <nvbench/nvbench.cuh>
namespace cudax = cuda::experimental;
namespace bench = cudax::cuco::benchmark;
/**
* @brief A benchmark evaluating `cudax::cuco::fixed_capacity_map::insert_async` performance.
*/
template <typename Key, typename Value, typename Dist>
void fixed_capacity_map_insert(nvbench::state& state, nvbench::type_list<Key, Value, Dist>)
{
if constexpr (sizeof(Key) != sizeof(Value))
{
state.skip("Key and Value must have the same size.");
}
else
{
using pair_type = cuda::std::pair<Key, Value>;
using map_type = cudax::cuco::fixed_capacity_map<Key, Value>;
const auto num_keys = state.get_int64("NumInputs");
const auto occupancy = state.get_float64("Occupancy");
const auto size = static_cast<cuda::std::size_t>(static_cast<double>(num_keys) / occupancy);
const auto device = cuda::device_ref{0};
cuda::stream stream{device};
const cuda::device_memory_pool_ref mr = cuda::device_default_memory_pool(device);
const auto exec_policy = thrust::cuda::par_nosync.on(stream.get());
auto keys = cuda::make_device_buffer<Key>(stream, device, num_keys, cuda::no_init);
bench::key_generator gen{};
gen.generate(bench::dist_from_state<Dist>(state), keys.begin(), keys.end(), exec_policy);
auto pairs = cuda::make_device_buffer<pair_type>(stream, device, num_keys, cuda::no_init);
thrust::transform(exec_policy, keys.begin(), keys.end(), pairs.begin(), [] __device__(Key const& key) {
return pair_type{key, Value{}};
});
map_type map{stream, mr, size, cudax::cuco::empty_key(Key{-1}), cudax::cuco::empty_value(Value{-1})};
stream.sync();
state.add_element_count(num_keys);
state.exec(nvbench::exec_tag::timer, [&](nvbench::launch& launch, auto& timer) {
timer.start();
map.insert_async({launch.get_stream()}, pairs.begin(), pairs.end());
timer.stop();
map.clear_async({launch.get_stream()});
});
}
}
NVBENCH_BENCH_TYPES(fixed_capacity_map_insert,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_insert_unique_capacity")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", bench::defaults::n_range_cache)
.add_float64_axis("Occupancy", {bench::defaults::occupancy});
NVBENCH_BENCH_TYPES(fixed_capacity_map_insert,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::unique>))
.set_name("fixed_capacity_map_insert_unique_occupancy")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", {bench::defaults::n})
.add_float64_axis("Occupancy", bench::defaults::occupancy_range);
NVBENCH_BENCH_TYPES(fixed_capacity_map_insert,
NVBENCH_TYPE_AXES(bench::defaults::key_type_range,
bench::defaults::value_type_range,
nvbench::type_list<bench::distribution::uniform>))
.set_name("fixed_capacity_map_insert_uniform_multiplicity")
.set_type_axes_names({"Key", "Value", "Distribution"})
.add_int64_axis("NumInputs", {bench::defaults::n})
.add_float64_axis("Occupancy", {bench::defaults::occupancy})
.add_float64_axis("Multiplicity", bench::defaults::multiplicity_range);

View File

@@ -1,131 +0,0 @@
// SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION. All rights reserved.
// SPDX-License-Identifier: SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <thrust/device_vector.h>
#include <cuda/std/cstddef>
#include <cuda/std/cstdint>
#include <cuda/experimental/__cuco/hash_functions.cuh>
#include <nvbench/nvbench.cuh>
#include <nvbench/range.cuh>
namespace cudax = cuda::experimental;
// repeat hash computation n times
static constexpr auto n_repeats = 100;
template <cuda::std::int32_t Words>
struct large_key
{
constexpr __host__ __device__ large_key(cuda::std::int32_t seed) noexcept
{
for (cuda::std::int32_t i = 0; i < Words; ++i)
{
data_[i] = seed;
}
}
private:
cuda::std::int32_t data_[Words];
};
template <cuda::std::int32_t BlockSize, typename Key, typename Hasher, typename OutputIt>
__global__ void hash_bench_kernel(Hasher hash, size_t n, OutputIt out, bool materialize_result)
{
size_t const gid = static_cast<size_t>(BlockSize) * blockIdx.x + threadIdx.x;
size_t const loop_stride = static_cast<size_t>(gridDim.x) * BlockSize;
size_t idx = gid;
using result_t = decltype(hash(0));
result_t agg{};
while (idx < n)
{
Key key(idx);
for (cuda::std::int32_t i = 0; i < n_repeats; ++i)
{ // execute hash func n times
agg += hash(key);
}
idx += loop_stride;
}
if (materialize_result)
{
out[gid] = agg;
}
}
// benchmark evaluating performance of various hash functions
template <typename HasherTag, typename Key>
void hash_eval(nvbench::state& state, nvbench::type_list<HasherTag, Key>)
{
using Hash = typename HasherTag::template fn<Key>;
bool const materialize_result = false;
constexpr auto block_size = 128;
auto const num_keys = state.get_int64("NumInputs");
auto const grid_size = (num_keys + block_size * 16 - 1) / block_size * 16;
using result_t = decltype(std::declval<Hash>()(std::declval<cuda::std::int32_t>()));
thrust::device_vector<result_t> hash_values((materialize_result) ? num_keys : 1);
state.add_element_count(num_keys);
state.exec([&](nvbench::launch& launch) {
hash_bench_kernel<block_size, Key>
<<<grid_size, block_size, 0, launch.get_stream()>>>(Hash{}, num_keys, hash_values.begin(), materialize_result);
});
}
struct xxhash_32_tag
{
template <typename Key>
using fn = cudax::cuco::hash<Key, cudax::cuco::hash_algorithm::xxhash_32>;
};
struct xxhash_64_tag
{
template <typename Key>
using fn = cudax::cuco::hash<Key, cudax::cuco::hash_algorithm::xxhash_64>;
};
struct murmurhash3_32_tag
{
template <typename Key>
using fn = cudax::cuco::hash<Key, cudax::cuco::hash_algorithm::murmurhash3_32>;
};
#if _CCCL_HAS_INT128()
struct murmurhash3_x86_128_tag
{
template <typename Key>
using fn = cudax::cuco::hash<Key, cudax::cuco::hash_algorithm::murmurhash3_x86_128>;
};
struct murmurhash3_x64_128_tag
{
template <typename Key>
using fn = cudax::cuco::hash<Key, cudax::cuco::hash_algorithm::murmurhash3_x64_128>;
};
#endif // _CCCL_HAS_INT128()
NVBENCH_BENCH_TYPES(
hash_eval,
NVBENCH_TYPE_AXES(
nvbench::type_list<xxhash_32_tag,
xxhash_64_tag,
murmurhash3_32_tag
#if _CCCL_HAS_INT128()
,
murmurhash3_x86_128_tag,
murmurhash3_x64_128_tag
#endif // _CCCL_HAS_INT128()
>,
nvbench::type_list<cuda::std::int32_t, large_key<4>, large_key<8>, large_key<16>, large_key<32>>))
.set_name("hash_function_eval")
.set_type_axes_names({"Hash", "Key"})
.add_int64_power_of_two_axis("NumInputs", nvbench::range(18, 26, 4));

View File

@@ -1,133 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <thrust/execution_policy.h>
#include <thrust/sequence.h>
#include <cuda/buffer>
#include <cuda/memory_resource>
#include <cuda/std/cmath>
#include <cuda/std/cstddef>
#include <cuda/stream>
#include <cuda/experimental/__cuco/hyperloglog.cuh>
#include "common/defaults.cuh"
#include <nvbench/nvbench.cuh>
namespace cudax = cuda::experimental;
namespace bench = cudax::cuco::benchmark;
namespace
{
template <typename Key>
void add_relative_error_summary(
nvbench::state& state,
cudax::cuco::hyperloglog<Key>& estimator,
cuda::stream_ref stream,
Key* first,
cuda::std::size_t num_items)
{
estimator.add(stream, first, first + num_items);
const auto estimated_cardinality = estimator.estimate(stream);
const auto relative_error =
cuda::std::abs(static_cast<double>(estimated_cardinality) / static_cast<double>(num_items) - 1.0);
estimator.clear(stream);
auto& summary = state.add_summary("RelativeError");
summary.set_string("hint", "RelErr");
summary.set_string("short_name", "RelativeError");
summary.set_string("description", "Relative approximation error.");
summary.set_float64("value", relative_error);
}
} // namespace
/**
* @brief A benchmark evaluating `cudax::cuco::hyperloglog` end-to-end performance.
*/
template <typename Key>
void hyperloglog_e2e(nvbench::state& state, nvbench::type_list<Key>)
{
using estimator_type = cudax::cuco::hyperloglog<Key>;
using sketch_size_kb_type = typename estimator_type::sketch_size_kb;
const auto num_items = static_cast<cuda::std::size_t>(state.get_int64("NumInputs"));
const auto sketch_size_kb = sketch_size_kb_type{static_cast<double>(state.get_int64("SketchSizeKB"))};
const auto device = cuda::device_ref{0};
cuda::stream stream{device};
const cuda::device_memory_pool_ref mr = cuda::device_default_memory_pool(device);
auto items = cuda::make_device_buffer<Key>(stream, device, num_items, cuda::no_init);
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), Key{0});
estimator_type estimator{stream, mr, sketch_size_kb};
stream.sync();
state.add_element_count(num_items);
state.add_global_memory_reads<Key>(num_items, "InputSize");
add_relative_error_summary(state, estimator, stream, items.data(), num_items);
state.exec(nvbench::exec_tag::sync | nvbench::exec_tag::timer, [&](nvbench::launch& launch, auto& timer) {
timer.start();
estimator.add_async({launch.get_stream()}, items.begin(), items.end());
[[maybe_unused]] const auto estimated_cardinality = estimator.estimate({launch.get_stream()});
timer.stop();
estimator.clear_async({launch.get_stream()});
});
}
/**
* @brief A benchmark evaluating `cudax::cuco::hyperloglog::add_async` performance.
*/
template <typename Key>
void hyperloglog_add(nvbench::state& state, nvbench::type_list<Key>)
{
using estimator_type = cudax::cuco::hyperloglog<Key>;
using sketch_size_kb_type = typename estimator_type::sketch_size_kb;
const auto num_items = static_cast<cuda::std::size_t>(state.get_int64("NumInputs"));
const auto sketch_size_kb = sketch_size_kb_type{static_cast<double>(state.get_int64("SketchSizeKB"))};
const auto device = cuda::device_ref{0};
cuda::stream stream{device};
const cuda::device_memory_pool_ref mr = cuda::device_default_memory_pool(device);
auto items = cuda::make_device_buffer<Key>(stream, device, num_items, cuda::no_init);
thrust::sequence(thrust::cuda::par_nosync.on(stream.get()), items.begin(), items.end(), Key{0});
estimator_type estimator{stream, mr, sketch_size_kb};
stream.sync();
state.add_element_count(num_items);
state.add_global_memory_reads<Key>(num_items, "InputSize");
state.exec(nvbench::exec_tag::timer, [&](nvbench::launch& launch, auto& timer) {
timer.start();
estimator.add_async({launch.get_stream()}, items.begin(), items.end());
timer.stop();
estimator.clear_async({launch.get_stream()});
});
}
NVBENCH_BENCH_TYPES(hyperloglog_e2e, NVBENCH_TYPE_AXES(bench::defaults::key_type_range))
.set_name("hyperloglog_e2e")
.set_type_axes_names({"Key"})
.add_int64_power_of_two_axis("NumInputs", {30})
.add_int64_axis("SketchSizeKB", {8, 16, 32, 64, 128, 256});
NVBENCH_BENCH_TYPES(hyperloglog_add, NVBENCH_TYPE_AXES(bench::defaults::key_type_range))
.set_name("hyperloglog_add")
.set_type_axes_names({"Key"})
.add_int64_power_of_two_axis("NumInputs", {30})
.add_int64_axis("SketchSizeKB", {8, 16, 32, 64, 128, 256});

View File

@@ -1 +0,0 @@
cccl_add_subdir_helper(cudax)

View File

@@ -1,65 +0,0 @@
# Including this file defines the following targets:
#
# cudax.compiler_interface
# - Interface target that includes all compiler settings for cudax tests, etc.
cccl_get_cub()
cccl_get_cudax()
cccl_get_libcudacxx()
cccl_get_thrust()
set(cuda_compile_options)
set(cxx_compile_options)
set(cxx_compile_definitions)
if ("MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
# C4848: support for attribute 'msvc::no_unique_address' in C++17 and earlier is a vendor extension
append_option_if_available("/wd4848" cxx_compile_options)
# XXX Temporary hack for STF !
# C4267: conversion from 'meow' to 'purr', possible loss of data
append_option_if_available("/wd4267" cxx_compile_options)
# C4459 : declaration of 'identifier' hides global declaration
# We work around std::chrono::last which hides some internal "last" variable
append_option_if_available("/wd4459" cxx_compile_options)
# stf used getenv which is potentially unsafe but not in our context
list(APPEND cxx_compile_definitions "_CRT_SECURE_NO_WARNINGS")
endif()
if ("Clang" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
# stf heavily uses host device lambdas which break on clang due to a warning about the implicitly
# deleted copy constructor
# TODO(bgruber): remove this when NVBug 4980157 is resolved
append_option_if_available("-Wno-deprecated-copy" cxx_compile_options)
endif()
list(APPEND cxx_compile_definitions CCCL_ENABLE_ASSERTIONS)
# Some groups related experimental code is located directly in libcu++ and is guarded by
# _CUDAX_ENABLE_GROUP_FEATURES_IN_LIBCUDACXX macro, otherwise it would lead to a lot of code duplication. We define this
# macro for cudax code globally, to get access to get access to the code.
#
# Can be removed once groups are no longer experimental.
list(APPEND cxx_compile_definitions _CUDAX_ENABLE_GROUP_FEATURES_IN_LIBCUDACXX)
cccl_build_compiler_interface(
cudax.compiler_flags
"${cuda_compile_options}"
"${cxx_compile_options}"
"${cxx_compile_definitions}"
)
add_library(cudax.compiler_interface INTERFACE)
target_link_libraries(
cudax.compiler_interface
INTERFACE
# order matters here, we need the cudax options to override the cccl options.
cccl.compiler_interface
cudax.compiler_flags
libcudacxx::libcudacxx
CUB::CUB
Thrust::Thrust
cudax::cudax
)

View File

@@ -1,109 +0,0 @@
# For every public header, build a translation unit containing `#include <header>`
# to let the compiler try to figure out warnings in that header if it is not otherwise
# included in tests, and also to verify if the headers are modular enough.
# .inl files are not globbed for, because they are not supposed to be used as public
# entrypoints.
cccl_get_cudatoolkit()
# Meta target for all configs' header builds:
add_custom_target(cudax.all.headers)
function(cudax_add_header_test label definitions)
###################
# Non-STF headers #
set(headertest_target cudax.headers.${label}.no_stf)
cccl_generate_header_tests(
${headertest_target}
cudax/include
# The cudax header template removes the check for the `small` macro.
HEADER_TEMPLATE "${cudax_SOURCE_DIR}/cmake/header_test.in.cu"
GLOBS "cuda/experimental/*.cuh"
EXCLUDES
# The following internal headers are not required to compile independently:
"cuda/experimental/__execution/prologue.cuh"
"cuda/experimental/__execution/epilogue.cuh"
# cuFile headers are compiled separately:
"cuda/experimental/cufile.cuh"
"cuda/experimental/__cufile/*"
# Places headers are compiled separately:
"cuda/experimental/places.cuh"
"cuda/experimental/__places/*"
# STF headers are compiled separately:
"cuda/experimental/stf.cuh"
"cuda/experimental/__stf/*"
)
target_link_libraries(${headertest_target} PUBLIC cudax.compiler_interface)
if (cudax_ENABLE_CUFILE)
###############
# cuFile headers #
set(headertest_target cudax.headers.${label}.cufile)
cccl_generate_header_tests(
${headertest_target}
cudax/include
HEADER_TEMPLATE "${cudax_SOURCE_DIR}/cmake/header_test.in.cu"
GLOBS #
"cuda/experimental/cufile.cuh"
"cuda/experimental/__cufile/*.cuh"
)
target_link_libraries(${headertest_target} PUBLIC cudax.compiler_interface)
endif()
# FIXME: Enable MSVC
if (cudax_ENABLE_PLACES AND NOT "MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
##################
# Places headers #
set(headertest_target cudax.headers.${label}.places)
cccl_generate_header_tests(
${headertest_target}
cudax/include
GLOBS #
"cuda/experimental/places.cuh"
"cuda/experimental/__places/*.cuh"
HEADER_TEMPLATE "${cudax_SOURCE_DIR}/cmake/header_test.in.cu"
)
target_link_libraries(${headertest_target} PUBLIC cudax.compiler_interface)
target_compile_options(
${headertest_target}
PRIVATE
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--expt-relaxed-constexpr>
)
endif()
# FIXME: Enable MSVC
if (cudax_ENABLE_CUDASTF AND NOT "MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
###############
# STF headers #
set(headertest_target cudax.headers.${label}.stf)
cccl_generate_header_tests(
${headertest_target}
cudax/include
GLOBS #
"cuda/experimental/stf.cuh"
"cuda/experimental/__stf/*.cuh"
# FIXME: The cudax header template removes the check for the `small` macro.
# cuda/experimental/__stf/utility/memory.cuh defines functions named `small`.
# These should be renamed to avoid conflicts with windows system headers, and
# the following line removed:
HEADER_TEMPLATE "${cudax_SOURCE_DIR}/cmake/header_test.in.cu"
)
target_link_libraries(
${headertest_target}
PUBLIC cudax.compiler_interface CUDA::cuda_driver
)
target_compile_options(
${headertest_target}
PRIVATE
# Required by stf headers:
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
# FIXME: We should be able to refactor away from needing this by
# using _CCCL_HOST_DEVICE and friends + `::cuda::std` utilities where
# necessary.
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--expt-relaxed-constexpr>
)
endif()
endfunction()
cudax_add_header_test(basic "")

View File

@@ -1,23 +0,0 @@
# Configures a target for the Places framework.
function(cudax_places_configure_target target_name)
target_link_libraries(
${target_name}
PRIVATE #
CUDA::cudart_static
CUDA::cuda_driver
)
target_compile_options(
${target_name}
PRIVATE
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--expt-relaxed-constexpr>
)
set_target_properties(
${target_name}
PROPERTIES #
CUDA_RUNTIME_LIBRARY Static
CUDA_SEPARABLE_COMPILATION ON
)
endfunction()

View File

@@ -1,58 +0,0 @@
# Configures a target for the STF framework.
function(cudax_stf_configure_target target_name)
set(options LINK_MATHLIBS)
set(oneValueArgs)
set(multiValueArgs)
cmake_parse_arguments(
CSCT
"${options}"
"${oneValueArgs}"
"${multiValueArgs}"
${ARGN}
)
target_link_libraries(
${target_name}
PRIVATE #
CUDA::cudart_static
CUDA::curand
CUDA::cuda_driver
)
if (cudax_ENABLE_CUDASTF_CODE_GENERATION)
target_compile_options(
${target_name}
PRIVATE $<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
)
else()
target_compile_definitions(
${target_name}
PRIVATE "CUDASTF_DISABLE_CODE_GENERATION"
)
endif()
target_compile_options(
${target_name}
PRIVATE $<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--expt-relaxed-constexpr>
)
set_target_properties(
${target_name}
PROPERTIES #
CUDA_RUNTIME_LIBRARY Static
CUDA_SEPARABLE_COMPILATION ON
)
if (CSCT_LINK_MATHLIBS)
target_link_libraries(
${target_name}
PRIVATE #
CUDA::cublas
CUDA::cusolver
)
endif()
if (cudax_ENABLE_CUDASTF_BOUNDSCHECK)
target_compile_definitions(${target_name} PRIVATE "CUDASTF_BOUNDSCHECK")
endif()
endfunction()

View File

@@ -1,66 +0,0 @@
// This source file checks that:
// 1) Header <@header@> compiles without error.
// 2) Common macro collisions with platform/system headers are avoided.
// 3) half/bf16 aren't included when these are explicitly disabled.
// Define CUDAX_MACRO_CHECK(macro, header), which emits a diagnostic indicating
// a potential macro collision and halts.
//
// Use raw platform checks instead of the CCCL macros since we
// don't want to #include any headers other than the one being tested.
//
// This is only implemented for MSVC/GCC/Clang.
#if defined(_MSC_VER) // MSVC
// Fake up an error for MSVC
# define CUDAX_MACRO_CHECK_IMPL(msg) \
/* Print message that looks like an error: */ \
__pragma(message(__FILE__ ":" CUDAX_MACRO_CHECK_IMPL0(__LINE__) ": error: " #msg)) static_assert(false, #msg);
# define CUDAX_MACRO_CHECK_IMPL0(x) CUDAX_MACRO_CHECK_IMPL1(x)
# define CUDAX_MACRO_CHECK_IMPL1(x) #x
#elif defined(__clang__) || defined(__GNUC__)
// GCC/clang are easy:
# define CUDAX_MACRO_CHECK_IMPL(msg) CUDAX_MACRO_CHECK_IMPL0(GCC error #msg)
# define CUDAX_MACRO_CHECK_IMPL0(expr) _Pragma(#expr)
#endif
// Hacky way to build a string, but it works on all tested platforms.
#define CUDAX_MACRO_CHECK(MACRO, HEADER) \
CUDAX_MACRO_CHECK_IMPL(Identifier MACRO should not be used from CCCL headers due to conflicts with HEADER macros.)
// complex.h conflicts
#define I CUDAX_MACRO_CHECK('I', complex.h)
// windows.h conflicts
// @eniebler 2024-08-30: This test is disabled because it causes build
// failures in some configurations.
// #define small CUDAX_MACRO_CHECK('small', windows.h)
// We can't enable these checks without breaking some builds -- some standard
// library implementations unconditionally `#undef` these macros, which then
// causes random failures later.
// Leaving these commented out as a warning: Here be dragons.
// #define min(...) CUDAX_MACRO_CHECK('min', windows.h)
// #define max(...) CUDAX_MACRO_CHECK('max', windows.h)
// termios.h conflicts (NVIDIA/thrust#1547)
#define B0 CUDAX_MACRO_CHECK("B0", termios.h)
#include <@header@>
#if defined(CCCL_DISABLE_BF16_SUPPORT)
# if defined(__CUDA_BF16_TYPES_EXIST__)
# error We should not include cuda_bf16.h when BF16 support is disabled
# endif // __CUDA_BF16_TYPES_EXIST__
#endif // CCCL_DISABLE_BF16_SUPPORT
#if defined(CCCL_DISABLE_FP16_SUPPORT)
# if defined(__CUDA_FP16_TYPES_EXIST__)
# error We should not include cuda_fp16.h when half support is disabled
# endif // __CUDA_FP16_TYPES_EXIST__
# if defined(__CUDA_BF16_TYPES_EXIST__)
# error We should not include cuda_bf16.h when half support is disabled
# endif // __CUDA_BF16_TYPES_EXIST__
#endif // CCCL_DISABLE_FP16_SUPPORT

View File

@@ -1,9 +0,0 @@
// This file is autogenerated by configuring places_header_unittest.in.cu.
// clang-format off
#define UNITTESTED_FILE "@source@"
#include <cuda/experimental/__stf/utility/unittest.cuh>
#include <@source@>
//clang-format on

View File

@@ -1,9 +0,0 @@
// This file is autogenerated by configuring stf_header_unittest.in.cu.
// clang-format off
#define UNITTESTED_FILE "@source@"
#include <cuda/experimental/__stf/utility/unittest.cuh>
#include <@source@>
//clang-format on

View File

@@ -1,56 +0,0 @@
thrust_create_target(cudax.examples.thrust)
function(cudax_add_example target_name_var example_src)
get_filename_component(example_name ${example_src} NAME_WE)
# The actual name of the test's target:
set(example_target cudax.example.${example_name})
set(${target_name_var} ${example_target} PARENT_SCOPE)
cccl_add_executable(${example_target} SOURCES "${example_src}" ADD_CTEST)
target_link_libraries(
${example_target}
PRIVATE #
cudax.compiler_interface
cudax.examples.thrust
)
target_compile_options(
${example_target}
PRIVATE
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--expt-relaxed-constexpr>
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
)
target_include_directories(
${example_target}
PRIVATE "${CUB_SOURCE_DIR}/examples"
)
endfunction()
file(
GLOB example_srcs
RELATIVE "${cudax_SOURCE_DIR}/examples"
CONFIGURE_DEPENDS
*.cu
*.cpp
)
cccl_get_cudatoolkit()
# Example requires pinned_memory_resource.
if (CUDAToolkit_VERSION VERSION_LESS 12.9)
list(REMOVE_ITEM example_srcs async_buffer_add.cu cub_reduce.cu)
endif()
foreach (example_src IN LISTS example_srcs)
cudax_add_example(example_target "${example_src}")
endforeach()
# FIXME: Enable MSVC
if (cudax_ENABLE_CUDASTF AND NOT "MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
# STF examples are handled separately:
add_subdirectory(stf)
endif()
if (cudax_ENABLE_PLACES AND NOT "MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
add_subdirectory(places)
endif()

View File

@@ -1,92 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* Vector addition: C = A + B.
*
* This sample is a very basic sample that implements element by element
* vector addition. It is the same as the sample illustrating Chapter 2
* of the programming guide with some additions like error checking.
*/
#include <thrust/execution_policy.h>
#include <thrust/random.h>
#include <thrust/tabulate.h>
#include <thrust/transform.h>
#include <cuda/experimental/container.cuh>
#include <cuda/experimental/memory_resource.cuh>
#include <cuda/experimental/stream.cuh>
#include <iostream>
namespace cudax = cuda::experimental;
constexpr int numElements = 50000;
struct generator
{
thrust::default_random_engine gen{};
thrust::uniform_real_distribution<float> dist{-10.0f, 10.0f};
__host__ __device__ generator(const unsigned seed)
: gen{seed}
{}
__host__ __device__ float operator()(cuda::std::size_t idx) noexcept
{
gen.discard(idx);
return dist(gen);
}
};
int main()
{
// A CUDA stream on which to execute the vector addition kernel
cudax::stream stream{cuda::device_ref{0}};
// The execution policy we want to use to run all work on the same stream
auto policy = thrust::cuda::par_nosync.on(stream.get());
cuda::device_memory_pool_ref device_resource = cuda::device_default_memory_pool(cuda::device_ref{0});
// Allocate the two inputs and output, but do not zero initialize via `cuda::no_init`
cuda::device_buffer<float> A{stream, device_resource, numElements, cuda::no_init};
cuda::device_buffer<float> B{stream, device_resource, numElements, cuda::no_init};
cuda::device_buffer<float> C{stream, device_resource, numElements, cuda::no_init};
// Fill both vectors on stream using a random number generator
thrust::tabulate(policy, A.begin(), A.end(), generator{42});
thrust::tabulate(policy, B.begin(), B.end(), generator{1337});
// Add the vectors together
thrust::transform(policy, A.begin(), A.end(), B.begin(), C.begin(), cuda::std::plus<>{});
cuda::pinned_memory_pool_ref pinned_resource = cuda::pinned_default_memory_pool();
// Verify that the result vector is correct, by copying it to host
cuda::host_buffer<float> h_A{stream, pinned_resource, A};
cuda::host_buffer<float> h_B{stream, pinned_resource, B};
cuda::host_buffer<float> h_C{stream, pinned_resource, C};
// Do not forget to sync afterwards
stream.sync();
for (int i = 0; i < numElements; ++i)
{
if (cuda::std::abs(h_A.get_unsynchronized(i) + h_B.get_unsynchronized(i) - h_C.get_unsynchronized(i)) > 1e-5)
{
std::cerr << "Result verification failed at element " << i << "\n";
exit(EXIT_FAILURE);
}
}
return 0;
}

View File

@@ -1,53 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
// Example of using `cub::DeviceReduce::Reduce` with cudax environment.
#include <cub/device/device_reduce.cuh>
#include <cuda/experimental/container.cuh>
#include <cuda/experimental/memory_resource.cuh>
#include <cuda/experimental/stream.cuh>
#include <iostream>
namespace cudax = cuda::experimental;
int main()
{
constexpr int num_items = 50000;
// A CUDA stream on which to execute the reduction
cuda::stream stream{cuda::devices[0]};
cuda::device_memory_pool_ref mr = cuda::device_default_memory_pool(cuda::devices[0]);
// Allocate input and output, but do not zero initialize output (`cuda::no_init`)
auto d_in = cuda::make_buffer<int>(stream, mr, num_items, 1);
auto d_out = cuda::make_buffer<float>(stream, mr, 1, cuda::no_init);
// An environment we use to pass all necessary information to CUB
cudax::env_t<cuda::mr::device_accessible> env{mr, stream};
auto error = cub::DeviceReduce::Reduce(d_in.begin(), d_out.begin(), num_items, cuda::std::plus{}, 0, env);
if (error != cudaSuccess)
{
std::cerr << "cub::DeviceReduce::Reduce failed: " << cudaGetErrorString(error) << "\n";
exit(EXIT_FAILURE);
}
auto h_out = cuda::make_buffer<float>(stream, cuda::pinned_default_memory_pool(), d_out);
stream.sync();
if (h_out.get_unsynchronized(0) != num_items)
{
std::cerr << "Result verification failed: " << h_out.get_unsynchronized(0) << " != " << num_items << "\n";
exit(EXIT_FAILURE);
}
}

View File

@@ -1,30 +0,0 @@
set(places_example_sources thrust_device_data_place_allocator.cu)
## cudax_add_places_example
#
# Add a places example executable and register it with ctest.
#
# target_name_var: Variable name to overwrite with the name of the example
# target. Useful for modifying the example/target after creation.
# source: The source file for the example.
#
function(cudax_add_places_example target_name_var source)
get_filename_component(filename ${source} NAME_WE)
set(example_target cudax.example.places.${filename})
cccl_add_executable(${example_target} SOURCES ${source} ADD_CTEST)
cudax_places_configure_target(${example_target})
target_link_libraries(
${example_target}
PRIVATE #
cudax.compiler_interface
cudax.examples.thrust
)
set(${target_name_var} ${example_target} PARENT_SCOPE)
endfunction()
foreach (source IN LISTS places_example_sources)
cudax_add_places_example(example_target "${source}")
endforeach()

View File

@@ -1,117 +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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Thrust device_vector with an allocator backed by a data_place.
*
* Wraps data_place::allocate/deallocate as a thrust::mr::memory_resource,
* then uses thrust::mr::allocator to create a compatible allocator.
* Storage is allocated via data_place (device, composite/VMM, or other
* place types). The same Thrust code works unchanged for single-device,
* multi-device (VMM), or green-context placement.
*/
#include <thrust/copy.h>
#include <thrust/device_ptr.h>
#include <thrust/device_vector.h>
#include <thrust/execution_policy.h>
#include <thrust/host_vector.h>
#include <thrust/iterator/counting_iterator.h>
#include <thrust/mr/allocator.h>
#include <thrust/mr/memory_resource.h>
#include <thrust/transform.h>
#include <cuda/experimental/__places/partitions/blocked_partition.cuh>
#include <cstdio>
using namespace cuda::experimental::places;
// Minimal adapter: data_place is STF's abstraction; Thrust expects a
// memory_resource. This class bridges the two. The resource must outlive
// any vectors/allocators that use it.
class data_place_memory_resource final : public thrust::mr::memory_resource<thrust::device_ptr<void>>
{
public:
explicit data_place_memory_resource(const data_place& place)
: place_(place)
{}
pointer do_allocate(std::size_t bytes, std::size_t /*alignment*/) override
{
// A memory resource hands out untyped bytes, so declare the geometry
// explicitly as a flat byte array: composite places distribute it with
// byte granularity (equivalent for every other place type).
void* raw = place_.allocate_nd(dim4(bytes), 1);
return thrust::device_ptr<void>(raw);
}
void do_deallocate(pointer p, std::size_t bytes, std::size_t /*alignment*/) override
{
place_.deallocate(p.get(), bytes);
}
__host__ __device__ bool do_is_equal(const memory_resource& other) const noexcept override
{
#if defined(__CUDA_ARCH__)
(void) other;
return false;
#else
auto* o = dynamic_cast<const data_place_memory_resource*>(&other);
return o && place_ == o->place_;
#endif
}
private:
data_place place_;
};
template <typename T>
using data_place_allocator = thrust::mr::allocator<T, data_place_memory_resource>;
bool run_with_place(const data_place& place, const char* label)
{
const size_t n = 1024 * 1024;
data_place_memory_resource memres(place);
data_place_allocator<double> alloc(&memres);
thrust::device_vector<double, data_place_allocator<double>> d_vec(n, 0.0, alloc);
thrust::transform(
thrust::device,
thrust::counting_iterator<size_t>(0),
thrust::counting_iterator<size_t>(n),
d_vec.begin(),
[] __device__(size_t i) {
return 2.0 * static_cast<double>(i);
});
thrust::host_vector<double> h_sample(4);
thrust::copy(d_vec.begin(), d_vec.begin() + 4, h_sample.begin());
bool ok = (h_sample[0] == 0.0 && h_sample[1] == 2.0 && h_sample[2] == 4.0 && h_sample[3] == 6.0);
printf(
"thrust_device_data_place_allocator: %s (%s): %s\n", label, place.to_string().c_str(), ok ? "PASSED" : "FAILED");
return ok;
}
int main()
{
bool all_ok = true;
all_ok &= run_with_place(data_place::device(0), "device(0)");
all_ok &= run_with_place(data_place::composite(blocked_partition(), exec_place::all_devices()),
"composite(blocked_partition, all_devices)");
return all_ok ? 0 : 1;
}

View File

@@ -1,262 +0,0 @@
/* Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
* This sample demonstrates a combination of Peer-to-Peer (P2P) and
* Unified Virtual Address Space (UVA) features.
*/
#include <cuda/algorithm>
#include <cuda/devices>
#include <cuda/memory_pool>
#include <cuda/memory_resource>
#include <cuda/experimental/container.cuh>
#include <cuda/experimental/launch.cuh>
#include <cuda/experimental/memory_resource.cuh>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
namespace cudax = cuda::experimental;
struct simple_kernel
{
template <typename Configuration>
__device__ void operator()(Configuration config, ::cuda::std::span<const float> src, ::cuda::std::span<float> dst)
{
// Just a dummy kernel, doing enough for us to verify that everything worked
const auto idx = cuda::gpu_thread.rank(cuda::grid, config);
dst[idx] = src[idx] * 2.0f;
}
};
void print_peer_accessibility()
{
// Check possibility for peer access
printf("\nChecking GPU(s) for support of peer to peer memory access...\n");
for (auto& dev_i : cuda::devices)
{
for (auto& dev_j : cuda::devices)
{
if (dev_i != dev_j)
{
bool can_access_peer = dev_i.has_peer_access_to(dev_j);
const auto dev_i_name = dev_i.name();
const auto dev_j_name = dev_j.name();
printf("> Peer access from %.*s (GPU%d) -> %.*s (GPU%d) : %s\n",
static_cast<int>(dev_i_name.size()),
dev_i_name.data(),
dev_i.get(),
static_cast<int>(dev_j_name.size()),
dev_j_name.data(),
dev_j.get(),
can_access_peer ? "Yes" : "No");
}
}
}
}
template <typename BufferType>
void benchmark_cross_device_ping_pong_copy(
cudax::stream_ref dev0_stream, cudax::stream_ref dev1_stream, BufferType& dev0_buffer, BufferType& dev1_buffer)
{
// Use dev1 stream due to some surprising performance issue
constexpr int cpy_count = 100;
auto start_event = dev1_stream.record_timed_event();
for (int i = 0; i < cpy_count; i++)
{
// Ping-pong copy between GPUs
if (i % 2 == 0)
{
cuda::copy_bytes(dev1_stream, dev0_buffer, dev1_buffer);
}
else
{
cuda::copy_bytes(dev1_stream, dev1_buffer, dev0_buffer);
}
}
auto end_event = dev1_stream.record_timed_event();
dev1_stream.sync();
cuda::std::chrono::duration<double> duration(end_event - start_event);
printf("Peer copy between GPU%d and GPU%d: %.2fGB/s\n",
dev0_stream.device().get(),
dev1_stream.device().get(),
(static_cast<float>(cpy_count * dev0_buffer.size_bytes()) / static_cast<float>(1024 * 1024 * 1024)
/ duration.count()));
}
template <typename BufferType>
void test_cross_device_access_from_kernel(
cudax::stream_ref dev0_stream, cudax::stream_ref dev1_stream, BufferType& dev0_buffer, BufferType& dev1_buffer)
{
cuda::device_ref dev0 = dev0_stream.device();
cuda::device_ref dev1 = dev1_stream.device();
// Prepare host buffer and copy to GPU 0
printf("Preparing host buffer and copy to GPU%d...\n", dev0.get());
// This will be a pinned memory vector once available
cudax::uninitialized_buffer<float, cuda::mr::host_accessible> host_buffer(
cuda::mr::legacy_pinned_memory_resource(), dev0_buffer.size());
std::generate(host_buffer.begin(), host_buffer.end(), []() {
static int i = 0;
return static_cast<float>((i++) % 4096);
});
cuda::copy_bytes(dev0_stream, host_buffer, dev0_buffer);
dev1_stream.wait(dev0_stream);
// Kernel launch configuration
auto config = cuda::distribute<512>(dev0_buffer.size());
// Run kernel on GPU 1, reading input from the GPU 0 buffer, writing output to the GPU 1 buffer
printf("Run kernel on GPU%d, taking source data from GPU%d and writing to "
"GPU%d...\n",
dev1.get(),
dev0.get(),
dev1.get());
cudax::launch(dev1_stream, config, simple_kernel{}, dev0_buffer, dev1_buffer);
dev0_stream.wait(dev1_stream);
// Run kernel on GPU 0, reading input from the GPU 1 buffer, writing output to the GPU 0 buffer
printf("Run kernel on GPU%d, taking source data from GPU%d and writing to "
"GPU%d...\n",
dev0.get(),
dev1.get(),
dev0.get());
cudax::launch(dev0_stream, config, simple_kernel{}, dev1_buffer, dev0_buffer);
// Copy data back to host and verify
printf("Copy data back to host from GPU%d and verify results...\n", dev0.get());
cuda::copy_bytes(dev0_stream, dev0_buffer, host_buffer);
dev0_stream.sync();
int error_count = 0;
for (size_t i = 0; i < host_buffer.size(); i++)
{
cuda::std::span<float> host_span(host_buffer);
// Re-generate input data and apply 2x '* 2.0f' computation of both kernel runs
float expected = float(i % 4096) * 2.0f * 2.0f;
if (host_span[i] != expected)
{
printf("Verification error @ element %zu: val = %f, ref = %f\n", i, host_span[i], expected);
if (error_count++ > 10)
{
break;
}
}
}
if (error_count != 0)
{
printf("Test failed!\n");
exit(EXIT_FAILURE);
}
}
int main([[maybe_unused]] int argc, char** argv)
try
{
printf("[%s] - Starting...\n", argv[0]);
// Number of GPUs
printf("Checking for multiple GPUs...\n");
printf("CUDA-capable device count: %zu\n", cuda::devices.size());
if (cuda::devices.size() < 2)
{
printf("Two or more GPUs with Peer-to-Peer access capability are required for %s.\n", argv[0]);
printf("Waiving test.\n");
return 0;
}
// Print full peer access matrix
print_peer_accessibility();
// But use a shorthand to find all peers of a device
std::vector<cuda::device_ref> peers;
for (auto& dev : cuda::devices)
{
const auto dev_peers = dev.peers();
if (dev_peers.size() != 0)
{
peers.assign(dev_peers.begin(), dev_peers.end());
peers.insert(peers.begin(), dev);
break;
}
}
if (peers.size() == 0)
{
printf("Two or more GPUs with Peer-to-Peer access capability are required, waving the test.\n");
return 0;
}
cuda::stream dev0_stream(peers[0]);
cuda::stream dev1_stream(peers[1]);
printf("Enabling peer access between GPU%d and GPU%d...\n", peers[0].get(), peers[1].get());
cuda::device_memory_pool_ref dev0_resource = cuda::device_default_memory_pool(peers[0]);
dev0_resource.enable_access_from(peers[1]);
cuda::device_memory_pool_ref dev1_resource = cuda::device_default_memory_pool(peers[1]);
dev1_resource.enable_access_from(peers[0]);
// Allocate buffers
constexpr size_t buf_cnt = 1024 * 1024 * 16;
printf("Allocating buffers (%iMB on GPU%d, GPU%d and CPU Host)...\n",
int(buf_cnt / 1024 / 1024 * sizeof(float)),
peers[0].get(),
peers[1].get());
cudax::uninitialized_buffer<float, cuda::mr::device_accessible> dev0_buffer(dev0_resource, buf_cnt);
cudax::uninitialized_buffer<float, cuda::mr::device_accessible> dev1_buffer(dev1_resource, buf_cnt);
benchmark_cross_device_ping_pong_copy(dev0_stream, dev1_stream, dev0_buffer, dev1_buffer);
test_cross_device_access_from_kernel(dev0_stream, dev1_stream, dev0_buffer, dev1_buffer);
// Disable peer access
printf("Disabling peer access...\n");
dev0_resource.disable_access_from(peers[1]);
dev1_resource.disable_access_from(peers[0]);
// No cleanup needed
printf("Test passed\n");
return 0;
}
catch (const std::exception& e)
{
printf("caught an exception: \"%s\"\n", e.what());
}
catch (...)
{
printf("caught an unknown exception\n");
}

View File

@@ -1,82 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cuda/experimental/execution.cuh>
#include <nv/target>
#include <cstdio>
#include <cuda_runtime_api.h>
namespace cudax = cuda::experimental;
namespace ex = cudax::execution;
// This example demonstrates how to use the experimental CUDA implementation of
// C++26's std::execution async tasking framework.
int main()
{
try
{
auto tctx = ex::thread_context{};
auto sctx = ex::stream_context{cuda::device_ref{0}};
auto gpu = sctx.get_scheduler();
const auto bulk_shape = 10;
const auto bulk_fn = [] __device__(const int index, int i) noexcept {
const int tid = static_cast<int>(blockIdx.x * blockDim.x + threadIdx.x);
if (tid < bulk_shape)
{
printf("Hello from bulk task on device! index = %d, i = %d\n", index, i);
}
};
auto start =
// begin work on the GPU:
ex::schedule(gpu)
// execute a device lambda on the GPU:
| ex::then([] __device__() noexcept -> int {
printf("Hello from lambda on device!\n");
return 42;
})
// do some parallel work on the GPU:
| ex::bulk(ex::par, bulk_shape, bulk_fn) //
// transfer execution back to the CPU:
| ex::continues_on(tctx.get_scheduler())
// execute a host/device lambda on the CPU:
| ex::then([] __host__ __device__(int i) noexcept -> int {
NV_IF_ELSE_TARGET(NV_IS_HOST,
(printf("Hello from lambda on host! i = %d\n", i);),
(printf("OOPS! still on the device! i = %d\n", i);))
return i + 1;
});
// run the task, wait for it to finish, and get the result
auto [i] = ex::sync_wait(std::move(start)).value();
printf("All done on the host! result = %d\n", i);
}
catch (cuda::cuda_error const& e)
{
std::printf("CUDA error: %s\n", e.what());
}
catch (std::exception const& e)
{
std::printf("Exception: %s\n", e.what());
}
catch (...)
{
std::printf("Unknown exception\n");
}
}

View File

@@ -1,73 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief An AXPY kernel described using a cuda_kernel construct
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void axpy(double a, slice<const double> x, slice<double> y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < x.size(); i += nthreads)
{
y(i) += a * x(i);
}
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
context ctx = graph_ctx();
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X */
ctx.cuda_kernel(lX.read(), lY.rw())->*[&](auto dX, auto dY) {
// axpy<<<16, 128, 0, ...>>>(alpha, dX, dY)
return cuda_kernel_desc{axpy, 16, 128, 0, alpha, dX, dY};
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + alpha * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,80 +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 Example of task implementing a chain of CUDA kernels
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void axpy(double a, slice<const double> x, slice<double> y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < x.size(); i += nthreads)
{
y(i) += a * x(i);
}
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
context ctx = graph_ctx();
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
double beta = 4.5;
double gamma = -4.1;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X, Y = Y + beta X and then Y = Y + gamma X */
ctx.cuda_kernel_chain(lX.read(), lY.rw())->*[&](auto dX, auto dY) {
// clang-format off
return std::vector<cuda_kernel_desc> {
{ axpy, 16, 128, 0, alpha, dX, dY },
{ axpy, 16, 128, 0, beta, dX, dY },
{ axpy, 16, 128, 0, gamma, dX, dY }
};
// clang-format on
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + (alpha + beta + gamma) * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,62 +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 Example of AXPY kernel implemented with the launch API
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
context ctx;
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X */
ctx.launch(lX.read(), lY.rw())->*[=] _CCCL_DEVICE(auto t, auto dX, auto dY) {
for (auto ind : t.apply_partition(shape(dX)))
{
dY(ind) += alpha * dX(ind);
}
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + alpha * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,61 +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 An AXPY kernel implemented using the parallel_for construct
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
context ctx;
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X */
ctx.parallel_for(lY.shape(), lX.read(), lY.rw())->*[alpha] __device__(size_t i, auto dX, auto dY) {
dY(i) += alpha * dX(i);
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + alpha * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,72 +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 An AXPY kernel implemented with CUDA kernel in a task
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void axpy(double a, slice<const double> x, slice<double> y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < x.size(); i += nthreads)
{
y(i) += a * x(i);
}
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
context ctx;
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X */
ctx.task(lX.read(), lY.rw())->*[&](cudaStream_t s, auto dX, auto dY) {
axpy<<<16, 128, 0, s>>>(alpha, dX, dY);
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + alpha * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,81 +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 An AXPY kernel implemented with a task of the CUDA graph backend and
* a host callback
*
* The host_launch mechanism is also illustrated
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void axpy(double a, slice<const double> x, slice<double> y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < x.size(); i += nthreads)
{
y(i) += a * x(i);
}
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
graph_ctx ctx;
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X */
ctx.task(lX.read(), lY.rw())->*[&](cudaStream_t s, auto dX, auto dY) {
axpy<<<16, 128, 0, s>>>(alpha, dX, dY);
};
/* Asynchronously check the result on the host */
ctx.host_launch(lX.read(), lY.read())->*[&](auto hX, auto hY) {
for (size_t ind = 0; ind < hX.extent(0); ind++)
{
// Y should be Y0 + alpha X0
EXPECT(fabs(hY(ind) - (Y0(ind) + alpha * X0(ind))) < 0.0001);
// X should be X0
EXPECT(fabs(hX(ind) - X0(ind)) < 0.0001);
}
};
ctx.finalize();
}

View File

@@ -1,69 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief This example illustrates how we can create temporary data from shapes, and use them in tasks
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
const int n = 4096;
int X[n];
int Y[n];
for (size_t i = 0; i < n; i++)
{
X[i] = 3 * i;
Y[i] = 2 * i - 3;
}
context ctx;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
// Select an odd number
int niter = 19;
assert(niter % 2 == 1);
for (int iter = 0; iter < niter; iter++)
{
// We here define a temporary vector with the same shape as X, for which there is no existing copy
// This data handle has a limited scope, so that it is automatically destroyed at each iteration of the loop
auto tmp = ctx.logical_data(lX.shape());
ctx.task(lY.rw(), lX.rw(), tmp.write())->*[](cudaStream_t s, auto sY, auto sX, auto sTMP) {
// We swap X and Y using TMP as temporary buffer
// TMP = X
cuda_safe_call(
cudaMemcpyAsync(sTMP.data_handle(), sX.data_handle(), n * sizeof(int), cudaMemcpyDeviceToDevice, s));
// X = Y
cuda_safe_call(cudaMemcpyAsync(sX.data_handle(), sY.data_handle(), n * sizeof(int), cudaMemcpyDeviceToDevice, s));
// Y = TMP
cuda_safe_call(
cudaMemcpyAsync(sY.data_handle(), sTMP.data_handle(), n * sizeof(int), cudaMemcpyDeviceToDevice, s));
};
}
ctx.finalize();
// We have exchanged an odd number of times, so they must be inverted
for (size_t i = 0; i < n; i++)
{
assert(X[i] == 2 * i - 3);
assert(Y[i] == 3 * i);
}
}

View File

@@ -1,79 +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 An example of Fibonacci sequence illustrating how we can use
* dynamically created logical data and the run_once utility
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int fibo_ref(int n)
{
if (n < 2)
{
return n;
}
else
{
return fibo_ref(n - 1) + fibo_ref(n - 2);
}
}
__global__ void add(slice<int> out, const slice<const int> in1, const slice<const int> in2)
{
out(0) = in1(0) + in2(0);
}
__global__ void set(slice<int> out, int val)
{
out(0) = val;
}
logical_data<slice<int>> compute_fibo(context& ctx, int n)
{
// The result for a given value n is memoized in a logical_data that will be reused every time we compute the same
// value
return run_once(n)->*[&](int n) {
auto result = ctx.logical_data(shape_of<slice<int>>(1)).set_symbol(std::to_string(n));
if (n < 2)
{
ctx.task(result.write()).set_symbol("fibo" + std::to_string(n))->*[=](cudaStream_t s, auto sresult) {
set<<<1, 1, 0, s>>>(sresult, n);
};
}
else
{
auto fib2 = compute_fibo(ctx, n - 2);
auto fib1 = compute_fibo(ctx, n - 1);
ctx.task(fib1.read(), fib2.read(), result.write()).set_symbol("fibo" + std::to_string(n))
->*[=](cudaStream_t s, auto s1, auto s2, auto sresult) {
add<<<1, 1, 0, s>>>(sresult, s1, s2);
};
}
return result;
};
}
int main(int argc, char** argv)
{
int n = (argc > 1) ? atoi(argv[1]) : 4;
context ctx;
auto result = compute_fibo(ctx, n);
ctx.host_launch(result.read())->*[&](auto res) {
EXPECT(res(0) == fibo_ref(n));
};
ctx.finalize();
}

View File

@@ -1,75 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief An example of Fibonacci sequence illustrating how we can use
* dynamically created logical data
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int fibo_ref(int n)
{
if (n < 2)
{
return n;
}
else
{
return fibo_ref(n - 1) + fibo_ref(n - 2);
}
}
__global__ void add(slice<int> out, const slice<const int> in1, const slice<const int> in2)
{
out(0) = in1(0) + in2(0);
}
__global__ void set(slice<int> out, int val)
{
out(0) = val;
}
logical_data<slice<int>> compute_fibo(context& ctx, int n)
{
auto out = ctx.logical_data(shape_of<slice<int>>(1));
if (n < 2)
{
ctx.task(out.write())->*[=](cudaStream_t s, auto sout) {
set<<<1, 1, 0, s>>>(sout, n);
};
}
else
{
auto fib1 = compute_fibo(ctx, n - 1);
auto fib2 = compute_fibo(ctx, n - 2);
ctx.task(fib1.read(), fib2.read(), out.write())->*[=](cudaStream_t s, auto s1, auto s2, auto sout) {
add<<<1, 1, 0, s>>>(sout, s1, s2);
};
}
return out;
}
int main(int argc, char** argv)
{
int n = (argc > 1) ? atoi(argv[1]) : 4;
context ctx; // = graph_ctx();
auto result = compute_fibo(ctx, n);
ctx.host_launch(result.read())->*[&](auto res) {
EXPECT(res(0) == fibo_ref(n));
};
ctx.finalize();
}

View File

@@ -1,95 +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 Example of reduction implementing using CUB kernels
*/
#include <thrust/device_vector.h>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
template <int BLOCK_THREADS, typename T>
__global__ void reduce(slice<const T> values, slice<T> partials, size_t nelems)
{
using namespace cub;
typedef BlockReduce<T, BLOCK_THREADS> BlockReduceT;
auto thread_id = BLOCK_THREADS * blockIdx.x + threadIdx.x;
// Local reduction
T local_sum = 0;
for (size_t ind = thread_id; ind < nelems; ind += blockDim.x * gridDim.x)
{
local_sum += values(ind);
}
__shared__ typename BlockReduceT::TempStorage temp_storage;
// Per-thread tile data
T result = BlockReduceT(temp_storage).Sum(local_sum);
if (threadIdx.x == 0)
{
partials(blockIdx.x) = result;
}
}
template <typename Ctx>
void run()
{
Ctx ctx;
const size_t N = 1024 * 16;
const size_t BLOCK_SIZE = 128;
const size_t num_blocks = 32;
int *X, ref_tot;
X = new int[N];
ref_tot = 0;
for (size_t ind = 0; ind < N; ind++)
{
X[ind] = rand() % N;
ref_tot += X[ind];
}
auto values = ctx.logical_data(X, {N});
auto partials = ctx.logical_data(shape_of<slice<int>>(num_blocks));
auto result = ctx.logical_data(shape_of<slice<int>>(1));
ctx.task(values.read(), partials.write(), result.write())->*[&](auto stream, auto values, auto partials, auto result) {
// reduce values into partials
reduce<BLOCK_SIZE, int><<<num_blocks, BLOCK_SIZE, 0, stream>>>(values, partials, N);
// reduce partials on a single block into result
reduce<BLOCK_SIZE, int><<<1, BLOCK_SIZE, 0, stream>>>(partials, result, num_blocks);
};
ctx.host_launch(result.read())->*[&](auto p) {
if (p(0) != ref_tot)
{
fprintf(stderr, "INCORRECT RESULT: p sum = %d, ref tot = %d\n", p(0), ref_tot);
abort();
}
};
ctx.finalize();
}
int main()
{
run<stream_ctx>();
run<graph_ctx>();
}

View File

@@ -1,55 +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 Implementation of the DOT kernel using a reduce access mode
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
const size_t N = 16;
double X[N], Y[N];
double ref_res = 0.0;
for (size_t i = 0; i < N; i++)
{
X[i] = cos(double(i));
Y[i] = sin(double(i));
// Compute the reference result of the DOT product of X and Y
ref_res += X[i] * Y[i];
}
context ctx;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
auto lsum = ctx.logical_data(shape_of<scalar_view<double>>());
/* Compute sum(x_i * y_i)*/
ctx.parallel_for(lY.shape(), lX.read(), lY.read(), lsum.reduce(reducer::sum<double>{}))
->*[] __device__(size_t i, auto dX, auto dY, double& sum) {
sum += dX(i) * dY(i);
};
double res = ctx.wait(lsum);
ctx.finalize();
_CCCL_ASSERT(fabs(res - ref_res) < 0.0001, "Invalid result");
}

View File

@@ -1,118 +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 Toy example to reproduce the asynchrony of a 1F1B pipeline
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void forward(slice<int>, 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;
}
}
__global__ void backward(slice<int>, 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;
}
}
int main(int argc, char** argv)
{
context ctx;
// Use a graph context if the second argument is set and not null
if (argc > 2 && atoi(argv[2]))
{
ctx = graph_ctx();
}
int device;
cudaGetDevice(&device);
// cudaDevAttrClockRate: Peak clock frequency in kilohertz;
int clock_rate;
cudaDeviceGetAttribute(&clock_rate, cudaDevAttrClockRate, device);
auto occ_f = reserved::compute_occupancy(forward);
auto occ_b = reserved::compute_occupancy(backward);
int factor = 1;
if (argc > 1)
{
factor = atoi(argv[1]);
}
size_t num_batches = 8 * factor;
int num_devs = 8;
int real_devs;
cuda_safe_call(cudaGetDeviceCount(&real_devs));
std::vector<logical_data<slice<int>>> data;
for (size_t b = 0; b < num_batches; b++)
{
auto batch_data = ctx.logical_data(shape_of<slice<int>>(1024));
data.push_back(batch_data);
ctx.task(exec_place::device(0), data[b].write())->*[](cudaStream_t, auto) {
// Init ...
};
}
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
size_t niter = 10;
for (size_t iter = 0; iter < niter; iter++)
{
for (size_t b = 0; b < num_batches; b++)
{
for (int d = 0; d < num_devs; d++)
{
ctx.task(exec_place::device(d % real_devs), data[b].rw())->*[=](cudaStream_t s, auto bd) {
int ms = 10;
long long int clock_cnt = (long long int) (ms * clock_rate / factor);
forward<<<occ_f.min_grid_size, occ_f.block_size, 0, s>>>(bd, clock_cnt);
};
}
// }
//
// for (size_t b = 0; b < num_batches; b++) {
for (int d = num_devs; d-- > 0;)
{
ctx.task(exec_place::device(d % real_devs), data[b].rw())->*[=](cudaStream_t s, auto bd) {
int ms = 20;
long long int clock_cnt = (long long int) (ms * clock_rate / factor);
backward<<<occ_b.min_grid_size, occ_b.block_size, 0, s>>>(bd, clock_cnt);
};
}
}
/* We introduce a fence because the actual pipeline would introduce
* some all to all communication to update coefficients */
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
}
ctx.finalize();
return 0;
}

View File

@@ -1,124 +0,0 @@
set(
stf_example_sources
01-axpy.cu
01-axpy-cuda_kernel.cu
01-axpy-cuda_kernel_chain.cu
02-axpy-host_launch.cu
03-temporary-data.cu
04-fibonacci.cu
04-fibonacci-run_once.cu
08-cub-reduce.cu
axpy-annotated.cu
void_data_interface.cu
explicit_data_places.cu
partitioned_axpy.cu
thrust_zip_iterator.cu
1f1b.cu
)
# Examples which rely on code generation (parallel_for or launch)
set(
stf_example_codegen_sources
01-axpy-launch.cu
01-axpy-parallel_for.cu
binary_fhe.cu
binary_fhe_stackable.cu
09-dot-reduce.cu
cfd.cu
custom_data_interface.cu
fdtd_mgpu.cu
fdtd_while.cu
fdtd_repeat_n.cu
frozen_data_init.cu
graph_algorithms/degree_centrality.cu
graph_algorithms/jaccard.cu
graph_algorithms/pagerank.cu
graph_algorithms/pagerank_batched.cu
graph_algorithms/pagerank_while.cu
graph_algorithms/tricount.cu
graph_scope.cu
heat.cu
heat_mgpu.cu
jacobi.cu
jacobi_pfor.cu
jacobi_stackable.cu
jacobi_stackable_raii.cu
jacobi_update_cond.cu
launch_histogram.cu
launch_scan.cu
launch_sum.cu
launch_sum_cub.cu
linear_algebra/burger.cu
linear_algebra/burger_sensitivity.cu
linear_algebra/cg_csr.cu
linear_algebra/cg_csr_stackable.cu
logical_gates_composition.cu
mandelbrot.cu
parallel_for_2D.cu
pi.cu
scan.cu
sqrt_newton_stackable.cu
standalone-launches.cu
word_count.cu
word_count_reduce.cu
)
# Examples using CUBLAS, CUSOLVER...
set(
stf_example_mathlib_sources
linear_algebra/06-pdgemm.cu
linear_algebra/06-pdgemm-stackable.cu
linear_algebra/07-cholesky.cu
linear_algebra/07-potri.cu
linear_algebra/cg_dense_2D.cu
linear_algebra/strassen.cu
)
cccl_get_cudatoolkit()
## cudax_add_stf_example
#
# Add an stf example executable and register it with ctest.
#
# target_name_var: Variable name to overwrite with the name of the example
# target. Useful for modifying the example/target after creation.
# source: The source file for the example.
#
# Additional args are passed to cudax_stf_configure_target.
function(cudax_add_stf_example target_name_var source)
get_filename_component(dir ${source} DIRECTORY)
get_filename_component(filename ${source} NAME_WE)
if (dir)
set(filename "${dir}/${filename}")
endif()
string(REPLACE "/" "." example_name "stf/${filename}")
set(example_target cudax.example.${example_name})
cccl_add_executable(${example_target} SOURCES ${source} ADD_CTEST)
cudax_stf_configure_target(${example_target} ${ARGN})
target_link_libraries(
${example_target}
PRIVATE #
cudax.compiler_interface
cudax.examples.thrust
)
set(${target_name_var} ${example_target} PARENT_SCOPE)
endfunction()
foreach (source IN LISTS stf_example_sources)
cudax_add_stf_example(example_target "${source}")
endforeach()
if (cudax_ENABLE_CUDASTF_CODE_GENERATION)
foreach (source IN LISTS stf_example_codegen_sources)
cudax_add_stf_example(example_target "${source}")
endforeach()
endif()
if (cudax_ENABLE_CUDASTF_MATHLIBS)
foreach (source IN LISTS stf_example_mathlib_sources)
cudax_add_stf_example(example_target "${source}" LINK_MATHLIBS)
endforeach()
endif()

View File

@@ -1,82 +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 we can annotate tasks and logical data with debugging symbol
*
* CUDASTF_DOT_FILE=axpy.dot build/examples/axpy-annotated
*
* # Generate the visualization from this dot file in PDF or PNG format
* dot -Tpdf axpy.dot -o axpy.pdf
* dot -Tpng axpy.dot -o axpy.png
*
* # Generate visualization with events (for advanced users)
* CUDASTF_DOT_IGNORE_PREREQS=0 CUDASTF_DOT_FILE=axpy-with-events.dot build/examples/axpy-annotated
* dot -Tpng axpy-with-events.dot -o axpy-with-events.png
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void axpy(double a, slice<const double> x, slice<double> y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < x.size(); i += nthreads)
{
y(i) += a * x(i);
}
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
context ctx;
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X).set_symbol("X");
auto lY = ctx.logical_data(Y).set_symbol("Y");
/* Compute Y = Y + alpha X */
ctx.task(lX.read(), lY.rw()).set_symbol("axpy")->*[&](cudaStream_t s, auto dX, auto dY) {
axpy<<<16, 128, 0, s>>>(alpha, dX, dY);
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + alpha * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,201 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief A toy example to illustrate how we can compose logical operations
* over encrypted data
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
class ciphertext;
class plaintext
{
public:
plaintext(const context& ctx)
: ctx(ctx)
{}
plaintext(context& ctx, std::vector<char> v)
: values(v)
, ctx(ctx)
{
l = ctx.logical_data(&values[0], values.size());
}
void set_symbol(std::string s)
{
l.set_symbol(s);
symbol = s;
}
std::string get_symbol() const
{
return symbol;
}
std::string symbol;
const logical_data<slice<char>>& data() const
{
return l;
}
logical_data<slice<char>>& data()
{
return l;
}
// This will asynchronously fill string s
void convert_to_vector(std::vector<char>& v)
{
ctx.host_launch(l.read()).set_symbol("to_vector")->*[&](auto dl) {
v.resize(dl.size());
for (size_t i = 0; i < dl.size(); i++)
{
v[i] = dl(i);
}
};
}
ciphertext encrypt() const;
logical_data<slice<char>> l;
private:
std::vector<char> values;
mutable context ctx;
};
class ciphertext
{
public:
ciphertext(const context& ctx)
: ctx(ctx)
{}
plaintext decrypt() const
{
plaintext p(ctx);
p.l = ctx.logical_data(shape_of<slice<char>>(l.shape().size()));
// fprintf(stderr, "Decrypting...\n");
ctx.parallel_for(l.shape(), l.read(), p.l.write()).set_symbol("decrypt")->*
[] _CCCL_DEVICE(size_t i, auto dctxt, auto dptxt) {
dptxt(i) = char((dctxt(i) >> 32));
// printf("DECRYPT %ld : %lx -> %x\n", i, dctxt(i), (int) dptxt(i));
};
return p;
}
ciphertext operator|(const ciphertext& other) const
{
ciphertext result(ctx);
result.l = ctx.logical_data(data().shape());
ctx.parallel_for(data().shape(), data().read(), other.data().read(), result.data().write()).set_symbol("OR")->*
[] _CCCL_DEVICE(size_t i, auto d_c1, auto d_c2, auto d_res) {
d_res(i) = d_c1(i) | d_c2(i);
};
return result;
}
ciphertext operator&(const ciphertext& other) const
{
ciphertext result(ctx);
result.l = ctx.logical_data(data().shape());
ctx.parallel_for(data().shape(), data().read(), other.data().read(), result.data().write()).set_symbol("AND")->*
[] _CCCL_DEVICE(size_t i, auto d_c1, auto d_c2, auto d_res) {
d_res(i) = d_c1(i) & d_c2(i);
};
return result;
}
ciphertext operator~() const
{
ciphertext result(ctx);
result.l = ctx.logical_data(data().shape());
ctx.parallel_for(data().shape(), data().read(), result.data().write()).set_symbol("NOT")->*
[] _CCCL_DEVICE(size_t i, auto d_c, auto d_res) {
d_res(i) = ~d_c(i);
};
return result;
}
const logical_data<slice<uint64_t>>& data() const
{
return l;
}
logical_data<slice<uint64_t>>& data()
{
return l;
}
logical_data<slice<uint64_t>> l;
private:
mutable context ctx;
};
ciphertext plaintext::encrypt() const
{
ciphertext c(ctx);
c.l = ctx.logical_data(shape_of<slice<uint64_t>>(l.shape().size()));
ctx.parallel_for(l.shape(), l.read(), c.l.write()).set_symbol("encrypt")->*
[] _CCCL_DEVICE(size_t i, auto dptxt, auto dctxt) {
// A super safe encryption !
dctxt(i) = ((uint64_t) (dptxt(i)) << 32 | 0x4);
};
return c;
}
template <typename T>
T circuit(const T& a, const T& b)
{
return (~((a | ~b) & (~a | b)));
}
int main()
{
context ctx;
std::vector<char> vA{3, 3, 2, 2, 17};
plaintext pA(ctx, vA);
pA.set_symbol("A");
std::vector<char> vB{1, 7, 7, 7, 49};
plaintext pB(ctx, vB);
pB.set_symbol("B");
auto eA = pA.encrypt();
auto eB = pB.encrypt();
auto out = circuit(eA, eB);
std::vector<char> v_out;
out.decrypt().convert_to_vector(v_out);
ctx.finalize();
for (size_t i = 0; i < v_out.size(); i++)
{
char expected = circuit(vA[i], vB[i]);
EXPECT(expected == v_out[i]);
}
}

View File

@@ -1,240 +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) 2024-2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief A toy example to illustrate how we can compose logical operations over encrypted data
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
#include <memory>
class ciphertext;
class plaintext
{
public:
plaintext(const stackable_ctx& ctx)
: ctx(ctx)
{}
plaintext(stackable_ctx& ctx, ::std::vector<char> v)
: values(mv(v))
, ctx(ctx)
, ld(ctx.logical_data(values.data(), values.size()))
{}
auto& set_symbol(const std::string& s)
{
ld.set_symbol(s);
symbol = s;
return *this;
}
const std::string& get_symbol() const
{
return symbol;
}
// This will asynchronously fill string s
void convert_to_vector(std::vector<char>& v)
{
ctx.host_launch(ld.read()).set_symbol("to_vector")->*[&](auto dl) {
v.resize(dl.size());
for (size_t i = 0; i < dl.size(); i++)
{
v[i] = dl(i);
}
};
}
ciphertext encrypt() const;
private:
std::vector<char> values;
mutable stackable_ctx ctx;
::std::string symbol;
public:
mutable stackable_logical_data<slice<char>> ld;
};
class ciphertext
{
public:
ciphertext() = default;
// We need a deep-copy semantic
ciphertext(const ciphertext& other)
: ctx(other.ctx)
, symbol(other.symbol)
{
copy_content(ctx, other, *this);
}
ciphertext(const stackable_ctx& ctx)
: ctx(ctx)
{}
ciphertext(ciphertext&&) = default;
ciphertext& operator=(ciphertext&&) = default;
static void copy_content(stackable_ctx& ctx, const ciphertext& src, ciphertext& dst)
{
dst.ld = ctx.logical_data(src.ld.shape());
ctx.parallel_for(src.ld.shape(), src.ld.read(), dst.ld.write()).set_symbol("copy")->*
[] __device__(size_t i, auto src, auto dst) {
dst(i) = src(i);
};
}
auto& set_symbol(std::string s)
{
ld.set_symbol(s);
symbol = mv(s);
return *this;
}
const std::string& get_symbol() const
{
return symbol;
}
plaintext decrypt() const
{
plaintext p(ctx);
p.ld = ctx.logical_data(shape_of<slice<char>>(ld.shape().size()));
ctx.parallel_for(ld.shape(), ld.read(), p.ld.write()).set_symbol("decrypt")->*
[] __device__(size_t i, auto cipher_data, auto plain_data) {
plain_data(i) = static_cast<char>(cipher_data(i) >> 32);
};
return p;
}
// Copy assignment operator
// We need a deep-copy semantic
ciphertext& operator=(const ciphertext& other)
{
if (this != &other)
{
ctx = other.ctx;
symbol = other.symbol;
copy_content(ctx, other, *this);
}
return *this;
}
ciphertext operator|(const ciphertext& other) const
{
ciphertext result(ctx);
result.ld = ctx.logical_data(ld.shape());
ctx.parallel_for(ld.shape(), ld.read(), other.ld.read(), result.ld.write()).set_symbol("OR")->*
[] __device__(size_t i, auto d_c1, auto d_c2, auto d_res) {
d_res(i) = d_c1(i) | d_c2(i);
};
return result;
}
ciphertext operator&(const ciphertext& other) const
{
ciphertext result(ctx);
result.ld = ctx.logical_data(ld.shape());
ctx.parallel_for(ld.shape(), ld.read(), other.ld.read(), result.ld.write()).set_symbol("AND")->*
[] __device__(size_t i, auto d_c1, auto d_c2, auto d_res) {
d_res(i) = d_c1(i) & d_c2(i);
};
return result;
}
ciphertext operator~() const
{
ciphertext result(ctx);
result.ld = ctx.logical_data(ld.shape());
ctx.parallel_for(ld.shape(), ld.read(), result.ld.write()).set_symbol("NOT")->*
[] __device__(size_t i, auto d_c, auto d_res) {
d_res(i) = ~d_c(i);
};
return result;
}
mutable stackable_logical_data<slice<uint64_t>> ld;
private:
mutable stackable_ctx ctx;
::std::string symbol;
};
ciphertext plaintext::encrypt() const
{
ciphertext c(ctx);
c.ld = ctx.logical_data(shape_of<slice<uint64_t>>(ld.shape().size()));
ctx.parallel_for(ld.shape(), ld.read(), c.ld.write()).set_symbol("encrypt")->*
[] __device__(size_t i, auto dptxt, auto dctxt) {
// A super safe encryption !
dctxt(i) = ((uint64_t) (dptxt(i)) << 32 | 0x4);
};
return c;
}
template <typename T>
T circuit(const T& a, const T& b)
{
return ~((a | ~b) & (~a | b));
}
int main()
{
stackable_ctx ctx;
const std::vector<char> vA{3, 3, 2, 2, 17};
plaintext pA(ctx, std::vector<char>(vA));
pA.set_symbol("A");
const std::vector<char> vB{1, 7, 7, 7, 49};
plaintext pB(ctx, std::vector<char>(vB));
pB.set_symbol("B");
auto s_encrypt = ctx.dot_section("encrypt");
auto eA = pA.encrypt().set_symbol("A");
auto eB = pB.encrypt().set_symbol("B");
s_encrypt.end();
ctx.push();
auto s_circuit = ctx.dot_section("circuit");
auto out = circuit(eA, eB);
s_circuit.end();
ctx.pop();
std::vector<char> v_out;
out.decrypt().convert_to_vector(v_out);
ctx.finalize();
for (size_t i = 0; i < v_out.size(); i++)
{
char expected = circuit(vA[i], vB[i]);
EXPECT(expected == v_out[i]);
}
}

View File

@@ -1,480 +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 Simulation of a fluid over a regular grid with an implicit Jacobi solver
*/
#include <cuda/experimental/stf.cuh>
#include <chrono>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
using namespace std::chrono;
using namespace cuda::experimental::stf;
/* wall-clock time */
double gettime()
{
auto now = system_clock::now().time_since_epoch();
return duration_cast<duration<double>>(now).count();
}
void writeplotfile(int m, int n, int scale)
{
FILE* gnuplot = EXPECT(fopen("cfd.plt", "w"));
SCOPE(exit)
{
EXPECT(fclose(gnuplot) == 0);
};
fprintf(gnuplot,
"set terminal pngcairo\n"
"set output 'cfd_output.png'\n"
"set size square\n"
"set key off\n"
"unset xtics\n"
"unset ytics\n");
fprintf(gnuplot, "set xrange [%i:%i]\n", 1 - scale, m + scale);
fprintf(gnuplot, "set yrange [%i:%i]\n", 1 - scale, n + scale);
fprintf(gnuplot,
"plot \"colourmap.dat\" w rgbimage, \"velocity.dat\" u "
"1:2:(%d*0.75*$3/sqrt($3**2+$4**2)):(%d*0.75*$4/sqrt($3**2+$4**2)) with vectors lc rgb \"#7F7F7F\"",
scale,
scale);
// printf("\nWritten gnuplot script 'cfd.plt'\n");
}
double colfunc(double x)
{
double x1 = 0.2;
double x2 = 0.5;
double absx = fabs(x);
if (absx > x2)
{
return 0.0;
}
else if (absx < x1)
{
return 1.0;
}
else
{
return 1.0 - pow((absx - x1) / (x2 - x1), 2);
}
}
void hue2rgb(double hue, int& r, int& g, int& b)
{
int rgbmax = 255;
r = (int) (rgbmax * colfunc(hue - 1.0));
g = (int) (rgbmax * colfunc(hue - 0.5));
b = (int) (rgbmax * colfunc(hue));
}
void writedatafiles(context& ctx, logical_data<slice<double, 2>> lpsi, int m, int n, int scale)
{
auto lvel = ctx.logical_data(shape_of<slice<double, 3>>(m, n, 2)).set_symbol("vel");
auto lrgb = ctx.logical_data(shape_of<slice<int, 3>>(m, n, 3)).set_symbol("rgb");
ctx.host_launch(lpsi.read(), lvel.write(), lrgb.write()).set_symbol("writedatafiles")
->*[=](auto psi, auto vel, auto rgb) {
// printf("\n\nWriting data files ...\n");
// calculate velocities and hues
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
vel(i, j, 0) = (psi(i + 1, j + 2) - psi(i + 1, j)) / 2.0;
vel(i, j, 1) = -(psi(i + 2, j + 1) - psi(i, j + 1)) / 2.0;
double v1 = vel(i, j, 0);
double v2 = vel(i, j, 1);
double modvsq = v1 * v1 + v2 * v2;
double hue = pow(modvsq, 0.4);
hue2rgb(hue, rgb(i, j, 0), rgb(i, j, 1), rgb(i, j, 2));
}
}
// write data
FILE* cfile = EXPECT(fopen("colourmap.dat", "w"));
SCOPE(exit)
{
fclose(cfile);
};
FILE* vfile = EXPECT(fopen("velocity.dat", "w"));
SCOPE(exit)
{
fclose(vfile);
};
for (int i = 0; i < m; i++)
{
int ix = i + 1;
for (int j = 0; j < n; j++)
{
int iy = j + 1;
fprintf(cfile, "%i %i %i %i %i\n", ix, iy, rgb(i, j, 0), rgb(i, j, 1), rgb(i, j, 2));
if ((ix - 1) % scale == (scale - 1) / 2 && (iy - 1) % scale == (scale - 1) / 2)
{
fprintf(vfile, "%i %i %f %f\n", ix, iy, vel(i, j, 0), vel(i, j, 1));
}
}
}
// printf("... done!\n");
writeplotfile(m, n, scale);
};
}
void jacobistep(context& ctx, logical_data<slice<double, 2>> lpsinew, logical_data<slice<double, 2>> lpsi, int m, int n)
{
ctx.parallel_for(box<2>({1, m + 1}, {1, n + 1}), lpsinew.write(), lpsi.read()).set_symbol("jacobi_step")
->*[] __device__(size_t i, size_t j, auto psinew, auto psi) {
psinew(i, j) = 0.25 * (psi(i - 1, j) + psi(i + 1, j) + psi(i, j + 1) + psi(i, j - 1));
};
}
void jacobistepvort(
context& ctx,
logical_data<slice<double, 2>> lzetnew,
logical_data<slice<double, 2>> lpsinew,
logical_data<slice<double, 2>> lzet,
logical_data<slice<double, 2>> lpsi,
int m,
int n,
double re)
{
ctx.parallel_for(box<2>({1, m + 1}, {1, n + 1}), lpsinew.write(), lpsi.read(), lzet.read())
.set_symbol("jacobi_step_psi")
->*[] __device__(size_t i, size_t j, auto psinew, auto psi, auto zet) {
psinew(i, j) = 0.25 * (psi(i - 1, j) + psi(i + 1, j) + psi(i, j + 1) + psi(i, j - 1)) - zet(i, j);
};
ctx.parallel_for(box<2>({1, m + 1}, {1, n + 1}), lzetnew.write(), lzet.read(), lpsi.read())
.set_symbol("jacobi_step_zet")
->*[=] __device__(size_t i, size_t j, auto zetnew, auto zet, auto psi) {
zetnew(i, j) = 0.25 * (zet(i - 1, j) + zet(i + 1, j) + zet(i, j + 1) + zet(i, j - 1))
- re / 16.0
* ((psi(i, j + 1) - psi(i, j - 1)) * (zet(i + 1, j) - zet(i - 1, j))
- (psi(i + 1, j) - psi(i - 1, j)) * (zet(i, j + 1) - zet(i, j - 1)));
};
}
double deltasq(context& ctx, logical_data<slice<double, 2>> lnewarr, logical_data<slice<double, 2>> loldarr)
{
auto ldsq = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("tmp_accumulator");
ctx.parallel_for(lnewarr.shape(), ldsq.reduce(reducer::sum<double>{}), lnewarr.read(), loldarr.read())
.set_symbol("deltasq")
->*[] __device__(size_t i, size_t j, auto& dsq, auto newarr, auto oldarr) {
double tmp = newarr(i, j) - oldarr(i, j);
dsq += tmp * tmp;
};
return ctx.wait(ldsq);
}
void boundarypsi(context& ctx, logical_data<slice<double, 2>> lpsi, int m, int /*n*/, int b, int h, int w)
{
// BCs on bottom edge
ctx.parallel_for(box({b + 1, b + w}), lpsi.rw()).set_symbol("boundary_bottom")->*[=] __device__(size_t i, auto psi) {
psi(i, 0) = double(i - b);
};
ctx.parallel_for(box<1>({b + w, m + 1}), lpsi.rw()).set_symbol("boundary_bottom")->*[=] __device__(size_t i, auto psi) {
psi(i, 0) = double(w);
};
// BCS on RHS
ctx.parallel_for(box({1, h + 1}), lpsi.rw()).set_symbol("boundary_right")->*[=] __device__(size_t j, auto psi) {
psi(m + 1, j) = double(w);
};
ctx.parallel_for(box({h + 1, h + w}), lpsi.rw()).set_symbol("boundary_right")->*[=] __device__(size_t j, auto psi) {
psi(m + 1, j) = (double) (w - j + h);
};
}
void boundaryzet(context& ctx, logical_data<slice<double, 2>> lzet, logical_data<slice<double, 2>> lpsi, int m, int n)
{
// set top/bottom BCs:
ctx.parallel_for(box({1, m + 1}), lzet.rw(), lpsi.read()).set_symbol("boundary_topbottom")
->*[=] __device__(size_t i, auto zet, auto psi) {
zet(i, 0) = 2.0 * (psi(i, 1) - psi(i, 0));
zet(i, n + 1) = 2.0 * (psi(i, n) - psi(i, n + 1));
};
// set left and right BCs:
ctx.parallel_for(box({1, n + 1}), lzet.rw(), lpsi.read()).set_symbol("boundary_leftright")
->*[=] __device__(size_t j, auto zet, auto psi) {
zet(0, j) = 2.0 * (psi(1, j) - psi(0, j));
zet(m + 1, j) = 2.0 * (psi(m, j) - psi(m + 1, j));
};
}
int main(int argc, char** argv)
{
context ctx;
int printfreq = 10; // output frequency
double error = -1.0;
double tolerance = 0.0001; //-1.0; // 0.0001; //tolerance for convergence. <=0 means do not check
// command line arguments
int scalefactor = 1, numiter = 10;
double re = -1.0; // Reynold's number - must be less than 3.7
// simulation sizes
int bbase = 10;
int hbase = 15;
int wbase = 5;
int mbase = 32;
int nbase = 32;
int irrotational = 1, checkerr = 0;
// do we stop because of tolerance?
if (tolerance > 0)
{
checkerr = 1;
}
// check command line parameters and parse them
if (argc > 5)
{
printf("Usage: cfd <scale> <numiter> [reynolds] [use_graphs]\n");
return 0;
}
if (argc > 1)
{
scalefactor = atoi(argv[1]);
}
if (argc > 2)
{
numiter = atoi(argv[2]);
}
if (argc > 3)
{
re = atof(argv[3]);
irrotational = 0;
}
// Use a CUDA graph backend
if (argc > 4)
{
if (atoi(argv[4]) == 1)
{
ctx = graph_ctx();
}
fprintf(stderr, "Using %s backend.\n", ctx.to_string().c_str());
}
// if (!checkerr) {
// printf("Scale Factor = %i, iterations = %i\n", scalefactor, numiter);
// } else {
// printf("Scale Factor = %i, iterations = %i, tolerance= %g\n", scalefactor, numiter, tolerance);
// }
// if (irrotational) {
// printf("Irrotational flow\n");
// } else {
// printf("Reynolds number = %f\n", re);
// }
tolerance /= scalefactor;
// Calculate b, h & w and m & n
int b = bbase * scalefactor;
int h = hbase * scalefactor;
int w = wbase * scalefactor;
int m = mbase * scalefactor;
int n = nbase * scalefactor;
re /= scalefactor;
// printf("Running CFD on %d x %d grid in serial\n", m, n);
// main arrays and their temporary versions
logical_data<slice<double, 2>> lzet, lzettmp, lpsi, lpsitmp;
// allocate arrays
lpsi = ctx.logical_data(shape_of<slice<double, 2>>(m + 2, n + 2)).set_symbol("psi");
lpsitmp = ctx.logical_data(lpsi.shape()).set_symbol("psi_tmp");
// zero the psi array
ctx.parallel_for(lpsi.shape(), lpsi.write()).set_symbol("InitPsi")->*[] __device__(size_t i, size_t j, auto psi) {
psi(i, j) = 0.0;
};
if (!irrotational)
{
lzet = ctx.logical_data(lpsi.shape()).set_symbol("zet");
lzettmp = ctx.logical_data(lpsi.shape()).set_symbol("zet_tmp");
// zero the zeta array
ctx.parallel_for(lzet.shape(), lzet.write()).set_symbol("InitZet")->*[] __device__(size_t i, size_t j, auto zet) {
zet(i, j) = 0.0;
};
}
// set the psi boundary conditions
boundarypsi(ctx, lpsi, m, n, b, h, w);
// compute normalisation factor for error
auto lbnorm = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("bnorm");
nvtx_range r_norm("Compute_Normalization");
// bnorm = psi * psi
ctx.parallel_for(lpsi.shape(), lpsi.read(), lbnorm.reduce(reducer::sum<double>{}))
->*[] __device__(size_t i, size_t j, auto psi, auto& bnorm) {
bnorm += psi(i, j) * psi(i, j);
};
if (!irrotational)
{
// update zeta BCs that depend on psi
boundaryzet(ctx, lzet, lpsi, m, n);
// update normalisation
ctx.parallel_for(lzet.shape(), lzet.read(), lbnorm.reduce(reducer::sum<double>{}, no_init{}))
->*[] __device__(size_t i, size_t j, auto zet, auto& bnorm_zet) {
bnorm_zet += zet(i, j) * zet(i, j);
};
}
r_norm.end();
double bnorm = ctx.wait(lbnorm);
bnorm = sqrt(bnorm);
// begin iterative Jacobi loop
// printf("\nStarting main loop...\n\n");
double tstart = gettime();
nvtx_range r_iter("Overall_Iteration");
int iter = 1;
for (; iter <= numiter; iter++)
{
// calculate psi for next iteration
if (irrotational)
{
jacobistep(ctx, lpsitmp, lpsi, m, n);
}
else
{
jacobistepvort(ctx, lzettmp, lpsitmp, lzet, lpsi, m, n, re);
}
// calculate current error if required
bool compute_error = (iter == numiter) || (checkerr && (iter % printfreq == 0));
if (compute_error)
{
error = deltasq(ctx, lpsitmp, lpsi);
if (!irrotational)
{
error += deltasq(ctx, lzettmp, lzet);
}
error = sqrt(error);
error = error / bnorm;
if (checkerr && (error < tolerance))
{
// printf("Converged on iteration %d\n", iter);
break;
}
}
// copy back
ctx.parallel_for(box<2>({1, m + 1}, {1, n + 1}), lpsi.rw(), lpsitmp.read()).set_symbol("SwitchPsi")
->*[] __device__(size_t i, size_t j, auto psi, auto psitmp) {
psi(i, j) = psitmp(i, j);
};
if (!irrotational)
{
ctx.parallel_for(box<2>({1, m + 1}, {1, n + 1}), lzet.rw(), lzettmp.read()).set_symbol("SwitchZet")
->*[] __device__(size_t i, size_t j, auto zet, auto zettmp) {
zet(i, j) = zettmp(i, j);
};
}
if (!irrotational)
{
// update zeta BCs that depend on psi
boundaryzet(ctx, lzet, lpsi, m, n);
}
// if (iter % printfreq == 0) {
// if (!checkerr) {
// printf("Completed iteration %d\n", iter);
// } else {
// printf("Completed iteration %d, error = %g\n", iter, error);
// }
// }
}
r_iter.end();
if (iter > numiter)
{
iter = numiter;
}
double tstop = gettime();
double ttot = tstop - tstart;
double titer = ttot / (double) iter;
// output results
writedatafiles(ctx, lpsi, m, n, scalefactor);
ctx.finalize();
// print out some stats
// printf("\n... finished\n");
printf("After %d iterations, the error is %g\n", iter, error);
printf("Time for %d iterations was %g seconds\n", iter, ttot);
printf("Each iteration took %g seconds\n", titer);
// printf("... finished\n");
return 0;
}

View File

@@ -1,327 +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 create a custom data interface and use them in tasks
*
*/
#include <cuda/std/array>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
/**
* @brief A simple class describing a contiguous matrix of size (m, n)
*/
template <typename T>
class matrix
{
public:
matrix(size_t m, size_t n, T* base)
: m(m)
, n(n)
, base(base)
{}
__host__ __device__ T& operator()(size_t i, size_t j)
{
return base[i + j * m];
}
__host__ __device__ const T& operator()(size_t i, size_t j) const
{
return base[i + j * m];
}
size_t m, n;
T* base;
};
/**
* @brief defines the shape of a matrix
*
* Note that we specialize cuda::experimental::stf::shape_of to avoid ambiguous specialization
*
* @extends shape_of
*/
template <typename T>
class cuda::experimental::stf::shape_of<matrix<T>>
{
public:
/**
* @brief The default constructor.
*
* All `shape_of` specializations must define this constructor.
*/
shape_of() = default;
explicit shape_of(size_t m, size_t n)
: m(m)
, n(n)
{}
/**
* @name Copies a shape.
*
* All `shape_of` specializations must define this constructor.
*/
shape_of(const shape_of&) = default;
/**
* @brief Extracts the shape from a matrix
*
* @param M matrix to get the shape from
*
* All `shape_of` specializations must define this constructor.
*/
shape_of(const matrix<T>& M)
: shape_of<matrix<T>>(M.m, M.n)
{}
/// Mandatory method : defined the total number of elements in the shape
size_t size() const
{
return m * n;
}
using coords_t = ::cuda::std::array<size_t, 2>;
// This transforms a tuple of (shape, 1D index) into a coordinate
_CCCL_HOST_DEVICE coords_t index_to_coords(size_t index) const
{
return {index % m, index / m};
}
size_t m;
size_t n;
};
/**
* @brief Data interface to manipulate a matrix in the CUDA stream backend
*/
template <typename T>
class matrix_stream_interface : public stream_data_interface_simple<matrix<T>>
{
public:
using base = stream_data_interface_simple<matrix<T>>;
using typename base::shape_t;
/// Initialize from an existing matrix
matrix_stream_interface(matrix<T> m)
: base(std::move(m))
{}
/// Initialize from a shape of matrix
matrix_stream_interface(typename base::shape_t s)
: base(s)
{}
/// Copy the content of an instance to another instance
///
/// This implementation assumes that we have registered memory if one of the data place is the host
void stream_data_copy(
const data_place& dst_memory_node,
instance_id_t dst_instance_id,
const data_place& src_memory_node,
instance_id_t src_instance_id,
cudaStream_t stream) override
{
assert(src_memory_node != dst_memory_node);
cudaMemcpyKind kind = cudaMemcpyDeviceToDevice;
if (src_memory_node.is_host())
{
kind = cudaMemcpyHostToDevice;
}
if (dst_memory_node.is_host())
{
kind = cudaMemcpyDeviceToHost;
}
const matrix<T>& src_instance = this->instance(src_instance_id);
const matrix<T>& dst_instance = this->instance(dst_instance_id);
size_t sz = src_instance.m * src_instance.n * sizeof(T);
cuda_safe_call(cudaMemcpyAsync((void*) dst_instance.base, (void*) src_instance.base, sz, kind, stream));
}
/// allocate an instance on a specific data place
///
/// setting *s to a negative value informs CUDASTF that the allocation
/// failed, and that a memory reclaiming mechanism need to be performed.
void stream_data_allocate(
backend_ctx_untyped& /*unused*/,
const data_place& memory_node,
instance_id_t instance_id,
::std::ptrdiff_t& s,
void** /*unused*/,
cudaStream_t stream) override
{
matrix<T>& instance = this->instance(instance_id);
size_t sz = instance.m * instance.n * sizeof(T);
T* base_ptr;
if (memory_node.is_host())
{
// Fallback to a synchronous method as there is no asynchronous host allocation API
cuda_safe_call(cudaStreamSynchronize(stream));
cuda_safe_call(cudaHostAlloc(&base_ptr, sz, cudaHostAllocMapped));
}
else
{
cuda_safe_call(cudaMallocAsync(&base_ptr, sz, stream));
}
// By filling a positive number, we notify that the allocation was successful
s = sz;
instance.base = base_ptr;
}
/// deallocate an instance
void stream_data_deallocate(
backend_ctx_untyped& /*unused*/,
const data_place& memory_node,
instance_id_t instance_id,
void* /*unused*/,
cudaStream_t stream) override
{
matrix<T>& instance = this->instance(instance_id);
if (memory_node.is_host())
{
// Fallback to a synchronous method as there is no asynchronous host deallocation API
cuda_safe_call(cudaStreamSynchronize(stream));
cuda_safe_call(cudaFreeHost(instance.base));
}
else
{
cuda_safe_call(cudaFreeAsync(instance.base, stream));
}
}
/// Register the host memory associated to an instance of matrix
///
/// Note that this pin_host_memory method is not mandatory, but then it is
/// the responsibility of the user to only passed memory that is already
/// registered, and the allocation method on the host must allocate
/// registered memory too. Otherwise, copy methods need to be synchronous.
bool pin_host_memory(instance_id_t instance_id) override
{
matrix<T>& instance = this->instance(instance_id);
if (!instance.base)
{
return false;
}
cuda_safe_call(pin_memory(instance.base, instance.m * instance.n * sizeof(T)));
return true;
}
/// Unregister memory pinned by pin_host_memory
void unpin_host_memory(instance_id_t instance_id) override
{
matrix<T>& instance = this->instance(instance_id);
unpin_memory(instance.base);
}
};
/**
* @brief Define how the CUDA stream backend must manipulate a matrix
*
* Note that we specialize cuda::experimental::stf::shape_of to avoid ambiguous specialization
*
* @extends streamed_interface_of
*/
template <typename T>
struct cuda::experimental::stf::streamed_interface_of<matrix<T>>
{
using type = matrix_stream_interface<T>;
};
/**
* @brief A hash of the matrix
*/
template <typename T>
struct cuda::experimental::stf::hash<matrix<T>>
{
std::size_t operator()(matrix<T> const& m) const noexcept
{
// Combine hashes from the base address and sizes
return cuda::experimental::stf::hash_all(m.m, m.n, m.base);
}
};
template <typename T>
__global__ void kernel(matrix<T> M)
{
int tid_x = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads_x = gridDim.x * blockDim.x;
int tid_y = blockIdx.y * blockDim.y + threadIdx.y;
int nthreads_y = gridDim.y * blockDim.y;
for (int x = tid_x; x < M.m; x += nthreads_x)
{
for (int y = tid_y; y < M.n; y += nthreads_y)
{
M(x, y) += -x + 7 * y;
}
}
}
int main()
{
stream_ctx ctx;
const size_t m = 8;
const size_t n = 10;
std::vector<int> v(m * n);
matrix<int> M(m, n, &v[0]);
// M(i,j) = 17 * i + 23 * j
for (size_t j = 0; j < n; j++)
{
for (size_t i = 0; i < m; i++)
{
M(i, j) = 17 * i + 23 * j;
}
}
auto lM = ctx.logical_data(M);
// M(i,j) += -i + 7*i
ctx.task(lM.rw())->*[](cudaStream_t s, auto dM) {
kernel<<<dim3(8, 8), dim3(8, 8), 0, s>>>(dM);
};
// M(i,j) += 2*i + 6*j
ctx.parallel_for(lM.shape(), lM.rw())->*[] _CCCL_DEVICE(size_t i, size_t j, auto dM) {
dM(i, j) += 2 * i + 6 * j;
};
ctx.finalize();
for (size_t j = 0; j < n; j++)
{
for (size_t i = 0; i < m; i++)
{
assert(M(i, j) == (17 * i + 23 * j) + (-i + 7 * j) + (2 * i + 6 * j));
}
}
}

View File

@@ -1,87 +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 An AXPY kernel implemented with a task of the CUDA stream backend
* where the task accesses host memory from the device
*
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
using namespace cuda::experimental::stf;
__global__ void axpy(double a, slice<const double> x, slice<double> y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (int i = tid; i < x.size(); i += nthreads)
{
y(i) += a * x(i);
}
}
double X0(int i)
{
return sin((double) i);
}
double Y0(int i)
{
return cos((double) i);
}
int main()
{
// Verify whether this device can access memory concurrently from CPU and GPU.
int dev;
cuda_safe_call(cudaGetDevice(&dev));
assert(dev >= 0);
cudaDeviceProp prop;
cuda_safe_call(cudaGetDeviceProperties(&prop, dev));
if (!prop.concurrentManagedAccess)
{
fprintf(stderr, "Concurrent CPU/GPU access not supported, skipping test.\n");
return 0;
}
stream_ctx ctx;
const size_t N = 16;
double X[N], Y[N];
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
double alpha = 3.14;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
/* Compute Y = Y + alpha X, but leave X on the host and access it with mapped memory */
ctx.task(lX.read(data_place::host()), lY.rw())->*[&](cudaStream_t s, auto dX, auto dY) {
axpy<<<16, 128, 0, s>>>(alpha, dX, dY);
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
assert(fabs(Y[i] - (Y0(i) + alpha * X0(i))) < 0.0001);
assert(fabs(X[i] - X0(i)) < 0.0001);
}
}

View File

@@ -1,300 +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 An example solving Maxwell equations in 3D using FDTD on multiple devices
*/
#include <cuda/experimental/stf.cuh>
#include <stdlib.h>
using namespace cuda::experimental::stf;
// FIXME : MSVC has trouble with box constructors
#if !_CCCL_COMPILER(MSVC)
void write_vtk_2D(const std::string& filename, slice<const double, 3> Ez, double dx, double dy, double /*unused*/)
{
FILE* f = fopen(filename.c_str(), "w");
const size_t pos_z = Ez.extent(2) / 2;
const size_t nx = Ez.extent(0);
const size_t size = Ez.extent(0) * Ez.extent(1);
fprintf(f, "# vtk DataFile Version 3.0\n");
fprintf(f, "vtk output\n");
fprintf(f, "ASCII\n");
fprintf(f, "DATASET UNSTRUCTURED_GRID\n");
fprintf(f, "POINTS %ld float\n", 4 * size);
for (size_t y = 0; y < Ez.extent(1); y++)
{
for (size_t x = 0; x < Ez.extent(0); x++)
{
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 0), dy * static_cast<float>(y + 0));
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 1), dy * static_cast<float>(y + 0));
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 1), dy * static_cast<float>(y + 1));
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 0), dy * static_cast<float>(y + 1));
}
}
fprintf(f, "CELLS %ld %ld\n", size, 5 * size);
size_t cell_id = 0;
for (size_t y = 0; y < Ez.extent(1); y++)
{
for (size_t x = 0; x < Ez.extent(0); x++)
{
const size_t point_offset = cell_id * 4;
fprintf(f,
"4 %d %d %d %d\n",
(int) (point_offset + 0),
(int) (point_offset + 1),
(int) (point_offset + 2),
(int) (point_offset + 3));
cell_id++;
}
}
fprintf(f, "CELL_TYPES %ld\n", size);
for (size_t ii = 0; ii < size; ii++)
{
fprintf(f, "5\n");
}
fprintf(f, "CELL_DATA %ld\n", size);
fprintf(f, "SCALARS Ez double 1\n");
fprintf(f, "LOOKUP_TABLE default\n");
for (size_t y = 0; y < Ez.extent(1); y++)
{
for (size_t x = 0; x < Ez.extent(0); x++)
{
fprintf(f, "%lf\n", Ez(x, y, pos_z));
}
}
fclose(f);
}
// Define the source function
_CCCL_DEVICE double Source(double t, double x, double y, double z)
{
constexpr double pi = 3.14159265358979323846;
constexpr double freq = 1e9;
constexpr double omega = (2 * pi * freq);
constexpr double wavelength = 3e8 / freq;
constexpr double k = 2 * pi / wavelength;
return sin(k * x - omega * t);
}
#endif // !_CCCL_COMPILER(MSVC)
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if !_CCCL_COMPILER(MSVC)
context ctx;
// Initialize the time loop
size_t timesteps = 10;
if (argc > 1)
{
timesteps = (size_t) atol(argv[1]);
}
// No output by default
int output_freq = -1;
if (argc > 2)
{
output_freq = atoi(argv[2]);
}
// Default value : grid of all devices
exec_place where = exec_place::all_devices();
if (argc > 3)
{
switch (atoi(argv[3]))
{
case 0:
where = exec_place::host();
break;
case 1:
where = exec_place::current_device();
break;
case 2:
where = exec_place::all_devices();
break;
case 3:
where = exec_place::repeat(exec_place::current_device(), 8);
break;
default:
fprintf(stderr, "Invalid exec place argument\n");
abort();
}
fprintf(stderr, "Running on %s\n", where.to_string().c_str());
}
if (argc > 4)
{
int use_graph = atoi(argv[4]);
if (use_graph)
{
ctx = graph_ctx();
}
fprintf(stderr, "Use %s backend.\n", use_graph ? "graph" : "stream");
}
// Domain dimensions
const size_t SIZE_X = 100;
const size_t SIZE_Y = 100;
const size_t SIZE_Z = 100;
// Grid spacing
const double DX = 0.01;
const double DY = 0.01;
const double DZ = 0.01;
// Define the electric and magnetic fields
auto data_shape = shape_of<slice<double, 3>>(SIZE_X, SIZE_Y, SIZE_Z);
// One structured partition drives every task's decomposition AND the data
// placement: dimension 2 blocked over the grid of devices (change the spec
// entry to split any other dimension). Interior boxes below iterate each
// place's owned coordinates restricted to the box, and uneven sizes are
// handled by predication.
auto part = make_partition(dim4(SIZE_X, SIZE_Y, SIZE_Z), partition_spec{whole, whole, blocked<0>}, where.get_dims());
auto lEx = ctx.logical_data(data_shape);
auto lEy = ctx.logical_data(data_shape);
auto lEz = ctx.logical_data(data_shape);
auto lHx = ctx.logical_data(data_shape);
auto lHy = ctx.logical_data(data_shape);
auto lHz = ctx.logical_data(data_shape);
// Define the permittivity and permeability of the medium
auto lepsilon = ctx.logical_data(data_shape);
auto lmu = ctx.logical_data(data_shape);
const double EPSILON = 8.85e-12; // Permittivity of free space
const double MU = 1.256e-6; // Permeability of free space
// CFL condition DT <= min(DX, DY, DZ) * sqrt(epsilon_max * mu_max)
double DT = 0.25 * min(min(DX, DY), DZ) * sqrt(EPSILON * MU);
// Initialize E
ctx.parallel_for(part, where, data_shape, lEx.write(), lEy.write(), lEz.write())
->*[] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ex, auto Ey, auto Ez) {
Ex(i, j, k) = 0.0;
Ey(i, j, k) = 0.0;
Ez(i, j, k) = 0.0;
};
// Initialize H
ctx.parallel_for(part, where, data_shape, lHx.write(), lHy.write(), lHz.write())
->*[] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hx, auto Hy, auto Hz) {
Hx(i, j, k) = 0.0;
Hy(i, j, k) = 0.0;
Hz(i, j, k) = 0.0;
};
// Initialize permittivity and permeability fields
ctx.parallel_for(part, where, data_shape, lepsilon.write(), lmu.write())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto epsilon, auto mu) {
epsilon(i, j, k) = EPSILON;
mu(i, j, k) = MU;
};
// Set the source function at the center of the grid
const size_t center_x = SIZE_X / 2;
const size_t center_y = SIZE_Y / 2;
const size_t center_z = SIZE_Z / 2;
// Index shapes for the electric and magnetic fields
box Es({1ul, SIZE_X - 1}, {1ul, SIZE_Y - 1}, {1ul, SIZE_Z - 1});
box Hs({0ul, SIZE_X - 1}, {0ul, SIZE_Y - 1}, {0ul, SIZE_Z - 1});
ctx.repeat(timesteps)->*[&](context ctx, size_t n) {
// Update the electric fields
// Update Ex
ctx.parallel_for(part, where, Es, lEx.rw(), lHy.read(), lHz.read(), lepsilon.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ex, auto Hy, auto Hz, auto epsilon) {
Ex(i, j, k) = Ex(i, j, k)
+ (DT / (epsilon(i, j, k) * DX)) * (Hz(i, j, k) - Hz(i, j - 1, k) - Hy(i, j, k) + Hy(i, j, k - 1));
};
// Update Ey
ctx.parallel_for(part, where, Es, lEy.rw(), lHx.read(), lHz.read(), lepsilon.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ey, auto Hx, auto Hz, auto epsilon) {
Ey(i, j, k) = Ey(i, j, k)
+ (DT / (epsilon(i, j, k) * DY)) * (Hx(i, j, k) - Hx(i, j, k - 1) - Hz(i, j, k) + Hz(i - 1, j, k));
};
// Update Ez and inject the point source in the same volumetric pass
ctx.parallel_for(part, where, Es, lEz.rw(), lHx.read(), lHy.read(), lepsilon.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ez, auto Hx, auto Hy, auto epsilon) {
Ez(i, j, k) = Ez(i, j, k)
+ (DT / (epsilon(i, j, k) * DZ)) * (Hy(i, j, k) - Hy(i - 1, j, k) - Hx(i, j, k) + Hx(i, j - 1, k));
if (i == center_x && j == center_y && k == center_z)
{
Ez(i, j, k) += Source(n * DT, i * DX, j * DY, k * DZ);
}
};
// Update the magnetic fields
// Update Hx
ctx.parallel_for(part, where, Hs, lHx.rw(), lEy.read(), lEz.read(), lmu.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hx, auto Ey, auto Ez, auto mu) {
Hx(i, j, k) = Hx(i, j, k)
- (DT / (mu(i, j, k) * DY)) * (Ez(i, j + 1, k) - Ez(i, j, k) - Ey(i, j, k + 1) + Ey(i, j, k));
};
// Update Hy
ctx.parallel_for(part, where, Hs, lHy.rw(), lEx.read(), lEz.read(), lmu.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hy, auto Ex, auto Ez, auto mu) {
Hy(i, j, k) = Hy(i, j, k)
- (DT / (mu(i, j, k) * DZ)) * (Ex(i, j, k + 1) - Ex(i, j, k) - Ez(i + 1, j, k) + Ez(i, j, k));
};
// Update Hz
ctx.parallel_for(part, where, Hs, lHz.rw(), lEx.read(), lEy.read(), lmu.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hz, auto Ex, auto Ey, auto mu) {
Hz(i, j, k) = Hz(i, j, k)
- (DT / (mu(i, j, k) * DX)) * (Ey(i + 1, j, k) - Ey(i, j, k) - Ex(i, j + 1, k) + Ex(i, j, k));
};
if (output_freq > 0 && n % output_freq == 0)
{
ctx.host_launch(lEz.read())->*[=](auto Ez) {
// Output the electric field at the center of the grid
fprintf(stderr, "%ld\t%le\n", n, Ez(center_x, center_y, center_z));
std::string filename = "Ez" + std::to_string(n) + ".vtk";
// Dump a 2D slice of Ez in VTK
write_vtk_2D(filename, Ez, DX, DY, DZ);
};
}
};
ctx.finalize();
#endif // !_CCCL_COMPILER(MSVC)
}

View File

@@ -1,186 +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 FDTD example using the repeat_n helper function
*
* This shows how to refactor the original fdtd_while.cu example
* to use the new repeat_n helper for cleaner loop patterns.
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
#include <stdlib.h>
using namespace cuda::experimental::stf;
// Define the source function
_CCCL_DEVICE double Source(double t, double x, double y, double z)
{
constexpr double pi = 3.14159265358979323846;
constexpr double freq = 1e9;
constexpr double omega = (2 * pi * freq);
constexpr double wavelength = 3e8 / freq;
constexpr double k = 2 * pi / wavelength;
return sin(k * x - omega * t);
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
// Initialize the time loop
size_t timesteps = 10;
if (argc > 1)
{
timesteps = (size_t) atol(argv[1]);
}
// Domain dimensions (smaller for this example)
const size_t SIZE_X = 50;
const size_t SIZE_Y = 50;
const size_t SIZE_Z = 50;
// Grid spacing
const double DX = 0.01;
const double DY = 0.01;
const double DZ = 0.01;
// Define the electric and magnetic fields
auto data_shape = shape_of<slice<double, 3>>(SIZE_X, SIZE_Y, SIZE_Z);
auto lEx = ctx.logical_data(data_shape);
auto lEy = ctx.logical_data(data_shape);
auto lEz = ctx.logical_data(data_shape);
auto lHx = ctx.logical_data(data_shape);
auto lHy = ctx.logical_data(data_shape);
auto lHz = ctx.logical_data(data_shape);
// Define the permittivity and permeability of the medium
auto lepsilon = ctx.logical_data(data_shape);
auto lmu = ctx.logical_data(data_shape);
const double EPSILON = 8.85e-12; // Permittivity of free space
const double MU = 1.256e-6; // Permeability of free space
// CFL condition DT <= min(DX, DY, DZ) * sqrt(epsilon_max * mu_max)
double DT = 0.25 * min(min(DX, DY), DZ) * sqrt(EPSILON * MU);
// Initialize E fields
ctx.parallel_for(data_shape, lEx.write(), lEy.write(), lEz.write())
->*[] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ex, auto Ey, auto Ez) {
Ex(i, j, k) = 0.0;
Ey(i, j, k) = 0.0;
Ez(i, j, k) = 0.0;
};
// Initialize H fields
ctx.parallel_for(data_shape, lHx.write(), lHy.write(), lHz.write())
->*[] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hx, auto Hy, auto Hz) {
Hx(i, j, k) = 0.0;
Hy(i, j, k) = 0.0;
Hz(i, j, k) = 0.0;
};
// Initialize permittivity and permeability fields
ctx.parallel_for(data_shape, lepsilon.write(), lmu.write())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto epsilon, auto mu) {
epsilon(i, j, k) = EPSILON;
mu(i, j, k) = MU;
};
// Set the source location
const size_t center_x = SIZE_X / 2;
const size_t center_y = SIZE_Y / 2;
const size_t center_z = SIZE_Z / 2;
// Index shapes for Electric fields, Magnetic fields, and the source
box Es({1ul, SIZE_X - 1}, {1ul, SIZE_Y - 1}, {1ul, SIZE_Z - 1});
box Hs({0ul, SIZE_X - 1}, {0ul, SIZE_Y - 1}, {0ul, SIZE_Z - 1});
box source_s({center_x, center_x + 1}, {center_y, center_y + 1}, {center_z, center_z + 1});
std::cout << "Running FDTD simulation for " << timesteps << " timesteps" << '\n';
std::cout << "Grid size: " << SIZE_X << "x" << SIZE_Y << "x" << SIZE_Z << '\n';
{
auto repeat_guard = ctx.repeat_graph_scope(timesteps);
// Update Ex
ctx.parallel_for(Es, lEx.rw(), lHy.read(), lHz.read(), lepsilon.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ex, auto Hy, auto Hz, auto epsilon) {
Ex(i, j, k) = Ex(i, j, k)
+ (DT / (epsilon(i, j, k) * DX)) * (Hz(i, j, k) - Hz(i, j - 1, k) - Hy(i, j, k) + Hy(i, j, k - 1));
};
// Update Ey
ctx.parallel_for(Es, lEy.rw(), lHx.read(), lHz.read(), lepsilon.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ey, auto Hx, auto Hz, auto epsilon) {
Ey(i, j, k) = Ey(i, j, k)
+ (DT / (epsilon(i, j, k) * DY)) * (Hx(i, j, k) - Hx(i, j, k - 1) - Hz(i, j, k) + Hz(i - 1, j, k));
};
// Update Ez
ctx.parallel_for(Es, lEz.rw(), lHx.read(), lHy.read(), lepsilon.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ez, auto Hx, auto Hy, auto epsilon) {
Ez(i, j, k) = Ez(i, j, k)
+ (DT / (epsilon(i, j, k) * DZ)) * (Hy(i, j, k) - Hy(i - 1, j, k) - Hx(i, j, k) + Hx(i, j - 1, k));
};
// Add the source function at the center of the grid
// Note: We could add a current iteration tracker if needed for time-dependent sources
ctx.parallel_for(source_s, lEz.rw())->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ez) {
// For simplicity, using a constant source in this example
// In the full version, you'd want to track the current timestep
Ez(i, j, k) = Ez(i, j, k) + 0.1 * sin(0.1 * (i + j + k));
};
// Update Hx
ctx.parallel_for(Hs, lHx.rw(), lEy.read(), lEz.read(), lmu.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hx, auto Ey, auto Ez, auto mu) {
Hx(i, j, k) = Hx(i, j, k)
- (DT / (mu(i, j, k) * DY)) * (Ez(i, j + 1, k) - Ez(i, j, k) - Ey(i, j, k + 1) + Ey(i, j, k));
};
// Update Hy
ctx.parallel_for(Hs, lHy.rw(), lEx.read(), lEz.read(), lmu.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hy, auto Ex, auto Ez, auto mu) {
Hy(i, j, k) = Hy(i, j, k)
- (DT / (mu(i, j, k) * DZ)) * (Ex(i, j, k + 1) - Ex(i, j, k) - Ez(i + 1, j, k) + Ez(i, j, k));
};
// Update Hz
ctx.parallel_for(Hs, lHz.rw(), lEx.read(), lEy.read(), lmu.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hz, auto Ex, auto Ey, auto mu) {
Hz(i, j, k) = Hz(i, j, k)
- (DT / (mu(i, j, k) * DX)) * (Ey(i + 1, j, k) - Ey(i, j, k) - Ex(i, j + 1, k) + Ex(i, j, k));
};
} // repeat_guard
// Print final result at center
ctx.host_launch(lEz.read())->*[=](auto Ez) {
std::cout << "Final Ez at center: " << Ez(center_x, center_y, center_z) << '\n';
};
ctx.finalize();
std::cout << "FDTD simulation completed!" << '\n';
return 0;
#endif
}

View File

@@ -1,288 +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 An example solving Maxwell equations in 3D using FDTD on multiple devices
*/
#include <cuda/experimental/stf.cuh>
#include <stdlib.h>
using namespace cuda::experimental::stf;
// FIXME : MSVC has trouble with box constructors
#if !_CCCL_COMPILER(MSVC)
void write_vtk_2D(const std::string& filename, slice<const double, 3> Ez, double dx, double dy, double /*unused*/)
{
FILE* f = fopen(filename.c_str(), "w");
const size_t pos_z = Ez.extent(2) / 2;
const size_t nx = Ez.extent(0);
const size_t size = Ez.extent(0) * Ez.extent(1);
fprintf(f, "# vtk DataFile Version 3.0\n");
fprintf(f, "vtk output\n");
fprintf(f, "ASCII\n");
fprintf(f, "DATASET UNSTRUCTURED_GRID\n");
fprintf(f, "POINTS %ld float\n", 4 * size);
for (size_t y = 0; y < Ez.extent(1); y++)
{
for (size_t x = 0; x < Ez.extent(0); x++)
{
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 0), dy * static_cast<float>(y + 0));
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 1), dy * static_cast<float>(y + 0));
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 1), dy * static_cast<float>(y + 1));
fprintf(f, "%lf %lf 0.0\n", dx * static_cast<float>(x + 0), dy * static_cast<float>(y + 1));
}
}
fprintf(f, "CELLS %ld %ld\n", size, 5 * size);
size_t cell_id = 0;
for (size_t y = 0; y < Ez.extent(1); y++)
{
for (size_t x = 0; x < Ez.extent(0); x++)
{
const size_t point_offset = cell_id * 4;
fprintf(f,
"4 %d %d %d %d\n",
(int) (point_offset + 0),
(int) (point_offset + 1),
(int) (point_offset + 2),
(int) (point_offset + 3));
cell_id++;
}
}
fprintf(f, "CELL_TYPES %ld\n", size);
for (size_t ii = 0; ii < size; ii++)
{
fprintf(f, "5\n");
}
fprintf(f, "CELL_DATA %ld\n", size);
fprintf(f, "SCALARS Ez double 1\n");
fprintf(f, "LOOKUP_TABLE default\n");
for (size_t y = 0; y < Ez.extent(1); y++)
{
for (size_t x = 0; x < Ez.extent(0); x++)
{
fprintf(f, "%lf\n", Ez(x, y, pos_z));
}
}
fclose(f);
}
// Define the source function
_CCCL_DEVICE double Source(double t, double x, double y, double z)
{
constexpr double pi = 3.14159265358979323846;
constexpr double freq = 1e9;
constexpr double omega = (2 * pi * freq);
constexpr double wavelength = 3e8 / freq;
constexpr double k = 2 * pi / wavelength;
return sin(k * x - omega * t);
}
#endif // !_CCCL_COMPILER(MSVC)
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if !_CCCL_COMPILER(MSVC)
# if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
# else
stackable_ctx ctx;
// Initialize the time loop
size_t timesteps = 10;
if (argc > 1)
{
timesteps = (size_t) atol(argv[1]);
}
// No output by default
int output_freq = -1;
if (argc > 2)
{
output_freq = atoi(argv[2]);
}
// Domain dimensions
const size_t SIZE_X = 100;
const size_t SIZE_Y = 100;
const size_t SIZE_Z = 100;
// Grid spacing
const double DX = 0.01;
const double DY = 0.01;
const double DZ = 0.01;
// Define the electric and magnetic fields
auto data_shape = shape_of<slice<double, 3>>(SIZE_X, SIZE_Y, SIZE_Z);
auto lEx = ctx.logical_data(data_shape);
auto lEy = ctx.logical_data(data_shape);
auto lEz = ctx.logical_data(data_shape);
auto lHx = ctx.logical_data(data_shape);
auto lHy = ctx.logical_data(data_shape);
auto lHz = ctx.logical_data(data_shape);
// Define the permittivity and permeability of the medium
auto lepsilon = ctx.logical_data(data_shape);
auto lmu = ctx.logical_data(data_shape);
const double EPSILON = 8.85e-12; // Permittivity of free space
const double MU = 1.256e-6; // Permeability of free space
// CFL condition DT <= min(DX, DY, DZ) * sqrt(epsilon_max * mu_max)
double DT = 0.25 * min(min(DX, DY), DZ) * sqrt(EPSILON * MU);
// Initialize E
ctx.parallel_for(data_shape, lEx.write(), lEy.write(), lEz.write())
->*[] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ex, auto Ey, auto Ez) {
Ex(i, j, k) = 0.0;
Ey(i, j, k) = 0.0;
Ez(i, j, k) = 0.0;
};
// Initialize H
ctx.parallel_for(data_shape, lHx.write(), lHy.write(), lHz.write())
->*[] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hx, auto Hy, auto Hz) {
Hx(i, j, k) = 0.0;
Hy(i, j, k) = 0.0;
Hz(i, j, k) = 0.0;
};
// Initialize permittivity and permeability fields
ctx.parallel_for(data_shape, lepsilon.write(), lmu.write())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto epsilon, auto mu) {
epsilon(i, j, k) = EPSILON;
mu(i, j, k) = MU;
};
// Set the source function at the center of the grid
const size_t center_x = SIZE_X / 2;
const size_t center_y = SIZE_Y / 2;
const size_t center_z = SIZE_Z / 2;
/* Index shapes for Electric fields, Magnetic fields, and the indices where there is a source */
box Es({1ul, SIZE_X - 1}, {1ul, SIZE_Y - 1}, {1ul, SIZE_Z - 1});
box Hs({0ul, SIZE_X - 1}, {0ul, SIZE_Y - 1}, {0ul, SIZE_Z - 1});
box source_s({center_x, center_x + 1}, {center_y, center_y + 1}, {center_z, center_z + 1});
int iterations_per_graph = (output_freq == -1) ? timesteps : output_freq;
for (size_t n = 0; n < timesteps / iterations_per_graph; n++)
{
fprintf(stderr, "WHILE BLAAAAA...\n");
// Counter for while loop iterations
auto counter_shape = shape_of<scalar_view<int>>();
auto lcounter = ctx.logical_data(counter_shape);
// Initialize counter
ctx.parallel_for(box(1), lcounter.write())->*[=] __device__(size_t, auto counter) {
*counter = iterations_per_graph;
};
auto while_guard = ctx.while_graph_scope();
{
// Update the electric fields
// Update Ex
ctx.parallel_for(Es, lEx.rw(), lHy.read(), lHz.read(), lepsilon.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ex, auto Hy, auto Hz, auto epsilon) {
Ex(i, j, k) =
Ex(i, j, k)
+ (DT / (epsilon(i, j, k) * DX)) * (Hz(i, j, k) - Hz(i, j - 1, k) - Hy(i, j, k) + Hy(i, j, k - 1));
};
// Update Ey
ctx.parallel_for(Es, lEy.rw(), lHx.read(), lHz.read(), lepsilon.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ey, auto Hx, auto Hz, auto epsilon) {
Ey(i, j, k) =
Ey(i, j, k)
+ (DT / (epsilon(i, j, k) * DY)) * (Hx(i, j, k) - Hx(i, j, k - 1) - Hz(i, j, k) + Hz(i - 1, j, k));
};
// Update Ez
ctx.parallel_for(Es, lEz.rw(), lHx.read(), lHy.read(), lepsilon.read())
->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ez, auto Hx, auto Hy, auto epsilon) {
Ez(i, j, k) =
Ez(i, j, k)
+ (DT / (epsilon(i, j, k) * DZ)) * (Hy(i, j, k) - Hy(i - 1, j, k) - Hx(i, j, k) + Hx(i, j - 1, k));
};
// Add the source function at the center of the grid
ctx.parallel_for(source_s, lEz.rw())->*[=] _CCCL_DEVICE(size_t i, size_t j, size_t k, auto Ez) {
Ez(i, j, k) = Ez(i, j, k) + Source(n * DT, i * DX, j * DY, k * DZ);
};
// Update the magnetic fields
// Update Hx
ctx.parallel_for(Hs, lHx.rw(), lEy.read(), lEz.read(), lmu.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hx, auto Ey, auto Ez, auto mu) {
Hx(i, j, k) =
Hx(i, j, k) - (DT / (mu(i, j, k) * DY)) * (Ez(i, j + 1, k) - Ez(i, j, k) - Ey(i, j, k + 1) + Ey(i, j, k));
};
// Update Hy
ctx.parallel_for(Hs, lHy.rw(), lEx.read(), lEz.read(), lmu.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hy, auto Ex, auto Ez, auto mu) {
Hy(i, j, k) =
Hy(i, j, k) - (DT / (mu(i, j, k) * DZ)) * (Ex(i, j, k + 1) - Ex(i, j, k) - Ez(i + 1, j, k) + Ez(i, j, k));
};
// Update Hz
ctx.parallel_for(Hs, lHz.rw(), lEx.read(), lEy.read(), lmu.read())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, size_t k, auto Hz, auto Ex, auto Ey, auto mu) {
Hz(i, j, k) =
Hz(i, j, k) - (DT / (mu(i, j, k) * DX)) * (Ey(i + 1, j, k) - Ey(i, j, k) - Ex(i, j + 1, k) + Ex(i, j, k));
};
auto handle = while_guard.cond_handle();
ctx.parallel_for(box(1), lcounter.rw())->*[handle] __device__(size_t, auto counter) {
(*counter)--;
bool should_continue = (*counter > 0);
cudaGraphSetConditional(handle, should_continue);
};
} // end of the while pattern
if (output_freq > 0 && n % output_freq == 0)
{
ctx.host_launch(lEz.read())->*[=](auto Ez) {
// Output the electric field at the center of the grid
fprintf(stderr, "%ld\t%le\n", n, Ez(center_x, center_y, center_z));
std::string filename = "Ez" + std::to_string(n) + ".vtk";
// Dump a 2D slice of Ez in VTK
write_vtk_2D(filename, Ez, DX, DY, DZ);
};
}
}
ctx.finalize();
# endif // _CCCL_CTK_AT_LEAST(12, 4)
#endif // !_CCCL_COMPILER(MSVC)
}

View File

@@ -1,53 +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 Illustrate how we can use frozen data to initialize constant data
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
context ctx;
/* Create a piece of data that can be use many times without further synchronizations */
auto buffer = ctx.logical_data(shape_of<slice<double, 2>>(128, 64)).set_symbol("buffer");
ctx.parallel_for(buffer.shape(), buffer.write())->*[] __device__(size_t i, size_t j, auto b) {
b(i, j) = sin(-1.0 * i) + cos(2.0 * j);
};
auto frozen_buffer = ctx.freeze(buffer);
auto h_buf = frozen_buffer.get(data_place::host()).first;
auto d_buf = frozen_buffer.get(data_place::current_device()).first;
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
auto lX = ctx.logical_data(buffer.shape()).set_symbol("X");
ctx.parallel_for(lX.shape(), lX.write()).set_symbol("X=buf")->*[d_buf] __device__(size_t i, size_t j, auto x) {
x(i, j) = d_buf(i, j);
};
ctx.parallel_for(exec_place::host(), lX.shape(), lX.read()).set_symbol("check buf")
->*[h_buf](size_t i, size_t j, auto x) {
EXPECT(fabs(x(i, j) - h_buf(i, j)) < 0.0001);
};
// Make sure all tasks are done before unfreezing
frozen_buffer.unfreeze(ctx.fence());
ctx.finalize();
}

View File

@@ -1,64 +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 Computes the Degree Centrality for each vertex within a graph
*
*/
#include <cuda/experimental/stf.cuh>
#include <vector>
using namespace cuda::experimental::stf;
/**
* @brief Computes the Degree Centrality for each vertex.
*
* @param idx The index of the vertex for which Degree Centrality is being calculated.
* @param d_offsets Slice containing the offset vector of the CSR representation.
* @return The degree of each vertex.
*/
__device__ int degree_centrality(int idx, slice<const int> loffsets)
{
return loffsets[idx + 1] - loffsets[idx];
}
int main()
{
stream_ctx ctx;
// row offsets in CSR format
std::vector<int> offsets = {0, 4, 11, 12, 14, 15, 16, 18, 19, 20};
// edges in CSR format
std::vector<int> nonzeros = {1, 2, 3, 6, 0, 3, 4, 5, 6, 7, 8, 0, 0, 1, 1, 1, 0, 1, 1, 1};
// output degrees for each vertex
int num_vertices = offsets.size() - 1;
std::vector<int> degrees(num_vertices, 0);
auto loffsets = ctx.logical_data(&offsets[0], offsets.size());
auto lnonzeros = ctx.logical_data(&nonzeros[0], nonzeros.size());
auto ldegrees = ctx.logical_data(&degrees[0], degrees.size());
ctx.parallel_for(box(num_vertices), loffsets.read(), ldegrees.rw())
->*[] __device__(size_t idx, auto loffsets, auto ldegrees) {
ldegrees[idx] = degree_centrality(idx, loffsets);
};
ctx.finalize();
// for (int i = 0; i < num_vertices; ++i) {
// printf("Vertex %d: Degree Centrality = %d\n", i, degrees[i]);
// }
return 0;
}

View File

@@ -1,137 +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 Computes the Jaccard Similarity for each vertex within a graph
*
*/
#include <cuda/experimental/stf.cuh>
#include <vector>
using namespace cuda::experimental::stf;
// Performs Binary Search on a given array with start/end bounds and a lookup element
__device__ int binary_search(slice<const int> arr, int start, int end, int lookup)
{
while (start <= end)
{
int mid = start + (end - start) / 2;
if (arr[mid] == lookup)
{
return mid;
}
else if (arr[mid] < lookup)
{
start = mid + 1;
}
else
{
end = mid - 1;
}
}
return -1;
}
/**
* @brief Computes the intersection size of neighbors of two vertices.
*
* @param loffsets Slice containing the offset vector of the CSR representation.
* @param lnonzeros Slice containing the non-zero elements (neighbors) vector of the CSR representation.
* @param u Index of the first vertex.
* @param v Index of the second vertex.
* @return The number of common neighbors (intersection size) of vertices u and v.
*/
__device__ int calculate_intersection_size(slice<const int> loffsets, slice<const int> lnonzeros, int u, int v)
{
int count = 0;
for (int i = loffsets[u]; i < loffsets[u + 1]; i++)
{
if (binary_search(lnonzeros, loffsets[v], loffsets[v + 1] - 1, lnonzeros[i]) != -1)
{
count++;
}
}
return count;
}
/**
* @brief Computes the union size of neighbors of two vertices.
*
* @param loffsets Slice containing the offset vector of the CSR representation.
* @param lnonzeros Slice containing the non-zero elements (neighbors) vector of the CSR representation.
* @param u Index of the first vertex.
* @param v Index of the second vertex.
* @return The number of unique neighbors (union size) of vertices u and v.
*/
__device__ int calculate_union_size(slice<const int> loffsets, slice<const int> lnonzeros, int u, int v)
{
int count = (loffsets[u + 1] - loffsets[u]) + (loffsets[v + 1] - loffsets[v]);
for (int i = loffsets[u]; i < loffsets[u + 1]; i++)
{
if (binary_search(lnonzeros, loffsets[v], loffsets[v + 1] - 1, lnonzeros[i]) != -1)
{
count--;
}
}
return count;
}
int main()
{
stream_ctx ctx;
// row offsets in CSR format
std::vector<int> offsets = {0, 4, 11, 12, 14, 15, 16, 18, 19, 20};
// edges in CSR format
std::vector<int> nonzeros = {1, 2, 3, 6, 0, 3, 4, 5, 6, 7, 8, 0, 0, 1, 1, 1, 0, 1, 1, 1};
// output jaccard similarities for each vertex
int num_vertices = offsets.size() - 1;
std::vector<float> jaccard_similarities(num_vertices * num_vertices, 0.0f);
auto loffsets = ctx.logical_data(&offsets[0], offsets.size());
auto lnonzeros = ctx.logical_data(&nonzeros[0], nonzeros.size());
auto ljaccard_similarities = ctx.logical_data(&jaccard_similarities[0], jaccard_similarities.size());
ctx.parallel_for(box(num_vertices), loffsets.read(), lnonzeros.read(), ljaccard_similarities.rw())
->*[] __device__(size_t idx, auto loffsets, auto lnonzeros, auto ljaccard_similarities) {
for (int j = 0; j < loffsets.size() - 1; j++)
{
if (idx != j)
{
int intersection = calculate_intersection_size(loffsets, lnonzeros, idx, j);
int uni = calculate_union_size(loffsets, lnonzeros, idx, j);
if (uni > 0)
{
ljaccard_similarities[idx * (loffsets.size() - 1) + j] = static_cast<float>(intersection) / uni;
}
}
}
};
ctx.finalize();
for (int u = 0; u < num_vertices; u++)
{
for (int v = 0; v < num_vertices; v++)
{
if (u != v)
{
printf(
"Jaccard similarity between vertex %d and vertex %d: %f\n", u, v, jaccard_similarities[u * num_vertices + v]);
}
}
}
return 0;
}

View File

@@ -1,121 +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 Computes the PageRank for vertices within a graph
*
*/
#include <cuda/experimental/stf.cuh>
#include <vector>
using namespace cuda::experimental::stf;
/**
* @brief Calculates the PageRank for a given vertex.
*
* @param idx The index of the vertex for which PageRank is being calculated.
* @param loffsets Slice containing the offset vector of the CSR representation.
* @param lnonzeros Slice containing the non-zero elements (neighbors) vector of the CSR representation.
* @param lpage_rank Slice containing current PageRank values for each vertex.
* @param lnew_page_rank Slice containing where new PageRank values will be stored.
* @param init_rank The initial PageRank value to be used in the calculation.
*/
__device__ void calculating_pagerank(
int idx,
const slice<const int>& loffsets,
const slice<const int>& lnonzeros,
const slice<const float>& lpage_rank,
slice<float>& lnew_page_rank,
float init_rank)
{
float rank_sum = 0.0;
for (int i = loffsets[idx]; i < loffsets[idx + 1]; i++)
{
int neighbor = lnonzeros[i];
int out_degree = loffsets[neighbor + 1] - loffsets[neighbor];
rank_sum += lpage_rank[neighbor] / out_degree;
}
lnew_page_rank[idx] = 0.85 * rank_sum + (1.0 - 0.85) * init_rank;
}
int main()
{
stream_ctx ctx;
// row offsets in CSR format
std::vector<int> offsets = {0, 4, 11, 12, 14, 15, 16, 18, 19, 20};
// edges in CSR format
std::vector<int> nonzeros = {1, 2, 3, 6, 0, 3, 4, 5, 6, 7, 8, 0, 0, 1, 1, 1, 0, 1, 1, 1};
int num_vertices = offsets.size() - 1;
float init_rank = 1.0f / num_vertices;
float tolerance = 1e-6f;
int NITER = 100;
// output pageranks for each vertex
std::vector<float> page_rank(num_vertices, init_rank);
std::vector<float> new_page_rank(num_vertices);
auto loffsets = ctx.logical_data(&offsets[0], offsets.size());
auto lnonzeros = ctx.logical_data(&nonzeros[0], nonzeros.size());
auto lpage_rank = ctx.logical_data(&page_rank[0], page_rank.size());
auto lnew_page_rank = ctx.logical_data(&new_page_rank[0], new_page_rank.size());
auto lmax_diff = ctx.logical_data(shape_of<scalar_view<float>>());
for (int iter = 0; iter < NITER; ++iter)
{
// Calculate Current Iteration PageRank
ctx.parallel_for(
box(num_vertices),
loffsets.read(),
lnonzeros.read(),
lpage_rank.rw(),
lnew_page_rank.rw(),
lmax_diff.reduce(reducer::maxval<float>{}))
->*[init_rank] __device__(
size_t idx, auto loffsets, auto lnonzeros, auto lpage_rank, auto lnew_page_rank, auto& max_diff) {
calculating_pagerank(idx, loffsets, lnonzeros, lpage_rank, lnew_page_rank, init_rank);
max_diff = ::std::max(max_diff, lnew_page_rank[idx] - lpage_rank[idx]);
};
// Reduce Error and Check for Convergence
bool converged = (ctx.wait(lmax_diff) < tolerance);
if (converged)
{
break;
}
// Update New PageRank Values
std::swap(lpage_rank, lnew_page_rank);
}
ctx.finalize();
/* CHECKING FOR ANSWER CORRECTNESS */
// sum of all page ranks should equal 1
double sum_pageranks = 0.0;
for (int64_t i = 0; i < num_vertices; i++)
{
sum_pageranks += page_rank[i];
}
printf("Page rank answer is %s.\n", abs(sum_pageranks - 1.0) < 0.001 ? "correct" : "not correct");
printf("PageRank Results:\n");
for (size_t i = 0; i < page_rank.size(); ++i)
{
printf("Vertex %zu: %f\n", i, page_rank[i]);
}
return 0;
}

View File

@@ -1,203 +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 Computes the PageRank for vertices within a graph
*
*/
#include <cuda/experimental/stf.cuh>
#include <vector>
using namespace cuda::experimental::stf;
#if _CCCL_CTK_AT_LEAST(12, 4)
/**
* @brief Calculates the PageRank for a given vertex.
*
* @param idx The index of the vertex for which PageRank is being calculated.
* @param loffsets Slice containing the offset vector of the CSR representation.
* @param lnonzeros Slice containing the non-zero elements (neighbors) vector of the CSR representation.
* @param lpage_rank Slice containing current PageRank values for each vertex.
* @param lnew_page_rank Slice containing where new PageRank values will be stored.
* @param lpersonalization Slice containing the personalization vector for each vertex.
*/
__device__ void calculating_pagerank(
int idx,
const slice<const int>& loffsets,
const slice<const int>& lnonzeros,
const slice<const float>& lpage_rank,
slice<float>& lnew_page_rank,
const slice<const float>& lpersonalization)
{
float rank_sum = 0.0;
for (int i = loffsets[idx]; i < loffsets[idx + 1]; i++)
{
int neighbor = lnonzeros[i];
int out_degree = loffsets[neighbor + 1] - loffsets[neighbor];
rank_sum += lpage_rank[neighbor] / out_degree;
}
lnew_page_rank[idx] = 0.85 * rank_sum + (1.0 - 0.85) * lpersonalization[idx];
}
/**
* @brief Computes PageRank using the power iteration method
*
* @param ctx The CUDASTF context
* @param loffsets Logical data for CSR offset vector
* @param lnonzeros Logical data for CSR non-zero elements vector
* @param lpage_rank Logical data for current PageRank values
* @param lpersonalization Logical data for personalization vector
* @param num_vertices Number of vertices in the graph
* @param NITER Maximum number of iterations
* @param tolerance Convergence tolerance
*/
void compute_pagerank(
stackable_ctx& ctx,
stackable_logical_data<slice<int>>& loffsets,
stackable_logical_data<slice<int>>& lnonzeros,
stackable_logical_data<slice<float>>& lpage_rank,
stackable_logical_data<slice<float>>& lpersonalization,
int num_vertices,
int NITER,
float tolerance)
{
// Create local temporary buffer and convergence tracking
auto lnew_page_rank = ctx.logical_data(lpage_rank.shape());
auto lmax_diff = ctx.logical_data(shape_of<scalar_view<float>>());
auto liter = ctx.logical_data(shape_of<scalar_view<int>>());
// Initialize iteration counter
ctx.parallel_for(box(1), liter.write())->*[] __device__(size_t, auto iter) {
*iter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
// Calculate Current Iteration PageRank
ctx.parallel_for(
box(num_vertices),
loffsets.read(),
lnonzeros.read(),
lpage_rank.rw(),
lnew_page_rank.write(),
lpersonalization.read(),
lmax_diff.reduce(reducer::maxval<float>{}))
->*
[] __device__(
size_t idx,
auto loffsets,
auto lnonzeros,
auto lpage_rank,
auto lnew_page_rank,
auto lpersonalization,
auto& max_diff) {
calculating_pagerank(idx, loffsets, lnonzeros, lpage_rank, lnew_page_rank, lpersonalization);
max_diff = ::std::max(max_diff, lnew_page_rank[idx] - lpage_rank[idx]);
};
// Update PageRank Values
ctx.parallel_for(lpage_rank.shape(), lpage_rank.write(), lnew_page_rank.read())
->*[] __device__(size_t i, auto page_rank, auto new_page_rank) {
page_rank(i) = new_page_rank(i);
};
while_guard.update_cond(lmax_diff.read(), liter.rw())->*[NITER, tolerance] __device__(auto max_diff, auto iter) {
bool converged = (*max_diff < tolerance);
bool max_reached = ((*iter)++ >= NITER); // Maximum iteration limit
return !converged && !max_reached; // Continue if not converged and under limit
};
}
}
#endif // _CCCL_CTK_AT_LEAST(12, 4)
int main()
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving example: while_graph_scope is only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
// row offsets in CSR format
std::vector<int> offsets = {0, 4, 11, 12, 14, 15, 16, 18, 19, 20};
// edges in CSR format
std::vector<int> nonzeros = {1, 2, 3, 6, 0, 3, 4, 5, 6, 7, 8, 0, 0, 1, 1, 1, 0, 1, 1, 1};
int num_vertices = offsets.size() - 1;
float init_rank = 1.0f / num_vertices;
float tolerance = 1e-6f;
int NITER = 100;
int num_personalization = 4;
::std::vector<stackable_logical_data<slice<float>>> lpage_rank_slices;
for (int i = 0; i < num_personalization; i++)
{
lpage_rank_slices.push_back(ctx.logical_data(shape_of<slice<float>>(num_vertices)));
}
auto loffsets = ctx.logical_data(&offsets[0], offsets.size());
auto lnonzeros = ctx.logical_data(&nonzeros[0], nonzeros.size());
loffsets.set_read_only();
lnonzeros.set_read_only();
{
auto scope = ctx.graph_scope();
for (int p = 0; p < num_personalization; p++)
{
// Initialize PageRank values to uniform distribution
ctx.parallel_for(lpage_rank_slices[p].shape(), lpage_rank_slices[p].write())
->*[init_rank] __device__(size_t i, auto page_rank) {
page_rank(i) = init_rank;
};
// Create personalization vector (uniform for this example)
auto lpersonalization = ctx.logical_data(shape_of<slice<float>>(num_vertices));
ctx.parallel_for(lpersonalization.shape(), lpersonalization.write())
->*[init_rank] __device__(size_t i, auto lpersonalization) {
lpersonalization(i) = init_rank;
};
compute_pagerank(ctx, loffsets, lnonzeros, lpage_rank_slices[p], lpersonalization, num_vertices, NITER, tolerance);
}
}
for (int p = 0; p < num_personalization; p++)
{
ctx.host_launch(lpage_rank_slices[p].read())->*[p, num_vertices] __host__(slice<const float> page_rank) {
double sum_pageranks = 0.0;
for (int64_t i = 0; i < num_vertices; i++)
{
sum_pageranks += page_rank[i];
}
printf("Page rank answer for personalization %d is %s.\n",
p,
abs(sum_pageranks - 1.0) < 0.001 ? "correct" : "not correct");
// Print first few results for verification
printf("Personalization %d - First 5 vertices: ", p);
for (size_t i = 0; i < std::min(5UL, page_rank.size()); ++i)
{
printf("%.6f ", page_rank[i]);
}
printf("\n");
};
}
ctx.finalize();
return 0;
#endif // !_CCCL_CTK_BELOW(12, 4)
}

View File

@@ -1,135 +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 Computes the PageRank for vertices within a graph
*
*/
#include <cuda/experimental/stf.cuh>
#include <vector>
using namespace cuda::experimental::stf;
/**
* @brief Calculates the PageRank for a given vertex.
*
* @param idx The index of the vertex for which PageRank is being calculated.
* @param loffsets Slice containing the offset vector of the CSR representation.
* @param lnonzeros Slice containing the non-zero elements (neighbors) vector of the CSR representation.
* @param lpage_rank Slice containing current PageRank values for each vertex.
* @param lnew_page_rank Slice containing where new PageRank values will be stored.
* @param init_rank The initial PageRank value to be used in the calculation.
*/
__device__ void calculating_pagerank(
int idx,
const slice<const int>& loffsets,
const slice<const int>& lnonzeros,
const slice<const float>& lpage_rank,
slice<float>& lnew_page_rank,
float init_rank)
{
float rank_sum = 0.0;
for (int i = loffsets[idx]; i < loffsets[idx + 1]; i++)
{
int neighbor = lnonzeros[i];
int out_degree = loffsets[neighbor + 1] - loffsets[neighbor];
rank_sum += lpage_rank[neighbor] / out_degree;
}
lnew_page_rank[idx] = 0.85 * rank_sum + (1.0 - 0.85) * init_rank;
}
int main()
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving example: while_graph_scope is only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
// row offsets in CSR format
std::vector<int> offsets = {0, 4, 11, 12, 14, 15, 16, 18, 19, 20};
// edges in CSR format
std::vector<int> nonzeros = {1, 2, 3, 6, 0, 3, 4, 5, 6, 7, 8, 0, 0, 1, 1, 1, 0, 1, 1, 1};
int num_vertices = offsets.size() - 1;
float init_rank = 1.0f / num_vertices;
float tolerance = 1e-6f;
int NITER = 100;
// output pageranks for each vertex
std::vector<float> page_rank(num_vertices, init_rank);
std::vector<float> new_page_rank(num_vertices);
auto loffsets = ctx.logical_data(&offsets[0], offsets.size());
auto lnonzeros = ctx.logical_data(&nonzeros[0], nonzeros.size());
auto lpage_rank = ctx.logical_data(&page_rank[0], page_rank.size());
auto lnew_page_rank = ctx.logical_data(&new_page_rank[0], new_page_rank.size());
auto lmax_diff = ctx.logical_data(shape_of<scalar_view<float>>());
auto liter = ctx.logical_data(shape_of<scalar_view<int>>());
// Initialize iteration counter
ctx.parallel_for(box(1), liter.write())->*[] __device__(size_t, auto iter) {
*iter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
// Calculate Current Iteration PageRank
ctx.parallel_for(
box(num_vertices),
loffsets.read(),
lnonzeros.read(),
lpage_rank.rw(),
lnew_page_rank.rw(),
lmax_diff.reduce(reducer::maxval<float>{}))
->*[init_rank] __device__(
size_t idx, auto loffsets, auto lnonzeros, auto lpage_rank, auto lnew_page_rank, auto& max_diff) {
calculating_pagerank(idx, loffsets, lnonzeros, lpage_rank, lnew_page_rank, init_rank);
max_diff = ::std::max(max_diff, lnew_page_rank[idx] - lpage_rank[idx]);
};
// Update PageRank Values
ctx.parallel_for(lpage_rank.shape(), lpage_rank.write(), lnew_page_rank.read())
->*[] __device__(size_t i, auto page_rank, auto new_page_rank) {
page_rank(i) = new_page_rank(i);
};
while_guard.update_cond(lmax_diff.read(), liter.rw())->*[NITER, tolerance] __device__(auto max_diff, auto iter) {
bool converged = (*max_diff < tolerance);
bool max_reached = ((*iter)++ >= NITER); // Maximum iteration limit
return !converged && !max_reached; // Continue if not converged and under limit
};
}
ctx.finalize();
/* CHECKING FOR ANSWER CORRECTNESS */
// sum of all page ranks should equal 1
double sum_pageranks = 0.0;
for (int64_t i = 0; i < num_vertices; i++)
{
sum_pageranks += page_rank[i];
}
printf("Page rank answer is %s.\n", abs(sum_pageranks - 1.0) < 0.001 ? "correct" : "not correct");
printf("PageRank Results:\n");
for (size_t i = 0; i < page_rank.size(); ++i)
{
printf("Vertex %zu: %f\n", i, page_rank[i]);
}
return 0;
#endif // !_CCCL_CTK_BELOW(12, 4)
}

View File

@@ -1,100 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Computes the total number of triangles within a graph
*
*/
#include <cuda/experimental/stf.cuh>
#include <vector>
using namespace cuda::experimental::stf;
// Performs Binary Search on a given array with start/end bounds and a lookup element
__device__ int binary_search(slice<const int> arr, int start, int end, int lookup)
{
while (start <= end)
{
int mid = start + (end - start) / 2;
if (arr[mid] == lookup)
{
return mid;
}
else if (arr[mid] < lookup)
{
start = mid + 1;
}
else
{
end = mid - 1;
}
}
return -1;
}
/**
* @brief Computes the Triangle Counting for each vertex.
*
* @param idx The index of the vertex for which Triangle Counting is being calculated.
* @param loffsets Slice containing the offset vector of the CSR representation.
* @param lnonzeros Slice containing the non-zero elements (neighbors) vector of the CSR representation.
* @return The local triangle count for the vertex.
*/
__device__ unsigned long long int triangle_count(int idx, slice<const int> loffsets, slice<const int> lnonzeros)
{
int lcount = 0;
for (int i = loffsets[idx]; i < loffsets[idx + 1]; i++)
{
int v = lnonzeros[i];
for (int j = loffsets[idx]; j < loffsets[idx + 1]; j++)
{
int w = lnonzeros[j];
if (binary_search(lnonzeros, loffsets[v], loffsets[v + 1] - 1, w) != -1)
{
lcount++;
}
}
}
return lcount;
}
int main()
{
stream_ctx ctx;
// row offsets in CSR format
std::vector<int> offsets = {0, 0, 1, 2, 4, 5, 6, 8, 9, 10};
// edges in CSR format
std::vector<int> nonzeros = {0, 0, 0, 1, 1, 1, 0, 1, 1, 1};
int num_vertices = offsets.size() - 1;
auto loffsets = ctx.logical_data(&offsets[0], offsets.size());
auto lnonzeros = ctx.logical_data(&nonzeros[0], nonzeros.size());
auto ltotal_count = ctx.logical_data(shape_of<scalar_view<unsigned long long>>());
ctx.parallel_for(
box(num_vertices), loffsets.read(), lnonzeros.read(), ltotal_count.reduce(reducer::sum<unsigned long long>{}))
->*[] __device__(size_t idx, auto loffsets, auto lnonzeros, auto& total_count) {
total_count += triangle_count(idx, loffsets, lnonzeros);
};
auto total_count = ctx.wait(ltotal_count);
ctx.finalize();
printf("Number of triangles: %lld\n", total_count);
return 0;
}

View File

@@ -1,105 +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 Demonstration of graph_scope RAII usage styles
*
* This example shows different ways to use stackable_ctx::graph_scope_guard
* for automatic push/pop management in nested contexts.
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
stackable_ctx ctx;
int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto lA = ctx.logical_data(data);
// Style 1: Direct constructor (like std::lock_guard)
// This is the most idiomatic C++ style
{
stackable_ctx::graph_scope_guard scope{ctx}; // Direct constructor - push() called
auto temp = ctx.logical_data(lA.shape());
ctx.parallel_for(temp.shape(), temp.write(), lA.read())->*[] __device__(size_t i, auto temp, auto a) {
temp(i) = a(i) * 2;
};
ctx.parallel_for(lA.shape(), lA.write(), temp.read())->*[] __device__(size_t i, auto a, auto temp) {
a(i) = temp(i);
};
// pop() called automatically when scope goes out of scope
}
// Style 2: Factory method (convenience)
// Useful when you prefer auto type deduction
{
auto scope = ctx.graph_scope(); // Factory method - push() called
ctx.parallel_for(lA.shape(), lA.rw())->*[] __device__(size_t i, auto a) {
a(i) += 1;
};
// pop() called automatically
}
// Style 3: Direct constructor with explicit type alias
// Useful for readability in complex scenarios
{
using scope_t = stackable_ctx::graph_scope_guard;
scope_t scope{ctx}; // Explicit type - push() called
ctx.parallel_for(lA.shape(), lA.rw())->*[] __device__(size_t i, auto a) {
a(i) *= 3;
};
// pop() called automatically
}
// Style 4: Iterative pattern (like in stackable2.cu)
// Demonstrates repeated nested contexts
for (int iter = 0; iter < 3; iter++)
{
stackable_ctx::graph_scope_guard iteration{ctx}; // New scope each iteration
auto temp = ctx.logical_data(lA.shape());
// tmp = a
ctx.parallel_for(temp.shape(), temp.write(), lA.read())->*[] __device__(size_t i, auto temp, auto a) {
temp(i) = a(i);
};
// a++
ctx.parallel_for(lA.shape(), lA.rw())->*[] __device__(size_t i, auto a) {
a(i) += 1;
};
// tmp *= 2
ctx.parallel_for(temp.shape(), temp.rw())->*[] __device__(size_t i, auto temp) {
temp(i) *= 2;
};
// a += tmp
ctx.parallel_for(lA.shape(), temp.read(), lA.rw())->*[] __device__(size_t i, auto temp, auto a) {
a(i) += temp(i);
};
// pop() called automatically at end of iteration
}
ctx.finalize();
return 0;
}

View File

@@ -1,126 +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 An example solving heat equation with finite differences using the
* parallel_for construct.
*
* A multi-gpu version is shown in the heat_mgpu.cu example.
*
* This example also illustrate how to annotate resources with set_symbol
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
void dump_iter(slice<const double, 2> sUn, int iter)
{
/* Create a binary file in the PPM format */
char name[64];
snprintf(name, 64, "heat_%06d.ppm", iter);
FILE* f = fopen(name, "wb");
fprintf(f, "P6\n%zu %zu\n255\n", sUn.extent(0), sUn.extent(1));
for (size_t j = 0; j < sUn.extent(1); j++)
{
for (size_t i = 0; i < sUn.extent(0); i++)
{
int v = (int) (255.0 * sUn(i, j) / 100.0);
// we assume values between 0.0 and 100.0 : max value is in red,
// min is in blue
unsigned char color[3];
color[0] = static_cast<char>(v); /* red */
color[1] = static_cast<char>(0); /* green */
color[2] = static_cast<char>(255 - v); /* blue */
fwrite(color, 1, 3, f);
}
}
fclose(f);
}
int main()
{
context ctx;
const size_t N = 800;
auto lU = ctx.logical_data(shape_of<slice<double, 2>>(N, N));
auto lU1 = ctx.logical_data(lU.shape());
// Initialize the Un field with boundary conditions, and a disk at a lower
// temperature in the middle.
ctx.parallel_for(lU.shape(), lU.write())->*[=] _CCCL_DEVICE(size_t i, size_t j, auto U) {
double rad = U.extent(0) / 8.0;
double dx = (double) i - U.extent(0) / 2;
double dy = (double) j - U.extent(1) / 2;
U(i, j) = (dx * dx + dy * dy < rad * rad) ? 100.0 : 0.0;
/* Set up boundary conditions */
if (j == 0.0)
{
U(i, j) = 100.0;
}
if (j == U.extent(1) - 1)
{
U(i, j) = 0.0;
}
if (i == 0.0)
{
U(i, j) = 0.0;
}
if (i == U.extent(0) - 1)
{
U(i, j) = 0.0;
}
};
// diffusion constant
double a = 0.5;
double dx = 0.1;
double dy = 0.1;
double dx2 = dx * dx;
double dy2 = dy * dy;
// time step
double dt = dx2 * dy2 / (2.0 * a * (dx2 + dy2));
double c = a * dt;
int nsteps = 1000;
int image_freq = -1;
for (int iter = 0; iter < nsteps; iter++)
{
if (image_freq > 0 && iter % image_freq == 0)
{
// Dump Un in a PPM file
ctx.host_launch(lU.read())->*[=](auto U) {
dump_iter(U, iter);
};
}
// Update Un using Un1 value with a finite difference scheme
ctx.parallel_for(inner<1>(lU.shape()), lU.read(), lU1.write())
->*[=]
_CCCL_DEVICE(size_t i, size_t j, auto U, auto U1) {
U1(i, j) =
U(i, j)
+ c * ((U(i - 1, j) - 2 * U(i, j) + U(i + 1, j)) / dx2 + (U(i, j - 1) - 2 * U(i, j) + U(i, j + 1)) / dy2);
};
std::swap(lU, lU1);
}
ctx.finalize();
}

View File

@@ -1,163 +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 An example solving heat equation with finite differences on multiple devices
*
* This example also illustrate how to annotate resources with set_symbol
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
void dump_iter(slice<const double, 2> sUn, int iter)
{
/* Create a binary file in the PPM format */
char name[64];
snprintf(name, 64, "heat_%06d.ppm", iter);
FILE* f = fopen(name, "wb");
fprintf(f, "P6\n%zu %zu\n255\n", sUn.extent(0), sUn.extent(1));
for (size_t j = 0; j < sUn.extent(1); j++)
{
for (size_t i = 0; i < sUn.extent(0); i++)
{
int v = (int) (255.0 * sUn(i, j) / 100.0);
// we assume values between 0.0 and 100.0 : max value is in red,
// min is in blue
unsigned char color[3];
color[0] = static_cast<char>(v); /* red */
color[1] = static_cast<char>(0); /* green */
color[2] = static_cast<char>(255 - v); /* blue */
fwrite(color, 1, 3, f);
}
}
fclose(f);
}
int main(int argc, char** argv)
{
context ctx;
size_t N = 1000;
int nsteps = 100;
int image_freq = -1;
if (argc > 1)
{
N = atol(argv[1]);
}
if (argc > 2)
{
nsteps = atoi(argv[2]);
}
if (argc > 3)
{
image_freq = atoi(argv[3]);
}
if (argc > 4)
{
int use_graphs = atoi(argv[4]);
if (use_graphs != 0)
{
ctx = graph_ctx();
}
}
auto lU = ctx.logical_data(shape_of<slice<double, 2>>(N, N));
auto lU1 = ctx.logical_data(lU.shape());
lU.set_symbol("U");
lU1.set_symbol("U1");
auto all_devs = exec_place::all_devices();
// Initialize the Un field with boundary conditions, and a disk at a lower
// temperature in the middle.
ctx.parallel_for(blocked_partition(), all_devs, lU.shape(), lU.write()).set_symbol("init")->*
[=] _CCCL_DEVICE(size_t i, size_t j, auto U) {
double rad = U.extent(0) / 8.0;
double dx = (double) i - U.extent(0) / 2;
double dy = (double) j - U.extent(1) / 2;
U(i, j) = (dx * dx + dy * dy < rad * rad) ? 100.0 : 0.0;
/* Set up boundary conditions */
if (j == 0.0)
{
U(i, j) = 100.0;
}
if (j == U.extent(1) - 1)
{
U(i, j) = 0.0;
}
if (i == 0.0)
{
U(i, j) = 0.0;
}
if (i == U.extent(0) - 1)
{
U(i, j) = 0.0;
}
};
// diffusion constant
double a = 0.5;
double dx = 0.1;
double dy = 0.1;
double dx2 = dx * dx;
double dy2 = dy * dy;
// time step
double dt = dx2 * dy2 / (2.0 * a * (dx2 + dy2));
double c = a * dt;
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
ctx.repeat(nsteps)->*[&](context ctx, size_t iter) {
if (image_freq > 0 && iter % image_freq == 0)
{
// Dump Un in a PPM file
ctx.host_launch(lU.read()).set_symbol("dump")->*[=](auto U) {
dump_iter(U, static_cast<int>(iter));
};
}
// Update Un using Un1 value with a finite difference scheme
ctx.parallel_for(blocked_partition(), all_devs, inner<1>(lU.shape()), lU.read(), lU1.write()).set_symbol("step")->*
[=] _CCCL_DEVICE(size_t i, size_t j, auto U, auto U1) {
U1(i, j) =
U(i, j)
+ c * ((U(i - 1, j) - 2 * U(i, j) + U(i + 1, j)) / dx2 + (U(i, j - 1) - 2 * U(i, j) + U(i, j + 1)) / dy2);
};
std::swap(lU, lU1);
};
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Elapsed time: %f ms\n", elapsedTime);
}

View File

@@ -1,153 +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 Jacobi method with launch
*
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
using namespace cuda::experimental::stf;
/* Implement atomicMax with a compare and swap */
_CCCL_DEVICE double atomicMax(double* address, double val)
{
unsigned long long int* address_as_ull = (unsigned long long int*) address;
unsigned long long int old = *address_as_ull, assumed;
do
{
assumed = old;
old = atomicCAS(address_as_ull, assumed, __double_as_longlong(fmax(val, __longlong_as_double(assumed))));
// Note: uses integer comparison to avoid hang in case of NaN (since NaN != NaN)
} while (assumed != old);
return __longlong_as_double(old);
}
template <typename thread_hierarchy_t>
_CCCL_DEVICE double reduce_max(thread_hierarchy_t& t, double local_max)
{
auto ti = t.inner();
slice<double> error = t.template storage<double>(0);
error(0) = 0.0;
t.sync();
// Note we do not use t.static_width(1) because t is a runtime variable so it
// cannot be used directly to statically evaluate the size.
__shared__ double block_max[thread_hierarchy_t::static_width(1)];
block_max[ti.rank()] = local_max;
for (size_t s = ti.size() / 2; s > 0; s /= 2)
{
if (ti.rank() < s)
{
block_max[ti.rank()] = fmax(block_max[ti.rank() + s], block_max[ti.rank()]);
}
ti.sync();
}
if (ti.rank() == 0)
{
atomicMax(&error(0), block_max[0]);
}
t.sync();
return error(0);
}
int main(int argc, char** argv)
{
context ctx;
size_t n = 4096;
size_t m = 4096;
size_t iter_max = 100;
double tol = 0.0000001;
if (argc > 2)
{
n = atol(argv[1]);
m = atol(argv[2]);
}
if (argc > 3)
{
iter_max = atoi(argv[3]);
}
if (argc > 4)
{
tol = atof(argv[4]);
}
auto lA = ctx.logical_data(shape_of<slice<double, 2>>(m, n));
auto lAnew = ctx.logical_data(lA.shape());
auto all_devs = exec_place::all_devices();
ctx.parallel_for(blocked_partition(), all_devs, lA.shape(), lA.write(), lAnew.write()).set_symbol("init")->*
[=] _CCCL_DEVICE(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = (i == j) ? 10.0 : -1.0;
};
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
auto spec = con(con<64>(), mem(sizeof(double)));
ctx.launch(spec, all_devs, lA.rw(), lAnew.write())->*[iter_max, tol, n, m] _CCCL_DEVICE(auto t, auto A, auto Anew) {
auto ti = t.inner();
for (size_t iter = 0; iter < iter_max; iter++)
{
// thread-local maximum error
double local_error = 0.0;
for (auto [i, j] : t.apply_partition(inner<1>(shape(A))))
{
Anew(i, j) = 0.25 * (A(i - 1, j) + A(i + 1, j) + A(i, j - 1) + A(i, j + 1));
local_error = fmax(local_error, fabs(A(i, j) - Anew(i, j)));
}
// compute the overall maximum error
double error = reduce_max(t, local_error);
/* Fill A with the new values */
for (auto [i, j] : t.apply_partition(shape(A)))
{
A(i, j) = Anew(i, j);
}
if (iter % 25 == 0 && t.rank() == 0)
{
printf("iter %zu : error %e (tol %e)\n", iter, error, tol);
}
}
};
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Elapsed time: %f ms\n", elapsedTime);
}

View File

@@ -1,95 +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 Jacobi method with parallel_for
*
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
using namespace cuda::experimental::stf;
int main(int argc, char** argv)
{
context ctx;
size_t n = 4096;
size_t m = 4096;
double tol = 0.5;
size_t iter_max = 1000;
if (argc > 2)
{
n = atol(argv[1]);
m = atol(argv[2]);
}
if (argc > 3)
{
tol = atof(argv[3]);
}
if (argc > 4)
{
iter_max = atoi(argv[4]);
}
auto lA = ctx.logical_data(shape_of<slice<double, 2>>(m, n));
auto lAnew = ctx.logical_data(lA.shape());
ctx.parallel_for(lA.shape(), lA.write(), lAnew.write()).set_symbol("init")->*
[=] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = (i == j) ? 10.0 : -1.0;
Anew(i, j) = A(i, j);
};
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
auto lresidual = ctx.logical_data(shape_of<scalar_view<double>>());
size_t iter = 0;
do
{
ctx.parallel_for(inner<1>(lA.shape()), lA.read(), lAnew.rw(), lresidual.reduce(reducer::maxval<double>{}))
->*[] __device__(size_t i, size_t j, auto A, auto Anew, auto& residual) {
Anew(i, j) = 0.25 * (A(i - 1, j) + A(i + 1, j) + A(i, j - 1) + A(i, j + 1));
residual = ::std::max(residual, fabs(A(i, j) - Anew(i, j)));
};
ctx.parallel_for(inner<1>(lA.shape()), lA.rw(), lAnew.read())->*[] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = Anew(i, j);
};
iter++;
} while (ctx.wait(lresidual) > tol && iter < iter_max);
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
double final_residual = ctx.wait(lresidual);
printf("Converged after %ld iterations, residual = %lf\n", iter, final_residual);
ctx.finalize();
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Elapsed time: %f ms\n", elapsedTime);
}

View File

@@ -1,97 +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 Jacobi method with parallel_for and graphs
*
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
using namespace cuda::experimental::stf;
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
size_t n = 4096;
size_t m = 4096;
double tol = 0.1;
if (argc > 2)
{
n = atol(argv[1]);
m = atol(argv[2]);
}
if (argc > 3)
{
tol = atof(argv[3]);
}
auto lA = ctx.logical_data(shape_of<slice<double, 2>>(m, n));
auto lAnew = ctx.logical_data(lA.shape());
ctx.parallel_for(lA.shape(), lA.write(), lAnew.write()).set_symbol("init")->*
[=] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = (i == j) ? 1.0 : -1.0;
};
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
auto lconverged = ctx.logical_data(shape_of<scalar_view<bool>>());
size_t iter = 0;
// Creating a conditional handle but not using it in a conditional node can make the graph instantiation fail.
cudaGraphConditionalHandle handle;
ctx.push_while(&handle, 1, cudaGraphCondAssignDefault);
ctx.parallel_for(inner<1>(lA.shape()), lA.read(), lAnew.write(), lconverged.reduce(reducer::logical_and<bool>{}))
->*[tol] __device__(size_t i, size_t j, auto A, auto Anew, auto& converged) {
Anew(i, j) = 0.25 * (A(i - 1, j) + A(i + 1, j) + A(i, j - 1) + A(i, j + 1));
double error = fabs(A(i, j) - Anew(i, j));
converged = converged && (error < tol);
};
ctx.parallel_for(inner<1>(lA.shape()), lA.rw(), lAnew.read())->*[] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = Anew(i, j);
};
ctx.parallel_for(box(1), lconverged.read())->*[handle] __device__(size_t, auto converged) {
cudaGraphSetConditional(handle, !*converged);
};
ctx.pop();
fprintf(stderr, "ITER %zu: converged\n", iter++);
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Elapsed time: %f ms\n", elapsedTime);
#endif
}

View File

@@ -1,104 +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 Jacobi method with a while scope guard and explicit management of the conditional handle
*
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
#include "cuda/experimental/__stf/stackable/stackable_ctx.cuh"
using namespace cuda::experimental::stf;
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
size_t n = 4096;
size_t m = 4096;
double tol = 0.1;
if (argc > 2)
{
n = atol(argv[1]);
m = atol(argv[2]);
}
if (argc > 3)
{
tol = atof(argv[3]);
}
auto lA = ctx.logical_data(shape_of<slice<double, 2>>(m, n));
auto lAnew = ctx.logical_data(lA.shape());
ctx.parallel_for(lA.shape(), lA.write(), lAnew.write()).set_symbol("init")->*
[=] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = (i == j) ? 1.0 : -1.0;
};
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
size_t iter = 0;
auto lresidual = ctx.logical_data(shape_of<scalar_view<double>>());
{
auto while_guard = ctx.while_graph_scope();
ctx.parallel_for(inner<1>(lA.shape()), lA.read(), lAnew.write(), lresidual.reduce(reducer::maxval<double>{}))
->*[] __device__(size_t i, size_t j, auto A, auto Anew, auto& residual) {
Anew(i, j) = 0.25 * (A(i - 1, j) + A(i + 1, j) + A(i, j - 1) + A(i, j + 1));
double error = fabs(A(i, j) - Anew(i, j));
residual = error;
};
ctx.parallel_for(inner<1>(lA.shape()), lA.rw(), lAnew.read())->*[] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = Anew(i, j);
};
auto handle = while_guard.cond_handle();
ctx.parallel_for(box(1), lresidual.read())->*[handle, tol] __device__(size_t, auto residual) {
bool converged = (*residual < tol);
cudaGraphSetConditional(handle, !converged);
};
}
// Store final residual for verification
double final_residual = ctx.wait(lresidual);
fprintf(stderr, "ITER %zu: converged residual %e\n", iter++, final_residual);
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
EXPECT(final_residual <= tol); // Algorithm should have converged within tolerance
float elapsedTime;
cudaEventElapsedTime(&elapsedTime, start, stop);
printf("Elapsed time: %f ms\n", elapsedTime);
#endif
}

View File

@@ -1,101 +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 Jacobi method using the update_cond helper for clean condition management
*/
#include <cuda/experimental/stf.cuh>
#include <iostream>
using namespace cuda::experimental::stf;
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
stackable_ctx ctx;
size_t n = 4096;
size_t m = 4096;
double tol = 0.5;
int max_iter = 1000;
if (argc > 2)
{
n = atol(argv[1]);
m = atol(argv[2]);
}
if (argc > 3)
{
tol = atof(argv[3]);
}
if (argc > 4)
{
max_iter = atoi(argv[4]);
}
auto lA = ctx.logical_data(shape_of<slice<double, 2>>(m, n));
auto lAnew = ctx.logical_data(lA.shape());
auto lresidual = ctx.logical_data(shape_of<scalar_view<double>>());
auto liter = ctx.logical_data(shape_of<scalar_view<int>>());
ctx.parallel_for(lA.shape(), lA.write(), lAnew.write()).set_symbol("init")->*
[=] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = (i == j) ? 10.0 : -1.0;
Anew(i, j) = A(i, j);
};
// Initialize iteration counter
ctx.parallel_for(box(1), liter.write())->*[] __device__(size_t, auto iter) {
*iter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
ctx.parallel_for(inner<1>(lA.shape()), lA.read(), lAnew.rw(), lresidual.reduce(reducer::maxval<double>()))
->*[tol] __device__(size_t i, size_t j, auto A, auto Anew, auto& residual) {
Anew(i, j) = 0.25 * (A(i - 1, j) + A(i + 1, j) + A(i, j - 1) + A(i, j + 1));
double error = fabs(A(i, j) - Anew(i, j));
residual = ::std::max(error, residual);
};
ctx.parallel_for(inner<1>(lA.shape()), lA.rw(), lAnew.read())->*[] __device__(size_t i, size_t j, auto A, auto Anew) {
A(i, j) = Anew(i, j);
};
while_guard.update_cond(lresidual.read(), liter.rw())->*[tol, max_iter] __device__(auto residual, auto iter) {
bool converged = (*residual < tol);
bool max_reached = ((*iter)++ >= max_iter); // Maximum iteration limit
return !converged && !max_reached; // Continue if not converged and under limit
};
}
int final_iterations = ctx.wait(liter);
double final_residual = ctx.wait(lresidual);
printf("Converged after %d iterations, residual = %lf\n", final_iterations, final_residual);
ctx.finalize();
EXPECT(final_residual <= tol);
EXPECT(final_iterations < max_iter);
return 0;
#endif
}

View File

@@ -1,170 +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 A naive parallel histogram algorithm written with launch
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__host__ __device__ double X0(int i)
{
return sin((double) i);
}
int main(int argc, char** argv)
{
stream_ctx ctx;
double lower_level = -1.0;
double upper_level = 1.0;
constexpr size_t num_levels = 21;
size_t N = 128 * 1024UL;
if (argc > 1)
{
N = size_t(atoll(argv[1]));
}
int check = 1;
if (argc > 2)
{
check = atoi(argv[2]);
}
// fprintf(stderr, "SIZE %s\n", pretty_print_bytes(N * sizeof(double)).c_str());
std::vector<double> X(N);
std::vector<size_t> histo(num_levels - 1);
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
}
// If we were to register each part one by one, there could be pages which
// cross multiple parts, and the pinning operation would fail.
cuda_safe_call(cudaHostRegister(&X[0], N * sizeof(double), cudaHostRegisterPortable));
auto lX = ctx.logical_data(&X[0], N);
lX.set_symbol("X");
auto lhisto = ctx.logical_data(&histo[0], num_levels - 1);
lhisto.set_symbol("histogram");
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
constexpr size_t BLOCK_THREADS = 128;
// size_t NDEVS = 1;
// auto where = exec_place::repeat(exec_place::current_device(), NDEVS);
auto where = exec_place::current_device();
auto spec = con<8>(con(BLOCK_THREADS, mem((num_levels - 1) * sizeof(size_t))));
ctx.launch(spec, where, lX.read(), lhisto.write())->*[=] _CCCL_DEVICE(auto th, auto x, auto histo) {
size_t block_id = th.rank(0);
slice<size_t> smem_hist = th.template storage<size_t>(1);
assert(smem_hist.size() == (num_levels - 1));
/* Thread local histogram */
size_t local_hist[num_levels - 1];
for (size_t k = 0; k < num_levels - 1; k++)
{
local_hist[k] = 0;
smem_hist[k] = 0;
}
if (th.rank() == 0)
{
for (size_t k = 0; k < num_levels - 1; k++)
{
histo[k] = 0;
}
}
for (size_t i = th.rank(); i < x.size(); i += th.size())
{
double xi = x(i);
if (xi >= lower_level && xi < upper_level)
{
size_t bin = size_t(((num_levels - 1) * (xi - lower_level)) / (upper_level - lower_level));
local_hist[bin]++;
}
}
// smem was zero'ed
th.inner().sync();
/* Each thread contributes to an histogram in shared memory */
for (size_t k = 0; k < num_levels - 1; k++)
{
atomicAdd((unsigned long long*) &smem_hist[k], local_hist[k]);
}
// histo was zero'ed
th.sync();
if (th.inner().rank() == 0)
{
for (size_t k = 0; k < num_levels - 1; k++)
{
atomicAdd((unsigned long long*) &histo[k], smem_hist[k]);
}
}
};
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
float ms = 0;
cuda_safe_call(cudaEventElapsedTime(&ms, start, stop));
// fprintf(stdout, "%zu %f ms\n", N / 1024 / 1024, ms);
if (check)
{
// fprintf(stderr, "Checking result...\n");
size_t refhist[num_levels - 1];
for (size_t i = 0; i < num_levels - 1; i++)
{
refhist[i] = 0;
}
for (size_t i = 0; i < N; i++)
{
double xi = X[i];
if (xi >= lower_level && xi < upper_level)
{
size_t bin = size_t(((num_levels - 1) * (xi - lower_level)) / (upper_level - lower_level));
refhist[bin]++;
}
}
// double dlevel = (upper_level - lower_level) / (num_levels - 1);
for (size_t i = 0; i < num_levels - 1; i++)
{
EXPECT(refhist[i] == histo[i]);
// fprintf(stderr, "[%lf:%lf[ %ld\n", lower_level + i * dlevel, lower_level + (i + 1) * dlevel, histo[i]);
}
}
}

View File

@@ -1,162 +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 A parallel scan algorithm
*
*/
#include <cub/cub.cuh> // or equivalently <cub/device/device_scan.cuh>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__host__ __device__ double X0(int)
{
// return sin((double) i);
return 1.0;
}
int main(int argc, char** argv)
{
stream_ctx ctx;
// graph_ctx ctx;
size_t N = 128 * 1024UL * 1024UL;
if (argc > 1)
{
N = size_t(atoll(argv[1]));
}
int check = 0;
if (argc > 2)
{
check = atoi(argv[2]);
}
std::vector<double> X(N);
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
}
auto lX = ctx.logical_data(&X[0], N);
// No need to move this back to the host if we do not check the result
if (!check)
{
lX.set_write_back(false);
}
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
constexpr size_t BLOCK_THREADS = 128;
constexpr size_t NBLOCKS = 8;
auto spec = con<NBLOCKS>(con<BLOCK_THREADS>(), mem(NBLOCKS * sizeof(double)));
// auto where = exec_place::repeat(exec_place::current_device(), NDEVS);
auto where = exec_place::current_device();
ctx.launch(spec, where, lX.rw())->*[=] _CCCL_DEVICE(auto th, auto x) {
const size_t block_id = th.rank(0);
const size_t tid = th.inner().rank();
// const size_t tid = th.rank(1, 0);
// Block-wide partials using static allocation
__shared__ double block_partial_sum[th.static_width(1)];
// Device-wide partial sums
slice<double> dev_partial_sum = th.template storage<double>(0);
/* Thread local prefix-sum */
const box<1> b = th.apply_partition(shape(x), std::tuple<blocked_partition, blocked_partition>());
for (size_t i = b.get_begin(0) + 1; i < b.get_end(0); i++)
{
x(i) += x(i - 1);
}
block_partial_sum[tid] = x(b.get_end(0) - 1);
th.inner().sync();
/* Block level : get partials sum across the different threads */
if (tid == 0)
{ // rank in scope block is 0
// Prefix sum on partial sums
for (size_t i = 1; i < BLOCK_THREADS; i++)
{
block_partial_sum[i] += block_partial_sum[i - 1];
}
dev_partial_sum[block_id] = block_partial_sum[BLOCK_THREADS - 1];
}
/* Reduce partial sums at device level : get sum across all blocks */
th.sync();
if (block_id == 0 && tid == 0)
{ // rank in scope 0
for (size_t i = 1; i < NBLOCKS; i++)
{
dev_partial_sum[i] += dev_partial_sum[i - 1];
// printf("SUMMED dev_partial_sum[%ld] = %f\n", i, dev_partial_sum[i]);
}
}
th.sync();
for (size_t i = b.get_begin(0); i < b.get_end(0); i++)
{
if (tid > 0)
{
x(i) += block_partial_sum[tid - 1];
}
if (block_id > 0)
{
x(i) += dev_partial_sum[block_id - 1];
}
}
};
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
float ms = 0;
cuda_safe_call(cudaEventElapsedTime(&ms, start, stop));
printf("%s in %f ms (%g GB/s)\n",
pretty_print_bytes(N * sizeof(double)).c_str(),
ms,
double(N * sizeof(double) / 1024 / 1024) / ms);
if (check)
{
fprintf(stderr, "Checking result...\n");
EXPECT(fabs(X[0] - X0(0)) < 0.00001);
for (size_t i = 0; i < N; i++)
{
if (fabs(X[i] - X[i - 1] - X0(i)) > 0.00001)
{
fprintf(stderr, "I %zu X[i] %f (X[i] - X[i-1]) %f expect %f\n", i, X[i], (X[i] - X[i - 1]), X0(i));
}
EXPECT(fabs(X[i] - X[i - 1] - X0(i)) < 0.00001);
}
}
}

View File

@@ -1,81 +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 A reduction kernel written using launch
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
double X0(int i)
{
return sin((double) i);
}
int main()
{
context ctx;
const size_t N = 128 * 1024 * 1024;
std::vector<double> X(N);
double sum = 0.0;
double ref_sum = 0.0;
for (size_t ind = 0; ind < N; ind++)
{
X[ind] = sin((double) ind);
ref_sum += X[ind];
}
auto lX = ctx.logical_data(&X[0], {N});
auto lsum = ctx.logical_data(&sum, {1});
auto number_devices = 1; //
auto where = exec_place::repeat(exec_place::device(0), number_devices);
auto spec = par<16>(con<32>());
ctx.launch(spec, where, lX.read(), lsum.rw())->*[] _CCCL_DEVICE(auto th, auto x, auto sum) {
// Each thread computes the sum of elements assigned to it
double local_sum = 0.0;
for (size_t i = th.rank(); i < x.size(); i += th.size())
{
local_sum += x(i);
}
auto ti = th.inner();
__shared__ double block_sum[th.static_width(1)];
block_sum[ti.rank()] = local_sum;
for (size_t s = ti.size() / 2; s > 0; s /= 2)
{
ti.sync();
if (ti.rank() < s)
{
block_sum[ti.rank()] += block_sum[ti.rank() + s];
}
}
if (ti.rank() == 0)
{
atomicAdd(&sum(0), block_sum[0]);
}
};
ctx.finalize();
EXPECT(fabs(sum - ref_sum) < 0.0001);
}

View File

@@ -1,73 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief A reduction kernel written using launch and CUB
*/
#include <cub/cub.cuh>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
double X0(int i)
{
return sin((double) i);
}
int main()
{
context ctx;
const size_t N = 128 * 1024 * 1024;
std::vector<double> X(N);
double sum = 0.0;
double ref_sum = 0.0;
for (size_t ind = 0; ind < N; ind++)
{
X[ind] = sin((double) ind);
ref_sum += X[ind];
}
auto lX = ctx.logical_data(&X[0], {N});
auto lsum = ctx.logical_data(&sum, {1});
auto number_devices = 2;
auto where = exec_place::repeat(exec_place::device(0), number_devices);
auto spec = par<32>(con<128>());
ctx.launch(spec, where, lX.read(), lsum.rw())->*[] _CCCL_DEVICE(auto th, auto x, auto sum) {
// Each thread computes the sum of elements assigned to it
double local_sum = 0.0;
for (auto ind : th.apply_partition(shape(x)))
{
local_sum += x(ind);
}
using BlockReduce = cub::BlockReduce<double, th.static_width(1)>;
__shared__ typename BlockReduce::TempStorage temp_storage;
double block_sum = BlockReduce(temp_storage).Sum(local_sum);
if (th.inner().rank() == 0)
{
atomicAdd(&sum(0), block_sum);
}
};
ctx.finalize();
EXPECT(fabs(sum - ref_sum) < 0.0001);
}

View File

@@ -1,484 +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 An example that implements a tiled matrix product over multiple devices using CUBLAS
*/
#include <cstdlib>
#include "cuda/experimental/stf.cuh"
#include <nvtx3/nvToolsExt.h>
#define TILED
using namespace cuda::experimental::stf;
static std::unordered_map<int, cublasHandle_t> cublas_handles;
/* Get a CUBLAS handle valid on the current device, or initialize it lazily */
cublasHandle_t get_cublas_handle()
{
int dev;
cuda_safe_call(cudaGetDevice(&dev));
auto& result = cublas_handles[dev];
if (result == cublasHandle_t())
{ // not found, default value inserted
// Lazy initialization, and save the handle for future use
cuda_safe_call(cublasCreate(&result));
}
return result;
}
template <typename T>
class matrix
{
public:
matrix(stackable_ctx& ctx,
size_t NROWS,
size_t NCOLS,
size_t BLOCKSIZE_ROWS,
size_t BLOCKSIZE_COLS,
const char* _symbol = "matrix")
: h_array(nullptr)
, m(NROWS)
, n(NCOLS)
, mb(BLOCKSIZE_ROWS)
, nb(BLOCKSIZE_COLS)
, mt(0)
, nt(0)
, symbol(_symbol)
, ndevs(0)
, grid_p(0)
, grid_q(0)
{
assert(m % mb == 0);
assert(n % nb == 0);
const size_t s = m * n * sizeof(T);
// cuda_safe_call(cudaMallocHost(&h_array, m*n*sizeof(T)));
// fprintf(stderr, "Allocating %ld x %ld x %ld = %ld bytes (%f GB) on host for %s\n", m, n, sizeof(T), s,
// s / (1024.0 * 1024.0 * 1024.0), _symbol);
h_array = static_cast<T*>(malloc(s));
assert(h_array);
cuda_safe_call(cudaHostRegister(h_array, s, cudaHostRegisterPortable));
// Compute the number of blocks
mt = m / mb;
nt = n / nb;
handles.resize(mt * nt);
for (size_t colb = 0; colb < nt; colb++)
{
for (size_t rowb = 0; rowb < mt; rowb++)
{
T* addr_h = get_block_h(rowb, colb);
#ifdef TILED
// tiles are stored contiguously
const size_t ld = mb;
#else
const size_t ld = m;
#endif
std::ignore = ld; // avoid warning #177-D: variable "ld" was declared but never referenced
auto s = make_slice(addr_h, std::tuple{mb, nb}, ld);
auto tile = ctx.logical_data(s);
tile.set_write_back(false);
tile.set_symbol(std::string(symbol) + "_" + std::to_string(rowb) + "_" + std::to_string(colb));
handles[rowb + colb * mt] = std::move(tile);
}
}
cuda_safe_call(cudaGetDeviceCount(&ndevs));
for (int a = 1; a * a <= ndevs; a++)
{
if (ndevs % a == 0)
{
grid_p = a;
grid_q = ndevs / a;
}
}
assert(grid_p * grid_q == ndevs);
// std::cout << "FOUND " << ndevs << " DEVICES "
// << "p=" << grid_p << " q=" << grid_q << '\n';
}
~matrix()
{
if (h_array)
{
cuda_safe_call(cudaHostUnregister(h_array));
free(h_array);
}
}
// Disable copy and move operations - this is a resource-owning class used locally
matrix(const matrix&) = delete;
matrix& operator=(const matrix&) = delete;
matrix(matrix&&) = delete;
matrix& operator=(matrix&&) = delete;
void push(access_mode mode)
{
for (auto& h : handles)
{
h.push(mode);
}
}
int get_preferred_devid(int row, int col) const
{
return (row % grid_p) + (col % grid_q) * grid_p;
}
auto& get_handle(int row, int col)
{
return handles[row + col * mt];
}
auto& get_handle(int row, int col) const
{
return handles[row + col * mt];
}
size_t get_index(size_t row, size_t col) const
{
#ifdef TILED
// Find which tile contains this element
const int tile_row = static_cast<int>(row / mb);
const int tile_col = static_cast<int>(col / nb);
const size_t tile_size = mb * nb;
// Look for the index of the beginning of the tile
const size_t tile_start = (tile_row + mt * tile_col) * tile_size;
// Offset within the tile
const size_t offset = (row % mb) + (col % nb) * mb;
return tile_start + offset;
#else
return row + col * m;
#endif
}
T* get_block_h(int brow, int bcol)
{
const size_t index = get_index(brow * mb, bcol * nb);
return &h_array[index];
}
// Fill with func(Matrix*,row, col)
template <typename Fun>
void fill(stackable_ctx& ctx, Fun&& fun)
{
nvtxRangePushA("FILL");
// Fill blocks by blocks
for (size_t colb = 0; colb < nt; colb++)
{
for (size_t rowb = 0; rowb < mt; rowb++)
{
// Each task fills a block
auto& h = get_handle(rowb, colb);
int devid = get_preferred_devid(rowb, colb);
ctx.parallel_for(exec_place::device(devid), h.shape(), h.write()).set_symbol("INIT")->*
[=] _CCCL_DEVICE(size_t lrow, size_t lcol, auto sA) {
const size_t row = lrow + rowb * sA.extent(0);
const size_t col = lcol + colb * sA.extent(1);
sA(lrow, lcol) = fun(row, col);
};
}
}
nvtxRangePop();
}
T* h_array;
size_t m; // nrows
size_t n; // ncols
size_t mb; // block size (rows)
size_t nb; // block size (cols)
size_t mt; // numter of column blocks
size_t nt; // numter of row blocks
// abstract data handles
std::vector<stackable_logical_data<slice<T, 2>>> handles;
const char* symbol;
// for the mapping
int ndevs;
int grid_p, grid_q;
};
void DGEMM(
stackable_ctx& ctx,
cublasOperation_t transa,
cublasOperation_t transb,
double alpha,
const matrix<double>& A,
int A_row,
int A_col,
const matrix<double>& B,
int B_row,
int B_col,
double beta,
matrix<double>& C,
int C_row,
int C_col)
{
const auto dev = exec_place::device(C.get_preferred_devid(C_row, C_col));
auto t = ctx.task(
dev, A.get_handle(A_row, A_col).read(), B.get_handle(B_row, B_col).read(), C.get_handle(C_row, C_col).rw());
t.set_symbol("DGEMM");
t->*[&](cudaStream_t stream, auto tA, auto tB, auto tC) {
cuda_safe_call(cublasSetStream(get_cublas_handle(), stream));
int k = tA.extent(transa == CUBLAS_OP_N ? 1 : 0);
cuda_safe_call(cublasDgemm(
get_cublas_handle(),
transa,
transb,
tC.extent(0),
tC.extent(1),
k,
&alpha,
tA.data_handle(),
tA.stride(1),
tB.data_handle(),
tB.stride(1),
&beta,
tC.data_handle(),
tC.stride(1)));
};
}
void PDGEMM(stackable_ctx& ctx,
cublasOperation_t transa,
cublasOperation_t transb,
double alpha,
const matrix<double>& A,
const matrix<double>& B,
double beta,
matrix<double>& C)
{
for (size_t m = 0; m < C.mt; m++)
{
for (size_t n = 0; n < C.nt; n++)
{
//=========================================
// alpha*A*B does not contribute; scale C
//=========================================
const size_t inner_k = transa == CUBLAS_OP_N ? A.n : A.m;
if (alpha == 0.0 || inner_k == 0)
{
DGEMM(ctx, transa, transb, alpha, A, 0, 0, B, 0, 0, beta, C, static_cast<int>(m), static_cast<int>(n));
}
else if (transa == CUBLAS_OP_N)
{
//================================
// CUBLAS_OP_N / CUBLAS_OP_N
//================================
if (transb == CUBLAS_OP_N)
{
assert(A.nt == B.mt);
for (size_t k = 0; k < A.nt; k++)
{
const double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx,
transa,
transb,
alpha,
A,
static_cast<int>(m),
static_cast<int>(k),
B,
static_cast<int>(k),
static_cast<int>(n),
zbeta,
C,
static_cast<int>(m),
static_cast<int>(n));
}
}
//=====================================
// CUBLAS_OP_N / CUBLAS_OP_T
//=====================================
else
{
for (size_t k = 0; k < A.nt; k++)
{
const double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx,
transa,
transb,
alpha,
A,
static_cast<int>(m),
static_cast<int>(k),
B,
static_cast<int>(n),
static_cast<int>(k),
zbeta,
C,
static_cast<int>(m),
static_cast<int>(n));
}
}
}
else
{
//=====================================
// CUBLAS_OP_T / CUBLAS_OP_N
//=====================================
if (transb == CUBLAS_OP_N)
{
for (size_t k = 0; k < A.mt; k++)
{
const double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx,
transa,
transb,
alpha,
A,
static_cast<int>(k),
static_cast<int>(m),
B,
static_cast<int>(k),
static_cast<int>(n),
zbeta,
C,
static_cast<int>(m),
static_cast<int>(n));
}
}
//==========================================
// CUBLAS_OP_T / CUBLAS_OP_T
//==========================================
else
{
for (size_t k = 0; k < A.mt; k++)
{
const double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx,
transa,
transb,
alpha,
A,
static_cast<int>(k),
static_cast<int>(m),
B,
static_cast<int>(n),
static_cast<int>(k),
zbeta,
C,
static_cast<int>(m),
static_cast<int>(n));
}
}
}
}
}
}
void run(stackable_ctx& ctx, size_t N, size_t NB)
{
/// auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, NB * NB * sizeof(double));
// ctx.set_allocator(fixed_alloc);
// Set up CUBLAS and CUSOLVER
int ndevs;
cuda_safe_call(cudaGetDeviceCount(&ndevs));
/* Warm up allocators */
for (int d = 0; d < ndevs; d++)
{
auto lX = ctx.logical_data(shape_of<slice<double>>(1));
ctx.parallel_for(exec_place::device(d), lX.shape(), lX.write())->*[] _CCCL_DEVICE(size_t, auto) {};
}
/* Initializes CUBLAS on all devices */
for (int d = 0; d < ndevs; d++)
{
cuda_safe_call(cudaSetDevice(d));
get_cublas_handle();
}
matrix<double> A(ctx, N, N, NB, NB, "A");
matrix<double> B(ctx, N, N, NB, NB, "B");
matrix<double> C(ctx, N, N, NB, NB, "C");
// (Hilbert matrix + 2*N*Id)
auto hilbert = [=] _CCCL_HOST_DEVICE(size_t row, size_t col) {
return 1.0 / (col + row + 1.0) + 2.0 * N * (col == row);
};
A.fill(ctx, hilbert);
B.fill(ctx, hilbert);
C.fill(ctx, hilbert);
cudaEvent_t startEvent, stopEvent;
cuda_safe_call(cudaEventCreate(&startEvent));
cuda_safe_call(cudaEventCreate(&stopEvent));
cuda_safe_call(cudaEventRecord(startEvent, ctx.fence()));
ctx.push();
A.push(access_mode::read);
B.push(access_mode::read);
C.push(access_mode::rw);
PDGEMM(ctx, CUBLAS_OP_N, CUBLAS_OP_N, 1.0, A, B, -2.0, C);
ctx.pop();
cuda_safe_call(cudaEventRecord(stopEvent, ctx.fence()));
ctx.finalize();
float milliseconds;
cuda_safe_call(cudaEventElapsedTime(&milliseconds, startEvent, stopEvent));
const double gflops_pdgemm =
2.0 * (static_cast<double>(N) * static_cast<double>(N) * static_cast<double>(N)) / 1000000000.0;
::std::cout
<< "[PDDGEMM] ELAPSED: " << milliseconds << " ms, GFLOPS: " << gflops_pdgemm / (milliseconds / 1000.0) << '\n';
}
int main(int argc, char** argv)
{
size_t N = 4096;
size_t NB = 512;
if (argc > 1)
{
N = static_cast<size_t>(::std::atoi(argv[1]));
}
if (argc > 2)
{
NB = static_cast<size_t>(::std::atoi(argv[2]));
}
assert(N % NB == 0);
stackable_ctx ctx;
run(ctx, N, NB);
}

View File

@@ -1,399 +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 An example that implements a tiled matrix product over multiple devices using CUBLAS
*
* This also illustrates how the same code base can be used both with a
* stream_ctx and a graph_ctx backend.
*/
#include <cuda/experimental/__stf/utility/nvtx.cuh>
#include <cuda/experimental/stf.cuh>
#define TILED
using namespace cuda::experimental::stf;
static std::unordered_map<exec_place, cublasHandle_t, hash<exec_place>> cublas_handles;
/* Get a CUBLAS handle valid on the current execution place, or initialize it lazily */
cublasHandle_t get_cublas_handle(const exec_place& ep = exec_place::current_device())
{
auto& result = cublas_handles[ep];
if (result == cublasHandle_t())
{ // not found, default value inserted
// Lazy initialization, and save the handle for future use
cuda_safe_call(cublasCreate(&result));
}
return result;
}
template <typename T>
class matrix
{
public:
matrix(stream_ctx& ctx,
size_t NROWS,
size_t NCOLS,
size_t BLOCKSIZE_ROWS,
size_t BLOCKSIZE_COLS,
const char* _symbol = "matrix")
{
symbol = _symbol;
m = NROWS;
mb = BLOCKSIZE_ROWS;
n = NCOLS;
nb = BLOCKSIZE_COLS;
assert(m % mb == 0);
assert(n % nb == 0);
size_t s = ((size_t) m) * ((size_t) n) * sizeof(T);
// cuda_safe_call(cudaMallocHost(&h_array, m*n*sizeof(T)));
// fprintf(stderr, "Allocating %ld x %ld x %ld = %ld bytes (%f GB) on host for %s\n", m, n, sizeof(T), s,
// s / (1024.0 * 1024.0 * 1024.0), _symbol);
h_array = (T*) malloc(s);
assert(h_array);
cuda_safe_call(cudaHostRegister(h_array, s, cudaHostRegisterPortable));
// Compute the number of blocks
mt = m / mb;
nt = n / nb;
handles.resize(mt * nt);
for (size_t colb = 0; colb < nt; colb++)
{
for (size_t rowb = 0; rowb < mt; rowb++)
{
T* addr_h = get_block_h(rowb, colb);
#ifdef TILED
// tiles are stored contiguously
const size_t ld = mb;
#else
const size_t ld = m;
#endif
std::ignore = ld; // avoid warning #177-D: variable "ld" was declared but never referenced
auto s = make_slice(addr_h, std::tuple{mb, nb}, ld);
auto tile = ctx.logical_data(s);
tile.set_write_back(false);
tile.set_symbol(std::string(symbol) + "_" + std::to_string(rowb) + "_" + std::to_string(colb));
handles[rowb + colb * mt] = std::move(tile);
}
}
cuda_safe_call(cudaGetDeviceCount(&ndevs));
for (int a = 1; a * a <= ndevs; a++)
{
if (ndevs % a == 0)
{
grid_p = a;
grid_q = ndevs / a;
}
}
assert(grid_p * grid_q == ndevs);
// std::cout << "FOUND " << ndevs << " DEVICES "
// << "p=" << grid_p << " q=" << grid_q << '\n';
}
int get_preferred_devid(int row, int col)
{
return (row % grid_p) + (col % grid_q) * grid_p;
}
logical_data<slice<T, 2>>& get_handle(int row, int col)
{
return handles[row + col * mt];
}
size_t get_index(size_t row, size_t col)
{
#ifdef TILED
// Find which tile contains this element
int tile_row = row / mb;
int tile_col = col / nb;
size_t tile_size = mb * nb;
// Look for the index of the beginning of the tile
size_t tile_start = (tile_row + mt * tile_col) * tile_size;
// Offset within the tile
size_t offset = (row % mb) + (col % nb) * mb;
return tile_start + offset;
#else
return row + col * m;
#endif
}
T* get_block_h(int brow, int bcol)
{
size_t index = get_index(brow * mb, bcol * nb);
return &h_array[index];
}
// Fill with func(Matrix*,row, col)
template <typename Fun>
void fill(stream_ctx& ctx, Fun&& fun)
{
nvtx_range r("fill");
// Fill blocks by blocks
for (size_t colb = 0; colb < nt; colb++)
{
for (size_t rowb = 0; rowb < mt; rowb++)
{
// Each task fills a block
auto& h = get_handle(rowb, colb);
int devid = get_preferred_devid(rowb, colb);
ctx.parallel_for(exec_place::device(devid), h.shape(), h.write()).set_symbol("INIT")->*
[=] _CCCL_DEVICE(size_t lrow, size_t lcol, auto sA) {
size_t row = lrow + rowb * sA.extent(0);
size_t col = lcol + colb * sA.extent(1);
sA(lrow, lcol) = fun(row, col);
};
}
}
}
T* h_array;
size_t m; // nrows
size_t n; // ncols
size_t mb; // block size (rows)
size_t nb; // block size (cols)
size_t mt; // numter of column blocks
size_t nt; // numter of row blocks
// abstract data handles
std::vector<logical_data<slice<T, 2>>> handles;
const char* symbol;
// for the mapping
int ndevs;
int grid_p, grid_q;
};
void DGEMM(
stream_ctx& ctx,
cublasOperation_t transa,
cublasOperation_t transb,
double alpha,
matrix<double>& A,
int A_row,
int A_col,
matrix<double>& B,
int B_row,
int B_col,
double beta,
matrix<double>& C,
int C_row,
int C_col)
{
auto dev = exec_place::device(C.get_preferred_devid(C_row, C_col));
auto t = ctx.task(
dev, A.get_handle(A_row, A_col).read(), B.get_handle(B_row, B_col).read(), C.get_handle(C_row, C_col).rw());
t.set_symbol("DGEMM");
t->*[&](cudaStream_t stream, auto tA, auto tB, auto tC) {
cuda_safe_call(cublasSetStream(get_cublas_handle(), stream));
int k = tA.extent(transa == CUBLAS_OP_N ? 1 : 0);
cuda_safe_call(cublasDgemm(
get_cublas_handle(),
transa,
transb,
tC.extent(0),
tC.extent(1),
k,
&alpha,
tA.data_handle(),
tA.stride(1),
tB.data_handle(),
tB.stride(1),
&beta,
tC.data_handle(),
tC.stride(1)));
};
}
void PDGEMM(stream_ctx& ctx,
cublasOperation_t transa,
cublasOperation_t transb,
double alpha,
matrix<double>& A,
matrix<double>& B,
double beta,
matrix<double>& C)
{
nvtx_range r("PDGEMM");
for (size_t m = 0; m < C.mt; m++)
{
for (size_t n = 0; n < C.nt; n++)
{
//=========================================
// alpha*A*B does not contribute; scale C
//=========================================
int inner_k = transa == CUBLAS_OP_N ? A.n : A.m;
if (alpha == 0.0 || inner_k == 0)
{
DGEMM(ctx, transa, transb, alpha, A, 0, 0, B, 0, 0, beta, C, m, n);
}
else if (transa == CUBLAS_OP_N)
{
//================================
// CUBLAS_OP_N / CUBLAS_OP_N
//================================
if (transb == CUBLAS_OP_N)
{
assert(A.nt == B.mt);
for (size_t k = 0; k < A.nt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx, transa, transb, alpha, A, m, k, B, k, n, zbeta, C, m, n);
}
}
//=====================================
// CUBLAS_OP_N / CUBLAS_OP_T
//=====================================
else
{
for (size_t k = 0; k < A.nt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx, transa, transb, alpha, A, m, k, B, n, k, zbeta, C, m, n);
}
}
}
else
{
//=====================================
// CUBLAS_OP_T / CUBLAS_OP_N
//=====================================
if (transb == CUBLAS_OP_N)
{
for (size_t k = 0; k < A.mt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx, transa, transb, alpha, A, k, m, B, k, n, zbeta, C, m, n);
}
}
//==========================================
// CUBLAS_OP_T / CUBLAS_OP_T
//==========================================
else
{
for (size_t k = 0; k < A.mt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(ctx, transa, transb, alpha, A, k, m, B, n, k, zbeta, C, m, n);
}
}
}
}
}
}
void run(stream_ctx& ctx, size_t N, size_t NB)
{
auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, NB * NB * sizeof(double));
ctx.set_allocator(fixed_alloc);
// Set up CUBLAS and CUSOLVER
int ndevs;
cuda_safe_call(cudaGetDeviceCount(&ndevs));
/* Warm up allocators */
for (int d = 0; d < ndevs; d++)
{
auto lX = ctx.logical_data(shape_of<slice<double>>(1));
ctx.parallel_for(exec_place::device(d), lX.shape(), lX.write())->*[] _CCCL_DEVICE(size_t, auto) {};
}
/* Initializes CUBLAS on all devices */
for (int d = 0; d < ndevs; d++)
{
cuda_safe_call(cudaSetDevice(d));
get_cublas_handle();
}
matrix<double> A(ctx, N, N, NB, NB, "A");
matrix<double> B(ctx, N, N, NB, NB, "B");
matrix<double> C(ctx, N, N, NB, NB, "C");
// (Hilbert matrix + 2*N*Id)
auto hilbert = [=] _CCCL_HOST_DEVICE(size_t row, size_t col) {
return 1.0 / (col + row + 1.0) + 2.0 * N * (col == row);
};
A.fill(ctx, hilbert);
B.fill(ctx, hilbert);
C.fill(ctx, hilbert);
cudaEvent_t startEvent, stopEvent;
cuda_safe_call(cudaEventCreate(&startEvent));
cuda_safe_call(cudaEventCreate(&stopEvent));
cuda_safe_call(cudaEventRecord(startEvent, ctx.fence()));
PDGEMM(ctx, CUBLAS_OP_N, CUBLAS_OP_N, 1.0, A, B, -2.0, C);
cuda_safe_call(cudaEventRecord(stopEvent, ctx.fence()));
ctx.finalize();
float milliseconds;
cuda_safe_call(cudaEventElapsedTime(&milliseconds, startEvent, stopEvent));
double gflops_pdgemm = 2.0 * ((double) N * (double) N * (double) N) / (1000000000.0);
std::cout
<< "[PDDGEMM] ELAPSED: " << milliseconds << " ms, GFLOPS: " << gflops_pdgemm / (milliseconds / 1000.0) << '\n';
}
int main(int argc, char** argv)
{
size_t N = 4096;
size_t NB = 512;
if (argc > 1)
{
N = atoi(argv[1]);
}
if (argc > 2)
{
NB = atoi(argv[2]);
}
assert(N % NB == 0);
stream_ctx ctx;
run(ctx, N, NB);
// // Also run using a graph context.
// ctx = graph_ctx();
// run(ctx, N, NB);
}

View File

@@ -1,748 +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 implements a Cholesky decomposition over multiple devices using CUBLAS and CUSOLVER
*
* It also illustrates how we can use CUDASTF to allocate temporary data for CUSOLVER in CUDASTF tasks
*/
#include <cuda/experimental/__stf/utility/nvtx.cuh>
#include <cuda/experimental/stf.cuh>
#include <iostream>
#include <cublas_v2.h>
#define TILED
using namespace cuda::experimental::stf;
// Global for the sake of simplicity !
stream_ctx ctx;
/* Get a CUBLAS handle valid on the current execution place, or initialize it lazily */
cublasHandle_t& get_cublas_handle(const exec_place& ep = exec_place::current_device())
{
static std::unordered_map<exec_place, cublasHandle_t, hash<exec_place>> cublas_handles;
auto& result = cublas_handles[ep];
if (result == cublasHandle_t())
{ // not found, default value inserted
// Lazy initialization, and save the handle for future use
cuda_safe_call(cublasCreate(&result));
}
return result;
}
/* Get a CUSOLVER handle valid on the current execution place, or initialize it lazily */
cusolverDnHandle_t& get_cusolver_handle(const exec_place& ep = exec_place::current_device())
{
static std::unordered_map<exec_place, cusolverDnHandle_t, hash<exec_place>> cusolver_handles;
auto& result = cusolver_handles[ep];
if (result == cusolverDnHandle_t())
{ // not found, default value inserted
// Lazy initialization, and save the handle for future use
cuda_safe_call(cusolverDnCreate(&result));
}
return result;
}
template <typename T>
class matrix
{
public:
matrix(int NROWS, int NCOLS, int BLOCKSIZE_ROWS, int BLOCKSIZE_COLS, bool is_sym, const char* _symbol = "matrix")
{
symbol = _symbol;
sym_matrix = is_sym;
m = NROWS;
mb = BLOCKSIZE_ROWS;
n = NCOLS;
nb = BLOCKSIZE_COLS;
assert(m % mb == 0);
assert(n % nb == 0);
// cuda_safe_call(cudaMallocHost(&h_array, m*n*sizeof(T)));
// fprintf(stderr, "Allocating %ld x %ld x %ld = %ld bytes (%f GB) on host for %s\n", m, n, sizeof(T), s,
// s / (1024.0 * 1024.0 * 1024.0), _symbol);
h_array.resize(m * n);
cuda_safe_call(cudaHostRegister(&h_array[0], h_array.size() * sizeof(T), cudaHostRegisterPortable));
// Compute the number of blocks
mt = m / mb;
nt = n / nb;
handles.resize(mt * nt);
for (size_t colb = 0; colb < nt; colb++)
{
int low_rowb = sym_matrix ? colb : 0;
for (size_t rowb = low_rowb; rowb < mt; rowb++)
{
T* addr_h = get_block_h(rowb, colb);
auto& h = handle(rowb, colb);
#ifdef TILED
// tiles are stored contiguously
size_t ld = mb;
#else
size_t ld = m;
#endif
std::ignore = ld; // work around bug in compiler
h = ctx.logical_data(make_slice(addr_h, std::tuple{mb, nb}, ld));
h.set_symbol(std::string(symbol) + "_" + std::to_string(rowb) + "_" + std::to_string(colb));
h.set_write_back(false);
}
}
cuda_safe_call(cudaGetDeviceCount(&ndevs));
for (int a = 1; a * a <= ndevs; a++)
{
if (ndevs % a == 0)
{
grid_p = a;
grid_q = ndevs / a;
}
}
assert(grid_p * grid_q == ndevs);
// std::cout << "FOUND " << ndevs << " DEVICES "
// << "p=" << grid_p << " q=" << grid_q << '\n';
}
int get_preferred_devid(int row, int col)
{
return (row % grid_p) + (col % grid_q) * grid_p;
}
auto& handle(int row, int col)
{
return handles[row + col * mt];
}
size_t get_index(size_t row, size_t col)
{
#ifdef TILED
// Find which tile contains this element
int tile_row = row / mb;
int tile_col = col / nb;
size_t tile_size = mb * nb;
// Look for the index of the beginning of the tile
size_t tile_start = (tile_row + mt * tile_col) * tile_size;
// Offset within the tile
size_t offset = (row % mb) + (col % nb) * mb;
return tile_start + offset;
#else
return row + col * m;
#endif
}
T* get_block_h(int brow, int bcol)
{
size_t index = get_index(brow * mb, bcol * nb);
return &h_array[index];
}
// Fill with func(Matrix*,row, col)
template <typename Fun>
void fill(Fun&& fun)
{
nvtx_range r("fill");
// Fill blocks by blocks
for (size_t colb = 0; colb < nt; colb++)
{
size_t low_rowb = sym_matrix ? colb : 0;
for (size_t rowb = low_rowb; rowb < mt; rowb++)
{
// Each task fills a block
auto& h = handle(rowb, colb);
int devid = get_preferred_devid(rowb, colb);
ctx.parallel_for(exec_place::device(devid), h.shape(), h.write()).set_symbol("INIT")->*
[=] _CCCL_DEVICE(size_t lrow, size_t lcol, auto sA) {
size_t row = lrow + rowb * sA.extent(0);
size_t col = lcol + colb * sA.extent(1);
sA(lrow, lcol) = fun(row, col);
};
}
}
}
std::vector<T> h_array;
size_t m; // nrows
size_t n; // ncols
// Is this a sym matrix ? (lower assumed)
bool sym_matrix;
size_t mb; // block size (rows)
size_t nb; // block size (cols)
size_t mt; // number of column blocks
size_t nt; // number of row blocks
// abstract data handles
std::vector<logical_data<slice<double, 2>>> handles;
const char* symbol;
// for the mapping
int ndevs;
int grid_p, grid_q;
};
void DPOTRF(cublasFillMode_t uplo, class matrix<double>& A, int A_row, int A_col)
{
auto& Akk = A.handle(A_row, A_col);
size_t m_akk = Akk.shape().extent(0);
// Note that the handle may be different from the actual handle...
int Lwork_expected;
cuda_safe_call(cusolverDnDpotrf_bufferSize(get_cusolver_handle(), uplo, m_akk, nullptr, 0, &Lwork_expected));
auto potrf_buffer = ctx.logical_data<double>(size_t(Lwork_expected));
potrf_buffer.set_allocator(ctx.get_default_allocator());
auto devInfo = ctx.logical_data(shape_of<slice<int>>(1));
auto t =
ctx.task(exec_place::device(A.get_preferred_devid(A_row, A_col)), Akk.rw(), potrf_buffer.write(), devInfo.write());
t.set_symbol("DPOTRF");
t->*[uplo](cudaStream_t s, auto sAkk, auto buffer, auto info) {
auto& h = get_cusolver_handle();
cuda_safe_call(cusolverDnSetStream(h, s));
cuda_safe_call(cusolverDnDpotrf(
h,
uplo,
sAkk.extent(0),
sAkk.data_handle(),
sAkk.stride(1),
buffer.data_handle(),
buffer.extent(0),
info.data_handle()));
};
}
void DGEMM(
cublasOperation_t transa,
cublasOperation_t transb,
double alpha,
class matrix<double>& A,
int A_row,
int A_col,
class matrix<double>& B,
int B_row,
int B_col,
double beta,
class matrix<double>& C,
int C_row,
int C_col)
{
auto t = ctx.task(exec_place::device(A.get_preferred_devid(C_row, C_col)),
A.handle(A_row, A_col).read(),
B.handle(B_row, B_col).read(),
C.handle(C_row, C_col).rw());
t.set_symbol("DGEMM");
t->*[transa, transb, alpha, beta](cudaStream_t s, auto sA, auto sB, auto sC) {
auto& h = get_cublas_handle();
cuda_safe_call(cublasSetStream(h, s));
auto k = (transa == CUBLAS_OP_N) ? sA.extent(1) : sA.extent(0);
cuda_safe_call(cublasDgemm(
h,
transa,
transb,
sC.extent(0),
sC.extent(1),
k,
&alpha,
sA.data_handle(),
sA.stride(1),
sB.data_handle(),
sB.stride(1),
&beta,
sC.data_handle(),
sC.stride(1)));
};
}
void DSYRK(
cublasFillMode_t uplo,
cublasOperation_t trans,
double alpha,
class matrix<double>& A,
int A_row,
int A_col,
double beta,
class matrix<double>& C,
int C_row,
int C_col)
{
auto t = ctx.task(exec_place::device(A.get_preferred_devid(C_row, C_col)),
A.handle(A_row, A_col).read(),
C.handle(C_row, C_col).rw());
t.set_symbol("DSYRK");
t->*[uplo, trans, alpha, beta](cudaStream_t s, auto sA, auto sC) {
auto& h = get_cublas_handle();
cuda_safe_call(cublasSetStream(h, s));
// number of rows of matrix op(A) and C
auto n = sC.extent(0);
// number of columns of matrix op(A)
auto k = (trans == CUBLAS_OP_N) ? sA.extent(1) : sA.extent(0);
cuda_safe_call(
cublasDsyrk(h, uplo, trans, n, k, &alpha, sA.data_handle(), sA.stride(1), &beta, sC.data_handle(), sC.stride(1)));
};
}
void DTRSM(
cublasSideMode_t side,
cublasFillMode_t uplo,
cublasOperation_t transa,
cublasDiagType_t diag,
double alpha,
class matrix<double>& A,
int A_row,
int A_col,
class matrix<double>& B,
int B_row,
int B_col)
{
auto t = ctx.task(exec_place::device(A.get_preferred_devid(B_row, B_col)),
A.handle(A_row, A_col).read(),
B.handle(B_row, B_col).rw());
t.set_symbol("DTRSM");
t->*[side, uplo, transa, diag, alpha](cudaStream_t s, auto sA, auto sB) {
auto& h = get_cublas_handle();
cuda_safe_call(cublasSetStream(h, s));
cuda_safe_call(cublasDtrsm(
h,
side,
uplo,
transa,
diag,
sB.extent(0),
sB.extent(1),
&alpha,
sA.data_handle(),
sA.stride(1),
sB.data_handle(),
sB.stride(1)));
};
}
void PDNRM2_HOST(matrix<double>* A, double* result)
{
#ifdef HAVE_DOT
reserved::dot::set_current_color("red");
#endif
for (size_t rowb = 0; rowb < A->mt; rowb++)
{
for (size_t colb = 0; colb < A->nt; colb++)
{
ctx.host_launch(A->handle(rowb, colb).read())->*[=](auto sA) {
double res2 = 0.0;
for (size_t col = 0; col < sA.extent(1); col++)
{
for (size_t row = 0; row < sA.extent(0); row++)
{
double v = sA(row, col);
res2 += v * v;
}
}
*result += res2;
};
}
}
}
void PDPOTRF(matrix<double>& A)
{
auto guard = ctx.dot_section("PDPOTRF");
#ifdef HAVE_DOT
reserved::dot::set_current_color("yellow");
#endif
assert(A.m == A.n);
assert(A.mt == A.nt);
int NBLOCKS = A.mt;
assert(A.mb == A.nb);
cuda_safe_call(cudaSetDevice(0));
for (int K = 0; K < NBLOCKS; K++)
{
int dev_akk = A.get_preferred_devid(K, K);
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(K, K)));
DPOTRF(CUBLAS_FILL_MODE_LOWER, A, K, K);
for (int row = K + 1; row < NBLOCKS; row++)
{
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, K)));
DTRSM(CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_T, CUBLAS_DIAG_NON_UNIT, 1.0, A, K, K, A, row, K);
for (int col = K + 1; col < row; col++)
{
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, col)));
DGEMM(CUBLAS_OP_N, CUBLAS_OP_T, -1.0, A, row, K, A, col, K, 1.0, A, row, col);
}
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, row)));
DSYRK(CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_N, -1.0, A, row, K, 1.0, A, row, row);
}
}
cuda_safe_call(cudaSetDevice(0));
}
// Algorithm from PLASMA
void PDTRSM(cublasSideMode_t side,
cublasFillMode_t uplo,
cublasOperation_t trans,
cublasDiagType_t diag,
double alpha,
class matrix<double>& A,
class matrix<double>& B)
{
auto guard = ctx.dot_section("PDTRSM");
// std::cout << "[PDTRSM] START B MT " << B.mt << " NT " << B.nt << '\n';
if (side == CUBLAS_SIDE_LEFT)
{
if (uplo == CUBLAS_FILL_MODE_UPPER)
{
// TODO
assert(0);
abort();
}
else
{
//===========================================
// CUBLAS_SIDE_LEFT / CUBLAS_FILL_MODE_LOWER / CUBLAS_OP_N
//===========================================
if (trans == CUBLAS_OP_N)
{
for (size_t k = 0; k < B.mt; k++)
{
double lalpha = k == 0 ? alpha : 1.0;
for (size_t n = 0; n < B.nt; n++)
{
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(k, k)));
DTRSM(side, uplo, trans, diag, lalpha, A, k, k, B, k, n);
}
for (size_t m = k + 1; m < B.mt; m++)
{
for (size_t n = 0; n < B.nt; n++)
{
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(m, k)));
DGEMM(CUBLAS_OP_N, CUBLAS_OP_N, -1.0, A, m, k, B, k, n, lalpha, B, m, n);
}
}
}
}
//================================================
// CUBLAS_SIDE_LEFT / CUBLAS_FILL_MODE_LOWER / CUBLAS_OP_[C|T]
//================================================
else
{
for (size_t k = 0; k < B.mt; k++)
{
double lalpha = k == 0 ? alpha : 1.0;
for (size_t n = 0; n < B.nt; n++)
{
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(B.mt - k - 1, B.mt - k - 1)));
DTRSM(side, uplo, trans, diag, lalpha, A, B.mt - k - 1, B.mt - k - 1, B, B.mt - k - 1, n);
}
for (size_t m = k + 1; m < B.mt; m++)
{
for (size_t n = 0; n < B.nt; n++)
{
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(B.mt - k - 1, B.mt - 1 - m)));
DGEMM(
trans, CUBLAS_OP_N, -1.0, A, B.mt - k - 1, B.mt - 1 - m, B, B.mt - k - 1, n, lalpha, B, B.mt - 1 - m, n);
}
}
}
}
}
}
else
{
// TODO
abort();
}
cuda_safe_call(cudaSetDevice(0));
// std::cout << "[PDTRSM] END" << '\n';
}
void PDPOTRS(matrix<double>& A, class matrix<double>& B, cublasFillMode_t uplo)
{
auto guard = ctx.dot_section("PDPOTRS");
#ifdef HAVE_DOT
reserved::dot::set_current_color("green");
#endif
// std::cout << "[PDPOTRS] START" << '\n';
// Call the parallel functions.
PDTRSM(
CUBLAS_SIDE_LEFT, uplo, uplo == CUBLAS_FILL_MODE_UPPER ? CUBLAS_OP_T : CUBLAS_OP_N, CUBLAS_DIAG_NON_UNIT, 1.0, A, B);
#ifdef HAVE_DOT
reserved::dot::set_current_color("darkgreen");
#endif
PDTRSM(
CUBLAS_SIDE_LEFT, uplo, uplo == CUBLAS_FILL_MODE_UPPER ? CUBLAS_OP_N : CUBLAS_OP_T, CUBLAS_DIAG_NON_UNIT, 1.0, A, B);
// std::cout << "[PDPOTRS] END" << '\n';
}
/*****************************************************************************
* Parallel tile matrix-matrix
*multiplication.
* @see plasma_omp_dgemm
******************************************************************************/
void PDGEMM(cublasOperation_t transa,
cublasOperation_t transb,
double alpha,
class matrix<double>& A,
class matrix<double>& B,
double beta,
class matrix<double>& C)
{
auto guard = ctx.dot_section("PDGEMM");
#ifdef HAVE_DOT
reserved::dot::set_current_color("blue");
#endif
for (size_t m = 0; m < C.mt; m++)
{
for (size_t n = 0; n < C.nt; n++)
{
//=========================================
// alpha*A*B does not contribute; scale C
//=========================================
int inner_k = transa == CUBLAS_OP_N ? A.n : A.m;
if (alpha == 0.0 || inner_k == 0)
{
DGEMM(transa, transb, alpha, A, 0, 0, B, 0, 0, beta, C, m, n);
}
else if (transa == CUBLAS_OP_N)
{
//================================
// CUBLAS_OP_N / CUBLAS_OP_N
//================================
if (transb == CUBLAS_OP_N)
{
for (size_t k = 0; k < A.nt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(transa, transb, alpha, A, m, k, B, k, n, zbeta, C, m, n);
}
}
//=====================================
// CUBLAS_OP_N / CUBLAS_OP_T
//=====================================
else
{
for (size_t k = 0; k < A.nt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(transa, transb, alpha, A, m, k, B, n, k, zbeta, C, m, n);
}
}
}
else
{
//=====================================
// CUBLAS_OP_T / CUBLAS_OP_N
//=====================================
if (transb == CUBLAS_OP_N)
{
for (size_t k = 0; k < A.mt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(transa, transb, alpha, A, k, m, B, k, n, zbeta, C, m, n);
}
}
//==========================================
// CUBLAS_OP_T / CUBLAS_OP_T
//==========================================
else
{
for (size_t k = 0; k < A.mt; k++)
{
double zbeta = k == 0 ? beta : 1.0;
DGEMM(transa, transb, alpha, A, k, m, B, n, k, zbeta, C, m, n);
}
}
}
}
}
}
int main(int argc, char** argv)
{
int N = 1024;
int NB = 128;
if (argc > 1)
{
N = atoi(argv[1]);
}
if (argc > 2)
{
NB = atoi(argv[2]);
}
int check_result = 1;
if (getenv("CHECK_RESULT"))
{
check_result = atoi(getenv("CHECK_RESULT"));
}
assert(N % NB == 0);
// Use pools of preallocated blocks
auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, NB * NB * sizeof(double));
ctx.set_allocator(fixed_alloc);
// Set up CUBLAS and CUSOLVER
int ndevs;
cuda_safe_call(cudaGetDeviceCount(&ndevs));
for (int d = 0; d < ndevs; d++)
{
auto lX = ctx.logical_data(shape_of<slice<double>>(1));
ctx.parallel_for(exec_place::device(d), lX.shape(), lX.write())->*[] _CCCL_DEVICE(size_t, auto) {};
cuda_safe_call(cudaSetDevice(d));
get_cublas_handle();
get_cusolver_handle();
}
cuda_safe_call(cudaSetDevice(0));
matrix<double> A(N, N, NB, NB, true, "A");
matrix<double> Aref(N, N, NB, NB, false, "Aref");
// (Hilbert matrix + 2*N*Id) to have a diagonal dominant matrix
auto hilbert = [=] _CCCL_HOST_DEVICE(size_t row, size_t col) {
return 1.0 / (col + row + 1.0) + 2.0 * N * (col == row);
};
auto s = ctx.dot_section("fillA");
if (check_result)
{
Aref.fill(hilbert);
}
A.fill(hilbert);
s.end();
/* Right-hand side */
matrix<double> B_potrs(N, 1, NB, 1, false, "B");
matrix<double> Bref_potrs(N, 1, NB, 1, false, "Bref");
if (check_result)
{
auto rhs_vals = [] _CCCL_HOST_DEVICE(size_t row, size_t /*unused*/) {
return 1.0 * (row + 1);
};
B_potrs.fill(rhs_vals);
Bref_potrs.fill(rhs_vals);
}
// // Compute ||Bref||
double Bref_nrm2 = 0.0;
double res_nrm2 = 0.0;
if (check_result)
{
PDNRM2_HOST(&Bref_potrs, &Bref_nrm2);
}
cudaEvent_t startEvent_pdpotrf, stopEvent_pdpotrf;
float milliseconds_pdpotrf = 0;
// for (size_t row = 0; row < A.mt; row++)
// {
// for (size_t col = 0; col <= row; col++)
// {
// cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, col)));
// NOOP(A, row, col);
// }
// }
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
cuda_safe_call(cudaEventCreate(&startEvent_pdpotrf));
cuda_safe_call(cudaEventCreate(&stopEvent_pdpotrf));
cuda_safe_call(cudaEventRecord(startEvent_pdpotrf, ctx.fence()));
PDPOTRF(A);
cuda_safe_call(cudaEventRecord(stopEvent_pdpotrf, ctx.fence()));
/*
* POTRS
*/
if (check_result)
{
// Solve AX = B and put the result in B
PDPOTRS(A, B_potrs, CUBLAS_FILL_MODE_LOWER);
// Compute (AX - B)
// Bref = (Aref*B - Bref)
PDGEMM(CUBLAS_OP_N, CUBLAS_OP_N, 1.0, Aref, B_potrs, -1.0, Bref_potrs);
// Compute ||AX - B|| = ||Bref||
PDNRM2_HOST(&Bref_potrs, &res_nrm2);
}
ctx.finalize();
cuda_safe_call(cudaEventElapsedTime(&milliseconds_pdpotrf, startEvent_pdpotrf, stopEvent_pdpotrf));
double gflops_pdpotrf = 1.0 / 3.0 * ((double) N * (double) N * (double) N) / (1000000000.0);
std::cout << "[PDPOTRF] ELAPSED: " << milliseconds_pdpotrf
<< " ms, GFLOPS: " << gflops_pdpotrf / (milliseconds_pdpotrf / 1000.0) << '\n';
if (check_result)
{
if (double residual = sqrt(res_nrm2) / sqrt(Bref_nrm2); residual >= 0.01)
{
std::cerr << "[POTRS] ||AX - B|| : " << sqrt(res_nrm2) << '\n';
std::cerr << "[POTRS] ||B|| : " << sqrt(Bref_nrm2) << '\n';
std::cerr << "[POTRS] RESIDUAL (||AX - B||/||B||) : " << residual << '\n';
assert(!"Algorithm did not converge.");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,372 +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 Sparse conjugate gradient algorithm
*/
#include <cuda/experimental/stf.cuh>
#include <chrono>
#include <iostream>
#include <string>
#include <type_traits>
#include <vector>
#include "dot.cuh"
#include "newton_solver.cuh"
using namespace cuda::experimental::stf;
#if !_CCCL_CTK_BELOW(12, 4)
void build_full_csr_structure(size_t* row_offsets, size_t* col_indices, size_t N)
{
size_t nnz = 0;
row_offsets[0] = 0;
for (size_t row = 0; row < N; row++)
{
if (row == 0 || row == N - 1)
{
// Boundary rows: only diagonal entry (identity for BC: u[i] = prescribed_value)
col_indices[nnz++] = row;
}
else
{
// Interior rows: tridiagonal structure (left, center, right)
col_indices[nnz++] = row - 1; // left neighbor
col_indices[nnz++] = row; // center (diagonal)
col_indices[nnz++] = row + 1; // right neighbor
}
row_offsets[row + 1] = nnz;
}
}
template <typename ctx_t>
void assemble_jacobian_full(
ctx_t& ctx, vector_t<double> U, vector_t<double> values, size_t N, double h, double dt, double nu)
{
ctx.parallel_for(box(N), U.read(), values.write()).set_symbol("assemble_jacobian_full")
->*[N, h, dt, nu] __device__(size_t row, auto dU, auto dvalues) {
if (row == 0)
{
// Left boundary: u[0] = 0 (homogeneous Dirichlet)
// Jacobian row: [1, 0, 0, ..., 0]
size_t val_idx = 0; // First entry in CSR values array
dvalues[val_idx] = 1.0;
}
else if (row == N - 1)
{
// Right boundary: u[N-1] = 0 (homogeneous Dirichlet)
// Jacobian row: [0, ..., 0, 1]
size_t val_idx = 1 + 3 * (N - 2); // Last entry in CSR values array
dvalues[val_idx] = 1.0;
}
else
{
// Interior point: Burger's equation discretization
double u_i = dU[row];
double u_ip1 = dU[row + 1];
double u_im1 = dU[row - 1];
// Jacobian entries: ∂F_i/∂u_{i-1}, ∂F_i/∂u_i, ∂F_i/∂u_{i+1}
double left = -u_i / (2 * h) - nu / (h * h);
double center = 1.0 / dt + (u_ip1 - u_im1) / (2 * h) + 2.0 * nu / (h * h);
double right = u_i / (2 * h) - nu / (h * h);
// CSR indexing for interior row i: starts at 1 + 3*(i-1)
size_t val_idx = 1 + 3 * (row - 1);
dvalues[val_idx] = left; // ∂F_i/∂u_{i-1}
dvalues[val_idx + 1] = center; // ∂F_i/∂u_i
dvalues[val_idx + 2] = right; // ∂F_i/∂u_{i+1}
}
};
}
// residual: length N (full system including boundaries)
template <typename ctx_t, typename T>
void compute_residual_full(
ctx_t& ctx, vector_t<T> U, vector_t<T> U_prev, vector_t<T> residual, size_t N, double h, double dt, double nu)
{
ctx.parallel_for(box(N), residual.write(), U.read(), U_prev.read()).set_symbol("compute_residual_full")
->*[N, h, dt, nu] __device__(size_t i, auto dresidual, auto dU, auto dU_prev) {
if (i == 0)
{
// Left boundary condition: u[0] = 0
dresidual(i) = dU(i) - 0.0;
}
else if (i == N - 1)
{
// Right boundary condition: u[N-1] = 0
dresidual(i) = dU(i) - 0.0;
}
else
{
// Interior point: Burger's equation F_i = ∂u/∂t + u*∂u/∂x - nu*∂²u/∂x²
double u_i = dU(i);
double u_ip1 = dU(i + 1);
double u_im1 = dU(i - 1);
double term_time = (u_i - dU_prev(i)) / dt; // ∂u/∂t
double term_conv = u_i * (u_ip1 - u_im1) / (2 * h); // u * ∂u/∂x (nonlinear convection)
double term_diff = -nu * (u_im1 - 2 * u_i + u_ip1) / (h * h); // -nu * ∂²u/∂x²
dresidual(i) = term_time + term_conv + term_diff;
}
};
}
// Callback function objects for Burger's equation
struct BurgerResidualCallback
{
size_t N;
double h, dt, nu;
template <typename ctx_t>
void
operator()(ctx_t& ctx, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>& residual) const
{
compute_residual_full(ctx, x, x_prev, residual, N, h, dt, nu);
}
};
struct BurgerJacobianCallback
{
size_t N;
double h, dt, nu;
template <typename ctx_t>
void operator()(ctx_t& ctx, const vector_t<double>& x, vector_t<double>& jacobian_values) const
{
assemble_jacobian_full(ctx, x, jacobian_values, N, h, dt, nu);
}
};
// Initialize the solution output file (call once at simulation start)
void initialize_solution_file(const char* filename, size_t N, double h)
{
FILE* fp = fopen(filename, "w");
if (fp)
{
fprintf(fp, "# Burger equation solution - block format\n");
fprintf(fp, "# Each timestep is a separate block, separated by blank lines\n");
fprintf(fp, "# Format: x_coordinate u(x,t)\n");
fprintf(fp, "# Grid points: %zu, h=%.6e\n", N, h);
fprintf(fp,
"# Use in gnuplot: plot for [i=0:*] 'solution.dat' index i with lines title sprintf('step %%d', i*10)\n");
fprintf(fp, "#\n");
fclose(fp);
}
else
{
printf("Error: Could not create %s for writing\n", filename);
}
}
// Function to append timestep block to solution file (simple and reliable)
template <typename ctx_t>
void dump_solution(
ctx_t& ctx, vector_t<double>& U, size_t timestep, size_t N, double h, double dt, const char* filename = "solution.dat")
{
ctx.host_launch(U.read()).set_symbol("dump solution")->*[timestep, h, N, dt, filename](auto hU) {
FILE* fp = fopen(filename, "a"); // Simple append - no read/modify/write
if (fp)
{
fprintf(fp, "# Timestep %zu, t=%.6e\n", timestep, timestep * dt);
for (size_t i = 0; i < N; i++)
{
double x = i * h;
fprintf(fp, "%.10e %.10e\n", x, hU(i));
}
fprintf(fp, "\n"); // Blank line to separate datasets
fclose(fp);
printf("Appended timestep %zu (t=%.4e) to %s\n", timestep, timestep * dt, filename);
}
else
{
printf("Error: Could not open %s for appending\n", filename);
}
};
}
#endif
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
// Usage: ./burger [N] [nsteps] [nu]
// N = Grid points (default: 100000)
// nsteps = Time steps (default: 10000)
// nu = Viscosity (default: 0.05, try 0.001 for shocks)
stackable_ctx ctx;
size_t N = 2560;
if (argc > 1)
{
N = atoi(argv[1]);
fprintf(stderr, "N = %zu\n", N);
}
size_t nsteps = 200;
if (argc > 2)
{
nsteps = atol(argv[2]);
fprintf(stderr, "nsteps = %ld\n", nsteps);
}
// Set reasonable parameters - implicit method allows larger time steps
double nu = 0.05; // Default viscosity
if (argc > 3)
{
nu = atof(argv[3]);
fprintf(stderr, "nu = %e\n", nu);
}
ssize_t output_freq = -1;
if (argc > 4)
{
output_freq = atoi(argv[4]);
fprintf(stderr, "output_freq %ld\n", output_freq);
}
// use_while = 0 => no while; 1 => while in CG, 2 => while in Newton and CG
int use_while = 2;
if (argc > 5)
{
use_while = atoi(argv[5]);
fprintf(stderr, "use_while = %d\n", use_while);
}
double h = 1.0 / (N - 1);
double dt_diffusion = 0.5 * h * h / nu; // Diffusion-limited time step
double dt_fixed = 0.001; // Fixed reasonable time step
double dt = std::max(dt_diffusion, dt_fixed); // Use larger of the two
// For very fine grids, cap the time step to prevent tiny steps
if (N > 10000)
{
dt = std::min(dt, 0.01); // Cap at 0.01 for large grids
}
double total_time = nsteps * dt;
fprintf(stderr, "=== Simulation Parameters ===\n");
fprintf(stderr, "Grid: N=%zu, h=%e\n", N, h);
fprintf(stderr, "Time: dt=%e, nsteps=%zu, total_time=%e\n", dt, nsteps, total_time);
fprintf(stderr, "Physics: nu=%e (viscosity)\n", nu);
fprintf(stderr, "Diffusion number: nu*dt/h^2 = %e\n", nu * dt / (h * h));
fprintf(stderr, "=============================\n");
// Full N×N system: boundary rows have 1 entry each, interior rows have 3 entries each
// Total: 2*1 + (N-2)*3 = 3*N - 4 non-zeros
size_t nz = 3 * N - 4;
size_t* row_offsets;
size_t* col_indices;
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
cuda_safe_call(cudaHostAlloc(&col_indices, nz * sizeof(size_t), cudaHostAllocMapped));
build_full_csr_structure(row_offsets, col_indices, N);
auto csr_row_offsets = ctx.logical_data(make_slice(row_offsets, N + 1)).set_symbol("csr_row");
auto csr_col_ind = ctx.logical_data(make_slice(col_indices, nz)).set_symbol("csr_col");
auto csr_values = ctx.logical_data(shape_of<slice<double>>(nz)).set_symbol("csr_val");
auto U = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("U");
// This will prevent erroneous modifications and may allow access from concurrent graphs
csr_row_offsets.set_read_only();
csr_col_ind.set_read_only();
// Initial condition
ctx.parallel_for(U.shape(), U.write()).set_symbol("init conditions")->*[h, N] __device__(size_t i, auto dU) {
double x = i * h;
if (i == 0 || i == N - 1)
{
dU(i) = 0.0; // Homogeneous Dirichlet boundary conditions
}
else
{
dU(i) = sin(M_PI * x);
}
};
// Initialize solution output file
initialize_solution_file("solution.dat", N, h);
auto start = std::chrono::high_resolution_clock::now();
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
// Parameters are now set above with auto-scaling
size_t substeps = (output_freq > 0) ? output_freq : nsteps;
size_t outer_iterations = nsteps / substeps;
if (use_while == 2)
{
for (size_t outer = 0; outer < outer_iterations; outer++)
{
auto g = ctx.graph_scope();
// Repeat substeps inner iterations using STF repeat block
{
auto repeat_guard = ctx.repeat_graph_scope(substeps);
// Create callback function objects for Burger's equation
BurgerResidualCallback residual_callback{N, h, dt, nu};
BurgerJacobianCallback jacobian_callback{N, h, dt, nu};
// Solve the nonlinear system using generic Newton solver
newton_solver(ctx, U, csr_values, csr_row_offsets, csr_col_ind, residual_callback, jacobian_callback);
} // repeat_guard automatically manages the loop condition
// Dump solution after each substep block
size_t current_timestep = (outer + 1) * substeps;
dump_solution(ctx, U, current_timestep, N, h, dt);
}
}
else
{
for (size_t outer = 0; outer < outer_iterations; outer++)
{
// Repeat substeps inner iterations using STF repeat block
for (size_t substep = 0; substep < substeps; substep++)
{
// Create callback function objects for Burger's equation
BurgerResidualCallback residual_callback{N, h, dt, nu};
BurgerJacobianCallback jacobian_callback{N, h, dt, nu};
// Solve the nonlinear system using generic Newton solver
newton_solver_no_while(
ctx, U, csr_values, csr_row_offsets, csr_col_ind, residual_callback, jacobian_callback, use_while == 1);
} // repeat_guard automatically manages the loop condition
// Dump solution after each substep block
size_t current_timestep = (outer + 1) * substeps;
dump_solution(ctx, U, current_timestep, N, h, dt);
}
}
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "Duration: " << duration << " milliseconds" << '\n';
ctx.finalize();
#endif
}

View File

@@ -1,479 +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 Sensitivity analysis for Burger equation - analyze shock formation vs viscosity
*/
#include <cuda/experimental/stf.cuh>
#include <algorithm>
#include <random>
#include <string>
#include <type_traits>
#include <vector>
#include "cg_solver.cuh"
#include "dot.cuh"
#include "newton_solver.cuh"
using namespace cuda::experimental::stf;
#if !_CCCL_CTK_BELOW(12, 4)
void build_tridiagonal_csr_structure(size_t* row_offsets, size_t* col_indices, size_t N)
{
size_t nnz = 0;
row_offsets[0] = 0;
for (size_t row = 0; row < N; row++)
{
if (row == 0 || row == N - 1)
{
// Boundary rows: only diagonal entry (identity for BC: u[i] = prescribed_value)
col_indices[nnz++] = row;
}
else
{
// Interior rows: tridiagonal structure (left, center, right)
col_indices[nnz++] = row - 1; // left neighbor
col_indices[nnz++] = row; // center (diagonal)
col_indices[nnz++] = row + 1; // right neighbor
}
row_offsets[row + 1] = nnz;
}
}
template <typename ctx_t>
void assemble_jacobian_full(
ctx_t& ctx, vector_t<double> U, vector_t<double> values, size_t N, double h, double dt, double nu)
{
ctx.parallel_for(box(N), U.read(), values.write()).set_symbol("assemble_jacobian_full")
->*[N, h, dt, nu] __device__(size_t row, auto dU, auto dvalues) {
if (row == 0)
{
// Left boundary: u[0] = 0 (homogeneous Dirichlet)
size_t val_idx = 0;
dvalues[val_idx] = 1.0;
}
else if (row == N - 1)
{
// Right boundary: u[N-1] = 0 (homogeneous Dirichlet)
size_t val_idx = 1 + 3 * (N - 2);
dvalues[val_idx] = 1.0;
}
else
{
// Interior point: Burger's equation discretization
double u_i = dU[row];
double u_ip1 = dU[row + 1];
double u_im1 = dU[row - 1];
// Jacobian entries: ∂F_i/∂u_{i-1}, ∂F_i/∂u_i, ∂F_i/∂u_{i+1}
double left = -u_i / (2 * h) - nu / (h * h);
double center = 1.0 / dt + (u_ip1 - u_im1) / (2 * h) + 2.0 * nu / (h * h);
double right = u_i / (2 * h) - nu / (h * h);
size_t val_idx = 1 + 3 * (row - 1);
dvalues[val_idx] = left;
dvalues[val_idx + 1] = center;
dvalues[val_idx + 2] = right;
}
};
}
template <typename ctx_t, typename T>
void compute_residual_full(
ctx_t& ctx, vector_t<T> U, vector_t<T> U_prev, vector_t<T> residual, size_t N, double h, double dt, double nu)
{
ctx.parallel_for(box(N), residual.write(), U.read(), U_prev.read()).set_symbol("compute_residual_full")
->*[N, h, dt, nu] __device__(size_t i, auto dresidual, auto dU, auto dU_prev) {
if (i == 0)
{
dresidual(i) = dU(i) - 0.0;
}
else if (i == N - 1)
{
dresidual(i) = dU(i) - 0.0;
}
else
{
// Interior point: Burger's equation F_i = ∂u/∂t + u*∂u/∂x - nu*∂²u/∂x²
double u_i = dU(i);
double u_ip1 = dU(i + 1);
double u_im1 = dU(i - 1);
double term_time = (u_i - dU_prev(i)) / dt;
double term_conv = u_i * (u_ip1 - u_im1) / (2 * h);
double term_diff = -nu * (u_im1 - 2 * u_i + u_ip1) / (h * h);
dresidual(i) = term_time + term_conv + term_diff;
}
};
}
// Shock detection: compute maximum gradient magnitude
template <typename ctx_t>
void detect_shock(
ctx_t& ctx, vector_t<double>& U, stackable_logical_data<scalar_view<double>>& max_gradient, size_t N, double h)
{
ctx.parallel_for(box(N - 1), U.read(), max_gradient.reduce(reducer::maxval<double>{})).set_symbol("detect_shock")
->*[h] __device__(size_t i, auto dU, double& dmax_grad) {
double gradient = fabs(dU(i + 1) - dU(i)) / h;
dmax_grad = fmax(dmax_grad, gradient);
};
}
// Callback function objects for Burger's equation
struct BurgerResidualCallback
{
size_t N;
double h, dt, nu;
template <typename ctx_t>
void
operator()(ctx_t& ctx, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>& residual) const
{
compute_residual_full(ctx, x, x_prev, residual, N, h, dt, nu);
}
};
struct BurgerJacobianCallback
{
size_t N;
double h, dt, nu;
template <typename ctx_t>
void operator()(ctx_t& ctx, const vector_t<double>& x, vector_t<double>& jacobian_values) const
{
assemble_jacobian_full(ctx, x, jacobian_values, N, h, dt, nu);
}
};
// Generate nu values around target with given distribution
std::vector<double> generate_nu_samples(double nu_target, double nu_std, size_t num_samples)
{
std::vector<double> nu_values;
std::random_device rd;
std::mt19937 gen(rd());
std::normal_distribution<double> dist(nu_target, nu_std);
// Generate samples and ensure they are positive
for (size_t i = 0; i < num_samples; ++i)
{
double nu_sample = dist(gen);
// Ensure nu > 0 for physical validity
if (nu_sample > 1e-6)
{
nu_values.push_back(nu_sample);
}
else
{
// Retry if we get non-physical values
i--;
}
}
// Sort for better output organization
std::sort(nu_values.begin(), nu_values.end());
return nu_values;
}
// Initialize sensitivity analysis output file
void initialize_sensitivity_file(const char* filename, double nu_target, double nu_std, size_t num_samples)
{
FILE* fp = fopen(filename, "w");
if (fp)
{
fprintf(fp, "# Burger equation sensitivity analysis\n");
fprintf(fp, "# Target nu: %.6e, std: %.6e, samples: %zu\n", nu_target, nu_std, num_samples);
fprintf(fp, "# Format: nu_value shock_time max_gradient final_time\n");
fprintf(fp, "# shock_time: time when max gradient exceeds threshold (or -1 if no shock)\n");
fprintf(fp, "# max_gradient: maximum gradient achieved\n");
fprintf(fp, "# final_time: total simulation time reached\n");
fprintf(fp, "#\n");
fclose(fp);
}
}
// Initialize shock solutions output file
void initialize_shock_file(const char* filename, double shock_threshold)
{
FILE* fp = fopen(filename, "w");
if (fp)
{
fprintf(fp, "# Burger equation shock solutions\n");
fprintf(fp, "# Solutions dumped when gradient exceeds threshold: %.1f\n", shock_threshold);
fprintf(fp, "# Each shock is a separate data block, separated by blank lines\n");
fprintf(fp, "# Block header: Sample ID, nu value, shock time, max gradient\n");
fprintf(fp, "# Block format: x_coordinate u(x,t_shock)\n");
fprintf(fp, "#\n");
fprintf(fp,
"# Use in gnuplot: plot for [i=0:*] 'shock_solutions.dat' index i with lines title sprintf('Sample %%d', "
"i+1)\n");
fprintf(fp, "#\n");
fclose(fp);
printf("Initialized shock solutions file: %s\n", filename);
}
}
// Dump solution when shock is detected
template <typename ctx_t>
void dump_shock_solution(
ctx_t& ctx,
vector_t<double>& U,
double nu,
double shock_time,
double max_gradient,
size_t sample_id,
size_t N,
double h,
const char* filename = "shock_solutions.dat")
{
ctx.host_launch(U.read()).set_symbol("dump shock solution")
->*
[nu, shock_time, max_gradient, sample_id, h, N, filename](auto hU) {
FILE* fp = fopen(filename, "a"); // Append to file
if (fp)
{
fprintf(
fp, "# Sample %zu: nu=%.6e, shock_time=%.6e, max_gradient=%.2f\n", sample_id, nu, shock_time, max_gradient);
fprintf(fp, "# Format: x_coordinate u(x,t_shock)\n");
for (size_t i = 0; i < N; i++)
{
double x = i * h;
fprintf(fp, "%.10e %.10e\n", x, hU(i));
}
fprintf(fp, "\n"); // Blank line to separate datasets
fclose(fp);
printf(" -> Solution saved to %s", filename);
}
else
{
printf(" -> Error: Could not save solution to %s", filename);
}
};
}
template <typename ctx_t>
void run_single_nu_simulation(
ctx_t& ctx,
double nu,
vector_t<double>& U,
vector_t<double>& csr_values,
const vector_t<size_t>& csr_row_offsets,
const vector_t<size_t>& csr_col_ind,
size_t N,
double h,
double dt,
double max_time,
double shock_threshold,
size_t sample_id,
double& shock_time,
double& max_gradient,
double& final_time)
{
// Reset solution to initial condition
ctx.parallel_for(U.shape(), U.write()).set_symbol("reset_initial_condition")->*[h, N] __device__(size_t i, auto dU) {
double x = i * h;
dU(i) = (i == 0 || i == N - 1) ? 0.0 : sin(M_PI * x);
};
auto current_time = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("current_time");
auto max_grad_global = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("max_grad_global");
auto shock_detected = ctx.logical_data(shape_of<scalar_view<int>>()).set_symbol("shock_detected");
// Initialize tracking variables
ctx.parallel_for(box(1), current_time.write(), max_grad_global.write(), shock_detected.write())
.set_symbol("init_tracking")
->*[] __device__(size_t i, auto dtime, auto dmax_grad, auto dshock) {
*dtime = 0.0;
*dmax_grad = 0.0;
*dshock = 0; // 0 = no shock, 1 = shock detected
};
// Time evolution loop with shock detection
{
auto while_guard = ctx.while_graph_scope();
// Create callback function objects
BurgerResidualCallback residual_callback{N, h, dt, nu};
BurgerJacobianCallback jacobian_callback{N, h, dt, nu};
// Solve the nonlinear system
newton_solver(ctx, U, csr_values, csr_row_offsets, csr_col_ind, residual_callback, jacobian_callback);
// Update time
ctx.parallel_for(box(1), current_time.rw()).set_symbol("update_time")->*[dt] __device__(size_t i, auto dtime) {
*dtime += dt;
};
// Detect shock by computing maximum gradient
auto current_grad = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("current_grad");
detect_shock(ctx, U, current_grad, N, h);
// Update global maximum gradient and check for shock
ctx.parallel_for(box(1), max_grad_global.rw(), current_grad.read(), shock_detected.rw())
.set_symbol("update_shock_detection")
->*[shock_threshold] __device__(size_t i, auto dmax_grad, auto dcurrent_grad, auto dshock) {
double grad = *dcurrent_grad;
if (grad > *dmax_grad)
{
*dmax_grad = grad;
}
if (grad > shock_threshold && *dshock == 0)
{
*dshock = 1; // First time shock threshold is exceeded
}
};
// Continue while time < max_time and no shock detected
while_guard.update_cond(current_time.read(), shock_detected.read())->*[max_time] __device__(auto dtime, auto dshock) {
return (*dtime < max_time) && (*dshock == 0);
};
}
// Extract results to host variables
ctx.host_launch(current_time.read(), max_grad_global.read(), shock_detected.read()).set_symbol("extract_results")
->*[&shock_time, &max_gradient, &final_time](auto htime, auto hmax_grad, auto hshock) {
final_time = *htime;
max_gradient = *hmax_grad;
shock_time = (*hshock == 1) ? *htime : -1.0; // -1 indicates no shock
if (shock_time > 0)
{
printf("shock at t=%.4f, max_grad=%.1f\n", shock_time, max_gradient);
}
else
{
printf("no shock, max_grad=%.1f\n", max_gradient);
}
};
// Dump solution if shock was detected
if (shock_time > 0)
{
dump_shock_solution(ctx, U, nu, shock_time, max_gradient, sample_id, N, h);
}
}
#endif
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
// Usage: ./burger_sensitivity [N] [nu_target] [nu_std] [num_samples] [shock_threshold]
size_t N = 1000; // Smaller grid for sensitivity analysis
double nu_target = 0.02; // Target viscosity
double nu_std = 0.01; // Standard deviation for nu distribution
size_t num_samples = 20; // Number of nu samples to test
double shock_threshold = 15.0; // Gradient threshold to detect shock (du/dx magnitude)
if (argc > 1)
{
N = atoi(argv[1]);
}
if (argc > 2)
{
nu_target = atof(argv[2]);
}
if (argc > 3)
{
nu_std = atof(argv[3]);
}
if (argc > 4)
{
num_samples = atoi(argv[4]);
}
if (argc > 5)
{
shock_threshold = atof(argv[5]);
}
double h = 1.0 / (N - 1);
double dt = 0.001; // Fixed time step
double max_time = 2.0; // Maximum simulation time per sample
fprintf(stderr, "=== Sensitivity Analysis Parameters ===\n");
fprintf(stderr, "Grid: N=%zu, h=%e\n", N, h);
fprintf(stderr, "Viscosity: target=%e, std=%e, samples=%zu\n", nu_target, nu_std, num_samples);
fprintf(stderr, "Time: dt=%e, max_time=%e\n", dt, max_time);
fprintf(stderr, "Shock threshold: %.1f (gradient magnitude)\n", shock_threshold);
fprintf(stderr, "======================================\n");
stackable_ctx ctx;
// Generate nu samples
auto nu_values = generate_nu_samples(nu_target, nu_std, num_samples);
// Set up CSR structure
size_t nz = 3 * N - 4;
size_t* row_offsets;
size_t* col_indices;
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
cuda_safe_call(cudaHostAlloc(&col_indices, nz * sizeof(size_t), cudaHostAllocMapped));
build_tridiagonal_csr_structure(row_offsets, col_indices, N);
auto csr_row_offsets = ctx.logical_data(make_slice(row_offsets, N + 1)).set_symbol("csr_row");
auto csr_col_ind = ctx.logical_data(make_slice(col_indices, nz)).set_symbol("csr_col");
csr_row_offsets.set_read_only();
csr_col_ind.set_read_only();
// Initialize output files
initialize_sensitivity_file("sensitivity_results.dat", nu_target, nu_std, num_samples);
initialize_shock_file("shock_solutions.dat", shock_threshold);
// Run sensitivity analysis
printf("Running sensitivity analysis with %zu samples...\n", num_samples);
{
auto g = ctx.graph_scope();
for (size_t i = 0; i < nu_values.size(); ++i)
{
double nu = nu_values[i];
double shock_time, max_gradient, final_time;
printf("Sample %zu/%zu: nu=%.6e... ", i + 1, nu_values.size(), nu);
fflush(stdout);
auto csr_values = ctx.logical_data(shape_of<slice<double>>(nz)).set_symbol("csr_val");
auto U = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("U");
run_single_nu_simulation(
ctx,
nu,
U,
csr_values,
csr_row_offsets,
csr_col_ind,
N,
h,
dt,
max_time,
shock_threshold,
i + 1,
shock_time,
max_gradient,
final_time);
}
}
ctx.finalize();
#endif
}

View File

@@ -1,232 +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 Sparse conjugate gradient algorithm
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
using vector_t = logical_data<slice<double>>;
using scalar_t = logical_data<scalar_view<double>>;
using context_t = context;
struct csr_matrix
{
csr_matrix(
context_t& ctx, size_t num_rows, size_t num_nonzeros, double* values, size_t* row_offsets, size_t* column_indices)
{
val_handle = ctx.logical_data(make_slice(values, num_nonzeros));
col_handle = ctx.logical_data(make_slice(column_indices, num_nonzeros));
row_handle = ctx.logical_data(make_slice(row_offsets, num_rows + 1));
}
/* Description of the CSR */
mutable logical_data<slice<double>> val_handle;
mutable logical_data<slice<size_t>> row_handle;
mutable logical_data<slice<size_t>> col_handle;
};
// Note that a and b might be the same logical data
void DOT(context_t& ctx, vector_t& a, vector_t& b, scalar_t& res)
{
ctx.parallel_for(a.shape(), a.read(), b.read(), res.reduce(reducer::sum<double>{}))
->*[] __device__(size_t i, auto da, auto db, double& dres) {
dres += da(i) * db(i);
};
};
void SPMV(context_t& ctx, csr_matrix& a, vector_t& x, vector_t& y)
{
ctx.parallel_for(y.shape(), a.val_handle.read(), a.col_handle.read(), a.row_handle.read(), x.read(), y.write())
->*[] _CCCL_DEVICE(size_t row, auto da_val, auto da_col, auto da_row, auto dx, auto dy) {
int row_start = da_row(row);
int row_end = da_row(row + 1);
double sum = 0.0;
for (int elt = row_start; elt < row_end; elt++)
{
sum += da_val(elt) * dx(da_col(elt));
}
dy(row) = sum;
};
}
/* genTridiag: generate a random tridiagonal symmetric matrix
from :
https://github.com/NVIDIA/cuda-samples/blob/master/Samples/4_CUDA_Libraries/conjugateGradientCudaGraphs/conjugateGradientCudaGraphs.cu
*/
void genTridiag(size_t* I, size_t* J, double* val, size_t N, size_t nz)
{
const double d = 2.0;
I[0] = 0, J[0] = 0, J[1] = 1;
val[0] = drand48() + d;
val[1] = drand48();
int start;
for (size_t i = 1; i < N; i++)
{
if (i > 1)
{
I[i] = I[i - 1] + 3;
}
else
{
I[1] = 2;
}
start = (i - 1) * 3 + 2;
J[start] = i - 1;
J[start + 1] = i;
if (i < N - 1)
{
J[start + 2] = i + 1;
}
val[start] = val[start - 1];
val[start + 1] = drand48() + d;
if (i < N - 1)
{
val[start + 2] = drand48();
}
}
I[N] = nz;
}
void cg_solver(context_t& ctx, csr_matrix& A, vector_t& X, vector_t& B)
{
// Initial guess X = 1
ctx.parallel_for(X.shape(), X.write())->*[] _CCCL_DEVICE(size_t i, auto dX) {
dX(i) = 1.0;
};
// Residual R initialized to B
auto R = ctx.logical_data(B.shape());
ctx.parallel_for(R.shape(), R.write(), B.read())->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
dR(i) = dB(i);
};
// R = R - A*X
auto Ax = ctx.logical_data(X.shape());
SPMV(ctx, A, X, Ax);
ctx.parallel_for(R.shape(), R.rw(), Ax.read())->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
dR(i) -= dAx(i);
};
// P = R;
auto P = ctx.logical_data(R.shape());
ctx.parallel_for(P.shape(), P.write(), R.read())->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
dP(i) = dR(i);
};
// RSOLD = R'*R
auto rsold = ctx.logical_data(shape_of<scalar_view<double>>());
DOT(ctx, R, R, rsold);
const int MAXITER = X.shape().size();
for (int k = 0; k < MAXITER; k++)
{
// Ap = A*P
auto Ap = ctx.logical_data(P.shape());
SPMV(ctx, A, P, Ap);
// We don't compute alpha explicitly
// alpha = rsold / (p' * Ap);
auto pAp = ctx.logical_data(shape_of<scalar_view<double>>());
DOT(ctx, P, Ap, pAp);
// x = x + alpha * p;
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read())
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
double alpha = (*drsold / *dpAp);
dX(i) += alpha * dP(i);
};
// r = r - alpha * Ap;
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read())
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
double alpha = (*drsold / *dpAp);
dR(i) -= alpha * dAp(i);
};
// rsnew = r' * r;
auto rsnew = ctx.logical_data(shape_of<scalar_view<double>>());
DOT(ctx, R, R, rsnew);
// Read the residual on the CPU, and halt the iterative process if we have converged
// (note that this will block the submission of tasks)
double err = ctx.wait(rsnew);
fprintf(stderr, "iter %d : residual %e\n", k, err);
if (err < 1e-10)
{
// We have converged
fprintf(stderr, "Successfully converged (err = %le)\n", err);
break;
}
// p = r + (rsnew / rsold) * p;
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read())
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
};
// update old residual
ctx.parallel_for(box(1), rsold.write(), rsnew.read())->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
*drsold = *drsnew;
};
}
}
int main(int argc, char** argv)
{
size_t N = 10485760;
context_t ctx;
if (argc > 1)
{
N = atoi(argv[1]);
fprintf(stderr, "N = %zu\n", N);
}
size_t nz = (N - 2) * 3 + 4;
size_t* row_offsets;
size_t* column_indices;
double* values;
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
cuda_safe_call(cudaHostAlloc(&column_indices, nz * sizeof(size_t), cudaHostAllocMapped));
cuda_safe_call(cudaHostAlloc(&values, nz * sizeof(double), cudaHostAllocMapped));
// Generate a random matrix that is supposed to be invertible
genTridiag(row_offsets, column_indices, values, N, nz);
csr_matrix A(ctx, N, nz, values, row_offsets, column_indices);
auto X = ctx.logical_data(shape_of<slice<double>>(N));
auto B = ctx.logical_data(shape_of<slice<double>>(N));
// RHS
ctx.parallel_for(B.shape(), B.write())->*[] __device__(size_t i, auto dB) {
dB(i) = 1.0;
};
cg_solver(ctx, A, X, B);
ctx.finalize();
}

View File

@@ -1,240 +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 Sparse conjugate gradient algorithm
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
#if !_CCCL_CTK_BELOW(12, 4)
using vector_t = stackable_logical_data<slice<double>>;
using scalar_t = stackable_logical_data<scalar_view<double>>;
using context_t = stackable_ctx;
struct csr_matrix
{
csr_matrix(
context_t& ctx, size_t num_rows, size_t num_nonzeros, double* values, size_t* row_offsets, size_t* column_indices)
{
val_handle = ctx.logical_data(make_slice(values, num_nonzeros));
col_handle = ctx.logical_data(make_slice(column_indices, num_nonzeros));
row_handle = ctx.logical_data(make_slice(row_offsets, num_rows + 1));
val_handle.set_symbol("csr_val");
col_handle.set_symbol("csr_col");
row_handle.set_symbol("csr_row");
}
/* Description of the CSR */
mutable stackable_logical_data<slice<double>> val_handle;
mutable stackable_logical_data<slice<size_t>> row_handle;
mutable stackable_logical_data<slice<size_t>> col_handle;
};
// Note that a and b might be the same logical data
void DOT(context_t& ctx, vector_t& a, vector_t& b, scalar_t& res)
{
ctx.parallel_for(a.shape(), a.read(), b.read(), res.reduce(reducer::sum<double>{})).set_symbol("DOT")->*
[] __device__(size_t i, auto da, auto db, double& dres) {
dres += da(i) * db(i);
};
};
void SPMV(context_t& ctx, csr_matrix& a, vector_t& x, vector_t& y)
{
ctx.parallel_for(y.shape(), a.val_handle.read(), a.col_handle.read(), a.row_handle.read(), x.read(), y.write())
.set_symbol("SPMV")
->*[] _CCCL_DEVICE(size_t row, auto da_val, auto da_col, auto da_row, auto dx, auto dy) {
int row_start = da_row(row);
int row_end = da_row(row + 1);
double sum = 0.0;
for (int elt = row_start; elt < row_end; elt++)
{
sum += da_val(elt) * dx(da_col(elt));
}
dy(row) = sum;
};
}
/* genTridiag: generate a random tridiagonal symmetric matrix
from :
https://github.com/NVIDIA/cuda-samples/blob/master/Samples/4_CUDA_Libraries/conjugateGradientCudaGraphs/conjugateGradientCudaGraphs.cu
*/
void genTridiag(size_t* I, size_t* J, double* val, size_t N, size_t nz)
{
const double d = 2.0;
I[0] = 0, J[0] = 0, J[1] = 1;
val[0] = drand48() + d;
val[1] = drand48();
int start;
for (size_t i = 1; i < N; i++)
{
if (i > 1)
{
I[i] = I[i - 1] + 3;
}
else
{
I[1] = 2;
}
start = (i - 1) * 3 + 2;
J[start] = i - 1;
J[start + 1] = i;
if (i < N - 1)
{
J[start + 2] = i + 1;
}
val[start] = val[start - 1];
val[start + 1] = drand48() + d;
if (i < N - 1)
{
val[start + 2] = drand48();
}
}
I[N] = nz;
}
void cg_solver(context_t& ctx, csr_matrix& A, vector_t& X, vector_t& B)
{
// Initial guess X = 1
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
dX(i) = 1.0;
};
// Residual R initialized to B
auto R = ctx.logical_data(B.shape());
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
dR(i) = dB(i);
};
// R = R - A*X
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
SPMV(ctx, A, X, Ax);
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
dR(i) -= dAx(i);
};
// P = R;
auto P = ctx.logical_data(R.shape()).set_symbol("P");
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
dP(i) = dR(i);
};
// RSOLD = R'*R
auto rsold = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("rsold");
DOT(ctx, R, R, rsold);
{
auto while_guard = ctx.while_graph_scope();
// Ap = A*P
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
SPMV(ctx, A, P, Ap);
// We don't compute alpha explicitly
// alpha = rsold / (p' * Ap);
auto pAp = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("pAp");
DOT(ctx, P, Ap, pAp);
// x = x + alpha * p;
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
double alpha = (*drsold / *dpAp);
dX(i) += alpha * dP(i);
};
// r = r - alpha * Ap;
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
double alpha = (*drsold / *dpAp);
dR(i) -= alpha * dAp(i);
};
// rsnew = r' * r;
auto rsnew = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("rsnew");
DOT(ctx, R, R, rsnew);
while_guard.update_cond(rsnew.read())->*[] __device__(auto drsnew) {
printf("RES %e\n", *drsnew);
bool converged = (*drsnew < 1e-13);
return !converged;
};
// p = r + (rsnew / rsold) * p;
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
};
// update old residual
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
*drsold = *drsnew;
};
}
}
#endif
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
return 0;
#else
size_t N = 10485760;
context_t ctx;
if (argc > 1)
{
N = atoi(argv[1]);
fprintf(stderr, "N = %zu\n", N);
}
size_t nz = (N - 2) * 3 + 4;
size_t* row_offsets;
size_t* column_indices;
double* values;
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
cuda_safe_call(cudaHostAlloc(&column_indices, nz * sizeof(size_t), cudaHostAllocMapped));
cuda_safe_call(cudaHostAlloc(&values, nz * sizeof(double), cudaHostAllocMapped));
// Generate a random matrix that is supposed to be invertible
genTridiag(row_offsets, column_indices, values, N, nz);
csr_matrix A(ctx, N, nz, values, row_offsets, column_indices);
auto X = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("X");
auto B = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("B");
// RHS
ctx.parallel_for(B.shape(), B.write()).set_symbol("B assembly")->*[] __device__(size_t i, auto dB) {
dB(i) = 1.0;
};
cg_solver(ctx, A, X, B);
ctx.finalize();
#endif
}

View File

@@ -1,462 +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 Conjugate gradient for a tiled dense matrix
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
static cublasHandle_t cublas_handle;
stream_ctx ctx;
class matrix
{
public:
matrix(size_t N)
: N(N)
{
h_addr.reset(new double[N * N]);
cuda_safe_call(cudaHostRegister(h_addr.get(), N * N * sizeof(double), cudaHostRegisterPortable));
handle = ::std::make_shared<logical_data<slice<double, 2>>>(
ctx.logical_data(make_slice(h_addr.get(), std::tuple{N, N}, N)));
}
void fill(const std::function<double(int, int)>& f)
{
ctx.task(exec_place::host(), handle->write())->*[&f](cudaStream_t stream, auto ds) {
cuda_safe_call(cudaStreamSynchronize(stream));
for (size_t col = 0; col < ds.extent(1); col++)
{
for (size_t row = 0; row < ds.extent(0); row++)
{
ds(row, col) = f(row, col);
}
}
};
}
size_t N;
std::unique_ptr<double[]> h_addr;
std::shared_ptr<logical_data<slice<double, 2>>> handle;
};
class vector
{
public:
vector(size_t N, size_t _block_size, bool is_tmp = false)
: N(N)
, block_size(_block_size)
, nblocks((N + block_size - 1) / block_size)
{
handles.resize(nblocks);
if (is_tmp)
{
// There is no physical backing for this temporary vector
for (size_t b = 0; b < nblocks; b++)
{
size_t bs = std::min(N - block_size * b, block_size);
handles[b] = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(bs)));
}
}
else
{
h_addr.reset(new double[N]);
cuda_safe_call(cudaHostRegister(h_addr.get(), N * sizeof(double), cudaHostRegisterPortable));
for (size_t b = 0; b < nblocks; b++)
{
size_t bs = std::min(N - block_size * b, block_size);
handles[b] =
::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(make_slice(&h_addr[block_size * b], bs)));
}
}
}
// Copy constructor
vector(const vector& a)
: N(a.N)
, block_size(a.block_size)
, nblocks(a.nblocks)
{
handles.resize(nblocks);
for (size_t b = 0; b < nblocks; b++)
{
size_t bs = std::min(N - block_size * b, block_size);
handles[b] = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(bs)));
ctx.task(handles[b]->write(), a.handles[b]->read())->*[bs](cudaStream_t stream, auto dthis, auto da) {
// There are likely much more efficient ways.
cuda_safe_call(cudaMemcpyAsync(
dthis.data_handle(), da.data_handle(), bs * sizeof(double), cudaMemcpyDeviceToDevice, stream));
};
}
}
void fill(const std::function<double(int)>& f)
{
size_t bs = block_size;
for (size_t b = 0; b < nblocks; b++)
{
ctx.task(exec_place::host(), handles[b]->write())->*[&f, b, bs](cudaStream_t stream, auto ds) {
cuda_safe_call(cudaStreamSynchronize(stream));
for (size_t local_row = 0; local_row < ds.extent(0); local_row++)
{
ds(local_row) = f(local_row + b * bs);
}
};
}
}
size_t N;
size_t block_size;
size_t nblocks;
mutable std::vector<std::shared_ptr<logical_data<slice<double>>>> handles;
std::unique_ptr<double[]> h_addr;
};
__global__ void scalar_div(const double* a, const double* b, double* c)
{
*c = *a / *b;
}
// A += B
__global__ void scalar_add(double* a, const double* b)
{
*a = *a + *b;
}
__global__ void scalar_minus(const double* a, double* res)
{
*res = -(*a);
}
class scalar
{
public:
scalar(bool is_tmp = false)
{
size_t s = sizeof(double);
if (is_tmp)
{
// There is no physical backing for this temporary vector
handle = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(1)));
}
else
{
h_addr.reset(new double);
cuda_safe_call(cudaHostRegister(h_addr.get(), s, cudaHostRegisterPortable));
handle = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(make_slice(h_addr.get(), 1)));
}
}
scalar(scalar&&) = default;
scalar& operator=(scalar&&) = default;
// Copy constructor
scalar(const scalar& a)
{
handle = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(1)));
ctx.task(handle->write(), a.handle->read())->*[](cudaStream_t stream, auto dthis, auto da) {
// There are likely much more efficient ways.
cuda_safe_call(
cudaMemcpyAsync(dthis.data_handle(), da.data_handle(), sizeof(double), cudaMemcpyDeviceToDevice, stream));
};
}
scalar operator/(scalar const& rhs) const
{
// Submit a task that computes this/rhs
scalar res(true);
ctx.task(handle->read(), rhs.handle->read(), res.handle->write())
->*[](cudaStream_t stream, auto da, auto db, auto dres) {
scalar_div<<<1, 1, 0, stream>>>(da.data_handle(), db.data_handle(), dres.data_handle());
};
return res;
}
// this += rhs
scalar& operator+=(const scalar& rhs)
{
ctx.task(handle->rw(), rhs.handle->read())->*[](cudaStream_t stream, auto dthis, auto drhs) {
scalar_add<<<1, 1, 0, stream>>>(dthis.data_handle(), drhs.data_handle());
};
return *this;
}
scalar operator-() const
{
// Submit a task that computes -s
scalar res(true);
ctx.task(handle->read(), res.handle->write())->*[](cudaStream_t stream, auto dthis, auto dres) {
scalar_minus<<<1, 1, 0, stream>>>(dthis.data_handle(), dres.data_handle());
};
return res;
}
// Get value on the host
double get_value()
{
double val;
ctx.task(exec_place::host(), handle->read())->*[&val](cudaStream_t stream, auto ds) {
cuda_safe_call(cudaStreamSynchronize(stream));
val = ds(0);
};
return val;
}
mutable std::shared_ptr<logical_data<slice<double>>> handle;
std::unique_ptr<double> h_addr;
};
class scalar DOT(vector& a, class vector& b)
{
assert(a.nblocks == b.nblocks);
scalar global_res(true);
// Loop over all blocks,
for (size_t bid = 0; bid < a.nblocks; bid++)
{
scalar res(true);
// Note that it works even if a.handle == b.handle because they have the same access mode
ctx.task(a.handles[bid]->read(), b.handles[bid]->read(), res.handle->write())
->*[](cudaStream_t stream, auto da, auto db, auto dres) {
cuda_safe_call(cublasSetStream(cublas_handle, stream));
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_DEVICE));
cuda_safe_call(
cublasDdot(cublas_handle, da.extent(0), da.data_handle(), 1, db.data_handle(), 1, dres.data_handle()));
};
if (bid == 0)
{
// First access requires an assignment because it was not initialized
global_res = std::move(res);
}
else
{
global_res += res;
}
}
return global_res;
};
// Y = Y + alpha * X
void AXPY(const class scalar& alpha, class vector& x, class vector& y)
{
assert(x.N == y.N);
assert(x.nblocks == y.nblocks);
for (size_t b = 0; b < x.nblocks; b++)
{
ctx.task(alpha.handle->read(), x.handles[b]->read(), y.handles[b]->rw())
->*
[](cudaStream_t stream, auto dalpha, auto dx, auto dy) {
auto nx = dx.extent(0);
cuda_safe_call(cublasSetStream(cublas_handle, stream));
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_DEVICE));
cuda_safe_call(cublasDaxpy(cublas_handle, nx, dalpha.data_handle(), dx.data_handle(), 1, dy.data_handle(), 1));
};
}
};
// Y = alpha*Y + X
void SCALE_AXPY(const scalar& alpha, const class vector& x, class vector& y)
{
assert(x.N == y.N);
assert(x.nblocks == y.nblocks);
for (size_t b = 0; b < x.nblocks; b++)
{
ctx.task(alpha.handle->read(), x.handles[b]->read(), y.handles[b]->rw())
->*[](cudaStream_t stream, auto dalpha, auto dx, auto dy) {
cuda_safe_call(cublasSetStream(cublas_handle, stream));
auto nx = dx.extent(0);
// Y = alpha Y
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_DEVICE));
cuda_safe_call(cublasDscal(cublas_handle, nx, dalpha.data_handle(), dy.data_handle(), 1));
// Y = Y + X
const double one = 1.0;
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_HOST));
cuda_safe_call(cublasDaxpy(cublas_handle, nx, &one, dx.data_handle(), 1, dy.data_handle(), 1));
};
}
};
// y = alpha Ax + beta y
void GEMV(double alpha, class matrix& a, class vector& x, double beta, class vector& y)
{
assert(a.N == x.N);
assert(x.N == y.N);
size_t block_size = x.block_size;
assert(block_size == y.block_size);
for (size_t row_y = 0; row_y < y.nblocks; row_y++)
{
for (size_t row_x = 0; row_x < x.nblocks; row_x++)
{
double local_beta = (row_x == 0) ? beta : 1.0;
// If beta is null, then this is a write only mode
auto y_mode = local_beta == 0.0 ? access_mode::write : access_mode::rw;
ctx.task(a.handle->read(), x.handles[row_x]->read(), task_dep<slice<double>>(*(y.handles[row_y].get()), y_mode))
->*[alpha, local_beta, row_x, row_y, block_size](cudaStream_t stream, auto da, auto dx, auto dy) {
auto nx = dx.extent(0);
auto ny = dy.extent(0);
auto ldA = da.stride(1);
const double* Ablock = &da(row_y * block_size, row_x * block_size);
cuda_safe_call(cublasSetStream(cublas_handle, stream));
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_HOST));
cuda_safe_call(cublasDgemv(
cublas_handle,
CUBLAS_OP_N,
ny,
nx,
&alpha,
Ablock,
ldA,
dx.data_handle(),
1,
&local_beta,
dy.data_handle(),
1));
};
}
}
}
void cg(matrix& A, vector& X, vector& B)
{
int N = A.N;
assert(N == X.N);
assert(N == B.N);
vector R = B;
// R = R - A*X
GEMV(-1.0, A, X, 1.0, R);
vector P = R;
// RSOLD = R'*R
scalar rsold = DOT(R, R);
int MAXITER = N;
if (getenv("MAXITER"))
{
MAXITER = atoi(getenv("MAXITER"));
}
for (int k = 0; k < MAXITER; k++)
{
vector Ap(N, P.block_size, true);
// Ap = A*P
GEMV(1.0, A, P, 0.0, Ap);
// alpha = rsold / (p' * Ap);
scalar alpha = rsold / DOT(P, Ap);
// x = x + alpha * p;
AXPY(alpha, P, X);
// r = r - alpha * Ap;
AXPY(-alpha, Ap, R);
// rsnew = r' * r;
scalar rsnew = DOT(R, R);
// Read the residual on the CPU, and halt the iterative process if we have converged
{
double err;
ctx.task(exec_place::host(), rsnew.handle->read())->*[&err](cudaStream_t stream, auto dres) {
cuda_safe_call(cudaStreamSynchronize(stream));
err = sqrt(dres(0));
};
if (err < 1e-10)
{
// We have converged
// fprintf(stderr, "Successfully converged (err = %le)\n", err);
break;
}
}
// p = r + (rsnew / rsold) * p;
SCALE_AXPY(rsnew / rsold, R, P);
rsold = std::move(rsnew);
}
}
int main(int argc, char** argv)
{
size_t N = 1024;
if (argc > 1)
{
N = atoi(argv[1]);
fprintf(stderr, "N = %zu\n", N);
}
size_t block_size = N / 4;
if (argc > 2)
{
block_size = atoi(argv[2]);
fprintf(stderr, "block_size = %zu\n", block_size);
}
// Do this lazily ?
cuda_safe_call(cublasCreate(&cublas_handle));
matrix A(N);
A.fill([&](int row, int col) {
return (1.0 / (row + col + 1) + (row == col ? 0.1 : 0.0));
});
vector B(N, block_size);
vector X(N, block_size);
B.fill([&](int /*unused*/) {
return 1.0;
});
X.fill([&](int /*unused*/) {
return 0.0;
});
cg(A, X, B);
ctx.finalize();
}

View File

@@ -1,189 +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.
//
//===----------------------------------------------------------------------===//
#pragma once
/**
* @file
* @brief Sparse conjugate gradient algorithm
*/
#include <cuda/experimental/stf.cuh>
#include "dot.cuh"
using namespace cuda::experimental::stf;
#if !_CCCL_CTK_BELOW(12, 4)
template <typename ctx_t, typename T>
void cg_solver(ctx_t& ctx, csr_matrix<T>& A, vector_t<T>& X, vector_t<T>& B, double cg_tol = 1e-10, size_t max_cg = 1000)
{
// Initial guess X = 0 (better for Newton corrections)
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
dX(i) = 0.0;
};
// Residual R initialized to B
auto R = ctx.logical_data(B.shape()).set_symbol("R");
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
dR(i) = dB(i);
};
// R = R - A*X
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
SPMV(ctx, A, X, Ax);
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
dR(i) -= dAx(i);
};
// P = R;
auto P = ctx.logical_data(R.shape()).set_symbol("P");
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
dP(i) = dR(i);
};
// RSOLD = R'*R
auto rsold = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsold");
DOT(ctx, R, R, rsold);
// CG iteration counter
auto cg_iter = ctx.logical_data(shape_of<scalar_view<int>>()).set_symbol("cg_iter");
ctx.parallel_for(box(1), cg_iter.write()).set_symbol("init_cg_iter")->*[] _CCCL_DEVICE(size_t i, auto diter) {
*diter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
// Ap = A*P
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
SPMV(ctx, A, P, Ap);
// We don't compute alpha explicitly
// alpha = rsold / (p' * Ap);
auto pAp = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("pAp");
DOT(ctx, P, Ap, pAp);
// x = x + alpha * p;
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
T alpha = (*drsold / *dpAp);
dX(i) += alpha * dP(i);
};
// r = r - alpha * Ap;
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
T alpha = (*drsold / *dpAp);
dR(i) -= alpha * dAp(i);
};
// rsnew = r' * r;
auto rsnew = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsnew");
DOT(ctx, R, R, rsnew);
while_guard.update_cond(rsnew.read(), cg_iter.rw())->*[cg_tol, max_cg] __device__(auto drsnew, auto diter) {
(*diter)++; // increment iteration counter
bool converged = (*drsnew < cg_tol * cg_tol);
// printf("CG iter %d: RES %e (tol=%e)\n", *diter, sqrt(*drsnew), cg_tol);
return !converged && (*diter < max_cg);
};
// p = r + (rsnew / rsold) * p;
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
};
// update old residual
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
*drsold = *drsnew;
};
}
}
template <typename ctx_t, typename T>
void cg_solver_no_while(
ctx_t& ctx, csr_matrix<T>& A, vector_t<T>& X, vector_t<T>& B, double cg_tol = 1e-10, size_t max_cg = 1000)
{
// Initial guess X = 0 (better for Newton corrections)
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
dX(i) = 0.0;
};
// Residual R initialized to B
auto R = ctx.logical_data(B.shape()).set_symbol("R");
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
dR(i) = dB(i);
};
// R = R - A*X
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
SPMV(ctx, A, X, Ax);
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
dR(i) -= dAx(i);
};
// P = R;
auto P = ctx.logical_data(R.shape()).set_symbol("P");
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
dP(i) = dR(i);
};
// RSOLD = R'*R
auto rsold = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsold");
DOT(ctx, R, R, rsold);
size_t iter = 0;
auto rsnew = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsnew");
do
{
// Ap = A*P
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
SPMV(ctx, A, P, Ap);
// We don't compute alpha explicitly
// alpha = rsold / (p' * Ap);
auto pAp = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("pAp");
DOT(ctx, P, Ap, pAp);
// x = x + alpha * p;
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
T alpha = (*drsold / *dpAp);
dX(i) += alpha * dP(i);
};
// r = r - alpha * Ap;
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
T alpha = (*drsold / *dpAp);
dR(i) -= alpha * dAp(i);
};
// rsnew = r' * r;
DOT(ctx, R, R, rsnew);
// p = r + (rsnew / rsold) * p;
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
};
// update old residual
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
*drsold = *drsnew;
};
} while ((++iter < max_cg) && (ctx.wait(rsnew) > cg_tol * cg_tol));
}
#endif

View File

@@ -1,70 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#pragma once
//! \file
//! \brief DOT algorithm
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
template <typename T>
using vector_t = stackable_logical_data<slice<T>>;
template <typename T>
using scalar_t = stackable_logical_data<scalar_view<T>>;
template <typename T = double>
struct csr_matrix
{
csr_matrix(stackable_logical_data<slice<T>> _val_handle,
stackable_logical_data<slice<size_t>> _row_handle,
stackable_logical_data<slice<size_t>> _col_handle)
: val_handle(mv(_val_handle))
, row_handle(mv(_row_handle))
, col_handle(mv(_col_handle))
{}
/* Description of the CSR */
mutable stackable_logical_data<slice<T>> val_handle;
mutable stackable_logical_data<slice<size_t>> row_handle;
mutable stackable_logical_data<slice<size_t>> col_handle;
};
// Note that a and b might be the same logical data
template <typename ctx_t, typename T>
void DOT(ctx_t& ctx, vector_t<T>& a, vector_t<T>& b, scalar_t<T>& res)
{
ctx.parallel_for(a.shape(), a.read(), b.read(), res.reduce(reducer::sum<T>{})).set_symbol("DOT")->*
[] __device__(size_t i, auto da, auto db, T& dres) {
dres += da(i) * db(i);
};
};
template <typename ctx_t, typename T>
void SPMV(ctx_t& ctx, csr_matrix<T>& a, vector_t<T>& x, vector_t<T>& y)
{
ctx.parallel_for(y.shape(), a.val_handle.read(), a.col_handle.read(), a.row_handle.read(), x.read(), y.write())
.set_symbol("SPMV")
->*[] _CCCL_DEVICE(size_t row, auto da_val, auto da_col, auto da_row, auto dx, auto dy) {
int row_start = da_row(row);
int row_end = da_row(row + 1);
double sum = 0.0;
for (int elt = row_start; elt < row_end; elt++)
{
sum += da_val(elt) * dx(da_col(elt));
}
dy(row) = sum;
};
}

View File

@@ -1,172 +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 Generic Newton Solver
*/
#include <cuda/experimental/stf.cuh>
#include "cg_solver.cuh"
#include "dot.cuh"
using namespace cuda::experimental::stf;
/**
* Generic Newton solver for nonlinear systems F(x) = 0
*
* @tparam ctx_t STF context type
* @tparam ResidualCallback Callback to compute residual F(x)
* @tparam JacobianCallback Callback to assemble Jacobian J = ∂F/∂x
*
* The callbacks must be callable with these signatures:
* - ResidualCallback: void fn(ctx_t&, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>&
* residual)
* - JacobianCallback: void fn(ctx_t&, const vector_t<double>& x, vector_t<double>& jacobian_values)
*/
template <typename ctx_t, typename ResidualCallback, typename JacobianCallback>
void newton_solver(
ctx_t& ctx,
vector_t<double>& U,
vector_t<double>& csr_values,
const vector_t<size_t>& csr_row_offsets,
const vector_t<size_t>& csr_col_ind,
ResidualCallback compute_residual_fn,
JacobianCallback assemble_jacobian_fn,
size_t max_newton = 20,
double newton_tol = 1e-10,
size_t max_cg = 100)
{
auto U_prev = ctx.logical_data(U.shape()).set_symbol("U_prev");
ctx.parallel_for(U.shape(), U_prev.write(), U.read()).set_symbol("init_guess")
->*[] __device__(size_t i, auto dU_prev, auto dU) {
dU_prev(i) = dU(i);
};
auto newton_norm2 = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("newton_norm2");
auto newton_iter = ctx.logical_data(shape_of<scalar_view<size_t>>()).set_symbol("newton_iter");
ctx.parallel_for(box(1), newton_iter.write()).set_symbol("init_newton_iter")->*[] _CCCL_DEVICE(size_t i, auto diter) {
*diter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
auto residual = ctx.logical_data(U.shape()).set_symbol("residual");
auto delta = ctx.logical_data(U.shape()).set_symbol("delta");
// Compute residual F(U)
compute_residual_fn(ctx, U, U_prev, residual);
// Compute Newton residual norm for convergence check
DOT(ctx, residual, residual, newton_norm2);
// Assemble Jacobian J = ∂F/∂U
assemble_jacobian_fn(ctx, U, csr_values);
auto rhs = ctx.logical_data(U.shape()).set_symbol("rhs");
// Set up RHS: rhs = -F(U)
ctx.parallel_for(rhs.shape(), rhs.write(), residual.read()).set_symbol("rhs = -residual")
->*[] __device__(size_t i, auto drhs, auto dresidual) {
drhs(i) = -dresidual(i);
};
csr_matrix<double> A(csr_values, csr_row_offsets, csr_col_ind);
// Solve linear system: J * delta = -F(U)
double cg_tol = 1e-8;
cg_solver(ctx, A, delta, rhs, cg_tol, max_cg);
// Newton update: U = U + delta (no special boundary handling needed)
ctx.parallel_for(U.shape(), U.rw(), delta.read()).set_symbol("newton_update")
->*[] __device__(size_t i, auto dU, auto ddelta) {
dU(i) += ddelta(i);
};
while_guard.update_cond(newton_norm2.read(), newton_iter.rw())
->*[newton_tol, max_newton] __device__(auto dnorm2, auto diter) {
(*diter)++; // increment iteration counter
bool converged = (*dnorm2 < newton_tol * newton_tol);
return !converged && (*diter < max_newton);
};
}
}
template <typename ctx_t, typename ResidualCallback, typename JacobianCallback>
void newton_solver_no_while(
ctx_t& ctx,
vector_t<double>& U,
vector_t<double>& csr_values,
const vector_t<size_t>& csr_row_offsets,
const vector_t<size_t>& csr_col_ind,
ResidualCallback compute_residual_fn,
JacobianCallback assemble_jacobian_fn,
bool cg_use_while = false,
size_t max_newton = 20,
double newton_tol = 1e-10,
size_t max_cg = 100)
{
auto U_prev = ctx.logical_data(U.shape()).set_symbol("U_prev");
ctx.parallel_for(U.shape(), U_prev.write(), U.read()).set_symbol("init_guess")
->*[] __device__(size_t i, auto dU_prev, auto dU) {
dU_prev(i) = dU(i);
};
auto newton_norm2 = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("newton_norm2");
size_t iter = 0;
do
{
auto residual = ctx.logical_data(U.shape()).set_symbol("residual");
auto delta = ctx.logical_data(U.shape()).set_symbol("delta");
// Compute residual F(U)
compute_residual_fn(ctx, U, U_prev, residual);
// Compute Newton residual norm for convergence check
DOT(ctx, residual, residual, newton_norm2);
// Assemble Jacobian J = ∂F/∂U
assemble_jacobian_fn(ctx, U, csr_values);
auto rhs = ctx.logical_data(U.shape()).set_symbol("rhs");
// Set up RHS: rhs = -F(U)
ctx.parallel_for(rhs.shape(), rhs.write(), residual.read()).set_symbol("rhs = -residual")
->*[] __device__(size_t i, auto drhs, auto dresidual) {
drhs(i) = -dresidual(i);
};
csr_matrix<double> A(csr_values, csr_row_offsets, csr_col_ind);
// Solve linear system: J * delta = -F(U)
double cg_tol = 1e-8;
if (cg_use_while)
{
// fprintf(stderr, "NEWTON NO WHILE, CG WHILE.\n");
cg_solver(ctx, A, delta, rhs, cg_tol, max_cg);
}
else
{
// fprintf(stderr, "NEWTON NO WHILE, CG NO WHILE.\n");
cg_solver_no_while(ctx, A, delta, rhs, cg_tol, max_cg);
}
// Newton update: U = U + delta (no special boundary handling needed)
ctx.parallel_for(U.shape(), U.rw(), delta.read()).set_symbol("newton_update")
->*[] __device__(size_t i, auto dU, auto ddelta) {
dU(i) += ddelta(i);
};
} while ((++iter < max_newton) && ctx.wait(newton_norm2) > newton_tol * newton_tol);
}

View File

@@ -1,489 +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 Strassen matrix multiplication algorithm
*
* This demonstrates how CUDASTF helps combining many interdependent tasks and
* deal with temporary data.
*/
#include <cuda/experimental/stf.cuh>
static const size_t BLOCKSIZE = 1024;
using namespace cuda::experimental::stf;
using logical_matrix = logical_data<slice<double, 2>>;
inline size_t get_m(logical_matrix& s)
{
return s.shape().extent(0);
}
inline size_t get_n(logical_matrix& s)
{
return s.shape().extent(1);
}
// XXX global for the sake of simplicity, yet ...
static std::vector<cublasHandle_t> cublas_handle;
cublasHandle_t get_cublas_handle()
{
int dev;
cuda_safe_call(cudaGetDevice(&dev));
return cublas_handle[dev];
}
// C = AB
void MULT_CLASSIC(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
{
ctx.task(A.read(), B.read(), C.write()).set_symbol("MULT")->*[](cudaStream_t s, auto a, auto b, auto c) {
cuda_safe_call(cublasSetStream(get_cublas_handle(), s));
size_t N = a.extent(0);
const double zero = 0.0;
const double one = 1.0;
cuda_safe_call(cublasDgemm(
get_cublas_handle(),
CUBLAS_OP_N,
CUBLAS_OP_N,
N,
N,
N,
&one,
a.data_handle(),
a.stride(1),
b.data_handle(),
b.stride(1),
&zero,
c.data_handle(),
c.stride(1)));
};
}
// A = A + alpha B
template <typename T>
__global__ void add_kernel(int m, int n, T* A, int ld_A, T alpha, const T* B, int ld_B)
{
for (int idx = threadIdx.x + blockIdx.x * blockDim.x; idx < n; idx += blockDim.x * gridDim.x)
{
for (int idy = threadIdx.y + blockIdx.y * blockDim.y; idy < m; idy += blockDim.y * gridDim.y)
{
A[idy + idx * ld_A] += alpha * B[idy + idx * ld_B];
}
}
}
// Compute A = A + B
template <typename T>
void ADD(context& ctx, logical_matrix& A, T alpha, logical_matrix& B)
{
ctx.task(A.rw(), B.read()).set_symbol("ADD")->*[&](cudaStream_t s, auto a, auto b) {
int m_A = a.extent(0);
int n_A = a.extent(1);
int ld_A = a.stride(1);
int ld_B = b.stride(1);
T* addr_A = a.data_handle();
const T* addr_B = b.data_handle();
add_kernel<<<16, 16, 0, s>>>(m_A, n_A, addr_A, ld_A, alpha, addr_B, ld_B);
};
}
template <typename T>
__global__ void copy_kernel(int m, int n, const T* src, int ld_src, T* dst, int ld_dst)
{
for (int idx = threadIdx.x + blockIdx.x * blockDim.x; idx < n; idx += blockDim.x * gridDim.x)
{
for (int idy = threadIdx.y + blockIdx.y * blockDim.y; idy < m; idy += blockDim.y * gridDim.y)
{
dst[idy + idx * ld_dst] = src[idy + idx * ld_src];
}
}
}
// row and col = 0 or 1
template <typename T>
void COPY_TO_SUBMATRIX(context& ctx, logical_data<slice<T, 2>>& A, logical_data<slice<T, 2>>& subA, int row, int col)
{
// To copy to a subset, this is a write only access, so that we did not need a valid copy for subA before ...
ctx.task(A.read(), subA.write()).set_symbol("COPY_TO")->*[&](cudaStream_t s, auto a, auto subA) {
int ld_A = a.stride(1);
int ld_subA = subA.stride(1);
int m_subA = subA.extent(0);
int n_subA = subA.extent(1);
T* addr_subA = subA.data_handle();
const T* addr_A_base = a.data_handle();
const T* addr_A = addr_A_base + row * m_subA + col * n_subA * ld_A;
// subA = A_row,col
copy_kernel<<<16, 16, 0, s>>>(m_subA, n_subA, addr_A, ld_A, addr_subA, ld_subA);
};
}
template <typename T>
void COPY_FROM_SUBMATRICES(context& ctx, logical_data<slice<T, 2>>& A, logical_data<slice<T, 2>> subA[2][2])
{
// To copy to a subset, this is a write only access, so that we did not need a valid copy for subA before ...
// When copying from a subset to the whole matrix, we need a RW because we only modify a part of the matrix
ctx.task(A.write(), subA[0][0].read(), subA[0][1].read(), subA[1][0].read(), subA[1][1].read()).set_symbol("COPY_FROM")
->*[&](cudaStream_t s, auto a, auto a00, auto a01, auto a10, auto a11) {
int ld_A = a.stride(1);
T* addr_A_base = a.data_handle();
for (int col = 0; col < 2; col++)
{
for (int row = 0; row < 2; row++)
{
auto& subA = col == 0 ? (row == 0 ? a00 : a10) : (row == 0 ? a01 : a11);
int m_subA = subA.extent(0);
int n_subA = subA.extent(1);
int ld_subA = subA.stride(1);
const T* addr_subA = subA.data_handle();
T* addr_A = addr_A_base + row * m_subA + col * n_subA * ld_A;
// A_row,col= subA
copy_kernel<<<16, 16, 0, s>>>(m_subA, n_subA, addr_subA, ld_subA, addr_A, ld_A);
}
}
};
}
template <typename T>
void COPY_MATRIX(context& ctx, logical_data<slice<T, 2>>& dst, logical_data<slice<T, 2>>& src)
{
// This is a write only access, so that we did not need a valid copy for subA before ...
ctx.task(dst.write(), src.read()).set_symbol("COPY")->*[&](cudaStream_t s, auto d_dst, auto d_src) {
int ld_src = d_dst.stride(1);
int ld_dst = d_src.stride(1);
auto m = d_src.extent(0);
assert(m == d_dst.extent(0));
auto n = d_src.extent(1);
assert(n == d_dst.extent(1));
const T* addr_src = d_src.data_handle();
T* addr_dst = d_dst.data_handle();
copy_kernel<<<16, 16, 0, s>>>(m, n, addr_src, ld_src, addr_dst, ld_dst);
};
}
void MULT(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C);
void MULT_REC_NAIVE(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
{
logical_matrix subA[2][2], subB[2][2], subC[2][2];
size_t N = get_m(A);
assert(get_m(A) == get_n(A));
assert(get_m(B) == get_n(B));
assert(get_m(C) == get_n(C));
assert(N % 2 == 0);
size_t half_N = N / 2;
// These are TMP data which don't have a valid copy yet
for (int col = 0; col < 2; col++)
{
for (int row = 0; row < 2; row++)
{
subA[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
subB[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
subC[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_TO_SUBMATRIX(ctx, A, subA[row][col], row, col);
COPY_TO_SUBMATRIX(ctx, B, subB[row][col], row, col);
}
}
for (int col = 0; col < 2; col++)
{
for (int row = 0; row < 2; row++)
{
for (int k = 0; k < 2; k++)
{
auto Ck = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
MULT(ctx, subA[row][k], subB[k][col], Ck);
ADD(ctx, subC[row][col], 1.0, Ck);
}
// C_row,col = subC[row][col]
COPY_FROM_SUBMATRICES(ctx, C, subC);
}
}
}
void MULT_STRASSEN(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
{
/*
* STRASSEN ALGORITHM
*
* M1 = (A00 + A11)(B00 + B11)
* M2 = (A10 + A11)B00
* M3 = A00(B01 - B11)
* M4 = A11(B10 - B00)
* M5 = (A00 + A01)B11
* M6 = (A10 - A00)(B00 + B01)
* M7 = (A01 - A11)(B10 + B11)
*
* C00 = M1 + M4 - M5 + M7
* C01 = M3 + M5
* C10 = M2 + M4
* C11 = M1 - M2 + M3 + M6
*
*/
size_t N = get_m(A);
assert(N % 2 == 0);
size_t half_N = N / 2;
logical_matrix subA[2][2], subB[2][2], subC[2][2];
auto M1 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto M2 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto M3 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto M4 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto M5 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto M6 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto M7 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
assert(get_m(A) == get_n(A));
assert(get_m(B) == get_n(B));
assert(get_m(C) == get_n(C));
// These are TMP data which don't have a valid copy yet
for (int col = 0; col < 2; col++)
{
for (int row = 0; row < 2; row++)
{
subA[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
subB[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
subC[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_TO_SUBMATRIX(ctx, A, subA[row][col], row, col);
COPY_TO_SUBMATRIX(ctx, B, subB[row][col], row, col);
}
}
// M1 = (A00 + A11)(B00 + B11)
{
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N)),
right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, left, subA[0][0]);
ADD(ctx, left, 1.0, subA[1][1]);
COPY_MATRIX(ctx, right, subB[0][0]);
ADD(ctx, right, 1.0, subB[1][1]);
MULT(ctx, left, right, M1);
}
// M2 = (A10 + A11)B00
{
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, left, subA[1][0]);
ADD(ctx, left, 1.0, subA[1][1]);
MULT(ctx, left, subB[0][0], M2);
}
// M3 = A00(B01 - B11)
{
auto right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, right, subB[0][1]);
ADD(ctx, right, -1.0, subB[1][1]);
MULT(ctx, subA[0][0], right, M3);
}
// M4 = A11(B10 - B00)
{
auto right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, right, subB[1][0]);
ADD(ctx, right, -1.0, subB[0][0]);
MULT(ctx, subA[1][1], right, M4);
}
// M5 = (A00 + A01)B11
{
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, left, subA[0][0]);
ADD(ctx, left, 1.0, subA[0][1]);
MULT(ctx, left, subB[1][1], M5);
}
// M6 = (A10 - A00)(B00 + B01)
{
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N)),
right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, left, subA[1][0]);
ADD(ctx, left, -1.0, subA[1][1]);
COPY_MATRIX(ctx, right, subB[0][0]);
ADD(ctx, right, 1.0, subB[0][1]);
MULT(ctx, left, right, M6);
}
// M7 = (A01 - A11)(B10 + B11)
{
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
auto right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
COPY_MATRIX(ctx, left, subA[0][1]);
ADD(ctx, left, -1.0, subA[1][1]);
COPY_MATRIX(ctx, right, subB[1][0]);
ADD(ctx, right, 1.0, subB[1][1]);
MULT(ctx, left, right, M7);
}
// C00 = M1 + M4 - M5 + M7
COPY_MATRIX(ctx, subC[0][0], M1);
ADD(ctx, subC[0][0], 1.0, M4);
ADD(ctx, subC[0][0], -1.0, M5);
ADD(ctx, subC[0][0], -1.0, M5);
ADD(ctx, subC[0][0], 1.0, M7);
// C01 = M3 + M5
COPY_MATRIX(ctx, subC[0][1], M3);
ADD(ctx, subC[0][1], 1.0, M5);
// C10 = M2 + M4
COPY_MATRIX(ctx, subC[1][0], M2);
ADD(ctx, subC[1][0], 1.0, M4);
// C11 = M1 - M2 + M3 + M6
COPY_MATRIX(ctx, subC[1][1], M1);
ADD(ctx, subC[1][1], -1.0, M2);
ADD(ctx, subC[1][1], 1.0, M3);
ADD(ctx, subC[1][1], 1.0, M6);
// Write back subsets of C to C
COPY_FROM_SUBMATRICES(ctx, C, subC);
}
void MULT(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
{
size_t N = get_m(A);
if (N <= BLOCKSIZE)
{
MULT_CLASSIC(ctx, A, B, C);
}
else
{
// MULT_REC_NAIVE(ctx, A, B, C);
MULT_STRASSEN(ctx, A, B, C);
}
}
void strassen_test(context& ctx, size_t N)
{
double* A = new double[N * N];
double* B = new double[N * N];
double* C = new double[N * N];
int ldA = N;
int ldB = N;
int ldC = N;
cuda_safe_call(cudaHostRegister(A, N * N * sizeof(double), cudaHostRegisterPortable));
cuda_safe_call(cudaHostRegister(B, N * N * sizeof(double), cudaHostRegisterPortable));
cuda_safe_call(cudaHostRegister(C, N * N * sizeof(double), cudaHostRegisterPortable));
for (size_t col = 0; col < N; col++)
{
for (size_t row = 0; row < N; row++)
{
A[row + N * col] = 1.0;
B[row + N * col] = -1.0;
C[row + N * col] = 0.0;
}
}
auto descA = ctx.logical_data(make_slice(A, std::tuple{N, N}, ldA)),
descB = ctx.logical_data(make_slice(B, std::tuple{N, N}, ldB)),
descC = ctx.logical_data(make_slice(C, std::tuple{N, N}, ldC));
descA.set_symbol("A");
descB.set_symbol("B");
descC.set_symbol("C");
std::chrono::steady_clock::time_point start, stop;
ctx.host_launch(descC.read())->*[&](auto /* ignored */) {
start = std::chrono::steady_clock::now();
};
MULT(ctx, descA, descB, descC);
ctx.host_launch(descC.read())->*[&](auto /* ignored */) {
stop = std::chrono::steady_clock::now();
};
ctx.finalize();
std::chrono::duration<double> duration = stop - start;
fprintf(stderr, "Elapsed: %.2lf ms\n", duration.count() * 1000.0);
}
int main(int argc, char** argv)
{
long N = 2 * BLOCKSIZE;
if (argc > 1)
{
N = atoi(argv[1]);
}
bool use_graphs = false;
if (argc > 2)
{
use_graphs = (atoi(argv[2]) > 0);
}
// Set up CUBLAS
int ndevs;
cuda_safe_call(cudaGetDeviceCount(&ndevs));
cublas_handle.resize(ndevs);
for (int d = 0; d < ndevs; d++)
{
cuda_safe_call(cudaSetDevice(d));
cuda_safe_call(cublasCreate(&cublas_handle[d]));
}
cuda_safe_call(cudaSetDevice(0));
context ctx;
if (use_graphs)
{
ctx = graph_ctx();
}
strassen_test(ctx, N);
}

View File

@@ -1,73 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief Composition of boolean operations applied on logical data
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// z = AND(x,y)
logical_data<slice<int>> AND(context& ctx, logical_data<slice<int>> x, logical_data<slice<int>> y)
{
assert(x.shape().size() == y.shape().size());
auto z = ctx.logical_data(x.shape());
std::string symbol = "(" + x.get_symbol() + " & " + y.get_symbol() + ")";
z.set_symbol(symbol);
ctx.parallel_for(z.shape(), x.read(), y.read(), z.write()).set_symbol("AND")->*
[] __device__(size_t i, auto dx, auto dy, auto dz) {
dz(i) = dx(i) & dy(i);
};
return z;
}
// y = NOT(x)
logical_data<slice<int>> NOT(context& ctx, logical_data<slice<int>> x)
{
auto y = ctx.logical_data(x.shape());
std::string symbol = "( !" + x.get_symbol() + ")";
y.set_symbol(symbol);
ctx.parallel_for(y.shape(), x.read(), y.write()).set_symbol("NOT")->*[] __device__(size_t i, auto dx, auto dy) {
dy(i) = ~dx(i);
};
return y;
}
int main()
{
const size_t n = 12;
int X[n], Y[n], Z[n];
context ctx;
auto lX = ctx.logical_data(X);
auto lY = ctx.logical_data(Y);
auto lZ = ctx.logical_data(Z);
lX.set_symbol("X");
lY.set_symbol("Y");
lZ.set_symbol("Z");
auto lB = AND(ctx, AND(ctx, lX, lY), AND(ctx, lX, lZ));
auto lC = AND(ctx, NOT(ctx, AND(ctx, lB, NOT(ctx, lY))), lX);
ctx.finalize();
}

View File

@@ -1,129 +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 A transparent multi-GPU implementation of Mandelbrot fractal using parallel_for
*/
#include <cuda/experimental/stf.cuh>
#include <fstream>
#include <iostream>
using namespace cuda::experimental::stf;
int main(int argc, char** argv)
{
context ctx;
// Image dimensions
size_t width = 2000;
size_t height = 1000;
// Complex plane boundaries
double xMin = -2.0;
double xMax = 1.0;
double yMin = -1.5;
double yMax = 1.5;
// Maximum number of iterations
int maxIterations = 256;
// Describe a 2D array of integers of size (width x height)
auto lbuffer = ctx.logical_data(shape_of<slice<int, 2>>(width, height));
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
// Compute each pixel
ctx.parallel_for(blocked_partition(), exec_place::all_devices(), lbuffer.shape(), lbuffer.write())
->*[=] _CCCL_DEVICE(size_t x, size_t y, auto buffer) {
// Map pixel coordinates to complex plane
// c = cr + i ci
double cr = x * (xMax - xMin) / width + xMin;
double ci = y * (yMax - yMin) / height + yMin;
// z = zr + i zi
double zr = 0.0;
double zi = 0.0;
int iterations = 0;
// Evaluate depth
while (zr * zr + zi * zi < 4 && iterations < maxIterations)
{
// compute : z = z * z + c;
//
// z = (zr + i zi) (zr + i zi) + cr + i ci
// z = zr zr - zi zi + 2 i zi zr + cr + i ci
// zr = (zr zr - zi zi + cr)
// zi = (2 zi zr + ci)
double zr_prev = zr;
double zi_prev = zi;
zr = zr_prev * zr_prev - zi_prev * zi_prev + cr;
zi = 2.0 * zr_prev * zi_prev + ci;
iterations++;
}
buffer(x, y) = iterations;
};
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
if (argc > 1)
{
auto fileName = std::string(argv[1]);
// Generate a PPM file from the buffer
ctx.host_launch(lbuffer.read())->*[&](auto buffer) {
std::ofstream imageFile(fileName, std::ios::binary);
if (!imageFile)
{
std::cerr << "Failed to create image file: " << fileName << '\n';
return;
}
imageFile << "P6\n";
imageFile << width << " " << height << "\n";
imageFile << "255\n";
for (size_t y = 0; y < height; y++)
{
for (size_t x = 0; x < width; x++)
{
int iterations = buffer(x, y);
// Convert iterations to RGB values
unsigned char r = (iterations % 8) * 32;
unsigned char g = (iterations % 16) * 16;
unsigned char b = (iterations % 32) * 8;
// Write pixel data to file
imageFile << r << g << b;
}
}
imageFile.close();
std::cout << "Mandelbrot image generated and saved as " << fileName << '\n';
};
}
ctx.finalize();
// Must call this first, see e.g.
// https://stackoverflow.com/questions/6551121/cuda-cudaeventelapsedtime-returns-device-not-ready-error
cuda_safe_call(cudaEventSynchronize(stop));
fprintf(stderr, "Mandelbrot took %.2f ms\n", cuda_try<cudaEventElapsedTime>(start, stop));
}

View File

@@ -1,74 +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 An example using parallel_for on shapes with different dimensions
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__host__ __device__ double x0(size_t i, size_t j)
{
return sin((double) (i - j));
}
__host__ __device__ double y0(size_t i, size_t j)
{
return cos((double) (i + j));
}
int main()
{
context ctx;
const size_t N = 16;
double X[2 * N * 2 * N];
double Y[N * N];
auto lx = ctx.logical_data(make_slice(&X[0], std::tuple{2 * N, 2 * N}, 2 * N));
auto ly = ctx.logical_data(make_slice(&Y[0], std::tuple{N, N}, N));
ctx.parallel_for(lx.shape(), lx.write())->*[=] _CCCL_DEVICE(size_t i, size_t j, auto sx) {
sx(i, j) = x0(i, j);
};
ctx.parallel_for(ly.shape(), lx.read(), ly.write())->*[=] _CCCL_DEVICE(size_t i, size_t j, auto sx, auto sy) {
sy(i, j) = y0(i, j);
for (size_t ii = 0; ii < 2; ii++)
{
for (size_t jj = 0; jj < 2; jj++)
{
sy(i, j) += sx(2 * i + ii, 2 * j + jj);
}
}
};
ctx.parallel_for(exec_place::host(), ly.shape(), ly.read())
->*[=] __host__(size_t i, size_t j, slice<const double, 2> sy) {
double expected = y0(i, j);
for (size_t ii = 0; ii < 2; ii++)
{
for (size_t jj = 0; jj < 2; jj++)
{
expected += x0(2 * i + ii, 2 * j + jj);
}
}
if (fabs(sy(i, j) - expected) > 0.001)
{
printf("sy(%zu, %zu) %f expect %f\n", i, j, sy(i, j), expected);
}
};
ctx.finalize();
}

View File

@@ -1,143 +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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief AXPY over data distributed across the machine's devices with a
* structured partition specification
*
* The partition ("dimension 0, blocked over the grid of devices") is
* expressed once as a cute_partition. The same description is then used to:
*
* 1. EVALUATE the placement before committing any memory
* (evaluate_localized_placement: bytes per place, placement accuracy);
* 2. back a logical data with a composite data place, so STF tasks operate
* on memory whose pages physically live on the device that owns them;
* 3. perform a raw geometry-aware allocation (allocate_nd(data_dims, elemsize))
* outside of any STF context.
*
* Each place computes its own blocked portion (the idiomatic grid-task
* pattern), so no cross-device access is required; peer/mempool access setup
* is handled by the places machinery itself.
*/
#include <cuda/experimental/stf.cuh>
#include <cmath>
#include <cstdio>
using namespace cuda::experimental::stf;
__global__ void axpy(size_t start, size_t cnt, double a, const double* x, double* y)
{
int tid = blockIdx.x * blockDim.x + threadIdx.x;
int nthreads = gridDim.x * blockDim.x;
for (size_t i = tid; i < cnt; i += nthreads)
{
y[start + i] += a * x[start + i];
}
}
double X0(size_t i)
{
return sin((double) i);
}
double Y0(size_t i)
{
return cos((double) i);
}
int main()
{
// The places machinery enumerates the devices and sets up peer/mempool
// access between them; on a single-GPU machine this is one place.
auto all_devs = exec_place::all_devices();
const size_t nplaces = all_devs.get_dims().size();
const size_t N = 4 * 1024 * 1024;
// "Dimension 0, blocked over grid axis 0" - the per-dimension specification
auto part = make_partition(dim4(N), partition_spec{blocked<0>}, all_devs.get_dims());
// 1. Score the mapping before allocating anything
auto stats = evaluate_localized_placement(all_devs, part, sizeof(double));
printf("Placement over %zu place(s): %zu blocks in %zu allocations, accuracy %.1f%%\n",
nplaces,
stats.nblocks,
stats.nallocs,
100.0 * stats.accuracy());
for (const auto& entry : stats.bytes_per_place)
{
printf(" %s: %.2f MB\n", entry.first.c_str(), entry.second / (1024.0 * 1024.0));
}
// 2. Run STF tasks over logical data placed by the same policy
stream_ctx ctx;
::std::vector<double> X(N), Y(N);
for (size_t i = 0; i < N; i++)
{
X[i] = X0(i);
Y[i] = Y0(i);
}
auto lX = ctx.logical_data(&X[0], {N});
auto lY = ctx.logical_data(&Y[0], {N});
const double alpha = 3.14;
// The composite data place distributes instances across the grid with the
// classic blocked partitioner (the callback form of the same policy)
auto dist = data_place::composite(blocked_partition_custom<0>{}, all_devs);
// One task over the grid; each place computes its own blocked chunk
auto t = ctx.task(all_devs, lX.read(dist), lY.rw(dist));
t->*[&](auto, auto dX, auto dY) {
const size_t chunk = (N + nplaces - 1) / nplaces;
for (size_t i = 0; i < nplaces; i++)
{
const size_t start = i * chunk;
if (start >= N)
{
// With ceil-division chunks, trailing places may have no work
continue;
}
const size_t cnt = ::std::min(chunk, N - start);
auto active = t.activate_place(i);
axpy<<<128, 128, 0, t.get_stream(i)>>>(start, cnt, alpha, dX.data_handle(), dY.data_handle());
}
};
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
if (fabs(Y[i] - (Y0(i) + alpha * X0(i))) > 0.0001)
{
fprintf(stderr, "Verification FAILED at %zu\n", i);
return 1;
}
}
printf("STF task over composite-placed data: verified\n");
// 3. Raw geometry-aware allocation, no STF context involved
auto dp = ::cuda::experimental::places::make_composite_data_place(all_devs, part);
void* raw = dp.allocate_nd(dim4(N), sizeof(double));
auto* d_buf = static_cast<double*>(raw);
cuda_safe_call(cudaMemset(d_buf, 0, N * sizeof(double)));
cuda_safe_call(cudaDeviceSynchronize());
dp.deallocate(raw, N * sizeof(double));
printf("Raw shaped allocation on the partitioned place: OK\n");
return 0;
}

View File

@@ -1,52 +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 Approximate pi using Monte Carlo method
*
*/
#include <cuda/experimental/stf.cuh>
#include <curand_kernel.h>
#include <stdio.h>
using namespace cuda::experimental::stf;
int main(int, char**)
{
context ctx;
auto lsum = ctx.logical_data(shape_of<scalar_view<size_t>>());
size_t N = 1000000;
ctx.parallel_for(box(N), lsum.reduce(reducer::sum<size_t>{}))->*[] __device__(size_t i, auto& sum) {
curandState local_state;
curand_init(1234, i, 0, &local_state);
double x = curand_uniform_double(&local_state); // Random x in [0, 1)
double y = curand_uniform_double(&local_state); // Random y in [0, 1)
// Count (x,y) coordinates which are within the unit circle
if (x * x + y * y <= 1.0)
{
sum++;
}
};
// We get the ratio of "shots" within the unit circle and the total number of
// "shots". The surface of the quarter of unit circle [0, 1) x [0, 1) is pi/4
auto res = ctx.wait(lsum);
double pi_val = (4.0 * res) / N;
ctx.finalize();
_CCCL_ASSERT(fabs(pi_val - 3.1415) < 0.1, "Invalid result");
}

View File

@@ -1,204 +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 A parallel scan algorithm using CUB kernels
*
*/
#include <cub/cub.cuh> // or equivalently <cub/device/device_scan.cuh>
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__host__ __device__ double X0(int i)
{
return sin((double) i);
}
/**
* @brief Performs an inclusive scan on a logical data slice using CUB.
*
* This function determines the temporary device storage requirements for a scan, allocates
* temporary storage, and then performs the scan using the CUB library. The scan is performed
* in place, modifying the input `logical_data` slice.
*
* @tparam Ctx The context type for data management and task execution.
* @tparam T The data type of the elements in the `logical_data` slice.
*
* @param ctx Reference to the context object.
* @param ld Reference to the `logical_data` object containing the data slice.
* @param dp The `data_place` enum specifying where the data should reside (e.g., CPU, GPU).
*/
template <typename Ctx, typename T>
void scan(Ctx& ctx, logical_data<slice<T>>& ld, data_place dp)
{
// Determine temporary device storage requirements
auto num_items = int(ld.shape().size());
size_t tmp_size = 0;
cub::DeviceScan::InclusiveSum(nullptr, tmp_size, (T*) nullptr, (T*) nullptr, num_items);
// fprintf(stderr, "SCAN %ld items TMP = %ld bytes\n", num_items, tmp_size);
logical_data<slice<char>> ltmp = ctx.logical_data(shape_of<slice<char>>(tmp_size)).set_symbol("tmp");
ctx.task(ld.rw(mv(dp)), ltmp.write()).set_symbol("scan " + ld.get_symbol())
->*[=](cudaStream_t stream, auto d, auto tmp) mutable {
T* buffer = d.data_handle();
cub::DeviceScan::InclusiveSum(tmp.data_handle(), tmp_size, buffer, buffer, num_items, stream);
};
}
int main(int argc, char** argv)
{
stream_ctx ctx;
// graph_ctx ctx;
// const size_t N = 128ULL*1024ULL*1024ULL;
size_t nmb = 128;
if (argc > 1)
{
nmb = atoi(argv[1]);
}
int check = 0;
if (argc > 2)
{
check = atoi(argv[2]);
}
const size_t N = nmb * 1024ULL * 1024ULL;
const int ndevs = cuda_try<cudaGetDeviceCount>();
const size_t NBLOCKS = 2 * ndevs;
size_t BLOCK_SIZE = (N + NBLOCKS - 1) / NBLOCKS;
auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, BLOCK_SIZE * sizeof(double));
ctx.set_allocator(fixed_alloc);
// dummy task to initialize the allocator XXX
{
auto ldummy = ctx.logical_data(shape_of<slice<double>>(NBLOCKS)).set_symbol("dummy");
ctx.task(ldummy.write(data_place::managed()))->*[](cudaStream_t, auto) {};
}
std::vector<double> X(N);
std::vector<logical_data<slice<double>>> lX(NBLOCKS);
logical_data<slice<double>> laux;
// If we were to register each part one by one, there could be pages which
// cross multiple parts, and the pinning operation would fail.
cuda_safe_call(cudaHostRegister(&X[0], N * sizeof(double), cudaHostRegisterPortable));
for (size_t b = 0; b < NBLOCKS; b++)
{
size_t start = b * BLOCK_SIZE;
size_t end = std::min(start + BLOCK_SIZE, N);
lX[b] = ctx.logical_data(&X[start], {end - start}).set_symbol("X_" + std::to_string(b));
// No need to move this back to the host if we do not check the result
if (!check)
{
lX[b].set_write_back(false);
}
}
for (size_t b = 0; b < NBLOCKS; b++)
{
cuda_safe_call(cudaSetDevice(b % ndevs));
size_t start = b * BLOCK_SIZE;
ctx.parallel_for(lX[b].shape(), lX[b].write())->*[=] _CCCL_DEVICE(size_t i, auto lx) {
lx(i) = X0(i + start);
};
}
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
cudaEvent_t start, stop;
cuda_safe_call(cudaEventCreate(&start));
cuda_safe_call(cudaEventCreate(&stop));
cuda_safe_call(cudaEventRecord(start, ctx.fence()));
for (size_t k = 0; k < 100; k++)
{
// Create an auxiliary temporary buffer and blank it
laux = ctx.logical_data(shape_of<slice<double>>(NBLOCKS)).set_symbol("aux");
ctx.parallel_for(laux.shape(), laux.write(data_place::managed())).set_symbol("init_aux")
->*[] _CCCL_DEVICE(size_t i, auto aux) {
aux(i) = 0.0;
};
// Scan each block
for (size_t b = 0; b < NBLOCKS; b++)
{
cuda_safe_call(cudaSetDevice(b % ndevs));
scan(ctx, lX[b], data_place::device(b % ndevs));
}
for (size_t b = 0; b < NBLOCKS; b++)
{
// cuda_safe_call(cudaSetDevice(b % ndevs));
ctx.parallel_for(exec_place::device(0),
box({b, b + 1}),
lX[b].read(data_place::device(b % ndevs)),
laux.rw(data_place::managed()))
.set_symbol("store sum X_" + std::to_string(b))
->*[] _CCCL_DEVICE(size_t ind, auto Xb, auto aux) {
aux(ind) = Xb(Xb.extent(0) - 1);
};
}
// Prefix sum of the per-block sums
scan(ctx, laux, data_place::managed());
// Add partial sum of Xi to X(i+1)
for (size_t b = 1; b < NBLOCKS; b++)
{
cuda_safe_call(cudaSetDevice(b % ndevs));
ctx.parallel_for(lX[b].shape(), lX[b].rw(), laux.read(data_place::managed()))
.set_symbol("add X_" + std::to_string(b))
->*[=] _CCCL_DEVICE(size_t i, auto Xb, auto aux) {
Xb(i) += aux(b - 1);
};
}
}
cuda_safe_call(cudaEventRecord(stop, ctx.fence()));
ctx.finalize();
float ms = 0;
cuda_safe_call(cudaEventElapsedTime(&ms, start, stop));
fprintf(stdout, "%zu %f ms\n", N / 1024 / 1024, ms);
if (check)
{
#if 0
for (size_t i = 0; i < N; i++) {
EXPECT(fabs(X[i] - expected_result[i]) < 0.00001);
}
#endif
#if 1
fprintf(stderr, "Checking result ...\n");
EXPECT(fabs(X[0] - X0(0)) < 0.00001);
for (size_t i = 0; i < N; i++)
{
EXPECT(fabs(X[i] - X[i - 1] - X0(i)) < 0.00001);
}
}
#endif
}

View File

@@ -1,80 +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 Compute square roots via Newton's method using while_graph_scope
*
* This is a minimal example of an iterative solver with convergence
* checking in a stackable context. Each iteration applies the
* Babylonian step x <- (x + S/x) / 2 and reduces the maximum
* absolute change across all elements. The while loop exits once
* the change drops below a tolerance.
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int main()
{
#if _CCCL_CTK_BELOW(12, 4)
fprintf(stderr, "Waiving example: while_graph_scope requires CUDA 12.4+.\n");
return 0;
#else
stackable_ctx ctx;
constexpr size_t N = 1024;
constexpr double tol = 1e-12;
::std::vector<double> host_S(N);
::std::vector<double> host_X(N);
for (size_t i = 0; i < N; i++)
{
host_S[i] = 1.0 + static_cast<double>(i);
host_X[i] = host_S[i]; // initial guess x0 = S
}
auto lS = ctx.logical_data(make_slice(host_S.data(), N)).set_symbol("S");
lS.set_read_only();
auto lX = ctx.logical_data(make_slice(host_X.data(), N)).set_symbol("X");
auto lmax_err = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("max_err");
{
auto while_guard = ctx.while_graph_scope();
// Babylonian step: x = (x + S/x) / 2, reduce max |change|
ctx.parallel_for(box(N), lX.rw(), lS.read(), lmax_err.reduce(reducer::maxval<double>{}))
->*[] __device__(size_t i, auto x, auto s, auto& max_err) {
double x_old = x(i);
double x_new = 0.5 * (x_old + s(i) / x_old);
x(i) = x_new;
max_err = fabs(x_new - x_old);
};
while_guard.update_cond(lmax_err.read())->*[tol] __device__(auto max_err) {
return (*max_err > tol);
};
}
ctx.finalize();
for (size_t i = 0; i < N; i++)
{
double expected = sqrt(1.0 + static_cast<double>(i));
EXPECT(fabs(host_X[i] - expected) < 1e-8);
}
return 0;
#endif
}

View File

@@ -1,77 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief This test illustrates how we can use multiple reserved::launch in a single task on different pieces of data
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
int X0(int i)
{
return i * i + 12;
}
int main()
{
stream_ctx ctx;
const int N = 16;
int X[N], Y[N], Z[N];
for (size_t ind = 0; ind < N; ind++)
{
X[ind] = X0(ind);
Y[ind] = 0;
Z[ind] = 0;
}
auto handle_X = ctx.logical_data(X, {N});
auto handle_Y = ctx.logical_data(Y, {N});
auto handle_Z = ctx.logical_data(Z, {N});
ctx.task(handle_X.read(), handle_Y.write(), handle_Z.write())
->*[](cudaStream_t s, slice<const int> x, slice<int> y, slice<int> z) {
std::vector<cudaStream_t> streams;
streams.push_back(s);
auto spec = par(1024);
reserved::launch(spec, exec_place::current_device(), streams, std::tuple{x, y})
->*[] _CCCL_DEVICE(auto t, slice<const int> x, slice<int> y) {
size_t tid = t.rank();
size_t nthreads = t.size();
for (size_t ind = tid; ind < N; ind += nthreads)
{
y(ind) = 2 * x(ind);
}
};
reserved::launch(spec, exec_place::current_device(), streams, std::tuple{y, z})
->*[] _CCCL_DEVICE(auto t, slice<int> y, slice<int> z) {
size_t tid = t.rank();
size_t nthreads = t.size();
for (size_t ind = tid; ind < N; ind += nthreads)
{
z(ind) = 3 * y(ind);
}
};
};
ctx.finalize();
for (size_t ind = 0; ind < N; ind++)
{
assert(Y[ind] == 2 * X[ind]);
assert(Z[ind] == 3 * Y[ind]);
}
}

View File

@@ -1,135 +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 we can convert Thrust iterators to
* logical data, and how to create thrust iterators from data instances in a
* task.
*/
#include <thrust/device_vector.h>
#include <thrust/functional.h>
#include <thrust/host_vector.h>
#include <thrust/iterator/zip_iterator.h>
#include <thrust/transform.h>
#include <cuda/experimental/stf.cuh>
#include <iostream>
using namespace cuda::experimental::stf;
// Functor to apply the transformation
struct my_transform_functor
{
__host__ __device__ int operator()(const cuda::std::tuple<int, char>& t) const
{
int a = cuda::std::get<0>(t);
char b = cuda::std::get<1>(t);
return a + static_cast<int>(b); // Example operation
}
};
/*
* How to use CUDASTF to manipulate data originally created using Thrust
*/
template <typename ZippedIt, typename OutIt>
void thrust_algorithm(context& ctx, ZippedIt& first, ZippedIt& last, OutIt& output, data_place data_location)
{
/*
* Interpret Thrust data structures as logical data
*/
size_t num_elements = cuda::std::distance(first, last);
// Extract underlying iterators from the zip iterator
auto itA = cuda::std::get<0>(first.get_iterator_tuple());
int* A = thrust::raw_pointer_cast(&(*itA));
auto itB = cuda::std::get<1>(first.get_iterator_tuple());
char* B = thrust::raw_pointer_cast(&(*itB));
int* C = thrust::raw_pointer_cast(output.data());
auto lA = ctx.logical_data(make_slice(A, num_elements), data_location);
auto lB = ctx.logical_data(make_slice(B, num_elements), data_location);
auto lC = ctx.logical_data(make_slice(C, num_elements), data_location);
/* Important : result C will only be valid once we finalize the context or introduce a task fence ! */
ctx.task(lA.read(), lB.read(), lC.write())->*[](cudaStream_t stream, auto dA, auto dB, auto dC) {
// Reconstruct a zipped iterator from the data instances passed to the lambda function
size_t num_elements = dA.size();
auto dfirst = thrust::make_zip_iterator(cuda::std::tuple(dA.data_handle(), dB.data_handle()));
auto dlast = dfirst + num_elements;
// Create a device pointer from the raw pointer
thrust::device_ptr<int> dout = thrust::device_pointer_cast(dC.data_handle());
thrust::transform(thrust::cuda::par_nosync.on(stream), dfirst, dlast, dout, my_transform_functor());
};
}
int main()
{
context ctx;
/*
* First create device vectors and zipped them
*/
thrust::device_vector<int> A(3);
thrust::device_vector<char> B(3);
thrust::device_vector<int> C(3);
A[0] = 10;
A[1] = 20;
A[2] = 30;
B[0] = 'x';
B[1] = 'y';
B[2] = 'z';
auto first = thrust::make_zip_iterator(cuda::std::tuple(A.begin(), B.begin()));
auto last = thrust::make_zip_iterator(cuda::std::tuple(A.end(), B.end()));
thrust_algorithm(ctx, first, last, C, data_place::current_device());
/*
* Use host data, and rely on CUDASTF for transfers
*/
thrust::host_vector<int> hA(3);
thrust::host_vector<char> hB(3);
thrust::host_vector<int> hC(3);
hA[0] = 10;
hA[1] = 20;
hA[2] = 30;
hB[0] = 'x';
hB[1] = 'y';
hB[2] = 'z';
auto hfirst = thrust::make_zip_iterator(cuda::std::tuple(hA.begin(), hB.begin()));
auto hlast = thrust::make_zip_iterator(cuda::std::tuple(hA.end(), hB.end()));
thrust_algorithm(ctx, hfirst, hlast, hC, data_place::host());
/* Before this, we cannot assume that the Thrust algorithms have been
* performed and/or that the results have been written back to their original
* location. */
ctx.finalize();
// Check results
for (size_t i = 0; i < 3; i++)
{
EXPECT(C[i] == (A[i] + static_cast<int>(B[i])));
EXPECT(hC[i] == (hA[i] + static_cast<int>(hB[i])));
}
return 0;
}

View File

@@ -1,57 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
*
* @brief Illustrate how to use the void data interface
*
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
__global__ void dummy_kernel() {}
int main()
{
context ctx;
auto token = ctx.token();
ctx.task(token.write())->*[](cudaStream_t) {
};
void_interface sync;
auto token2 = ctx.logical_data(sync);
auto token3 = ctx.token();
ctx.task(token2.write(), token.read())->*[](cudaStream_t) {
};
// Do not pass useless arguments by removing void_interface arguments
// Note that the rw() access is possible even if there was no prior write()
// or actual underlying data.
ctx.task(token3.rw(), token.read())->*[](cudaStream_t) {
};
ctx.cuda_kernel(token3.rw())->*[]() {
return cuda_kernel_desc{dummy_kernel, 16, 128, 0};
};
EXPECT(token.is_void_interface());
EXPECT(token2.is_void_interface());
EXPECT(token3.is_void_interface());
ctx.finalize();
}

View File

@@ -1,91 +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 Counting words in a text using a launch kernel
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// determines whether the character is alphabetical
__host__ __device__ bool is_alpha(const char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
int main()
{
// Paragraph from 'The Raven' by Edgar Allan Poe
// http://en.wikipedia.org/wiki/The_Raven
const char raw_input[] =
" But the raven, sitting lonely on the placid bust, spoke only,\n"
" That one word, as if his soul in that one word he did outpour.\n"
" Nothing further then he uttered - not a feather then he fluttered -\n"
" Till I scarcely more than muttered `Other friends have flown before -\n"
" On the morrow he will leave me, as my hopes have flown before.'\n"
" Then the bird said, `Nevermore.'\n";
context ctx;
auto ltext = ctx.logical_data(const_cast<char*>(&raw_input[0]), {sizeof(raw_input)});
int cnt = 0;
auto lcnt = ctx.logical_data(&cnt, {1});
auto number_devices = 2;
auto all_devs = exec_place::repeat(exec_place::device(0), number_devices);
auto spec = par(con(128));
ctx.launch(spec, all_devs, ltext.read(), lcnt.rw())->*[] _CCCL_DEVICE(auto th, auto text, auto cnt) {
int local_cnt = 0;
for (size_t i = th.rank(); i < text.size() - 1; i += th.size())
{
/* If the thread encounters the beginning of a new word, increment
* its local counter */
if (!is_alpha(text(i)) && is_alpha(text(i + 1)))
{
local_cnt++;
}
}
// Get a piece of shared memory, and zero it
__shared__ int block_cnt;
block_cnt = 0;
th.inner().sync();
// In every block, partial sums are gathered, and added to the result
// by the first thread of the block.
atomicAdd(&block_cnt, local_cnt);
th.inner().sync();
if (th.inner().rank() == 0)
{
atomicAdd(&cnt(0), block_cnt);
}
};
ctx.finalize();
int ref_cnt = 0;
for (size_t i = 0; i < sizeof(raw_input) - 1; i++)
{
if (!is_alpha(raw_input[i]) && is_alpha(raw_input[i + 1]))
{
ref_cnt++;
}
}
// fprintf(stderr, "Result : found %d words (expected %d)\n", cnt, ref_cnt);
EXPECT(cnt == ref_cnt);
}

View File

@@ -1,68 +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 Counting words in a text using a launch kernel
*/
#include <cuda/experimental/stf.cuh>
using namespace cuda::experimental::stf;
// determines whether the character is alphabetical
__host__ __device__ bool is_alpha(const char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
}
int main()
{
// Paragraph from 'The Raven' by Edgar Allan Poe
// http://en.wikipedia.org/wiki/The_Raven
const char raw_input[] =
" But the raven, sitting lonely on the placid bust, spoke only,\n"
" That one word, as if his soul in that one word he did outpour.\n"
" Nothing further then he uttered - not a feather then he fluttered -\n"
" Till I scarcely more than muttered `Other friends have flown before -\n"
" On the morrow he will leave me, as my hopes have flown before.'\n"
" Then the bird said, `Nevermore.'\n";
context ctx;
size_t text_len = sizeof(raw_input);
auto ltext = ctx.logical_data(const_cast<char*>(&raw_input[0]), {text_len});
auto lcnt = ctx.logical_data(shape_of<scalar_view<int>>());
ctx.parallel_for(box(text_len - 1), ltext.read(), lcnt.reduce(reducer::sum<int>{}))
->*[] _CCCL_DEVICE(size_t i, auto text, int& s) {
/* When we have the beginning of a new word, increment the counter */
if (!is_alpha(text(i)) && is_alpha(text(i + 1)))
{
s++;
}
};
int cnt = ctx.wait(lcnt);
printf("Got %d words.\n", cnt);
ctx.finalize();
int ref_cnt = 0;
for (size_t i = 0; i < sizeof(raw_input) - 1; i++)
{
if (!is_alpha(raw_input[i]) && is_alpha(raw_input[i + 1]))
{
ref_cnt++;
}
}
_CCCL_ASSERT(cnt == ref_cnt, "Count mismatch");
}

View File

@@ -1,159 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef _CUDAX__CONTAINER_VECTOR
#define _CUDAX__CONTAINER_VECTOR
#include <cuda/__cccl_config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <thrust/device_vector.h>
#include <thrust/host_vector.h>
#include <cuda/__stream/stream_ref.h>
#include <cuda/std/__type_traits/maybe_const.h>
#include <cuda/std/span>
#include <cuda/experimental/__detail/utility.cuh>
#include <cuda/experimental/__launch/param_kind.cuh>
#include <cstdio>
namespace cuda::experimental
{
using ::cuda::std::span;
using ::thrust::device_vector;
using ::thrust::host_vector;
template <typename _Ty>
class vector
{
public:
vector() = default;
explicit vector(size_t __n)
: __h_(__n)
{}
_Ty& operator[](size_t __i) noexcept
{
__dirty_ = true;
return __h_[__i];
}
const _Ty& operator[](size_t __i) const noexcept
{
return __h_[__i];
}
private:
void sync_host_to_device([[maybe_unused]] ::cuda::stream_ref __str, __detail::__param_kind __p) const
{
if (__dirty_)
{
if (__p == __detail::__param_kind::_out)
{
// There's no need to copy the data from host to device if the data is
// only going to be written to. We can just allocate the device memory.
__d_.resize(__h_.size());
}
else
{
// TODO: use a memcpy async here
__d_ = __h_;
}
__dirty_ = false;
}
}
void sync_device_to_host(::cuda::stream_ref __str, __detail::__param_kind __p) const
{
if (__p != __detail::__param_kind::_in)
{
// TODO: use a memcpy async here
__str.sync(); // wait for the kernel to finish executing
__h_ = __d_;
}
}
template <__detail::__param_kind _Kind>
class __action //: private __detail::__immovable
{
using __cv_vector = ::cuda::std::__maybe_const<_Kind == __detail::__param_kind::_in, vector>;
public:
explicit __action(::cuda::stream_ref __str, __cv_vector& __v)
: __str_(__str)
, __v_(__v)
{
__v_.sync_host_to_device(__str_, _Kind);
}
__action(__action&&) = delete;
~__action()
{
try
{
__v_.sync_device_to_host(__str_, _Kind);
}
catch (const ::std::exception& e)
{
static_cast<void>(
::fprintf(stderr, "Exception occurred during host to device synchronization: %s\n", e.what()));
}
catch (...)
{
static_cast<void>(::fprintf(stderr, "Unknown exception occurred during host to device synchronization\n"));
}
}
::cuda::std::span<_Ty> transformed_argument() const
{
return {__v_.__d_.data().get(), __v_.__d_.size()};
}
private:
::cuda::stream_ref __str_;
__cv_vector& __v_;
};
[[nodiscard]] friend __action<__detail::__param_kind::_inout>
transform_launch_argument(::cuda::stream_ref __str, vector& __v)
{
return __action<__detail::__param_kind::_inout>{__str, __v};
}
[[nodiscard]] friend __action<__detail::__param_kind::_in>
transform_launch_argument(::cuda::stream_ref __str, const vector& __v)
{
return __action<__detail::__param_kind::_in>{__str, __v};
}
template <__detail::__param_kind _Kind>
[[nodiscard]] friend __action<_Kind>
transform_launch_argument(::cuda::stream_ref __str, __detail::__box<vector, _Kind> __b)
{
return __action<_Kind>{__str, __b.__val};
}
mutable host_vector<_Ty> __h_;
mutable device_vector<_Ty> __d_{};
mutable bool __dirty_ = true;
};
} // namespace cuda::experimental
#endif

View File

@@ -1,127 +0,0 @@
/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Vector addition: C = A + B.
*
* This sample is a very basic sample that implements element by element
* vector addition. It is the same as the sample illustrating Chapter 2
* of the programming guide with some additions like error checking.
*/
#include <cstdio>
// For the CUDA runtime routines (prefixed with "cuda_")
#include <cuda/std/span>
#include <cuda/experimental/launch.cuh>
#include <cuda/experimental/stream.cuh>
#include <cuda_runtime.h>
#include "vector.cuh"
namespace cudax = cuda::experimental;
using cudax::in;
using cudax::out;
/**
* CUDA Kernel Device code
*
* Computes the vector addition of A and B into C. The 3 vectors have the same
* number of elements numElements.
*/
__global__ void vectorAdd(cudax::span<const float> A, cudax::span<const float> B, cudax::span<float> C)
{
int i = static_cast<int>(blockDim.x * blockIdx.x + threadIdx.x);
if (i < A.size())
{
C[i] = A[i] + B[i] + 0.0f;
}
}
/**
* Host main routine
*/
int main()
try
{
// A CUDA stream on which to execute the vector addition kernel
cudax::stream stream(cuda::devices[0]);
// Print the vector length to be used, and compute its size
int numElements = 50000;
printf("[Vector addition of %d elements]\n", numElements);
// Allocate the host vectors
cudax::vector<float> A(numElements); // input
cudax::vector<float> B(numElements); // input
cudax::vector<float> C(numElements); // output
// Initialize the host input vectors
for (int i = 0; i < numElements; ++i)
{
A[i] = static_cast<float>(rand()) / (float) RAND_MAX;
B[i] = static_cast<float>(rand()) / (float) RAND_MAX;
}
// Define the kernel launch parameters
constexpr int threadsPerBlock = 256;
auto config = cuda::distribute<threadsPerBlock>(numElements);
// Launch the vectorAdd kernel
printf("CUDA kernel launch with %zu blocks of %d threads\n", cuda::block.count(cuda::grid, config), threadsPerBlock);
cudax::launch(stream, config, vectorAdd, in(A), in(B), out(C));
printf("waiting for the stream to finish\n");
stream.sync();
printf("verifying the results\n");
// Verify that the result vector is correct
for (int i = 0; i < numElements; ++i)
{
if (fabs(A[i] + B[i] - C[i]) > 1e-5)
{
fprintf(stderr, "Result verification failed at element %d!\n", i);
exit(EXIT_FAILURE);
}
}
printf("Test PASSED\n");
printf("Done\n");
return 0;
}
catch (const std::exception& e)
{
printf("caught an exception: \"%s\"\n", e.what());
}
catch (...)
{
printf("caught an unknown exception\n");
}

View File

@@ -1,318 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef _CUDAX__CONTAINER_GRAPH_BUFFER_CUH
#define _CUDAX__CONTAINER_GRAPH_BUFFER_CUH
#include <cuda/std/detail/__config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#if _CCCL_CTK_AT_LEAST(12, 2)
# include <cuda/__memory_resource/properties.h>
# include <cuda/__runtime/api_wrapper.h>
# include <cuda/__stream/invalid_stream.h>
# include <cuda/__stream/stream_ref.h>
# include <cuda/__type_traits/is_trivially_copyable.h>
# include <cuda/__utility/no_init.h>
# include <cuda/std/__utility/exchange.h>
# include <cuda/std/__utility/move.h>
# include <cuda/std/cstddef>
# include <cuda/std/initializer_list>
# include <cuda/std/span>
# include <cuda/experimental/__graph/copy_bytes.cuh>
# include <cuda/experimental/__graph/fill_bytes.cuh>
# include <cuda/experimental/__graph/graph_memory_resource.cuh>
# include <cuda/experimental/__graph/graph_node_ref.cuh>
# include <cuda/experimental/__graph/path_builder.cuh>
# include <cuda/std/__cccl/prologue.h>
namespace cuda::experimental
{
//! @rst
//! .. _cudax-container-graph-buffer:
//!
//! Graph buffer
//! ------------
//!
//! ``graph_buffer`` provides typed device memory allocated as a CUDA graph node.
//! It mirrors the API of ``cuda::buffer`` but takes a ``path_builder&`` instead of
//! a ``stream_ref``. Allocation inserts a ``cuGraphAddMemAllocNode`` into the graph.
//!
//! Memory can be freed in three ways:
//! - ``destroy(path_builder&)`` — inserts a free node into the graph
//! - ``destroy(stream_ref)`` — frees asynchronously on a stream (for memory that outlives the graph)
//! - Destructor — frees on the stored stream if one was set via ``set_stream()``
//!
//! If the destructor runs with no stream set and the buffer is non-empty, it asserts
//! in debug mode. In release mode the memory leaks.
//!
//! @endrst
//! @tparam _Tp The element type stored in the buffer. Must be trivially copyable.
template <class _Tp>
class graph_buffer
{
static_assert(::cuda::is_trivially_copyable_v<_Tp>, "graph_buffer requires T to be trivially copyable.");
public:
using value_type = _Tp;
using pointer = _Tp*;
using const_pointer = const _Tp*;
using size_type = ::cuda::std::size_t;
using properties_list = ::cuda::mr::properties_list<::cuda::mr::device_accessible>;
private:
graph_memory_resource __mr_;
size_type __count_ = 0;
_Tp* __buf_ = nullptr;
::cudaStream_t __stream_ = ::cuda::__invalid_stream();
[[nodiscard]] _CCCL_HOST_API pointer __get_data() const noexcept
{
return __buf_;
}
//! @brief Causes the buffer to be treated as a span when passed to cudax::launch.
[[nodiscard]] _CCCL_HOST_API friend auto transform_launch_argument(::cuda::stream_ref, graph_buffer& __self) noexcept
-> ::cuda::std::span<_Tp>
{
return {__self.__get_data(), __self.__count_};
}
//! @brief Causes the buffer to be treated as a const span when passed to cudax::launch.
[[nodiscard]] _CCCL_HOST_API friend auto
transform_launch_argument(::cuda::stream_ref, const graph_buffer& __self) noexcept -> ::cuda::std::span<const _Tp>
{
return {__self.__get_data(), __self.__count_};
}
public:
graph_buffer() = delete;
//! @brief Allocates uninitialized storage for \p __count elements.
_CCCL_HOST_API graph_buffer(path_builder& __pb, graph_memory_resource __mr, size_type __count, ::cuda::no_init_t)
: __mr_(::cuda::std::move(__mr))
, __count_(__count)
, __buf_(__count_ == 0 ? nullptr : static_cast<_Tp*>(__mr_.allocate(__pb, __count_ * sizeof(_Tp), alignof(_Tp))))
{}
//! @brief Allocates storage and fills with \p __value.
_CCCL_HOST_API graph_buffer(path_builder& __pb, graph_memory_resource __mr, size_type __count, const _Tp& __value)
: __mr_(::cuda::std::move(__mr))
, __count_(__count)
, __buf_(__count_ == 0 ? nullptr : static_cast<_Tp*>(__mr_.allocate(__pb, __count_ * sizeof(_Tp), alignof(_Tp))))
{
if (__count_ > 0)
{
if constexpr (sizeof(_Tp) == 1)
{
::cuda::std::uint8_t __byte_val =
static_cast<::cuda::std::uint8_t>(reinterpret_cast<const unsigned char&>(__value));
::cuda::experimental::fill_bytes(__pb, ::cuda::std::span<_Tp>(__get_data(), __count_), __byte_val);
}
else
{
// TODO: support non-zero multi-byte values via a kernel node
::cuda::experimental::fill_bytes(
__pb, ::cuda::std::span<_Tp>(__get_data(), __count_), static_cast<::cuda::std::uint8_t>(0));
}
}
}
//! @brief Allocates storage and copies from a contiguous span.
_CCCL_HOST_API graph_buffer(path_builder& __pb, graph_memory_resource __mr, ::cuda::std::span<const _Tp> __src)
: __mr_(::cuda::std::move(__mr))
, __count_(__src.size())
, __buf_(__count_ == 0 ? nullptr : static_cast<_Tp*>(__mr_.allocate(__pb, __count_ * sizeof(_Tp), alignof(_Tp))))
{
if (__count_ > 0)
{
::cuda::experimental::copy_bytes(__pb, __src, ::cuda::std::span<_Tp>{__get_data(), __count_});
}
}
//! @brief Allocates storage and copies from an initializer list.
_CCCL_HOST_API graph_buffer(path_builder& __pb, graph_memory_resource __mr, ::cuda::std::initializer_list<_Tp> __ilist)
: graph_buffer(__pb, ::cuda::std::move(__mr), ::cuda::std::span<const _Tp>{__ilist.begin(), __ilist.size()})
{}
graph_buffer(const graph_buffer&) = delete;
graph_buffer& operator=(const graph_buffer&) = delete;
//! @brief Move-constructs from another graph_buffer.
_CCCL_HOST_API graph_buffer(graph_buffer&& __other) noexcept
: __mr_(::cuda::std::move(__other.__mr_))
, __count_(::cuda::std::exchange(__other.__count_, 0))
, __buf_(::cuda::std::exchange(__other.__buf_, nullptr))
, __stream_(::cuda::std::exchange(__other.__stream_, ::cuda::__invalid_stream()))
{}
//! @brief Move-assigns from another graph_buffer.
_CCCL_HOST_API graph_buffer& operator=(graph_buffer&& __other) noexcept
{
if (this != &__other)
{
_CCCL_ASSERT(__buf_ == nullptr || __stream_ != ::cuda::__invalid_stream(),
"graph_buffer move-assigned over non-empty buffer with no stream set");
if (__buf_ != nullptr && __stream_ != ::cuda::__invalid_stream())
{
destroy(::cuda::stream_ref{__stream_});
}
__mr_ = ::cuda::std::move(__other.__mr_);
__count_ = ::cuda::std::exchange(__other.__count_, 0);
__buf_ = ::cuda::std::exchange(__other.__buf_, nullptr);
__stream_ = ::cuda::std::exchange(__other.__stream_, ::cuda::__invalid_stream());
}
return *this;
}
//! @brief Destructor. Frees device memory on the stored stream if one was set.
_CCCL_HOST_API ~graph_buffer()
{
if (__buf_ != nullptr)
{
_CCCL_ASSERT(__stream_ != ::cuda::__invalid_stream(),
"graph_buffer destroyed with live memory but no stream set. "
"Call set_stream(), destroy(stream_ref), or destroy(path_builder&) before destruction.");
if (__stream_ != ::cuda::__invalid_stream())
{
destroy(::cuda::stream_ref{__stream_});
}
}
}
//! @brief Set the stream to use for automatic cleanup in the destructor.
_CCCL_HOST_API void set_stream(::cuda::stream_ref __stream) noexcept
{
__stream_ = __stream.get();
}
//! @brief Returns the stream set for automatic cleanup.
[[nodiscard]] _CCCL_HOST_API ::cuda::stream_ref stream() const noexcept
{
return ::cuda::stream_ref{__stream_};
}
//! @brief Insert a free node into the graph to deallocate the buffer.
_CCCL_HOST_API graph_node_ref destroy(path_builder& __pb)
{
if (__buf_ == nullptr)
{
return graph_node_ref{};
}
__mr_.deallocate(__pb, __buf_, __count_ * sizeof(_Tp), alignof(_Tp));
auto __free_node = __pb.get_dependencies()[0];
__buf_ = nullptr;
__count_ = 0;
return graph_node_ref{__free_node, __pb.get_native_graph_handle()};
}
//! @brief Free the buffer's device memory asynchronously on a stream.
_CCCL_HOST_API void destroy(::cuda::stream_ref __stream)
{
if (__buf_ != nullptr)
{
__mr_.deallocate(__stream, __buf_, __count_ * sizeof(_Tp), alignof(_Tp));
__buf_ = nullptr;
__count_ = 0;
}
}
[[nodiscard]] _CCCL_HOST_API pointer data() noexcept
{
return __get_data();
}
[[nodiscard]] _CCCL_HOST_API const_pointer data() const noexcept
{
return __get_data();
}
[[nodiscard]] _CCCL_HOST_API pointer begin() noexcept
{
return __get_data();
}
[[nodiscard]] _CCCL_HOST_API const_pointer begin() const noexcept
{
return __get_data();
}
[[nodiscard]] _CCCL_HOST_API pointer end() noexcept
{
return __get_data() + __count_;
}
[[nodiscard]] _CCCL_HOST_API const_pointer end() const noexcept
{
return __get_data() + __count_;
}
[[nodiscard]] _CCCL_HOST_API constexpr size_type size() const noexcept
{
return __count_;
}
[[nodiscard]] _CCCL_HOST_API constexpr size_type size_bytes() const noexcept
{
return __count_ * sizeof(_Tp);
}
[[nodiscard]] _CCCL_HOST_API constexpr bool empty() const noexcept
{
return __count_ == 0;
}
[[nodiscard]] _CCCL_HOST_API const graph_memory_resource& memory_resource() const noexcept
{
return __mr_;
}
};
//! @brief Create a graph_buffer with uninitialized storage.
template <class _Tp>
[[nodiscard]] _CCCL_HOST_API graph_buffer<_Tp>
make_buffer(path_builder& __pb, graph_memory_resource __mr, ::cuda::std::size_t __count, ::cuda::no_init_t)
{
return graph_buffer<_Tp>{__pb, ::cuda::std::move(__mr), __count, ::cuda::no_init};
}
//! @brief Create a graph_buffer filled with a value.
template <class _Tp>
[[nodiscard]] _CCCL_HOST_API graph_buffer<_Tp>
make_buffer(path_builder& __pb, graph_memory_resource __mr, ::cuda::std::size_t __count, const _Tp& __value)
{
return graph_buffer<_Tp>{__pb, ::cuda::std::move(__mr), __count, __value};
}
//! @brief Create a graph_buffer from a span of data.
template <class _Tp>
[[nodiscard]] _CCCL_HOST_API graph_buffer<_Tp>
make_buffer(path_builder& __pb, graph_memory_resource __mr, ::cuda::std::span<const _Tp> __src)
{
return graph_buffer<_Tp>{__pb, ::cuda::std::move(__mr), __src};
}
} // namespace cuda::experimental
# include <cuda/std/__cccl/epilogue.h>
#endif // _CCCL_CTK_AT_LEAST(12, 2)
#endif // _CUDAX__CONTAINER_GRAPH_BUFFER_CUH

View File

@@ -1,292 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2025 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef __CUDAX__CONTAINERS_UNINITIALIZED_BUFFER_H
#define __CUDAX__CONTAINERS_UNINITIALIZED_BUFFER_H
#include <cuda/std/detail/__config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <cuda/__memory_resource/any_resource.h>
#include <cuda/__memory_resource/properties.h>
#include <cuda/std/__memory/align.h>
#include <cuda/std/__new/launder.h>
#include <cuda/std/__type_traits/type_set.h>
#include <cuda/std/__utility/exchange.h>
#include <cuda/std/__utility/move.h>
#include <cuda/std/__utility/swap.h>
#include <cuda/std/span>
#include <cuda/std/__cccl/prologue.h>
//! @file
//! The \c uninitialized_buffer class provides a typed buffer allocated from a given memory resource.
namespace cuda::experimental
{
//! @rst
//! .. _cudax-containers-uninitialized-buffer:
//!
//! Uninitialized type-safe memory storage
//! ---------------------------------------
//!
//! ``uninitialized_buffer`` provides a typed buffer allocated from a given :ref:`memory resource
//! <libcudacxx-extended-api-memory-resources-resource>`. It handles alignment and release of the allocation.
//! The memory is uninitialized, so that a user needs to ensure elements are properly constructed.
//!
//! In addition to being type-safe, ``uninitialized_buffer`` also takes a set of :ref:`properties
//! <libcudacxx-extended-api-memory-resources-properties>` to ensure that e.g. execution space constraints are checked
//! at compile time. However, we can only forward stateless properties. If a user wants to use a stateful one, then they
//! need to implement :ref:`get_property(const device_buffer&, Property)
//! <libcudacxx-extended-api-memory-resources-properties>`.
//!
//! @endrst
//! @tparam _Tp the type to be stored in the buffer
//! @tparam _Properties... The properties the allocated memory satisfies
template <class _Tp, class... _Properties>
class uninitialized_buffer
{
private:
static_assert(::cuda::mr::__contains_execution_space_property<_Properties...>,
"The properties of cuda::experimental::uninitialized_buffer must contain at least one execution space "
"property!");
using __resource = ::cuda::mr::any_synchronous_resource<_Properties...>;
__resource __mr_;
size_t __count_ = 0;
void* __buf_ = nullptr;
template <class, class...>
friend class uninitialized_buffer;
//! @brief Helper to check whether a different buffer still satisfies all properties of this one
template <class... _OtherProperties>
static constexpr bool __properties_match =
!::cuda::std::is_same_v<::cuda::std::__make_type_set<_Properties...>,
::cuda::std::__make_type_set<_OtherProperties...>>
&& ::cuda::std::__type_set_contains_v<::cuda::std::__make_type_set<_OtherProperties...>, _Properties...>;
//! @brief Determines the allocation size given the alignment and size of `T`
[[nodiscard]] _CCCL_HIDE_FROM_ABI static constexpr size_t __get_allocation_size(const size_t __count) noexcept
{
constexpr size_t __alignment = alignof(_Tp);
return (__count * sizeof(_Tp) + (__alignment - 1)) & ~(__alignment - 1);
}
//! @brief Determines the properly aligned start of the buffer given the alignment and size of `T`
[[nodiscard]] _CCCL_HIDE_FROM_ABI _Tp* __get_data() const noexcept
{
constexpr size_t __alignment = alignof(_Tp);
size_t __space = __get_allocation_size(__count_);
void* __ptr = __buf_;
return ::cuda::std::launder(
static_cast<_Tp*>(::cuda::std::align(__alignment, __count_ * sizeof(_Tp), __ptr, __space)));
}
//! @brief Causes the buffer to be treated as a span when passed to cudax::launch.
//! @pre The buffer must have the cuda::mr::device_accessible property.
template <class _Tp2 = _Tp>
[[nodiscard]] _CCCL_HIDE_FROM_ABI friend auto
transform_launch_argument(::cuda::stream_ref, uninitialized_buffer& __self) noexcept
_CCCL_TRAILING_REQUIRES(::cuda::std::span<_Tp>)(
::cuda::std::same_as<_Tp, _Tp2>&& ::cuda::std::__is_included_in_v<::cuda::mr::device_accessible, _Properties...>)
{
return {__self.__get_data(), __self.size()};
}
//! @brief Causes the buffer to be treated as a span when passed to cudax::launch
//! @pre The buffer must have the cuda::mr::device_accessible property.
template <class _Tp2 = _Tp>
[[nodiscard]] _CCCL_HIDE_FROM_ABI friend auto
transform_launch_argument(::cuda::stream_ref, const uninitialized_buffer& __self) noexcept
_CCCL_TRAILING_REQUIRES(::cuda::std::span<const _Tp>)(
::cuda::std::same_as<_Tp, _Tp2>&& ::cuda::std::__is_included_in_v<::cuda::mr::device_accessible, _Properties...>)
{
return {__self.__get_data(), __self.size()};
}
public:
using value_type = _Tp;
using reference = _Tp&;
using const_reference = const _Tp&;
using pointer = _Tp*;
using const_pointer = const _Tp*;
using size_type = size_t;
//! @brief Constructs an \c uninitialized_buffer and allocates sufficient storage for \p __count elements through
//! \p __mr
//! @param __mr The memory resource to allocate the buffer with.
//! @param __count The desired size of the buffer.
//! @note Depending on the alignment requirements of `T` the size of the underlying allocation might be larger
//! than `count * sizeof(T)`.
//! @note Only allocates memory when \p __count > 0
_CCCL_HIDE_FROM_ABI uninitialized_buffer(__resource __mr, const size_t __count)
: __mr_(::cuda::std::move(__mr))
, __count_(__count)
, __buf_(__count_ == 0 ? nullptr : __mr_.allocate_sync(__get_allocation_size(__count_), alignof(_Tp)))
{}
_CCCL_HIDE_FROM_ABI uninitialized_buffer(const uninitialized_buffer&) = delete;
_CCCL_HIDE_FROM_ABI uninitialized_buffer& operator=(const uninitialized_buffer&) = delete;
//! @brief Move-constructs a \c uninitialized_buffer from \p __other
//! @param __other Another \c uninitialized_buffer
//! Takes ownership of the allocation in \p __other and resets it
_CCCL_HIDE_FROM_ABI uninitialized_buffer(uninitialized_buffer&& __other) noexcept
: __mr_(::cuda::std::move(__other.__mr_))
, __count_(::cuda::std::exchange(__other.__count_, 0))
, __buf_(::cuda::std::exchange(__other.__buf_, nullptr))
{}
//! @brief Move-constructs a \c uninitialized_buffer from another \c uninitialized_buffer with matching properties
//! @param __other Another \c uninitialized_buffer
//! Takes ownership of the allocation in \p __other and resets it
_CCCL_TEMPLATE(class... _OtherProperties)
_CCCL_REQUIRES(__properties_match<_OtherProperties...>)
_CCCL_HIDE_FROM_ABI uninitialized_buffer(uninitialized_buffer<_Tp, _OtherProperties...>&& __other) noexcept
: __mr_(::cuda::std::move(__other.__mr_))
, __count_(::cuda::std::exchange(__other.__count_, 0))
, __buf_(::cuda::std::exchange(__other.__buf_, nullptr))
{}
//! @brief Move-assigns a \c uninitialized_buffer from \p __other
//! @param __other Another \c uninitialized_buffer
//! Deallocates the current allocation and then takes ownership of the allocation in \p __other and resets it
_CCCL_HIDE_FROM_ABI uninitialized_buffer& operator=(uninitialized_buffer&& __other) noexcept
{
if (this == ::cuda::std::addressof(__other))
{
return *this;
}
if (__buf_)
{
__mr_.deallocate_sync(__buf_, __get_allocation_size(__count_), alignof(_Tp));
}
__mr_ = ::cuda::std::move(__other.__mr_);
__count_ = ::cuda::std::exchange(__other.__count_, 0);
__buf_ = ::cuda::std::exchange(__other.__buf_, nullptr);
return *this;
}
//! @brief Destroys an \c uninitialized_buffer, deallocates the buffer and destroys the memory resource
//! @warning destroy does not destroy any objects that may or may not reside within the buffer. It is the
//! user's responsibility to ensure that all objects within the buffer have been properly destroyed.
_CCCL_HIDE_FROM_ABI void destroy()
{
if (__buf_)
{
__mr_.deallocate_sync(__buf_, __get_allocation_size(__count_), alignof(_Tp));
__buf_ = nullptr;
__count_ = 0;
}
auto __tmp_mr = ::cuda::std::move(__mr_);
}
//! @brief Destroys an \c uninitialized_buffer, deallocates the buffer and destroys the memory resource
//! @warning The destructor does not destroy any objects that may or may not reside within the buffer. It is the
//! user's responsibility to ensure that all objects within the buffer have been properly destroyed.
_CCCL_HIDE_FROM_ABI ~uninitialized_buffer()
{
destroy();
}
//! @brief Returns an aligned pointer to the first element in the buffer
[[nodiscard]] _CCCL_HIDE_FROM_ABI pointer begin() noexcept
{
return __get_data();
}
//! @overload
[[nodiscard]] _CCCL_HIDE_FROM_ABI const_pointer begin() const noexcept
{
return __get_data();
}
//! @brief Returns an aligned pointer to the element following the last element of the buffer.
//! This element acts as a placeholder; attempting to access it results in undefined behavior.
[[nodiscard]] _CCCL_HIDE_FROM_ABI pointer end() noexcept
{
return __get_data() + __count_;
}
//! @overload
[[nodiscard]] _CCCL_HIDE_FROM_ABI const_pointer end() const noexcept
{
return __get_data() + __count_;
}
//! @brief Returns an aligned pointer to the first element in the buffer
[[nodiscard]] _CCCL_HIDE_FROM_ABI pointer data() noexcept
{
return __get_data();
}
//! @overload
[[nodiscard]] _CCCL_HIDE_FROM_ABI const_pointer data() const noexcept
{
return __get_data();
}
//! @brief Returns the size of the allocation
[[nodiscard]] _CCCL_HIDE_FROM_ABI constexpr size_type size() const noexcept
{
return __count_;
}
//! @brief Returns the size of the buffer in bytes
[[nodiscard]] _CCCL_HIDE_FROM_ABI constexpr size_type size_bytes() const noexcept
{
return __count_ * sizeof(_Tp);
}
//! @rst
//! Returns a \c const reference to the :ref:`any_resource <libcudacxx-memory-resource-any-resource>`
//! that holds the memory resource used to allocate the buffer
//! @endrst
[[nodiscard]] _CCCL_HIDE_FROM_ABI const __resource& memory_resource() const noexcept
{
return __mr_;
}
//! @brief Forwards the passed Properties
_CCCL_TEMPLATE(class _Property)
_CCCL_REQUIRES((!property_with_value<_Property>) _CCCL_AND ::cuda::std::__is_included_in_v<_Property, _Properties...>)
_CCCL_HIDE_FROM_ABI friend constexpr void get_property(const uninitialized_buffer&, _Property) noexcept {}
//! @brief Internal method to grow the allocation to a new size \p __count.
//! @param __count The new size of the allocation.
//! @return An \c uninitialized_buffer that holds the previous allocation
//! @warning This buffer must outlive the returned buffer
_CCCL_HIDE_FROM_ABI uninitialized_buffer __replace_allocation(const size_t __count)
{
// Create a new buffer with a reference to the stored memory resource and swap allocation information
uninitialized_buffer __ret{::cuda::mr::synchronous_resource_ref<_Properties...>{__mr_}, __count};
::cuda::std::swap(__count_, __ret.__count_);
::cuda::std::swap(__buf_, __ret.__buf_);
return __ret;
}
};
template <class _Tp>
using uninitialized_device_buffer = uninitialized_buffer<_Tp, ::cuda::mr::device_accessible>;
} // namespace cuda::experimental
#include <cuda/std/__cccl/epilogue.h>
#endif //__CUDAX__CONTAINERS_UNINITIALIZED_BUFFER_H

View File

@@ -1,137 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef _CUDA_EXPERIMENTAL___COOP_ANY_OF_CUH
#define _CUDA_EXPERIMENTAL___COOP_ANY_OF_CUH
#include <cuda/std/detail/__config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <cuda/std/__concepts/concept_macros.h>
#include <cuda/std/__cstddef/types.h>
#include <cuda/std/__functional/operations.h>
#include <cuda/std/__numeric/reduce.h>
#include <cuda/std/__type_traits/integral_constant.h>
#include <cuda/std/__type_traits/is_same.h>
#include <cuda/std/optional>
#include <cuda/experimental/__utility/result_policy.cuh>
#include <cuda/experimental/group.cuh>
#include <cuda/std/__cccl/prologue.h>
#if !defined(_CCCL_DOXYGEN_INVOKED)
// todo: Can we make any_of be implemented as reduce(group, data, cuda::std::logical_or{})?
namespace cuda::experimental::coop
{
template <bool _Dummy = false>
_CCCL_DEVICE_API auto __any_of_impl(...)
{
static_assert(_Dummy, "cudax::coop::any_of is not supported for the group");
}
template <bool _Broadcasted, class _Hierarchy>
[[nodiscard]] _CCCL_DEVICE_API auto
__any_of_impl(::cuda::std::bool_constant<_Broadcasted>, const this_thread<_Hierarchy>&, bool __thread_data)
{
if constexpr (_Broadcasted)
{
return __thread_data;
}
else
{
return ::cuda::std::optional{__thread_data};
}
}
_CCCL_TEMPLATE(bool _Broadcasted, class _Group)
_CCCL_REQUIRES(is_group<_Group> _CCCL_AND ::cuda::std::is_same_v<typename _Group::level_type, warp_level>)
[[nodiscard]] _CCCL_DEVICE_API auto
__any_of_impl(::cuda::std::bool_constant<_Broadcasted>, const _Group& __group, bool __thread_data) noexcept
{
const auto& __mapping_result = __group.__mapping_result();
const auto __result = static_cast<bool>(::__any_sync(__mapping_result.lane_mask().value(), __thread_data));
if constexpr (_Broadcasted)
{
return __result;
}
else
{
return (gpu_thread.is_root_rank(__group)) ? ::cuda::std::optional{__result} : ::cuda::std::nullopt;
}
}
_CCCL_TEMPLATE(class _Group, class _Tp)
_CCCL_REQUIRES(::cuda::std::is_same_v<_Tp, bool>)
[[nodiscard]] _CCCL_DEVICE_API ::cuda::std::optional<bool> any_of(const _Group& __group, _Tp __thread_data)
{
_CCCL_ASSERT(gpu_thread.is_part_of(__group), "Only threads that are part of the group can call cudax::coop::any_of");
return ::cuda::experimental::coop::__any_of_impl(::cuda::std::false_type{}, __group, __thread_data);
}
_CCCL_TEMPLATE(class _Group, class _Tp)
_CCCL_REQUIRES(::cuda::std::is_same_v<_Tp, bool>)
[[nodiscard]] _CCCL_DEVICE_API bool any_of(broadcasted_t, const _Group& __group, _Tp __thread_data)
{
_CCCL_ASSERT(gpu_thread.is_part_of(__group), "Only threads that are part of the group can call cudax::coop::any_of");
return ::cuda::experimental::coop::__any_of_impl(::cuda::std::true_type{}, __group, __thread_data);
}
_CCCL_TEMPLATE(class _Group, class _Tp, ::cuda::std::size_t _Np)
_CCCL_REQUIRES(::cuda::std::is_same_v<_Tp, bool>)
[[nodiscard]] _CCCL_DEVICE_API ::cuda::std::optional<bool> any_of(const _Group& __group, _Tp (&__thread_data)[_Np])
{
_CCCL_ASSERT(gpu_thread.is_part_of(__group), "Only threads that are part of the group can call cudax::coop::any_of");
return ::cuda::experimental::coop::any_of(
__group, ::cuda::std::reduce(__thread_data, __thread_data + _Np, false, ::cuda::std::logical_or<bool>{}));
}
_CCCL_TEMPLATE(class _Group, class _Tp, ::cuda::std::size_t _Np)
_CCCL_REQUIRES(::cuda::std::is_same_v<_Tp, bool>)
[[nodiscard]] _CCCL_DEVICE_API bool any_of(broadcasted_t, const _Group& __group, _Tp (&__thread_data)[_Np])
{
_CCCL_ASSERT(gpu_thread.is_part_of(__group), "Only threads that are part of the group can call cudax::coop::any_of");
return ::cuda::experimental::coop::any_of(
broadcasted,
__group,
::cuda::std::reduce(__thread_data, __thread_data + _Np, false, ::cuda::std::logical_or<bool>{}));
}
_CCCL_TEMPLATE(class _Group, class _Tp)
_CCCL_REQUIRES((!::cuda::std::is_same_v<_Tp, bool>) )
auto any_of(const _Group& __group, _Tp __thread_data) = delete;
_CCCL_TEMPLATE(class _Group, class _Tp)
_CCCL_REQUIRES((!::cuda::std::is_same_v<_Tp, bool>) )
auto any_of(broadcasted_t, const _Group& __group, _Tp __thread_data) = delete;
_CCCL_TEMPLATE(class _Group, class _Tp, ::cuda::std::size_t _Np)
_CCCL_REQUIRES((!::cuda::std::is_same_v<_Tp, bool>) )
auto any_of(const _Group& __group, _Tp (&__thread_data)[_Np]) = delete;
_CCCL_TEMPLATE(class _Group, class _Tp, ::cuda::std::size_t _Np)
_CCCL_REQUIRES((!::cuda::std::is_same_v<_Tp, bool>) )
auto any_of(broadcasted_t, const _Group& __group, _Tp (&__thread_data)[_Np]) = delete;
} // namespace cuda::experimental::coop
#endif // !_CCCL_DOXYGEN_INVOKED
#include <cuda/std/__cccl/epilogue.h>
#endif // _CUDA_EXPERIMENTAL___COOP_ANY_OF_CUH

View File

@@ -1,414 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef _CUDA_EXPERIMENTAL___COOP_REDUCE_CUH
#define _CUDA_EXPERIMENTAL___COOP_REDUCE_CUH
#include <cuda/std/detail/__config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <cub/block/block_reduce.cuh>
#include <cub/thread/thread_reduce.cuh>
#include <cub/warp/warp_reduce.cuh>
#include <cuda/__cmath/ceil_div.h>
#include <cuda/__cmath/pow2.h>
#include <cuda/__functional/operator_properties.h>
#include <cuda/__ptx/instructions/get_sreg.h>
#include <cuda/__warp/warp_shuffle.h>
#include <cuda/std/__cstddef/types.h>
#include <cuda/std/__functional/operations.h>
#include <cuda/std/__type_traits/integral_constant.h>
#include <cuda/std/array>
#include <cuda/std/optional>
#include <cuda/experimental/__coop/shuffle_down.cuh>
#include <cuda/experimental/__utility/result_policy.cuh>
#include <cuda/experimental/group.cuh>
#include <cuda/std/__cccl/prologue.h>
#if !defined(_CCCL_DOXYGEN_INVOKED)
// todo(dabayer): We share the temporary storage in shared/global memory for all reduce invocations. This is a temporary
// state before we make it a parameter.
namespace cuda::experimental::coop
{
template <bool _Dummy = false>
_CCCL_DEVICE_API auto __reduce_impl(...)
{
static_assert(_Dummy, "cudax::coop::reduce is not supported for the group");
}
template <bool _Broadcasted, class _Hierarchy, class _Tp, ::cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API auto __reduce_impl(
::cuda::std::bool_constant<_Broadcasted>, this_thread<_Hierarchy>, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
const auto __result = ::cub::ThreadReduce(__thread_data, __red_fn);
if constexpr (_Broadcasted)
{
return __result;
}
else
{
return ::cuda::std::optional{__result};
}
}
template <bool _Broadcasted, class _Hierarchy, class _Tp, ::cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API auto __reduce_impl(
::cuda::std::bool_constant<_Broadcasted>, this_warp<_Hierarchy> __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
using _BlockExts = decltype(gpu_thread.extents(block, __group.hierarchy()));
constexpr auto __nwarps_in_block =
::cuda::ceil_div(_BlockExts::static_extent(0) * _BlockExts::static_extent(1) * _BlockExts::static_extent(2), 32);
using _WarpReduce = ::cub::WarpReduce<_Tp>;
union _Scratch
{
typename _WarpReduce::TempStorage __warp_reduce_[__nwarps_in_block];
};
__shared__ _Scratch __scratch;
const auto __warp_rank_in_block = __group.rank(block);
const auto __result = _WarpReduce{__scratch.__warp_reduce_[__warp_rank_in_block]}.Reduce(__thread_data, __red_fn);
if constexpr (_Broadcasted)
{
return ::cuda::device::warp_shuffle_idx(__result, 0).data;
}
else
{
return (gpu_thread.is_root_rank(__group)) ? ::cuda::std::optional{__result} : ::cuda::std::nullopt;
}
}
template <bool _Broadcasted, class _Hierarchy, class _Tp, cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API auto __reduce_impl(
::cuda::std::bool_constant<_Broadcasted>, this_block<_Hierarchy> __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
using _BlockExts = decltype(gpu_thread.extents(block, __group.hierarchy()));
static_assert(_BlockExts::rank_dynamic() == 0,
"cuda::coop::reduce requires the block level to have all static extents.");
using _BlockReduce =
::cub::BlockReduce<_Tp,
static_cast<int>(_BlockExts::static_extent(0)),
::cub::BLOCK_REDUCE_WARP_REDUCTIONS,
static_cast<int>(_BlockExts::static_extent(1)),
static_cast<int>(_BlockExts::static_extent(2))>;
union _Scratch
{
typename _BlockReduce::TempStorage __block_reduce_;
_Tp __bcast_;
};
__shared__ _Scratch __scratch;
const auto __result = _BlockReduce{__scratch.__block_reduce_}.Reduce(__thread_data, __red_fn);
if constexpr (_Broadcasted)
{
if (gpu_thread.is_root_rank(__group))
{
__scratch.__bcast_ = __result;
}
__group.sync_aligned();
return __scratch.__bcast_;
}
else
{
return (gpu_thread.is_root_rank(__group)) ? ::cuda::std::optional{__result} : ::cuda::std::nullopt;
}
}
template <bool _Broadcasted, class _Hierarchy, class _Tp, cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API auto __reduce_impl(
::cuda::std::bool_constant<_Broadcasted>, this_cluster<_Hierarchy> __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
using _ClusterExts = decltype(block.extents(cluster, __group.hierarchy()));
static_assert(_ClusterExts::rank_dynamic() == 0,
"cuda::coop::reduce requires the cluster level to have all static extents.");
constexpr auto __nblocks_in_cluster =
_ClusterExts::static_extent(0) * _ClusterExts::static_extent(1) * _ClusterExts::static_extent(2);
if constexpr (__nblocks_in_cluster == 1)
{
return ::cuda::experimental::coop::__reduce_impl(
::cuda::std::bool_constant<_Broadcasted>{}, this_block{__group.hierarchy()}, __thread_data, __red_fn);
}
else
{
using _BlockExts = decltype(gpu_thread.extents(block, __group.hierarchy()));
static_assert(_BlockExts::rank_dynamic() == 0,
"cuda::coop::reduce requires the block level to have all static extents.");
using _BlockReduce =
::cub::BlockReduce<_Tp,
static_cast<int>(_BlockExts::static_extent(0)),
::cub::BLOCK_REDUCE_WARP_REDUCTIONS,
static_cast<int>(_BlockExts::static_extent(1)),
static_cast<int>(_BlockExts::static_extent(2))>;
using _RootWarpReduce = ::cub::WarpReduce<_Tp>;
struct _RootScratch
{
_Tp __partials_[__nblocks_in_cluster];
typename _RootWarpReduce::TempStorage __warp_reduce_;
_Tp __bcast_;
};
union _Scratch
{
typename _BlockReduce::TempStorage __block_;
_RootScratch __root_;
};
__shared__ _Scratch __scratch;
const auto __partial = _BlockReduce{__scratch.__block_}.Reduce(__thread_data, __red_fn);
_Tp __result{};
NV_IF_TARGET(NV_PROVIDES_SM_90, ({
const auto __root_scratch = static_cast<_Scratch*>(::__cluster_map_shared_rank(&__scratch, 0));
auto& __partials_root = __root_scratch->__root_.__partials_;
__group.sync_aligned();
if (gpu_thread.is_root_rank(this_block{__group.hierarchy()}))
{
__partials_root[block.rank(__group)] = __partial;
}
__group.sync_aligned();
if (warp.is_root_rank(__group))
{
this_warp __warp{__group.hierarchy()};
const auto __value = (gpu_thread.rank(__warp) < __nblocks_in_cluster)
? __scratch.__root_.__partials_[gpu_thread.rank(__warp)]
: ::cuda::identity_element<_RedFn, _Tp>();
__result = _RootWarpReduce{__scratch.__root_.__warp_reduce_}.Reduce(__value, __red_fn);
}
if constexpr (_Broadcasted)
{
if (gpu_thread.is_root_rank(__group))
{
__scratch.__root_.__bcast_ = __result;
}
__group.sync_aligned();
__result = __root_scratch->__root_.__bcast_;
// Wait until all threads are done reading the result.
__group.sync_aligned();
}
}))
if constexpr (_Broadcasted)
{
return __result;
}
else
{
return (gpu_thread.is_root_rank(__group)) ? ::cuda::std::optional{__result} : ::cuda::std::nullopt;
}
}
}
template <class _Tp, ::cuda::std::size_t _Np>
_CCCL_DEVICE ::cuda::std::array<_Tp, _Np> __reduce_grid_partials;
template <bool _Broadcasted, class _Hierarchy, class _Tp, cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API auto __reduce_impl(
::cuda::std::bool_constant<_Broadcasted>, this_grid<_Hierarchy> __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
using _GridExts = decltype(cluster.extents(grid, __group.hierarchy()));
static_assert(_GridExts::rank_dynamic() == 0,
"cuda::coop::reduce requires the grid level to have all static extents.");
constexpr auto __nclusters_in_grid =
_GridExts::static_extent(0) * _GridExts::static_extent(1) * _GridExts::static_extent(2);
this_cluster __cluster{__group.hierarchy()};
const auto __partial =
::cuda::experimental::coop::__reduce_impl(::cuda::std::false_type{}, __cluster, __thread_data, __red_fn);
if (gpu_thread.is_root_rank(__cluster))
{
__reduce_grid_partials<_Tp, __nclusters_in_grid>[cluster.rank(__group)] = __partial.value();
}
__group.sync_aligned();
::cuda::std::optional<_Tp> __result;
if (block.is_root_rank(__group))
{
this_block __block{__group.hierarchy()};
constexpr auto __npartials_per_thread = ::cuda::ceil_div(__nclusters_in_grid, gpu_thread.static_count(__block));
_Tp __thread_partials[__npartials_per_thread];
const auto __offset = gpu_thread.rank(__block) * __npartials_per_thread;
// todo(dabayer): This is not the most efficient way to load values, it doesn't take into account element size and
// reads N consecutive elements by 1 thread.
for (unsigned __i = 0; __i < __npartials_per_thread; ++__i)
{
__thread_partials[__i] =
(__offset + __i < __nclusters_in_grid)
? __reduce_grid_partials<_Tp, __nclusters_in_grid>[__offset + __i]
: ::cuda::identity_element<_RedFn, _Tp>();
}
__result =
::cuda::experimental::coop::__reduce_impl(::cuda::std::false_type{}, __block, __thread_partials, __red_fn);
}
if constexpr (_Broadcasted)
{
if (gpu_thread.is_root_rank(__group))
{
__reduce_grid_partials<_Tp, __nclusters_in_grid>[0] = *__result;
}
__group.sync_aligned();
const auto __result2 = __reduce_grid_partials<_Tp, __nclusters_in_grid>[0];
// Wait until all threads are done reading the result.
__group.sync_aligned();
return __result2;
}
else
{
return __result;
}
}
_CCCL_TEMPLATE(bool _Broadcasted, class _Group, class _Tp, ::cuda::std::size_t _Np, class _RedFn)
_CCCL_REQUIRES(::cuda::std::is_same_v<thread_level, typename _Group::unit_type>
_CCCL_AND ::cuda::std::is_same_v<warp_level, typename _Group::level_type>)
[[nodiscard]] _CCCL_DEVICE_API auto
__reduce_impl(::cuda::std::bool_constant<_Broadcasted>, _Group __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
using _MappingResult = typename _Group::__mapping_result_type;
const auto& __mapping_result = __group.__mapping_result();
const auto __lane_mask = __mapping_result.lane_mask();
const auto __lane = ::cuda::ptx::get_sreg_laneid();
auto __result = ::cub::ThreadReduce(__thread_data, __red_fn);
_CCCL_PRAGMA_UNROLL_FULL()
for (unsigned __stride = 1; __stride < ::cuda::next_power_of_two(__mapping_result.unit_count()); __stride *= 2)
{
const auto __other = ::cuda::experimental::coop::shuffle_down(__group, __result, __stride);
if (__other.has_value())
{
__result = __red_fn(__result, *__other);
}
}
if constexpr (_Broadcasted)
{
return ::cuda::device::warp_shuffle_idx(__result, ::cuda::std::countr_zero(__lane_mask.value()), __lane_mask.value())
.data;
}
else
{
return (__mapping_result.unit_rank() == 0) ? ::cuda::std::optional{__result} : ::cuda::std::nullopt;
}
}
_CCCL_TEMPLATE(bool _Broadcasted, class _Group, class _Tp, ::cuda::std::size_t _Np, class _RedFn)
_CCCL_REQUIRES(::cuda::std::is_same_v<warp_level, typename _Group::unit_type>
_CCCL_AND ::cuda::std::is_same_v<block_level, typename _Group::level_type>)
[[nodiscard]] _CCCL_DEVICE_API auto
__reduce_impl(::cuda::std::bool_constant<_Broadcasted>, _Group __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
constexpr auto __nwarps_in_group = warp.static_count(__group);
static_assert(__nwarps_in_group != ::cuda::std::dynamic_extent,
"cuda::coop::reduce requires the group to have statically known size");
using _WarpReduce = ::cub::WarpReduce<_Tp>;
struct _AdditionalScratch
{
_Tp __partials_[__nwarps_in_group];
_Tp __bcast_;
};
union _Scratch
{
typename _WarpReduce::TempStorage __warp_reduce_[__nwarps_in_group];
_AdditionalScratch __additional_;
};
__shared__ _Scratch __scratch;
const auto __partial = _WarpReduce{__scratch.__warp_reduce_[warp.rank(__group)]}.Reduce(__thread_data, __red_fn);
__group.sync_aligned();
this_warp __warp{__group.hierarchy()};
if (gpu_thread.is_root_rank(__warp))
{
__scratch.__additional_.__partials_[warp.rank(__group)] = __partial;
}
__group.sync_aligned();
_Tp __result;
if (warp.is_root_rank(__group))
{
const auto __value = (gpu_thread.rank(__warp) < __nwarps_in_group)
? __scratch.__additional_.__partials_[gpu_thread.rank(__warp)]
: ::cuda::identity_element<_RedFn, _Tp>();
__result = _WarpReduce{__scratch.__warp_reduce_[0]}.Reduce(__value, __red_fn);
}
if constexpr (_Broadcasted)
{
if (gpu_thread.is_root_rank(__group))
{
__scratch.__additional_.__bcast_ = __result;
}
__group.sync_aligned();
return __scratch.__additional_.__bcast_;
}
else
{
return (gpu_thread.is_root_rank(__group)) ? ::cuda::std::optional{__result} : ::cuda::std::nullopt;
}
}
template <class _Group, class _Tp, ::cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API ::cuda::std::optional<_Tp>
reduce(_Group __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
static_assert(gpu_thread.static_count(__group) != ::cuda::std::dynamic_extent,
"cuda::coop::reduce requires the group to have statically known size");
_CCCL_ASSERT(gpu_thread.is_part_of(__group), "Only threads that are part of the group can call cudax::coop::reduce");
return ::cuda::experimental::coop::__reduce_impl(::cuda::std::false_type{}, __group, __thread_data, __red_fn);
}
template <class _Group, class _Tp, ::cuda::std::size_t _Np, class _RedFn>
[[nodiscard]] _CCCL_DEVICE_API _Tp reduce(broadcasted_t, _Group __group, _Tp (&__thread_data)[_Np], _RedFn __red_fn)
{
static_assert(gpu_thread.static_count(__group) != ::cuda::std::dynamic_extent,
"cuda::coop::reduce requires the group to have statically known size");
_CCCL_ASSERT(gpu_thread.is_part_of(__group), "Only threads that are part of the group can call cudax::coop::reduce");
return ::cuda::experimental::coop::__reduce_impl(::cuda::std::true_type{}, __group, __thread_data, __red_fn);
}
} // namespace cuda::experimental::coop
#endif // !_CCCL_DOXYGEN_INVOKED
#include <cuda/std/__cccl/epilogue.h>
#endif // _CUDA_EXPERIMENTAL___COOP_REDUCE_CUH

View File

@@ -1,85 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef _CUDA_EXPERIMENTAL___COOP_SHUFFLE_CUH
#define _CUDA_EXPERIMENTAL___COOP_SHUFFLE_CUH
#include <cuda/std/detail/__config>
#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header
#include <cuda/__ptx/instructions/get_sreg.h>
#include <cuda/__warp/warp_shuffle.h>
#include <cuda/std/__type_traits/is_same.h>
#include <cuda/experimental/group.cuh>
#include <cuda/std/__cccl/prologue.h>
#if !defined(_CCCL_DOXYGEN_INVOKED)
namespace cuda::experimental::coop
{
template <bool _Dummy = false>
[[nodiscard]] _CCCL_DEVICE_API auto __shuffle_impl(...)
{
static_assert(_Dummy, "cudax::coop::shuffle is not implemented for this group");
}
_CCCL_TEMPLATE(class _Group, class _Tp)
_CCCL_REQUIRES(is_group<_Group> _CCCL_AND ::cuda::std::is_same_v<typename _Group::unit_type, thread_level>
_CCCL_AND ::cuda::std::is_same_v<typename _Group::level_type, warp_level>)
[[nodiscard]] _CCCL_DEVICE_API _Tp __shuffle_impl(const _Group& __group, _Tp __value, unsigned __src_unit_rank) noexcept
{
using _MappingResult = typename _Group::__mapping_result_type;
const auto& __mapping_result = __group.__mapping_result();
_CCCL_ASSERT(__src_unit_rank < __mapping_result.unit_count(),
"invalid __src_unit_rank - must be less than the number of units within the group");
const auto __lane_mask = __mapping_result.lane_mask();
const auto __lane_offset = static_cast<int>(__src_unit_rank) - static_cast<int>(__mapping_result.unit_rank());
unsigned __src_lane{};
if constexpr (_MappingResult::is_always_contiguous())
{
const auto __lane = ::cuda::ptx::get_sreg_laneid();
__src_lane = static_cast<unsigned>(__lane + __lane_offset);
}
else
{
__src_lane = ::__fns(__lane_mask.value(), 0, static_cast<int>(__src_unit_rank) + 1);
}
return ::cuda::device::warp_shuffle_idx(__value, static_cast<int>(__src_lane), __lane_mask.value());
}
//! @brief Shuffles values among units within a group.
//! @param[in] __group The group.
//! @param[in] __value This thread's value to be shuffled.
//! @param[in] __src_unit_rank The rank of the unit whose value should be taken by this unit.
//! @return The value passed to the function by the equivalent thread from the source rank unit.
template <class _Group, class _Tp>
[[nodiscard]] _CCCL_DEVICE_API _Tp shuffle(const _Group& __group, _Tp __value, unsigned __src_unit_rank) noexcept
{
return ::cuda::experimental::coop::__shuffle_impl(__group, __value, __src_unit_rank);
}
} // namespace cuda::experimental::coop
#endif // !_CCCL_DOXYGEN_INVOKED
#include <cuda/std/__cccl/epilogue.h>
#endif // _CUDA_EXPERIMENTAL___COOP_SHUFFLE_CUH

Some files were not shown because too many files have changed in this diff Show More