[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples
变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
async_reduce, custom_temporary_allocation, explicit_cuda_stream,
global_device_vector, range_view, unwrap_pointer, wrap_pointer, device
结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
27/27 tuning headers, 78 benchmarks, 243 tests,
60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
@@ -1,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()
|
||||
@@ -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.
|
||||
@@ -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`.
|
||||
@@ -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()
|
||||
@@ -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)");
|
||||
@@ -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
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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));
|
||||
@@ -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});
|
||||
@@ -1 +0,0 @@
|
||||
cccl_add_subdir_helper(cudax)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 "")
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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>();
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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(°rees[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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user