[INFRA] Import NVIDIA/CCCL upstream as optimization reference library
CCCL (CUDA C++ Core Libraries) provides: - CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk) - Thrust: high-level parallel algorithms (transform_reduce, sort, scan) - libcudacxx: CUDA C++ standard library (atomics, barriers, memory) - cudax: experimental features (memory resources, allocators) - Tuning policies: per-SM hardware-specific algorithm parameters Competition optimization vectors mapped to CCCL: - Output TPS (83% weight): warp_reduce, block_reduce, device_topk - Input TPS (14% weight): device_scan, block_load, prefetch - Cache TPS (3% weight): prefix caching strategy patterns - Memory (0.9 util): pooled/cached/buddy allocators Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only) License: Apache-2.0
This commit is contained in:
110
cccl_upstream/examples/CMakeLists.txt
Normal file
110
cccl_upstream/examples/CMakeLists.txt
Normal file
@@ -0,0 +1,110 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
enable_language(CUDA) # Ensure that CUDA compiler vars are defined
|
||||
|
||||
set(
|
||||
cmake_opts
|
||||
"--log-level=VERBOSE"
|
||||
"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
|
||||
"-DCMAKE_MAKE_PROGRAM=${CMAKE_MAKE_PROGRAM}"
|
||||
"-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
|
||||
"-DCMAKE_CUDA_COMPILER=${CMAKE_CUDA_COMPILER}"
|
||||
"-DCMAKE_CUDA_HOST_COMPILER=${CMAKE_CUDA_HOST_COMPILER}"
|
||||
"-DCMAKE_CUDA_ARCHITECTURES=75"
|
||||
)
|
||||
|
||||
set(
|
||||
CCCL_EXAMPLE_CPM_REPOSITORY
|
||||
"${CCCL_SOURCE_DIR}"
|
||||
CACHE STRING
|
||||
"Git repository used for CPM examples."
|
||||
)
|
||||
set(
|
||||
CCCL_EXAMPLE_CPM_TAG
|
||||
"HEAD"
|
||||
CACHE STRING
|
||||
"Git tag/branch used for CPM examples."
|
||||
)
|
||||
set(
|
||||
CCCL_EXAMPLE_CTEST_COMMAND
|
||||
"${CMAKE_CTEST_COMMAND}"
|
||||
CACHE STRING
|
||||
"CTest command used for CCCL example build-and-tests."
|
||||
)
|
||||
|
||||
set(
|
||||
cmake_cpm_opts
|
||||
"-DCCCL_REPOSITORY=${CCCL_EXAMPLE_CPM_REPOSITORY}"
|
||||
"-DCCCL_TAG=${CCCL_EXAMPLE_CPM_TAG}"
|
||||
)
|
||||
|
||||
if (MSVC)
|
||||
message(STATUS "Skipping example build-and-test CTests on MSVC")
|
||||
return()
|
||||
endif()
|
||||
|
||||
cccl_add_compile_test(
|
||||
test_name
|
||||
cccl.example
|
||||
basic
|
||||
"default"
|
||||
CTEST_COMMAND "${CCCL_EXAMPLE_CTEST_COMMAND}"
|
||||
${cmake_opts}
|
||||
${cmake_cpm_opts}
|
||||
)
|
||||
|
||||
cccl_add_compile_test(
|
||||
test_name
|
||||
cccl.example
|
||||
image_pipeline
|
||||
"default"
|
||||
CTEST_COMMAND "${CCCL_EXAMPLE_CTEST_COMMAND}"
|
||||
${cmake_opts}
|
||||
${cmake_cpm_opts}
|
||||
)
|
||||
|
||||
cccl_add_compile_test(
|
||||
test_name
|
||||
cccl.example
|
||||
cudax
|
||||
"default"
|
||||
CTEST_COMMAND "${CCCL_EXAMPLE_CTEST_COMMAND}"
|
||||
${cmake_opts}
|
||||
${cmake_cpm_opts}
|
||||
)
|
||||
|
||||
cccl_add_compile_test(
|
||||
test_name
|
||||
cccl.example
|
||||
cudax_stf
|
||||
"default"
|
||||
CTEST_COMMAND "${CCCL_EXAMPLE_CTEST_COMMAND}"
|
||||
${cmake_opts}
|
||||
${cmake_cpm_opts}
|
||||
)
|
||||
|
||||
foreach (DEVICE_SYSTEM IN ITEMS CUDA OMP TBB CPP)
|
||||
cccl_add_compile_test(
|
||||
test_name
|
||||
cccl.example
|
||||
thrust_flexible_device_system
|
||||
"${DEVICE_SYSTEM}"
|
||||
CTEST_COMMAND "${CCCL_EXAMPLE_CTEST_COMMAND}"
|
||||
"-DCCCL_THRUST_DEVICE_SYSTEM=${DEVICE_SYSTEM}"
|
||||
${cmake_opts}
|
||||
${cmake_cpm_opts}
|
||||
)
|
||||
endforeach()
|
||||
12
cccl_upstream/examples/README.md
Normal file
12
cccl_upstream/examples/README.md
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
This directory contains examples of how to use CCCL in your project.
|
||||
|
||||
See the `README.md` in each subdirectory for more information.
|
||||
|
||||
To build and run only the examples, run the following commands from the root directory of the repository:
|
||||
|
||||
```bash
|
||||
cmake -S . -B build -DCCCL_ENABLE_EXAMPLES=ON -DCCCL_ENABLE_THRUST=OFF -DCCCL_ENABLE_CUB=OFF -DCCCL_ENABLE_LIBCUDACXX=OFF -DCCCL_ENABLE_TESTING=OFF
|
||||
cmake --build build
|
||||
ctest --test-dir build --output-on-failure
|
||||
```
|
||||
53
cccl_upstream/examples/basic/CMakeLists.txt
Normal file
53
cccl_upstream/examples/basic/CMakeLists.txt
Normal file
@@ -0,0 +1,53 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
project(CCCLDemo CUDA)
|
||||
|
||||
# This example uses the CMake Package Manager (CPM) to simplify fetching CCCL from GitHub
|
||||
# For more information, see https://github.com/cpm-cmake/CPM.cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# We define these as variables so they can be overridden in CI to pull from a PR instead of CCCL `main`
|
||||
# In your project, these variables are unnecessary and you can just use the values directly
|
||||
set(
|
||||
CCCL_REPOSITORY
|
||||
"https://github.com/NVIDIA/cccl"
|
||||
CACHE STRING
|
||||
"Git repository to fetch CCCL from"
|
||||
)
|
||||
set(CCCL_TAG "main" CACHE STRING "Git tag/branch to fetch from CCCL repository")
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
|
||||
CPMAddPackage(NAME CCCL GIT_REPOSITORY "${CCCL_REPOSITORY}" GIT_TAG ${CCCL_TAG})
|
||||
|
||||
# Default to building for the GPU on the current system
|
||||
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
|
||||
# Creates a cmake executable target for the main program
|
||||
add_executable(example_project example.cu)
|
||||
target_compile_features(example_project PUBLIC cuda_std_17)
|
||||
|
||||
# "Links" the CCCL Cmake target to the `example_project` executable. This configures everything needed to use
|
||||
# CCCL headers, including setting up include paths, compiler flags, etc.
|
||||
target_link_libraries(example_project PRIVATE CCCL::CCCL)
|
||||
|
||||
# This is only relevant for internal testing and not needed by end users.
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
add_test(NAME example_project COMMAND example_project)
|
||||
141
cccl_upstream/examples/basic/README.md
Normal file
141
cccl_upstream/examples/basic/README.md
Normal file
@@ -0,0 +1,141 @@
|
||||
|
||||
# Example Project Using CCCL From GitHub
|
||||
|
||||
Many CUDA C++ users are accustomed to using CCCL headers (Thrust, CUB, libcu++) provided with the [NVIDIA CUDA Toolkit](https://developer.nvidia.com/cuda-toolkit) or [NVIDIA HPC SDK](https://developer.nvidia.com/hpc-sdk).
|
||||
|
||||
In addition, we also support using CCCL directly from GitHub.
|
||||
The primary benefit is that this allows users to use the latest version of CCCL without having to wait for a new release of the CUDA Toolkit or HPC SDK.
|
||||
|
||||
This example demonstrates how to use CCCL from GitHub in a CMake project.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a standalone example of how to use [CCCL](https://github.com/nvidia/cccl) in a CMake project.
|
||||
|
||||
This example demonstrates fetching CCCL from GitHub and linking it with a simple example CUDA program ([`example.cu`](example.cu)) that utilizes the headers from CCCL.
|
||||
|
||||
This is intended to be a starting point for users who want to use CCCL in their own projects.
|
||||
|
||||
## How to Adapt This Example to Your Project
|
||||
|
||||
This example is intended to be a starting point for users who want to use CCCL in their own projects.
|
||||
In order to adapt this example to your project, you will need to do the following:
|
||||
1. Download `CPM.cmake` into your project's `cmake/` directory ([see below for instructions](#downloading-cpm)).
|
||||
2. Add the following lines to your project's `CMakeLists.txt` file:
|
||||
```cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
|
||||
CPMAddPackage(
|
||||
NAME CCCL
|
||||
GITHUB_REPOSITORY nvidia/cccl
|
||||
GIT_TAG main # Fetches the latest commit on the main branch
|
||||
)
|
||||
|
||||
# If you're building an executable
|
||||
add_executable(your_executable your_file.cu)
|
||||
target_link_libraries(your_executable PRIVATE CCCL::CCCL)
|
||||
|
||||
# Alternatively, if you're building a library
|
||||
add_library(your_library SHARED your_file.cu)
|
||||
target_link_libraries(your_library PRIVATE CCCL::CCCL)
|
||||
```
|
||||
See the [CMakeLists.txt](CMakeLists.txt) file in this directory for a complete example.
|
||||
3. Configure and build your project as normal and verify that it builds successfully.
|
||||
|
||||
For more information on using CPM, see [below](#using-cmake-package-manager).
|
||||
|
||||
## Using CMake Package Manager
|
||||
|
||||
This example uses the CMake Package Manager (CPM) to fetch CCCL from GitHub.
|
||||
|
||||
See the [CMakeLists.txt](CMakeLists.txt) file in this directory for the complete example.
|
||||
|
||||
If you are not familiar with CPM, you can find more information [here](https://github.com/cpm-cmake/CPM.cmake).
|
||||
In short, CPM is a CMake module that simplifies dependency management for CMake projects.
|
||||
It automatically downloads and integrates dependencies into your CMake project.
|
||||
|
||||
### Downloading CPM
|
||||
|
||||
In order to get the latest version of CPM.cmake, you can run the following command in the root directory of your project:
|
||||
|
||||
```bash
|
||||
mkdir -p cmake
|
||||
wget -O cmake/CPM.cmake https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/get_cpm.cmake
|
||||
```
|
||||
|
||||
This will download and create the file `cmake/CPM.cmake` in your project directory.
|
||||
Most projects will want to commit this file to their source control system.
|
||||
You can then use `include(cmake/CPM.cmake)` in your project's `CMakeLists.txt` file to include CPM in your project.
|
||||
|
||||
Alternatively, you can add the following logic to your `CMakeLists.txt` to download CPM if it is not already present in your project directory.
|
||||
|
||||
```cmake
|
||||
set(CPM_DOWNLOAD_VERSION 0.34.0)
|
||||
|
||||
if(CPM_SOURCE_CACHE)
|
||||
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
|
||||
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
else()
|
||||
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
|
||||
endif()
|
||||
|
||||
if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION}))
|
||||
message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}")
|
||||
file(DOWNLOAD
|
||||
https://github.com/TheLartians/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
|
||||
${CPM_DOWNLOAD_LOCATION}
|
||||
)
|
||||
endif()
|
||||
|
||||
include(${CPM_DOWNLOAD_LOCATION})
|
||||
```
|
||||
|
||||
## Building and Running the Example
|
||||
|
||||
Most people will want to adapt this example to their own project as described [above](#how-to-adapt-this-example-to-your-project). If you would like to build and run this example as-is, you will need to follow the instructions below.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
If you would like to build and run this example as-is, you will need:
|
||||
|
||||
- A CUDA-capable GPU
|
||||
- NVIDIA CUDA Toolkit (12 or later)
|
||||
- CMake (3.14 or later)
|
||||
- A C++17 standard-compliant compiler
|
||||
- git
|
||||
|
||||
### Instructions
|
||||
|
||||
1. Clone this repository to your local machine.
|
||||
```bash
|
||||
git clone https://github.com/NVIDIA/cccl.git
|
||||
```
|
||||
|
||||
2. Enter the directory of the cloned repository.
|
||||
```bash
|
||||
cd cccl/examples/example_project
|
||||
```
|
||||
|
||||
3. Run the CMake configure step
|
||||
```bash
|
||||
cmake -S . -B build
|
||||
```
|
||||
Alternatively,
|
||||
```bash
|
||||
mkdir -p build
|
||||
cd build
|
||||
cmake ..
|
||||
```
|
||||
4. Run the CMake build step.
|
||||
```bash
|
||||
cmake --build .
|
||||
```
|
||||
|
||||
6. Run the executable.
|
||||
```bash
|
||||
./build/example_project
|
||||
```
|
||||
|
||||
If everything is configured correctly, the program will execute and print the sum of an array of integers, demonstrating the use of cccl.
|
||||
1297
cccl_upstream/examples/basic/cmake/CPM.cmake
Normal file
1297
cccl_upstream/examples/basic/cmake/CPM.cmake
Normal file
File diff suppressed because it is too large
Load Diff
84
cccl_upstream/examples/basic/example.cu
Normal file
84
cccl_upstream/examples/basic/example.cu
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
This is a simple example demonstrating the use of CCCL functionality from Thrust, CUB, and libcu++.
|
||||
|
||||
The example computes the sum of an array of integers using a simple parallel reduction. Each thread block
|
||||
computes the sum of a subset of the array using cuB::BlockRecuce. The sum of each block is then reduced
|
||||
to a single value using an atomic add via cuda::atomic_ref from libcu++. The result is stored in a device_vector
|
||||
from Thrust. The sum is then printed to the console.
|
||||
*/
|
||||
|
||||
#include <cub/block/block_reduce.cuh>
|
||||
|
||||
#include <thrust/device_vector.h>
|
||||
|
||||
#include <cuda/atomic>
|
||||
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
|
||||
constexpr int block_size = 256;
|
||||
|
||||
__global__ void sumKernel(int const* data, int* result, std::size_t N)
|
||||
{
|
||||
using BlockReduce = cub::BlockReduce<int, block_size>;
|
||||
|
||||
__shared__ typename BlockReduce::TempStorage temp_storage;
|
||||
|
||||
int index = threadIdx.x + blockIdx.x * blockDim.x;
|
||||
|
||||
int sum = 0;
|
||||
if (index < N)
|
||||
{
|
||||
sum += data[index];
|
||||
}
|
||||
|
||||
sum = BlockReduce(temp_storage).Sum(sum);
|
||||
|
||||
if (threadIdx.x == 0)
|
||||
{
|
||||
cuda::atomic_ref<int, cuda::thread_scope_device> atomic_result(*result);
|
||||
atomic_result.fetch_add(sum, cuda::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::size_t N = 1000;
|
||||
thrust::device_vector<int> data(N, 1);
|
||||
thrust::device_vector<int> result(1);
|
||||
|
||||
int num_blocks = (N + block_size - 1) / block_size;
|
||||
|
||||
sumKernel<<<num_blocks, block_size>>>(
|
||||
thrust::raw_pointer_cast(data.data()), thrust::raw_pointer_cast(result.data()), N);
|
||||
|
||||
auto err = cudaDeviceSynchronize();
|
||||
if (err != cudaSuccess)
|
||||
{
|
||||
std::cout << "Error: " << cudaGetErrorString(err) << '\n';
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::cout << "Sum: " << result[0] << '\n';
|
||||
|
||||
assert(result[0] == N);
|
||||
|
||||
return 0;
|
||||
}
|
||||
42
cccl_upstream/examples/ccclrt/CMakeLists.txt
Normal file
42
cccl_upstream/examples/ccclrt/CMakeLists.txt
Normal file
@@ -0,0 +1,42 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
|
||||
project(cccl.examples.ccclrt CUDA)
|
||||
|
||||
# This example uses the CMake Package Manager (CPM) to simplify fetching CCCL from GitHub
|
||||
# For more information, see https://github.com/cpm-cmake/CPM.cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# We define these as variables so they can be overridden in CI to pull from a PR instead of CCCL `main`
|
||||
# In your project, these variables are unnecessary and you can just use the values directly
|
||||
set(
|
||||
CCCL_REPOSITORY
|
||||
"https://github.com/NVIDIA/cccl"
|
||||
CACHE STRING
|
||||
"Git repository to fetch CCCL from"
|
||||
)
|
||||
set(CCCL_TAG "main" CACHE STRING "Git tag/branch to fetch from CCCL repository")
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
|
||||
CPMAddPackage(NAME CCCL GIT_REPOSITORY "${CCCL_REPOSITORY}" GIT_TAG ${CCCL_TAG})
|
||||
|
||||
# Default to building for the GPU on the current system
|
||||
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
|
||||
add_subdirectory(kernel_launch_patterns)
|
||||
1297
cccl_upstream/examples/ccclrt/cmake/CPM.cmake
Normal file
1297
cccl_upstream/examples/ccclrt/cmake/CPM.cmake
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,57 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
add_executable(cccl.examples.ccclrt.kernel_launch_patterns.kernel kernel.cu)
|
||||
set_target_properties(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel
|
||||
PROPERTIES CUDA_STANDARD 17 CUDA_STANDARD_REQUIRED ON
|
||||
)
|
||||
target_link_libraries(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel
|
||||
PRIVATE libcudacxx::libcudacxx
|
||||
)
|
||||
|
||||
add_executable(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_functor
|
||||
kernel_functor.cu
|
||||
)
|
||||
set_target_properties(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_functor
|
||||
PROPERTIES CUDA_STANDARD 17 CUDA_STANDARD_REQUIRED ON
|
||||
)
|
||||
target_link_libraries(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_functor
|
||||
PRIVATE libcudacxx::libcudacxx
|
||||
)
|
||||
|
||||
# Kernel lambdas require extended lambda support.
|
||||
if ("${CMAKE_CUDA_COMPILER_ID}" STREQUAL NVIDIA)
|
||||
add_executable(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_lambda
|
||||
kernel_lambda.cu
|
||||
)
|
||||
set_target_properties(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_lambda
|
||||
PROPERTIES CUDA_STANDARD 17 CUDA_STANDARD_REQUIRED ON
|
||||
)
|
||||
target_compile_options(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_lambda
|
||||
PRIVATE -extended-lambda
|
||||
)
|
||||
target_link_libraries(
|
||||
cccl.examples.ccclrt.kernel_launch_patterns.kernel_lambda
|
||||
PRIVATE libcudacxx::libcudacxx
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,3 @@
|
||||
# Kernel Launch Patterns
|
||||
|
||||
This example showcases how kernels and kernel functors can be launched using the `cuda::launch` function.
|
||||
@@ -0,0 +1,66 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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 COMMON_CUH
|
||||
#define COMMON_CUH
|
||||
|
||||
#include <cuda/std/algorithm>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/string_view>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
class KernelName
|
||||
{
|
||||
static constexpr cuda::std::size_t max_size = 128;
|
||||
|
||||
char name_[max_size]; // The name buffer.
|
||||
|
||||
public:
|
||||
__host__ __device__ KernelName(cuda::std::string_view name)
|
||||
{
|
||||
assert(name.size() < max_size);
|
||||
|
||||
// Copy the name.
|
||||
cuda::std::copy_n(name.data(), name.size(), name_);
|
||||
|
||||
// Zero terminate the string.
|
||||
name_[name.size()] = '\0';
|
||||
}
|
||||
|
||||
// Returns the stored name.
|
||||
__host__ __device__ const char* get() const
|
||||
{
|
||||
return name_;
|
||||
}
|
||||
};
|
||||
|
||||
__device__ void say_hello(uint3 from_tindex, const KernelName& kernel_name)
|
||||
{
|
||||
const auto this_tindex = cuda::gpu_thread.index(cuda::block);
|
||||
|
||||
printf("[%u, %u]: Hello from thread [%u, %u] launched as %s!\n",
|
||||
this_tindex.x,
|
||||
this_tindex.y,
|
||||
from_tindex.x,
|
||||
from_tindex.y,
|
||||
kernel_name.get());
|
||||
|
||||
// Wait for all threads in block to print the output.
|
||||
__syncthreads();
|
||||
|
||||
// Print additional new line once.
|
||||
if (this_tindex.x == 0 && this_tindex.y == 0)
|
||||
{
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
#endif // COMMON_CUH
|
||||
148
cccl_upstream/examples/ccclrt/kernel_launch_patterns/kernel.cu
Normal file
148
cccl_upstream/examples/ccclrt/kernel_launch_patterns/kernel.cu
Normal file
@@ -0,0 +1,148 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// This example demonstrates how kernels can be launched using cuda::launch.
|
||||
|
||||
#include <cuda/devices>
|
||||
#include <cuda/hierarchy>
|
||||
#include <cuda/launch>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
|
||||
#include "common.cuh"
|
||||
|
||||
// Regular kernel.
|
||||
__global__ void kernel(KernelName kernel_name)
|
||||
{
|
||||
// Call say_hello with this thread's index.
|
||||
say_hello(cuda::gpu_thread.index(cuda::block), kernel_name);
|
||||
}
|
||||
|
||||
// Regular kernel with dynamic shared memory.
|
||||
__global__ void kernel_with_dynamic_smem(KernelName kernel_name)
|
||||
{
|
||||
// Get the dynamic shared memory handle.
|
||||
extern __shared__ uint3 smem[];
|
||||
|
||||
// Get all necessary hierarchy values.
|
||||
const auto tindex = cuda::gpu_thread.index(cuda::block);
|
||||
const auto trank = cuda::gpu_thread.rank(cuda::block);
|
||||
const auto tcount = cuda::gpu_thread.count(cuda::block);
|
||||
|
||||
// Each thread will write it's index to the next thread's index in the shared memory.
|
||||
smem[(trank + 1) % tcount] = tindex;
|
||||
|
||||
// Wait for the all threads to finish the write to shared memory.
|
||||
__syncthreads();
|
||||
|
||||
// Call say_hello with previous thread's index.
|
||||
say_hello(smem[trank], kernel_name);
|
||||
}
|
||||
|
||||
// Kernel that takes cuda::kernel_config as the first parameter. That way the kernel has access to compile time
|
||||
// information of the block and grid dimensions which can produce better optimized kernels.
|
||||
template <class Config>
|
||||
__global__ void kernel_with_config(Config config, KernelName kernel_name)
|
||||
{
|
||||
// Call say_hello with this thread's index. Note that passing config to hierarchy queries can improve their
|
||||
// performance since the functionality has access to the compile time specified dimensions.
|
||||
say_hello(cuda::gpu_thread.index(cuda::block, config), kernel_name);
|
||||
}
|
||||
|
||||
// Kernel that takes cuda::kernel_config that contains the cuda::dynamic_shared_memory_option.
|
||||
template <class Config>
|
||||
__global__ void kernel_with_config_and_dynamic_smem(Config config, KernelName kernel_name)
|
||||
{
|
||||
// Retrieve the dynamic shared memory view. Since we passed uint3[4], we will get cuda::std::span<uint3>.
|
||||
const auto smem = cuda::dynamic_shared_memory(config);
|
||||
|
||||
// Get all necessary hierarchy values.
|
||||
const auto tindex = cuda::gpu_thread.index(cuda::block, config);
|
||||
const auto trank = cuda::gpu_thread.rank(cuda::block, config);
|
||||
const auto tcount = cuda::gpu_thread.count(cuda::block, config);
|
||||
|
||||
// Each thread will write it's index to the next thread's index in the shared memory.
|
||||
smem[(trank + 1) % tcount] = tindex;
|
||||
|
||||
// Wait for the all threads to finish the write to shared memory.
|
||||
__syncthreads();
|
||||
|
||||
// Call say hello with received previous thread's index.
|
||||
say_hello(smem[trank], kernel_name);
|
||||
}
|
||||
|
||||
int main()
|
||||
try
|
||||
{
|
||||
// Check we have at least one device.
|
||||
if (cuda::devices.size() == 0)
|
||||
{
|
||||
std::fprintf(stderr, "No CUDA devices found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We will use the first device.
|
||||
cuda::device_ref device = cuda::devices[0];
|
||||
|
||||
// cuda::launch always requires a work submitter, so let's create a CUDA stream.
|
||||
cuda::stream stream{device};
|
||||
|
||||
// Set block and grid dimensions to be used with the kernel config. Dimensions specified as template parameters will
|
||||
// be statically known in the kernel.
|
||||
const auto block_dims = cuda::block_dims<2, 2>();
|
||||
const auto grid_dims = cuda::grid_dims(dim3{1});
|
||||
|
||||
// Make the kernel config.
|
||||
const auto kernel_config = cuda::make_config(grid_dims, block_dims);
|
||||
|
||||
// For kernels that use dynamic shared memory, we need a dynamic shared memory option to be passed in the kernel
|
||||
// config.
|
||||
const auto dyn_smem_opt = cuda::dynamic_shared_memory<uint3[]>(cuda::gpu_thread.count(cuda::block, kernel_config));
|
||||
|
||||
// Make the kernel config with dynamic shared memory option.
|
||||
const auto kernel_config_with_dyn_smem = cuda::make_config(grid_dims, block_dims, dyn_smem_opt);
|
||||
|
||||
// Launch the kernel using the kernel config.
|
||||
cuda::launch(stream, kernel_config, kernel, KernelName{"kernel"});
|
||||
|
||||
// Launch the kernel using the kernel config with dynamic shared memory option.
|
||||
cuda::launch(
|
||||
stream, kernel_config_with_dyn_smem, kernel_with_dynamic_smem, KernelName{"kernel with dynamic shared memory"});
|
||||
|
||||
// Launching kernels with template parameters is more complicated. All of the template parameters must be specified to
|
||||
// obtain the kernel address. Kernel functors can simplify this case a lot.
|
||||
cuda::launch(stream, kernel_config, kernel_with_config<decltype(kernel_config)>, KernelName{"kernel with config"});
|
||||
|
||||
// Kernels with configs that contain dynamic shared memory option must be launched similarly.
|
||||
cuda::launch(stream,
|
||||
kernel_config_with_dyn_smem,
|
||||
kernel_with_config_and_dynamic_smem<decltype(kernel_config_with_dyn_smem)>,
|
||||
KernelName{"kernel with config and dynamic shared memory"});
|
||||
|
||||
// Wait for all of the tasks in the stream to complete.
|
||||
stream.sync();
|
||||
}
|
||||
catch (const cuda::cuda_error& e)
|
||||
{
|
||||
std::fprintf(stderr, "CUDA error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::fprintf(stderr, "Error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::fprintf(stderr, "An unknown error was encountered\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// This example demonstrates how kernel functors can be launched using cuda::launch.
|
||||
|
||||
#include <cuda/devices>
|
||||
#include <cuda/hierarchy>
|
||||
#include <cuda/launch>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
|
||||
#include "common.cuh"
|
||||
|
||||
// This is a kernel functor, a callable object with operator() decorated with __device__ attribute. When launched, the
|
||||
// object is copied to the device and operator() is invoked on the device.
|
||||
struct KernelFunctor
|
||||
{
|
||||
// The operator() must be decorated with __device__ attribute. It can also be a template.
|
||||
__device__ void operator()(const KernelName& kernel_name) const
|
||||
{
|
||||
say_hello(cuda::gpu_thread.index(cuda::block), kernel_name);
|
||||
}
|
||||
};
|
||||
|
||||
// Kernel functor can contain data. However, the functor must be trivially copyable.
|
||||
struct KernelFunctorWithData
|
||||
{
|
||||
KernelName kernel_name_;
|
||||
|
||||
__device__ void operator()() const
|
||||
{
|
||||
say_hello(cuda::gpu_thread.index(cuda::block), kernel_name_);
|
||||
}
|
||||
};
|
||||
|
||||
// Kernel functors can also take the cuda::kernel_config objects as the first argument. That way the kernel has access
|
||||
// to compile time information of the block and grid dimensions which can produce better optimized kernels.
|
||||
struct KernelFunctorWithConfig
|
||||
{
|
||||
template <class Config>
|
||||
__device__ void operator()(const Config& config, const KernelName& kernel_name) const
|
||||
{
|
||||
// The config can be used in hierarchy queries for better performance.
|
||||
say_hello(cuda::gpu_thread.index(cuda::block, config), kernel_name);
|
||||
}
|
||||
};
|
||||
|
||||
// Kernel functors can provide a default config that is combined with the config passed to cuda::launch. This can be
|
||||
// useful for example when a kernel functor requires cooperative launch.
|
||||
struct KernelFunctorWithDefaultConfig
|
||||
{
|
||||
// Kernel functor provides the default config by implementing the .default_config() method.
|
||||
auto default_config() const
|
||||
{
|
||||
// This default config only specifies that the block dimensions are 2x2. The config passed to cuda::launch must
|
||||
// provide grid dimensions, otherwise the kernel functor wouldn't be able to be launched.
|
||||
return cuda::make_config(cuda::block_dims<2, 2>());
|
||||
}
|
||||
|
||||
template <class Config>
|
||||
__device__ void operator()(const Config& config, const KernelName& kernel_name) const
|
||||
{
|
||||
say_hello(cuda::gpu_thread.index(cuda::block, config), kernel_name);
|
||||
}
|
||||
};
|
||||
|
||||
// Kernel functor can use the
|
||||
struct KernelFunctorWithDynamicSmem
|
||||
{
|
||||
template <class Config>
|
||||
__device__ void operator()(const Config& config, const KernelName& kernel_name) const
|
||||
{
|
||||
// Retrieve the dynamic shared memory view. Since we passed uint3[4], we will get cuda::std::span<uint3>.
|
||||
const auto smem = cuda::dynamic_shared_memory(config);
|
||||
|
||||
// Get all necessary hierarchy values.
|
||||
const auto tindex = cuda::gpu_thread.index(cuda::block, config);
|
||||
const auto trank = cuda::gpu_thread.rank(cuda::block, config);
|
||||
const auto tcount = cuda::gpu_thread.count(cuda::block, config);
|
||||
|
||||
// Each thread will write it's index to the next thread's index in the shared memory.
|
||||
smem[(trank + 1) % tcount] = tindex;
|
||||
|
||||
// Wait for the all threads to finish the write to shared memory.
|
||||
__syncthreads();
|
||||
|
||||
// Call say hello with received previous thread's index.
|
||||
say_hello(smem[trank], kernel_name);
|
||||
}
|
||||
};
|
||||
|
||||
int main()
|
||||
try
|
||||
{
|
||||
// Check we have at least one device.
|
||||
if (cuda::devices.size() == 0)
|
||||
{
|
||||
std::fprintf(stderr, "No CUDA devices found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We will use the first device.
|
||||
cuda::device_ref device = cuda::devices[0];
|
||||
|
||||
// cuda::launch always requires a work submitter, so let's create a CUDA stream.
|
||||
cuda::stream stream{device};
|
||||
|
||||
// Set block and grid dimensions to be used with the kernel config. Dimensions specified as template parameters will
|
||||
// be statically known in the kernel.
|
||||
const auto block_dims = cuda::block_dims<2, 2>();
|
||||
const auto grid_dims = cuda::grid_dims(dim3{1});
|
||||
|
||||
// Make the kernel config.
|
||||
const auto kernel_config = cuda::make_config(grid_dims, block_dims);
|
||||
|
||||
// For kernels that use dynamic shared memory, we need a dynamic shared memory option to be passed in the kernel
|
||||
// config.
|
||||
const auto dyn_smem_opt = cuda::dynamic_shared_memory<uint3[]>(cuda::gpu_thread.count(cuda::block, kernel_config));
|
||||
|
||||
// Make the kernel config with dynamic shared memory option.
|
||||
const auto kernel_config_with_dyn_smem = cuda::make_config(grid_dims, block_dims, dyn_smem_opt);
|
||||
|
||||
// Launch the kernel functor using the kernel config.
|
||||
cuda::launch(stream, kernel_config, KernelFunctor{}, KernelName{"kernel functor"});
|
||||
|
||||
// Kernel functor can also contain data.
|
||||
cuda::launch(stream, kernel_config, KernelFunctorWithData{KernelName{"kernel functor with data"}});
|
||||
|
||||
// If the kernel functor in invocable with the kernel config, it's automatically passed as the first parameter by the
|
||||
// cuda::launch function.
|
||||
cuda::launch(stream, kernel_config, KernelFunctorWithConfig{}, KernelName{"kernel functor with config"});
|
||||
|
||||
// When launching a kernel functor with default config, we need to pass just a partial config as the launch parameter.
|
||||
// The missing parts are supplied from the default config inside the cuda::launch function.
|
||||
cuda::launch(stream,
|
||||
cuda::make_config(grid_dims),
|
||||
KernelFunctorWithDefaultConfig{},
|
||||
KernelName{"kernel functor with default config"});
|
||||
|
||||
// Launch the kernel functor that uses dynamic shared memory.
|
||||
cuda::launch(stream,
|
||||
kernel_config_with_dyn_smem,
|
||||
KernelFunctorWithDynamicSmem{},
|
||||
KernelName{"kernel functor with dynamic shared memory"});
|
||||
|
||||
// Wait for all of the tasks in the stream to complete.
|
||||
stream.sync();
|
||||
}
|
||||
catch (const cuda::cuda_error& e)
|
||||
{
|
||||
std::fprintf(stderr, "CUDA error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::fprintf(stderr, "Error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::fprintf(stderr, "An unknown error was encountered\n");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
// This example demonstrates how kernel lambdas can be launched using cuda::launch. Kernel lambdas behave mostly the
|
||||
// same way as other kernel functors, but there are some differences.
|
||||
|
||||
#if !defined(__CUDACC_EXTENDED_LAMBDA__)
|
||||
# error "This example requires extended lambda support."
|
||||
#endif // !__CUDACC_EXTENDED_LAMBDA__
|
||||
|
||||
#include <cuda/devices>
|
||||
#include <cuda/hierarchy>
|
||||
#include <cuda/launch>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cstdio>
|
||||
#include <exception>
|
||||
|
||||
#include "common.cuh"
|
||||
|
||||
int main()
|
||||
try
|
||||
{
|
||||
// Check we have at least one device.
|
||||
if (cuda::devices.size() == 0)
|
||||
{
|
||||
std::fprintf(stderr, "No CUDA devices found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// We will use the first device.
|
||||
cuda::device_ref device = cuda::devices[0];
|
||||
|
||||
// cuda::launch always requires a work submitter, so let's create a CUDA stream.
|
||||
cuda::stream stream{device};
|
||||
|
||||
// Set block and grid dimensions to be used with the kernel config. Dimensions specified as template parameters will
|
||||
// be statically known in the kernel.
|
||||
const auto block_dims = cuda::block_dims<2, 2>();
|
||||
const auto grid_dims = cuda::grid_dims(dim3{1});
|
||||
|
||||
// Make the kernel config.
|
||||
const auto kernel_config = cuda::make_config(grid_dims, block_dims);
|
||||
|
||||
// For kernels that use dynamic shared memory, we need a dynamic shared memory option to be passed in the kernel
|
||||
// config.
|
||||
const auto dyn_smem_opt = cuda::dynamic_shared_memory<uint3[]>(cuda::gpu_thread.count(cuda::block, kernel_config));
|
||||
|
||||
// Make the kernel config with dynamic shared memory option.
|
||||
const auto kernel_config_with_dyn_smem = cuda::make_config(grid_dims, block_dims, dyn_smem_opt);
|
||||
|
||||
// Launch the kernel lambda using the kernel config. Unlike with kernel functors, the config is not automatically
|
||||
// passed as the first parameter and must be passed explicitly.
|
||||
cuda::launch(
|
||||
stream,
|
||||
kernel_config,
|
||||
[] __device__(auto config, auto kernel_name) {
|
||||
say_hello(cuda::gpu_thread.index(cuda::block, config), kernel_name);
|
||||
},
|
||||
kernel_config,
|
||||
KernelName{"kernel lambda"});
|
||||
|
||||
// The kernel lambda with captures be launched the same way. All parameters must be captured by value and each thread
|
||||
// will get a copy of the lambda.
|
||||
cuda::launch(
|
||||
stream,
|
||||
kernel_config,
|
||||
[kernel_name = KernelName{"kernel lambda with capture"}] __device__(auto config) {
|
||||
say_hello(cuda::gpu_thread.index(cuda::block, config), kernel_name);
|
||||
},
|
||||
kernel_config);
|
||||
|
||||
// The kernel lambda can use dynamic shared memory in the same way as kernel functors.
|
||||
cuda::launch(
|
||||
stream,
|
||||
kernel_config_with_dyn_smem,
|
||||
[] __device__(auto config, auto kernel_name) {
|
||||
// Retrieve the dynamic shared memory view. Since we passed uint3[4], we will get cuda::std::span<uint3>.
|
||||
const auto smem = cuda::dynamic_shared_memory(config);
|
||||
|
||||
// Get all necessary hierarchy values.
|
||||
const auto tindex = cuda::gpu_thread.index(cuda::block, config);
|
||||
const auto trank = cuda::gpu_thread.rank(cuda::block, config);
|
||||
const auto tcount = cuda::gpu_thread.count(cuda::block, config);
|
||||
|
||||
// Each thread will write it's index to the next thread's index in the shared memory.
|
||||
smem[(trank + 1) % tcount] = tindex;
|
||||
|
||||
// Wait for the all threads to finish the write to shared memory.
|
||||
__syncthreads();
|
||||
|
||||
// Call say hello with received previous thread's index.
|
||||
say_hello(smem[trank], kernel_name);
|
||||
},
|
||||
kernel_config_with_dyn_smem,
|
||||
KernelName{"kernel lambda with dynamic shared memory"});
|
||||
|
||||
// Wait for all of the tasks in the stream to complete.
|
||||
stream.sync();
|
||||
}
|
||||
catch (const cuda::cuda_error& e)
|
||||
{
|
||||
std::fprintf(stderr, "CUDA error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::fprintf(stderr, "Error: %s\n", e.what());
|
||||
return 1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::fprintf(stderr, "An unknown error was encountered\n");
|
||||
return 1;
|
||||
}
|
||||
75
cccl_upstream/examples/cudax/CMakeLists.txt
Normal file
75
cccl_upstream/examples/cudax/CMakeLists.txt
Normal file
@@ -0,0 +1,75 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
project(CUDAX_SAMPLES CUDA CXX)
|
||||
|
||||
# This example uses the CMake Package Manager (CPM) to simplify fetching CCCL from GitHub
|
||||
# For more information, see https://github.com/cpm-cmake/CPM.cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# We define these as variables so they can be overridden in CI to pull from a PR instead of CCCL `main`
|
||||
# In your project, these variables are unnecessary and you can just use the values directly
|
||||
set(
|
||||
CCCL_REPOSITORY
|
||||
"https://github.com/NVIDIA/cccl"
|
||||
CACHE STRING
|
||||
"Git repository to fetch CCCL from"
|
||||
)
|
||||
set(CCCL_TAG "main" CACHE STRING "Git tag/branch to fetch from CCCL repository")
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
|
||||
CPMAddPackage(
|
||||
NAME CCCL
|
||||
GIT_REPOSITORY "${CCCL_REPOSITORY}"
|
||||
GIT_TAG ${CCCL_TAG}
|
||||
GIT_SHALLOW ON
|
||||
# The following is required to make the `CCCL::cudax` target available:
|
||||
OPTIONS "CCCL_ENABLE_UNSTABLE ON"
|
||||
)
|
||||
|
||||
# Default to building for the GPU on the current system
|
||||
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
|
||||
add_library(cudax_samples_interface INTERFACE)
|
||||
|
||||
target_link_libraries(cudax_samples_interface INTERFACE CCCL::CCCL CCCL::cudax)
|
||||
|
||||
if ("MSVC" STREQUAL "${CMAKE_CXX_COMPILER_ID}")
|
||||
# mdspan on windows only works in C++20 mode
|
||||
target_compile_features(cudax_samples_interface INTERFACE cxx_std_20)
|
||||
|
||||
if (MSVC_TOOLSET_VERSION LESS 143)
|
||||
# winbase.h(9572): warning C5105: macro expansion producing 'defined' has undefined behavior
|
||||
target_compile_options(
|
||||
cudax_samples_interface
|
||||
INTERFACE
|
||||
$<$<COMPILE_LANGUAGE:CXX>:/wd5105>
|
||||
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:-Xcompiler=/wd5105>
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# The vector_add sample demonstrates a simple CUDA kernel that adds two vectors
|
||||
add_executable(vector_add vector_add/vector_add.cu)
|
||||
target_link_libraries(vector_add PUBLIC cudax_samples_interface)
|
||||
|
||||
# This is only relevant for internal testing and not needed by end users.
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
add_test(NAME vector_add COMMAND vector_add)
|
||||
5
cccl_upstream/examples/cudax/README.md
Normal file
5
cccl_upstream/examples/cudax/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
# Overview
|
||||
|
||||
This example extends the `example_project` by additionally enabling the CUDA Experimental library, `cudax`.
|
||||
See the `example_project`'s README for additional information.
|
||||
1297
cccl_upstream/examples/cudax/cmake/CPM.cmake
Normal file
1297
cccl_upstream/examples/cudax/cmake/CPM.cmake
Normal file
File diff suppressed because it is too large
Load Diff
145
cccl_upstream/examples/cudax/vector_add/vector.cuh
Normal file
145
cccl_upstream/examples/cudax/vector_add/vector.cuh
Normal file
@@ -0,0 +1,145 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// 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/std/__type_traits/maybe_const.h>
|
||||
#include <cuda/std/span>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <cuda/experimental/__detail/utility.cuh>
|
||||
#include <cuda/experimental/__launch/param_kind.cuh>
|
||||
|
||||
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) noexcept
|
||||
: __str_(__str)
|
||||
, __v_(__v)
|
||||
{
|
||||
__v_.sync_host_to_device(__str_, _Kind);
|
||||
}
|
||||
|
||||
__action(__action&&) = delete;
|
||||
|
||||
~__action()
|
||||
{
|
||||
__v_.sync_device_to_host(__str_, _Kind);
|
||||
}
|
||||
|
||||
::cuda::std::span<_Ty> transformed_argument()
|
||||
{
|
||||
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) noexcept
|
||||
{
|
||||
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) noexcept
|
||||
{
|
||||
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) noexcept
|
||||
{
|
||||
return __action<_Kind>{__str, __b.__val};
|
||||
}
|
||||
|
||||
mutable host_vector<_Ty> __h_;
|
||||
mutable device_vector<_Ty> __d_{};
|
||||
mutable bool __dirty_ = true;
|
||||
};
|
||||
} // namespace cuda::experimental
|
||||
|
||||
#endif
|
||||
127
cccl_upstream/examples/cudax/vector_add/vector_add.cu
Normal file
127
cccl_upstream/examples/cudax/vector_add/vector_add.cu
Normal file
@@ -0,0 +1,127 @@
|
||||
/* 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 <stdio.h>
|
||||
|
||||
// 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 = blockDim.x * blockIdx.x + threadIdx.x;
|
||||
|
||||
if (i < A.size())
|
||||
{
|
||||
C[i] = A[i] + B[i] + 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Host main routine
|
||||
*/
|
||||
int main(void)
|
||||
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] = rand() / (float) RAND_MAX;
|
||||
B[i] = 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");
|
||||
}
|
||||
62
cccl_upstream/examples/cudax_stf/CMakeLists.txt
Normal file
62
cccl_upstream/examples/cudax_stf/CMakeLists.txt
Normal file
@@ -0,0 +1,62 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
project(CUDAX_SAMPLES CUDA CXX)
|
||||
|
||||
# This example uses the CMake Package Manager (CPM) to simplify fetching CCCL from GitHub
|
||||
# For more information, see https://github.com/cpm-cmake/CPM.cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# We define these as variables so they can be overridden in CI to pull from a PR instead of CCCL `main`
|
||||
# In your project, these variables are unnecessary and you can just use the values directly
|
||||
set(
|
||||
CCCL_REPOSITORY
|
||||
"https://github.com/NVIDIA/cccl"
|
||||
CACHE STRING
|
||||
"Git repository to fetch CCCL from"
|
||||
)
|
||||
set(CCCL_TAG "main" CACHE STRING "Git tag/branch to fetch from CCCL repository")
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
|
||||
CPMAddPackage(
|
||||
NAME CCCL
|
||||
GIT_REPOSITORY "${CCCL_REPOSITORY}"
|
||||
GIT_TAG ${CCCL_TAG}
|
||||
# The following is required to make the `CCCL::cudax` target available:
|
||||
OPTIONS "CCCL_ENABLE_UNSTABLE ON"
|
||||
)
|
||||
|
||||
# Default to building for the GPU on the current system
|
||||
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
|
||||
# If you're building an executable
|
||||
add_executable(simple_stf simple_stf.cu)
|
||||
|
||||
target_link_libraries(simple_stf PUBLIC cuda)
|
||||
|
||||
if (CMAKE_CUDA_COMPILER)
|
||||
target_compile_options(
|
||||
simple_stf
|
||||
PUBLIC
|
||||
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--expt-relaxed-constexpr>
|
||||
$<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(simple_stf PRIVATE CCCL::CCCL CCCL::cudax)
|
||||
1297
cccl_upstream/examples/cudax_stf/cmake/CPM.cmake
Normal file
1297
cccl_upstream/examples/cudax_stf/cmake/CPM.cmake
Normal file
File diff suppressed because it is too large
Load Diff
40
cccl_upstream/examples/cudax_stf/simple_stf.cu
Normal file
40
cccl_upstream/examples/cudax_stf/simple_stf.cu
Normal file
@@ -0,0 +1,40 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
int main()
|
||||
{
|
||||
context ctx;
|
||||
|
||||
int array[128];
|
||||
for (size_t i = 0; i < 128; i++)
|
||||
{
|
||||
array[i] = i;
|
||||
}
|
||||
|
||||
auto A = ctx.logical_data(array);
|
||||
|
||||
ctx.parallel_for(A.shape(), A.rw())->*[] __device__(size_t i, auto a) {
|
||||
a(i) += 4;
|
||||
};
|
||||
|
||||
ctx.finalize();
|
||||
|
||||
for (size_t i = 0; i < 128; i++)
|
||||
{
|
||||
printf("array[%ld] = %d\n", i, array[i]);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
76
cccl_upstream/examples/image_pipeline/CMakeLists.txt
Normal file
76
cccl_upstream/examples/image_pipeline/CMakeLists.txt
Normal file
@@ -0,0 +1,76 @@
|
||||
#===----------------------------------------------------------------------===//
|
||||
#
|
||||
# Part of libcu++, the C++ Standard Library for your entire system,
|
||||
# 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.
|
||||
#
|
||||
#===----------------------------------------------------------------------===//
|
||||
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
# Default to building for the GPU on the current system.
|
||||
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
|
||||
project(IMAGE_PIPELINE_EXAMPLE CUDA CXX)
|
||||
|
||||
# This example requires CUDA 13.1+ for the libcu++ runtime APIs.
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
if (CUDAToolkit_VERSION VERSION_LESS 13.1)
|
||||
message(
|
||||
STATUS
|
||||
"Skipping image_pipeline example: requires CUDA 13.1+ (found ${CUDAToolkit_VERSION})"
|
||||
)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# This example uses the CMake Package Manager (CPM) to simplify fetching CCCL from GitHub
|
||||
# For more information, see https://github.com/cpm-cmake/CPM.cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# We define these as variables so they can be overridden in CI to pull from a PR instead of CCCL `main`
|
||||
# In your project, these variables are unnecessary and you can just use the values directly
|
||||
set(
|
||||
CCCL_REPOSITORY
|
||||
"https://github.com/NVIDIA/cccl"
|
||||
CACHE STRING
|
||||
"Git repository to fetch CCCL from"
|
||||
)
|
||||
set(CCCL_TAG "main" CACHE STRING "Git tag/branch to fetch from CCCL repository")
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available
|
||||
CPMAddPackage(
|
||||
NAME CCCL
|
||||
GIT_REPOSITORY "${CCCL_REPOSITORY}"
|
||||
GIT_TAG ${CCCL_TAG}
|
||||
GIT_SHALLOW ON
|
||||
)
|
||||
|
||||
# Image processing pipeline — a multi-file example showcasing the libcu++
|
||||
# runtime APIs: device selection, memory pools, buffers, copy_bytes,
|
||||
# fill_bytes, double-buffered streams, events, timed events, and CUB
|
||||
# operations (histogram, transform, transform-reduce, block reduce).
|
||||
add_executable(image_pipeline main.cu detail.cu)
|
||||
target_include_directories(image_pipeline PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
target_compile_features(image_pipeline PRIVATE cuda_std_17)
|
||||
target_link_libraries(image_pipeline PRIVATE CCCL::CCCL)
|
||||
# Device lambdas (used in CUB transform/reduce ops) require extended lambda support.
|
||||
target_compile_options(
|
||||
image_pipeline
|
||||
PRIVATE $<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
|
||||
)
|
||||
|
||||
option(
|
||||
IMAGE_PIPELINE_ENABLE_RUNTIME_TEST
|
||||
"Enable the image_pipeline CTest runtime test. Requires a GPU and several GB of host/device memory."
|
||||
OFF
|
||||
)
|
||||
|
||||
if (IMAGE_PIPELINE_ENABLE_RUNTIME_TEST)
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
add_test(NAME image_pipeline COMMAND image_pipeline)
|
||||
endif()
|
||||
53
cccl_upstream/examples/image_pipeline/README.md
Normal file
53
cccl_upstream/examples/image_pipeline/README.md
Normal file
@@ -0,0 +1,53 @@
|
||||
|
||||
# Image Processing Pipeline Example
|
||||
|
||||
A multi-file example showcasing the CCCL Runtime and CUB APIs working together
|
||||
in a semi-realistic tiled image processing pipeline.
|
||||
|
||||
## What it does
|
||||
|
||||
The example generates a synthetic 65K x 65K (~4 GB) grayscale space observation
|
||||
on the GPU, then processes it in tiles that fit in GPU memory:
|
||||
|
||||
1. **Pass 1 - Histogram**: Upload each tile, compute per-tile histograms with
|
||||
`cub::DeviceHistogram`, download and accumulate into a global histogram.
|
||||
|
||||
2. **Host interlude**: Compute Otsu's threshold (optimal foreground/background
|
||||
split) and build a histogram equalization lookup table from the CDF.
|
||||
|
||||
3. **Pass 2 - Equalize + Analyze**: For each tile, apply the equalization LUT
|
||||
(`cub::DeviceTransform`), normalize to float (`cub::DeviceTransform`),
|
||||
compute thresholded count/min/max/sum (`cub::DeviceReduce::TransformReduce`),
|
||||
and GPU-downscale a preview (`cub::BlockReduce`).
|
||||
|
||||
4. **Output**: Write `input_preview.bmp` and `equalized_preview.bmp` (1024 x
|
||||
1024 previews). The equalized image reveals nebula structure and stars that
|
||||
are barely visible in the dark original.
|
||||
|
||||
## CCCL APIs demonstrated
|
||||
|
||||
| File | APIs |
|
||||
|------|------|
|
||||
| `image_pipeline.h` | Shared constants and buffer/plan structs using `cuda::device_buffer`, `cuda::host_buffer`, `cuda::mr::shared_resource`, and spans |
|
||||
| `detail.h` | Example-local declarations for image generation, preview output, reporting, and downscaling helpers |
|
||||
| `detail.cu` | Synthetic image generation with `cuda::launch`/`cuda::distribute`, tile preview downscaling, `cuda::copy_bytes`, memory-pool statistics, and BMP output |
|
||||
| `main.cu` | `cuda::devices`, `cuda::device_ref`, `cuda::device_attributes`, `cuda::arch_traits_for`, `cuda::device_memory_pool`, `cuda::memory_pool_properties`, `cuda::mr::shared_resource`, `cuda::make_buffer`, `cuda::make_pinned_buffer`, `cuda::copy_bytes`, `cuda::copy_configuration`, `cuda::fill_bytes`, `cuda::stream`, `cuda::timed_event`, `stream.wait(...)`, `buffer.first()`, `buffer.subspan()`, `buffer.get_unsynchronized()`, CUB `DeviceHistogram`, `DeviceTransform`, `DeviceReduce::TransformReduce`, and `BlockReduce` |
|
||||
| `CMakeLists.txt` | Standalone CMake/CPM setup for consuming CCCL from a chosen repository and tag |
|
||||
|
||||
## Building and running
|
||||
|
||||
```bash
|
||||
cd examples/image_pipeline
|
||||
cmake -S . -B build -DCMAKE_CUDA_ARCHITECTURES=native
|
||||
cmake --build build
|
||||
./build/image_pipeline
|
||||
```
|
||||
|
||||
The example requires CUDA Toolkit 13.1 or newer for the CCCL Runtime APIs used
|
||||
by the sample. The standalone CMake project uses the vendored `cmake/CPM.cmake`
|
||||
helper to fetch CCCL; override `CCCL_REPOSITORY` and `CCCL_TAG` to build against
|
||||
a local checkout or a specific branch.
|
||||
|
||||
The example requires ~4 GB of pinned host memory for the full image and uses
|
||||
60% of GPU memory for the per-tile working set. It should run on any GPU with
|
||||
at least 4 GB of memory.
|
||||
1297
cccl_upstream/examples/image_pipeline/cmake/CPM.cmake
Normal file
1297
cccl_upstream/examples/image_pipeline/cmake/CPM.cmake
Normal file
File diff suppressed because it is too large
Load Diff
345
cccl_upstream/examples/image_pipeline/detail.cu
Normal file
345
cccl_upstream/examples/image_pipeline/detail.cu
Normal file
@@ -0,0 +1,345 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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
|
||||
/// Implementation of supporting details: synthetic image generation and
|
||||
/// printing/output helpers. See detail.h for the interface.
|
||||
|
||||
#include <cuda/algorithm>
|
||||
#include <cuda/cmath>
|
||||
#include <cuda/launch>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/std/algorithm>
|
||||
#include <cuda/std/span>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include <detail.h>
|
||||
|
||||
// ── Image generation ─────────────────────────────────────────────────
|
||||
|
||||
__device__ unsigned pixel_hash(int x, int y, unsigned seed)
|
||||
{
|
||||
unsigned h = static_cast<unsigned>(x) * 1103515245u + static_cast<unsigned>(y) * 12345u + seed;
|
||||
h = (h ^ (h >> 16)) * 0x45d9f3bu;
|
||||
h = (h ^ (h >> 13)) * 0x85ebca6bu;
|
||||
return h ^ (h >> 16);
|
||||
}
|
||||
|
||||
__device__ float hash01(int x, int y, unsigned seed)
|
||||
{
|
||||
return static_cast<float>(pixel_hash(x, y, seed) & 0xFFFF) / 65535.0f;
|
||||
}
|
||||
|
||||
__device__ float value_noise(float fx, float fy, float freq, unsigned seed)
|
||||
{
|
||||
const float sx = fx * freq, sy = fy * freq;
|
||||
const int ix = static_cast<int>(floorf(sx)), iy = static_cast<int>(floorf(sy));
|
||||
float tx = sx - ix, ty = sy - iy;
|
||||
tx = tx * tx * (3.0f - 2.0f * tx);
|
||||
ty = ty * ty * (3.0f - 2.0f * ty);
|
||||
const float a = hash01(ix, iy, seed) + (hash01(ix + 1, iy, seed) - hash01(ix, iy, seed)) * tx;
|
||||
const float b = hash01(ix, iy + 1, seed) + (hash01(ix + 1, iy + 1, seed) - hash01(ix, iy + 1, seed)) * tx;
|
||||
return a + (b - a) * ty;
|
||||
}
|
||||
|
||||
__device__ float fbm(float fx, float fy, int octaves, float freq, unsigned seed)
|
||||
{
|
||||
float val = 0, amp = 1.0f, total = 0;
|
||||
for (int i = 0; i < octaves; ++i)
|
||||
{
|
||||
val += amp * value_noise(fx, fy, freq, seed + static_cast<unsigned>(i) * 7919u);
|
||||
total += amp;
|
||||
amp *= 0.5f;
|
||||
freq *= 2.0f;
|
||||
}
|
||||
return val / total;
|
||||
}
|
||||
|
||||
struct generate_kernel
|
||||
{
|
||||
template <typename Config>
|
||||
__device__ void operator()(Config config, cuda::std::span<pixel_t> out, int width, int row_offset)
|
||||
{
|
||||
const auto tid = cuda::gpu_thread.rank(cuda::grid, config);
|
||||
if (tid >= out.size())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int gx = static_cast<int>(tid) % width;
|
||||
const int gy = static_cast<int>(tid) / width + row_offset;
|
||||
const float fx = static_cast<float>(gx) / image_width;
|
||||
const float fy = static_cast<float>(gy) / image_height;
|
||||
|
||||
float val = 4.0f + 3.0f * fy;
|
||||
const float noise = (hash01(gx, gy, 0u) - 0.5f) * 6.0f;
|
||||
val += noise;
|
||||
|
||||
const float neb1_d = ((fx - 0.6f) * (fx - 0.6f) + (fy - 0.35f) * (fy - 0.35f)) / 0.06f;
|
||||
float neb1 = 40.0f * expf(-neb1_d);
|
||||
const float neb2_d = ((fx - 0.35f) * (fx - 0.35f) + (fy - 0.6f) * (fy - 0.6f)) / 0.03f;
|
||||
float neb2 = 22.0f * expf(-neb2_d);
|
||||
|
||||
const float tex = fbm(fx, fy, 5, 8.0f, 42u);
|
||||
neb1 *= (0.5f + tex);
|
||||
neb2 *= (0.3f + 0.7f * tex);
|
||||
|
||||
const float dust = fbm(fx + 0.1f, fy, 4, 6.0f, 137u);
|
||||
const float dust_mask = fmaxf(0.0f, 1.0f - 2.0f * fabsf(dust - 0.5f));
|
||||
neb1 *= (1.0f - 0.6f * dust_mask * expf(-neb1_d * 2.0f));
|
||||
val += neb1 + neb2;
|
||||
|
||||
constexpr int star_grid = 64;
|
||||
const int cx = (gx / star_grid) * star_grid + star_grid / 2;
|
||||
const int cy = (gy / star_grid) * star_grid + star_grid / 2;
|
||||
for (int dy = -1; dy <= 1; ++dy)
|
||||
{
|
||||
for (int dx = -1; dx <= 1; ++dx)
|
||||
{
|
||||
const int scx = cx + dx * star_grid;
|
||||
const int scy = cy + dy * star_grid;
|
||||
const unsigned sh = pixel_hash(scx, scy, 9999u);
|
||||
if (static_cast<float>(sh & 0xFFFF) / 65535.0f < 0.08f)
|
||||
{
|
||||
const float jx = static_cast<float>((sh >> 4) & 0xFF) / 255.0f - 0.5f;
|
||||
const float jy = static_cast<float>((sh >> 12) & 0xFF) / 255.0f - 0.5f;
|
||||
const float sx = scx + jx * star_grid;
|
||||
const float sy = scy + jy * star_grid;
|
||||
const float d2 = (gx - sx) * (gx - sx) + (gy - sy) * (gy - sy);
|
||||
const float radius = 2.0f + static_cast<float>((sh >> 20) & 0xF);
|
||||
const float bright = 80.0f + static_cast<float>((sh >> 24) & 0x7F);
|
||||
val += bright * expf(-d2 / (2.0f * radius * radius));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
out[tid] = static_cast<pixel_t>(fminf(255.0f, fmaxf(0.0f, val)));
|
||||
}
|
||||
};
|
||||
|
||||
// ── Image generation ─────────────────────────────────────────────────
|
||||
|
||||
void generate_image(cuda::stream_ref stream, tile_buffers& bufs, int num_tiles, cuda::std::span<pixel_t> host_preview)
|
||||
{
|
||||
std::cout << "=== Image generation (GPU) ===\n";
|
||||
cuda::timed_event gen_start{stream};
|
||||
|
||||
for (int t = 0; t < num_tiles; ++t)
|
||||
{
|
||||
const size_t offset = static_cast<size_t>(t) * bufs.tile_pixels;
|
||||
const size_t count = cuda::std::min(bufs.tile_pixels, image_pixels - offset);
|
||||
const int tile_rows = static_cast<int>(count / image_width);
|
||||
const int row_offset = t * static_cast<int>(bufs.tile_pixels / image_width);
|
||||
|
||||
constexpr int block_size = 256;
|
||||
const auto config = cuda::distribute<block_size>(static_cast<int>(count));
|
||||
cuda::launch(stream, config, generate_kernel{}, bufs.dev_tile[0].first(count), image_width, row_offset);
|
||||
|
||||
// Downscale the generated tile for the input preview while it's still on device.
|
||||
downscale_tile(stream, bufs, 0, bufs.dev_tile[0].first(count), row_offset, tile_rows, host_preview);
|
||||
|
||||
// Copy generated tile to host for the histogram pass.
|
||||
cuda::copy_bytes(stream, bufs.dev_tile[0].first(count), bufs.host_image.subspan(offset, count));
|
||||
}
|
||||
|
||||
cuda::timed_event gen_end{stream};
|
||||
stream.sync();
|
||||
const double gen_ms = (gen_end - gen_start).count() / 1e6;
|
||||
std::cout
|
||||
<< " Generated " << image_width << 'x' << image_height << " space observation (" << std::fixed
|
||||
<< std::setprecision(0) << image_pixels * sizeof(pixel_t) / (1024.0 * 1024.0) << " MB) in ~" << std::setprecision(1)
|
||||
<< gen_ms << " ms\n\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
// ── Printing / output helpers ────────────────────────────────────────
|
||||
|
||||
void print_device_info(cuda::device_ref dev, cuda::arch_traits_t traits, size_t total_mem)
|
||||
{
|
||||
const auto name = dev.name();
|
||||
const auto cc = dev.attribute(cuda::device_attributes::compute_capability);
|
||||
std::cout << "\nSelected device " << dev.get() << ": ";
|
||||
std::cout.write(name.data(), static_cast<std::streamsize>(name.size()));
|
||||
std::cout
|
||||
<< "\n Compute capability: " << cc.major_cap() << '.' << cc.minor_cap() << "\n Total memory : " << std::fixed
|
||||
<< std::setprecision(0) << total_mem / (1024.0 * 1024.0) << " MB"
|
||||
<< "\n Max threads/block : " << traits.max_threads_per_block
|
||||
<< "\n Max shared memory : " << traits.max_shared_memory_per_block << " bytes\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
void print_tile_plan(int tile_rows, int tile_alignment, int num_tiles, size_t budget, size_t total_mem)
|
||||
{
|
||||
std::cout
|
||||
<< "\nTile plan:\n"
|
||||
<< " Image : " << image_width << " x " << image_height << " (" << std::fixed << std::setprecision(0)
|
||||
<< image_pixels * sizeof(pixel_t) / (1024.0 * 1024.0) << " MB)\n"
|
||||
<< " GPU budget : " << budget / (1024.0 * 1024.0) << " MB (60% of " << total_mem / (1024.0 * 1024.0)
|
||||
<< " MB)\n"
|
||||
<< " Tile rows : " << tile_rows << " (aligned to " << tile_alignment << ")\n"
|
||||
<< " Number of tiles: " << num_tiles << "\n\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
void print_allocation_info(size_t device_total, size_t gpu_budget, size_t tile_pixels, int tile_rows)
|
||||
{
|
||||
std::cout
|
||||
<< std::fixed << std::setprecision(1) << " Device pool: " << device_total / (1024.0 * 1024.0) << " MB (initial), "
|
||||
<< gpu_budget / (1024.0 * 1024.0) << " MB (max)\n"
|
||||
<< " Tile size: " << tile_pixels << " pixels (" << tile_rows << " rows x " << image_width << " cols)\n"
|
||||
<< " Pinned host: image=" << image_pixels * sizeof(pixel_t) / (1024.0 * 1024.0)
|
||||
<< " MB, histogram=" << num_bins * sizeof(int) << " bytes\n\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
void print_pool_stats(tile_buffers& bufs)
|
||||
{
|
||||
const auto reserved = bufs.device_pool.get().attribute(cuda::memory_pool_attributes::reserved_mem_current);
|
||||
const auto used = bufs.device_pool.get().attribute(cuda::memory_pool_attributes::used_mem_current);
|
||||
std::cout
|
||||
<< std::fixed << std::setprecision(1) << " Device pool: reserved=" << reserved / (1024.0 * 1024.0)
|
||||
<< " MB, used=" << used / (1024.0 * 1024.0) << " MB\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
iqr_result compute_iqr(cuda::std::span<const histogram_count_t> hist, size_t total)
|
||||
{
|
||||
auto find_percentile = [&](float pct) {
|
||||
const auto target = static_cast<histogram_count_t>(static_cast<double>(total) * pct);
|
||||
histogram_count_t cumulative{};
|
||||
for (size_t i = 0; i < hist.size(); ++i)
|
||||
{
|
||||
cumulative += hist[i];
|
||||
if (cumulative >= target)
|
||||
{
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
return static_cast<int>(hist.size()) - 1;
|
||||
};
|
||||
return {find_percentile(0.25f), find_percentile(0.75f)};
|
||||
}
|
||||
|
||||
void print_pass_stats(double ms, long long total_selected, double mean_selected, float global_min, float global_max)
|
||||
{
|
||||
// Note: times measured via cuda::timed_event are approximate GPU-side measurements.
|
||||
std::cout
|
||||
<< std::fixed << std::setprecision(1) << " Pass time: ~" << ms << " ms\n"
|
||||
<< " Pixels above threshold: " << total_selected << " / " << image_pixels << " ("
|
||||
<< 100.0 * total_selected / image_pixels << "%)\n"
|
||||
<< std::setprecision(4) << " Selected range: [" << global_min << ", " << global_max << "], mean=" << mean_selected
|
||||
<< '\n'
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
void print_sanity_check(iqr_result orig, iqr_result eq)
|
||||
{
|
||||
const bool ok = eq.width() > orig.width();
|
||||
std::cout
|
||||
<< "=== Sanity check ===\n"
|
||||
<< " Original IQR (25th-75th): [" << orig.p25 << ", " << orig.p75 << "] (span " << orig.width() << ")\n"
|
||||
<< " Equalized IQR (25th-75th): [" << eq.p25 << ", " << eq.p75 << "] (span " << eq.width() << ")\n"
|
||||
<< " Equalization spread distribution: " << (ok ? "YES" : "NO") << "\n\n";
|
||||
}
|
||||
|
||||
void print_summary(int num_tiles, int tile_rows, double pass1_ms, double pass2_ms, bool ok)
|
||||
{
|
||||
std::cout
|
||||
<< "=== Summary ===\n"
|
||||
<< " Image: " << image_width << " x " << image_height << '\n'
|
||||
<< " Tiles: " << num_tiles << " (" << tile_rows << " rows each)\n"
|
||||
<< std::fixed << std::setprecision(1) << " Pass 1 (hist): ~" << pass1_ms << " ms\n"
|
||||
<< " Pass 2 (stats): ~" << pass2_ms << " ms\n"
|
||||
<< " Total pipeline: ~" << pass1_ms + pass2_ms << " ms\n"
|
||||
<< " Result: " << (ok ? "PASSED" : "FAILED") << '\n'
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
}
|
||||
|
||||
bool write_bmp(const char* filename, cuda::std::span<const pixel_t> data, int width, int height)
|
||||
{
|
||||
std::ofstream file(filename, std::ios::binary);
|
||||
if (!file)
|
||||
{
|
||||
std::cerr << "Failed to open " << filename << " for writing\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
// BMP rows must be padded to a 4-byte boundary.
|
||||
const int row_stride = (width + 3) & ~3;
|
||||
const int pixel_size = row_stride * height;
|
||||
const int file_size = 54 + 256 * 4 + pixel_size; // header + palette + pixels
|
||||
|
||||
auto put2 = [&](int v) {
|
||||
const char b[2] = {static_cast<char>(v & 0xFF), static_cast<char>((v >> 8) & 0xFF)};
|
||||
file.write(b, 2);
|
||||
};
|
||||
auto put4 = [&](int v) {
|
||||
const char b[4] = {static_cast<char>(v & 0xFF),
|
||||
static_cast<char>((v >> 8) & 0xFF),
|
||||
static_cast<char>((v >> 16) & 0xFF),
|
||||
static_cast<char>((v >> 24) & 0xFF)};
|
||||
file.write(b, 4);
|
||||
};
|
||||
|
||||
// File header (14 bytes).
|
||||
file.put('B');
|
||||
file.put('M');
|
||||
put4(file_size);
|
||||
put4(0); // reserved
|
||||
put4(54 + 256 * 4); // pixel data offset (after header + palette)
|
||||
|
||||
// DIB header (BITMAPINFOHEADER, 40 bytes).
|
||||
put4(40); // header size
|
||||
put4(width);
|
||||
put4(height);
|
||||
put2(1); // color planes
|
||||
put2(8); // bits per pixel (8-bit indexed)
|
||||
put4(0); // no compression
|
||||
put4(pixel_size);
|
||||
put4(2835); // horizontal resolution (72 DPI)
|
||||
put4(2835); // vertical resolution
|
||||
put4(256); // palette entries
|
||||
put4(0); // all colors important
|
||||
|
||||
// Grayscale palette: 256 entries of (B, G, R, 0).
|
||||
for (int i = 0; i < 256; ++i)
|
||||
{
|
||||
const char c = static_cast<char>(i);
|
||||
file.put(c);
|
||||
file.put(c);
|
||||
file.put(c);
|
||||
file.put(0);
|
||||
}
|
||||
|
||||
// Pixel data — BMP stores rows bottom-to-top.
|
||||
const char pad[3] = {0, 0, 0};
|
||||
const int pad_bytes = row_stride - width;
|
||||
for (int y = height - 1; y >= 0; --y)
|
||||
{
|
||||
file.write(reinterpret_cast<const char*>(&data[static_cast<size_t>(y) * width]), width);
|
||||
if (pad_bytes > 0)
|
||||
{
|
||||
file.write(pad, pad_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
if (!file)
|
||||
{
|
||||
std::cerr << "Failed to write " << filename << '\n';
|
||||
return false;
|
||||
}
|
||||
|
||||
std::cout << " Wrote " << filename << " (" << width << " x " << height << ")\n";
|
||||
return true;
|
||||
}
|
||||
63
cccl_upstream/examples/image_pipeline/detail.h
Normal file
63
cccl_upstream/examples/image_pipeline/detail.h
Normal file
@@ -0,0 +1,63 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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 DETAIL_H
|
||||
#define DETAIL_H
|
||||
|
||||
/// @file
|
||||
/// Supporting details for the image pipeline example: synthetic image
|
||||
/// generation and printing/output helpers. These are not part of the
|
||||
/// pipeline itself — in a real application, image generation would be
|
||||
/// replaced by actual data loading, and the printing would be replaced
|
||||
/// by your application's logging.
|
||||
|
||||
#include <image_pipeline.h>
|
||||
|
||||
// ── Image generation ─────────────────────────────────────────────────
|
||||
|
||||
/// Generate a synthetic space observation on the GPU, tile by tile,
|
||||
/// and produce a downscaled input preview.
|
||||
/// In a real application this would be replaced by loading actual data.
|
||||
void generate_image(cuda::stream_ref stream, tile_buffers& bufs, int num_tiles, cuda::std::span<pixel_t> host_preview);
|
||||
|
||||
// ── Printing / output helpers ────────────────────────────────────────
|
||||
|
||||
void print_device_info(cuda::device_ref dev, cuda::arch_traits_t traits, size_t total_mem);
|
||||
void print_tile_plan(int tile_rows, int tile_alignment, int num_tiles, size_t budget, size_t total_mem);
|
||||
void print_allocation_info(size_t device_total, size_t gpu_budget, size_t tile_pixels, int tile_rows);
|
||||
void print_pool_stats(tile_buffers& bufs);
|
||||
|
||||
struct iqr_result
|
||||
{
|
||||
int p25, p75;
|
||||
[[nodiscard]] int width() const noexcept
|
||||
{
|
||||
return p75 - p25;
|
||||
}
|
||||
};
|
||||
|
||||
[[nodiscard]] iqr_result compute_iqr(cuda::std::span<const histogram_count_t> hist, size_t total);
|
||||
void print_pass_stats(double ms, long long total_selected, double mean_selected, float global_min, float global_max);
|
||||
void print_sanity_check(iqr_result orig, iqr_result eq);
|
||||
void print_summary(int num_tiles, int tile_rows, double pass1_ms, double pass2_ms, bool ok);
|
||||
[[nodiscard]] bool write_bmp(const char* filename, cuda::std::span<const pixel_t> data, int width, int height);
|
||||
|
||||
/// Downscale a device tile using CUB BlockReduce and copy the result
|
||||
/// to the host preview buffer.
|
||||
void downscale_tile(
|
||||
cuda::stream_ref stream,
|
||||
tile_buffers& bufs,
|
||||
int slot,
|
||||
cuda::std::span<const pixel_t> dev_src,
|
||||
int row_offset,
|
||||
int tile_rows,
|
||||
cuda::std::span<pixel_t> host_preview);
|
||||
|
||||
#endif // DETAIL_H
|
||||
85
cccl_upstream/examples/image_pipeline/image_pipeline.h
Normal file
85
cccl_upstream/examples/image_pipeline/image_pipeline.h
Normal file
@@ -0,0 +1,85 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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 IMAGE_PIPELINE_H
|
||||
#define IMAGE_PIPELINE_H
|
||||
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/devices>
|
||||
#include <cuda/memory_resource>
|
||||
#include <cuda/std/cstddef>
|
||||
#include <cuda/std/cstdint>
|
||||
#include <cuda/std/limits>
|
||||
#include <cuda/std/span>
|
||||
#include <cuda/stream>
|
||||
|
||||
// ── Constants ────────────────────────────────────────────────────────
|
||||
|
||||
/// Pixel type: 8-bit grayscale.
|
||||
using pixel_t = uint8_t;
|
||||
|
||||
/// Full-image histogram count type.
|
||||
using histogram_count_t = long long;
|
||||
|
||||
/// Image dimensions. 65K x 65K (~4 GB raw). Large enough that
|
||||
/// the working set won't fit in most GPUs, forcing real tiling.
|
||||
/// Note: the full image is held in pinned host memory (~4 GB).
|
||||
/// Ensure the system has at least 8 GB of RAM.
|
||||
inline constexpr int image_width = 65536;
|
||||
inline constexpr int image_height = 65536;
|
||||
inline constexpr size_t image_pixels = static_cast<size_t>(image_width) * image_height;
|
||||
|
||||
/// Preview downscale factor. The output preview images are
|
||||
/// (image_width / preview_scale) x (image_height / preview_scale).
|
||||
inline constexpr int preview_scale = 64;
|
||||
|
||||
/// Histogram bins (one per possible grayscale value).
|
||||
inline constexpr int num_bins = cuda::std::numeric_limits<pixel_t>::max() + 1;
|
||||
inline constexpr int num_levels = num_bins + 1; // CUB needs num_levels = num_bins + 1
|
||||
|
||||
// ── Shared data structures ───────────────────────────────────────────
|
||||
|
||||
/// Device selection and tile sizing results.
|
||||
struct device_plan
|
||||
{
|
||||
cuda::device_ref device;
|
||||
int tile_rows;
|
||||
int num_tiles;
|
||||
size_t gpu_budget;
|
||||
};
|
||||
|
||||
/// All working memory for the pipeline.
|
||||
struct tile_buffers
|
||||
{
|
||||
cuda::device_buffer<pixel_t> dev_tile[2]; // double-buffered H2D
|
||||
cuda::device_buffer<float> dev_float_tile[2]; // normalized float tile
|
||||
cuda::device_buffer<int> dev_histogram[2]; // per-tile histogram
|
||||
cuda::device_buffer<float4> dev_tile_stats; // per-tile reduction: {count, min, max, sum}
|
||||
cuda::device_buffer<pixel_t> dev_lut; // equalization LUT
|
||||
cuda::device_buffer<pixel_t> dev_equalized[2]; // equalized tile
|
||||
cuda::host_buffer<pixel_t> host_image; // full image in pinned memory
|
||||
cuda::host_buffer<int> host_tile_histograms; // per-tile histograms
|
||||
cuda::host_buffer<float4> host_tile_stats; // per-tile stats readback
|
||||
cuda::device_buffer<pixel_t> dev_preview[2]; // downscaled preview tile
|
||||
|
||||
cuda::mr::shared_resource<cuda::device_memory_pool> device_pool;
|
||||
size_t tile_pixels;
|
||||
};
|
||||
|
||||
/// Per-tile processing statistics.
|
||||
struct tile_stats
|
||||
{
|
||||
float min_val;
|
||||
float max_val;
|
||||
float sum;
|
||||
long long num_selected;
|
||||
};
|
||||
|
||||
#endif // IMAGE_PIPELINE_H
|
||||
616
cccl_upstream/examples/image_pipeline/main.cu
Normal file
616
cccl_upstream/examples/image_pipeline/main.cu
Normal file
@@ -0,0 +1,616 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of libcu++, the C++ Standard Library for your entire system,
|
||||
// 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.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* Image processing pipeline — all runtime API usage in one file.
|
||||
*
|
||||
* This file contains the complete pipeline:
|
||||
* - Device selection and tile sizing
|
||||
* - Memory pool creation and buffer allocation
|
||||
* - Tile upload/download with copy_bytes and fill_bytes
|
||||
* - CUB-based processing (histogram, equalization, thresholding, reduction)
|
||||
* - GPU downscale with CUB BlockReduce
|
||||
* - Double-buffered two-stream orchestration
|
||||
*
|
||||
* Supporting details (image generation, printing) are in detail.cu.
|
||||
*/
|
||||
|
||||
#include <cub/block/block_reduce.cuh>
|
||||
#include <cub/device/device_histogram.cuh>
|
||||
#include <cub/device/device_reduce.cuh>
|
||||
#include <cub/device/device_transform.cuh>
|
||||
|
||||
#include <cuda/algorithm>
|
||||
#include <cuda/buffer>
|
||||
#include <cuda/cmath>
|
||||
#include <cuda/devices>
|
||||
#include <cuda/launch>
|
||||
#include <cuda/memory_pool>
|
||||
#include <cuda/memory_resource>
|
||||
#include <cuda/std/__exception/cuda_error.h>
|
||||
#include <cuda/std/algorithm>
|
||||
#include <cuda/std/array>
|
||||
#include <cuda/std/execution>
|
||||
#include <cuda/std/functional>
|
||||
#include <cuda/std/limits>
|
||||
#include <cuda/std/span>
|
||||
#include <cuda/stream>
|
||||
|
||||
#include <exception>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include <cuda_runtime_api.h>
|
||||
#include <detail.h>
|
||||
#include <image_pipeline.h>
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// Device selection
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
static device_plan select_device_and_plan()
|
||||
{
|
||||
std::cout << "=== Device selection ===\n";
|
||||
|
||||
cuda::device_ref best = cuda::devices[0];
|
||||
size_t best_mem = 0;
|
||||
|
||||
for (auto dev : cuda::devices)
|
||||
{
|
||||
const size_t total_bytes = dev.attribute(cuda::device_attributes::total_global_memory);
|
||||
const int sms = dev.attribute(cuda::device_attributes::multiprocessor_count);
|
||||
const auto name = dev.name();
|
||||
std::cout << " [" << dev.get() << "] ";
|
||||
std::cout.write(name.data(), static_cast<std::streamsize>(name.size()));
|
||||
std::cout
|
||||
<< " " << std::setw(3) << sms << " SMs " << std::fixed << std::setprecision(0)
|
||||
<< total_bytes / (1024.0 * 1024.0) << " MB\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
|
||||
if (total_bytes > best_mem)
|
||||
{
|
||||
best = dev;
|
||||
best_mem = total_bytes;
|
||||
}
|
||||
}
|
||||
|
||||
const auto cc = best.attribute(cuda::device_attributes::compute_capability);
|
||||
const auto traits = cuda::arch_traits_for(cc);
|
||||
print_device_info(best, traits, best_mem);
|
||||
|
||||
// Budget 60% of total GPU memory for the per-tile working set.
|
||||
const size_t budget = static_cast<size_t>(best_mem * 0.60);
|
||||
const size_t overhead = 128 * 1024 * 1024;
|
||||
const size_t bytes_per_pixel = 4 * sizeof(pixel_t) + 2 * sizeof(float);
|
||||
const size_t usable_budget = (budget > overhead) ? (budget - overhead) : 0;
|
||||
const size_t budget_rows = usable_budget / bytes_per_pixel / image_width;
|
||||
|
||||
constexpr int tile_alignment = preview_scale;
|
||||
const size_t max_launch_rows =
|
||||
(static_cast<size_t>(cuda::std::numeric_limits<int>::max()) / image_width / tile_alignment) * tile_alignment;
|
||||
const size_t max_tile_rows = cuda::std::min(static_cast<size_t>(image_height), max_launch_rows);
|
||||
const size_t aligned_tile_rows = (budget_rows / tile_alignment) * tile_alignment;
|
||||
const auto clamped_tile_rows =
|
||||
cuda::std::clamp(aligned_tile_rows, static_cast<size_t>(tile_alignment), max_tile_rows);
|
||||
const int tile_rows = static_cast<int>(clamped_tile_rows);
|
||||
const int num_tiles = cuda::ceil_div(image_height, tile_rows);
|
||||
|
||||
print_tile_plan(tile_rows, tile_alignment, num_tiles, budget, best_mem);
|
||||
return {best, tile_rows, num_tiles, budget};
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// Buffer allocation
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
static tile_buffers
|
||||
allocate_tile_buffers(cuda::stream_ref stream, cuda::device_ref device, int tile_rows, size_t gpu_budget, int num_tiles)
|
||||
{
|
||||
std::cout << "=== Tile buffer allocation ===\n";
|
||||
const size_t tile_pixels = static_cast<size_t>(tile_rows) * image_width;
|
||||
const size_t preview_px = (tile_rows / preview_scale) * (image_width / preview_scale);
|
||||
|
||||
// Single pool for our buffers and CUB temporaries.
|
||||
const size_t device_total =
|
||||
2 * tile_pixels * sizeof(pixel_t) // double-buffered pixel tiles
|
||||
+ 2 * tile_pixels * sizeof(pixel_t) // double-buffered equalized tiles
|
||||
+ 2 * tile_pixels * sizeof(float) // double-buffered normalized float tiles
|
||||
+ sizeof(float4) // reduction output
|
||||
+ num_bins * sizeof(pixel_t) // equalization LUT
|
||||
+ 2 * num_bins * sizeof(int) // double-buffered histograms
|
||||
+ 2 * preview_px * sizeof(pixel_t); // double-buffered preview tiles
|
||||
|
||||
cuda::memory_pool_properties props{};
|
||||
props.initial_pool_size = device_total;
|
||||
props.max_pool_size = gpu_budget;
|
||||
|
||||
auto device_pool = cuda::mr::shared_resource<cuda::device_memory_pool>(
|
||||
cuda::std::in_place_type<cuda::device_memory_pool>, device, props);
|
||||
|
||||
// Device buffers — no_init since kernels/copies write before reading.
|
||||
auto dev_tile_0 = cuda::make_buffer<pixel_t>(stream, device_pool, tile_pixels, cuda::no_init);
|
||||
auto dev_tile_1 = cuda::make_buffer<pixel_t>(stream, device_pool, tile_pixels, cuda::no_init);
|
||||
auto dev_float_0 = cuda::make_buffer<float>(stream, device_pool, tile_pixels, cuda::no_init);
|
||||
auto dev_float_1 = cuda::make_buffer<float>(stream, device_pool, tile_pixels, cuda::no_init);
|
||||
auto dev_hist_0 = cuda::make_buffer<int>(stream, device_pool, num_bins, cuda::no_init);
|
||||
auto dev_hist_1 = cuda::make_buffer<int>(stream, device_pool, num_bins, cuda::no_init);
|
||||
auto dev_tile_stats = cuda::make_buffer<float4>(stream, device_pool, num_tiles, cuda::no_init);
|
||||
auto dev_lut = cuda::make_buffer<pixel_t>(stream, device_pool, num_bins, cuda::no_init);
|
||||
auto dev_equalized_0 = cuda::make_buffer<pixel_t>(stream, device_pool, tile_pixels, cuda::no_init);
|
||||
auto dev_equalized_1 = cuda::make_buffer<pixel_t>(stream, device_pool, tile_pixels, cuda::no_init);
|
||||
auto dev_preview_0 = cuda::make_buffer<pixel_t>(stream, device_pool, preview_px, cuda::no_init);
|
||||
auto dev_preview_1 = cuda::make_buffer<pixel_t>(stream, device_pool, preview_px, cuda::no_init);
|
||||
|
||||
// Pinned host buffers — make_pinned_buffer uses the default pinned pool.
|
||||
auto host_image = cuda::make_pinned_buffer<pixel_t>(stream, image_pixels, pixel_t{0});
|
||||
auto host_tile_hists = cuda::make_pinned_buffer<int>(stream, static_cast<size_t>(num_tiles) * num_bins, int{0});
|
||||
auto host_tile_stats = cuda::make_pinned_buffer<float4>(stream, num_tiles, float4{0, 0, 0, 0});
|
||||
|
||||
print_allocation_info(device_total, gpu_budget, tile_pixels, tile_rows);
|
||||
|
||||
return {
|
||||
{cuda::std::move(dev_tile_0), cuda::std::move(dev_tile_1)},
|
||||
{cuda::std::move(dev_float_0), cuda::std::move(dev_float_1)},
|
||||
{cuda::std::move(dev_hist_0), cuda::std::move(dev_hist_1)},
|
||||
cuda::std::move(dev_tile_stats),
|
||||
cuda::std::move(dev_lut),
|
||||
{cuda::std::move(dev_equalized_0), cuda::std::move(dev_equalized_1)},
|
||||
cuda::std::move(host_image),
|
||||
cuda::std::move(host_tile_hists),
|
||||
cuda::std::move(host_tile_stats),
|
||||
{cuda::std::move(dev_preview_0), cuda::std::move(dev_preview_1)},
|
||||
device_pool,
|
||||
tile_pixels,
|
||||
};
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// Tile transfer helpers
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
static size_t upload_tile(cuda::stream_ref stream, tile_buffers& bufs, int slot, int tile_idx, int tile_rows)
|
||||
{
|
||||
const size_t offset = static_cast<size_t>(tile_idx) * bufs.tile_pixels;
|
||||
const size_t count = cuda::std::min(bufs.tile_pixels, image_pixels - offset);
|
||||
|
||||
cuda::copy_configuration config{};
|
||||
config.src_access_order = cuda::source_access_order::stream;
|
||||
cuda::copy_bytes(stream, bufs.host_image.subspan(offset, count), bufs.dev_tile[slot].first(count), config);
|
||||
cuda::fill_bytes(stream, bufs.dev_histogram[slot], uint8_t{0});
|
||||
return count;
|
||||
}
|
||||
|
||||
static void download_tile_histogram(cuda::stream_ref stream, tile_buffers& bufs, int slot, int tile_idx)
|
||||
{
|
||||
const size_t offset = static_cast<size_t>(tile_idx) * num_bins;
|
||||
cuda::copy_bytes(stream, bufs.dev_histogram[slot], bufs.host_tile_histograms.subspan(offset, num_bins));
|
||||
}
|
||||
|
||||
static void accumulate_histograms(tile_buffers& bufs, int num_tiles, cuda::std::span<histogram_count_t> result)
|
||||
{
|
||||
for (int i = 0; i < num_bins; ++i)
|
||||
{
|
||||
result[i] = 0;
|
||||
}
|
||||
for (int t = 0; t < num_tiles; ++t)
|
||||
{
|
||||
const size_t offset = static_cast<size_t>(t) * num_bins;
|
||||
for (int i = 0; i < num_bins; ++i)
|
||||
{
|
||||
result[i] += bufs.host_tile_histograms.get_unsynchronized(offset + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void upload_lut(cuda::stream_ref stream, tile_buffers& bufs, cuda::std::span<const pixel_t> host_lut)
|
||||
{
|
||||
cuda::copy_bytes(stream, host_lut, bufs.dev_lut);
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// CUB-based processing
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
static auto make_cub_env(cuda::stream_ref stream, cuda::mr::shared_resource<cuda::device_memory_pool>& pool)
|
||||
{
|
||||
const auto mr_prop = cuda::std::execution::prop{cuda::mr::get_memory_resource_t{}, pool};
|
||||
return cuda::std::execution::env{stream, mr_prop};
|
||||
}
|
||||
|
||||
static void check_cub(cudaError_t err, const char* msg)
|
||||
{
|
||||
if (err != cudaSuccess)
|
||||
{
|
||||
throw cuda::cuda_error(err, msg, "CUB");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Processing functions ─────────────────────────────────────────────
|
||||
|
||||
static void compute_histogram(cuda::stream_ref stream, tile_buffers& bufs, int slot, size_t tile_pixel_count)
|
||||
{
|
||||
auto env = make_cub_env(stream, bufs.device_pool);
|
||||
check_cub(
|
||||
cub::DeviceHistogram::HistogramEven(
|
||||
bufs.dev_tile[slot].first(tile_pixel_count).data(),
|
||||
bufs.dev_histogram[slot].data(),
|
||||
num_levels,
|
||||
0,
|
||||
num_bins,
|
||||
static_cast<int>(tile_pixel_count),
|
||||
env),
|
||||
"HistogramEven (pass 1)");
|
||||
}
|
||||
|
||||
static void process_tile(
|
||||
cuda::stream_ref stream, tile_buffers& bufs, int slot, int tile_idx, size_t tile_pixel_count, float threshold)
|
||||
{
|
||||
const int n = static_cast<int>(tile_pixel_count);
|
||||
auto pixel_data = bufs.dev_tile[slot].first(tile_pixel_count).data();
|
||||
auto eq_data = bufs.dev_equalized[slot].data();
|
||||
auto float_data = bufs.dev_float_tile[slot].data();
|
||||
auto env = make_cub_env(stream, bufs.device_pool);
|
||||
|
||||
// Equalize: apply the LUT to remap pixel intensities.
|
||||
auto lut_span = bufs.dev_lut.first(num_bins);
|
||||
auto equalize = [lut_span] __device__(pixel_t p) -> pixel_t {
|
||||
return lut_span[p];
|
||||
};
|
||||
check_cub(cub::DeviceTransform::Transform(pixel_data, eq_data, n, equalize, env), "Transform (equalize)");
|
||||
|
||||
// Normalize: convert uint8 pixels to [0, 1] floats.
|
||||
auto normalize = [] __device__(pixel_t p) -> float {
|
||||
constexpr float pixel_max = cuda::std::numeric_limits<pixel_t>::max();
|
||||
return static_cast<float>(p) / pixel_max;
|
||||
};
|
||||
check_cub(cub::DeviceTransform::Transform(eq_data, float_data, n, normalize, env), "Transform (normalize)");
|
||||
|
||||
check_cub(
|
||||
cub::DeviceHistogram::HistogramEven(eq_data, bufs.dev_histogram[slot].data(), num_levels, 0, num_bins, n, env),
|
||||
"HistogramEven (pass 2)");
|
||||
|
||||
// Combined threshold + count/min/max/sum in a single pass via float4: x=count, y=min, z=max, w=sum.
|
||||
// Each tile writes to its own output index — no sync needed between tiles.
|
||||
constexpr float flt_max = cuda::std::numeric_limits<float>::max();
|
||||
constexpr float flt_low = cuda::std::numeric_limits<float>::lowest();
|
||||
const float4 identity{0.0f, flt_max, flt_low, 0.0f};
|
||||
|
||||
auto threshold_stats = [threshold] __device__(float v) -> float4 {
|
||||
if (v > threshold)
|
||||
{
|
||||
return {1.0f, v, v, v}; // count=1, min=v, max=v, sum=v
|
||||
}
|
||||
return {0.0f, flt_max, flt_low, 0.0f};
|
||||
};
|
||||
|
||||
auto stats_reduce = [] __device__(float4 a, float4 b) -> float4 {
|
||||
return {a.x + b.x, cuda::std::min(a.y, b.y), cuda::std::max(a.z, b.z), a.w + b.w};
|
||||
};
|
||||
|
||||
check_cub(cub::DeviceReduce::TransformReduce(
|
||||
float_data, bufs.dev_tile_stats.data() + tile_idx, n, stats_reduce, threshold_stats, identity, env),
|
||||
"TransformReduce");
|
||||
|
||||
// D2H copy into this tile's slot — no sync, read after all tiles finish.
|
||||
cuda::copy_bytes(stream, bufs.dev_tile_stats.subspan(tile_idx, 1), bufs.host_tile_stats.subspan(tile_idx, 1));
|
||||
}
|
||||
|
||||
/// Accumulate per-tile stats into a single result after all tiles finish.
|
||||
static tile_stats accumulate_tile_stats(tile_buffers& bufs, int num_tiles)
|
||||
{
|
||||
tile_stats result{};
|
||||
result.min_val = cuda::std::numeric_limits<float>::max();
|
||||
result.max_val = cuda::std::numeric_limits<float>::lowest();
|
||||
|
||||
for (int t = 0; t < num_tiles; ++t)
|
||||
{
|
||||
const auto s = bufs.host_tile_stats.get_unsynchronized(t);
|
||||
result.num_selected += static_cast<long long>(s.x);
|
||||
result.min_val = cuda::std::min(result.min_val, s.y);
|
||||
result.max_val = cuda::std::max(result.max_val, s.z);
|
||||
result.sum += s.w;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ── Host-side algorithms ─────────────────────────────────────────────
|
||||
|
||||
static float compute_otsu_threshold(cuda::std::span<const histogram_count_t> histogram, size_t total_pixels)
|
||||
{
|
||||
double total_sum = 0;
|
||||
for (int i = 0; i < num_bins; ++i)
|
||||
{
|
||||
total_sum += static_cast<double>(i) * histogram[i];
|
||||
}
|
||||
double sum_bg = 0, weight_bg = 0, max_var = 0;
|
||||
int best_t = 0;
|
||||
for (int t = 0; t < num_bins; ++t)
|
||||
{
|
||||
weight_bg += histogram[t];
|
||||
if (weight_bg == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const double weight_fg = static_cast<double>(total_pixels) - weight_bg;
|
||||
if (weight_fg == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
sum_bg += static_cast<double>(t) * histogram[t];
|
||||
const double mean_bg = sum_bg / weight_bg;
|
||||
const double mean_fg = (total_sum - sum_bg) / weight_fg;
|
||||
const double var = weight_bg * weight_fg * (mean_bg - mean_fg) * (mean_bg - mean_fg);
|
||||
if (var > max_var)
|
||||
{
|
||||
max_var = var;
|
||||
best_t = t;
|
||||
}
|
||||
}
|
||||
return static_cast<float>(best_t) / cuda::std::numeric_limits<pixel_t>::max();
|
||||
}
|
||||
|
||||
static void build_equalization_lut(
|
||||
cuda::std::span<const histogram_count_t> histogram, size_t total_pixels, cuda::std::span<pixel_t> lut_out)
|
||||
{
|
||||
constexpr double max_val = cuda::std::numeric_limits<pixel_t>::max();
|
||||
const double scale = max_val / static_cast<double>(total_pixels);
|
||||
double cdf = 0;
|
||||
for (int i = 0; i < num_bins; ++i)
|
||||
{
|
||||
cdf += histogram[i];
|
||||
lut_out[i] = static_cast<pixel_t>(cuda::std::min(cdf * scale, max_val));
|
||||
}
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// Downscale kernel
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Each block produces one output pixel by box-averaging a scale×scale
|
||||
// source block. Threads cooperatively load source pixels and sum
|
||||
// them locally, then cub::BlockReduce merges the partial sums.
|
||||
//
|
||||
// The block size is extracted from the launch config at compile time
|
||||
// via cuda::gpu_thread.count(cuda::block, config), which is then used
|
||||
// as the template parameter for cub::BlockReduce.
|
||||
|
||||
struct downscale_kernel
|
||||
{
|
||||
template <typename Config>
|
||||
__device__ void
|
||||
operator()(Config config, cuda::std::span<const pixel_t> src, cuda::std::span<pixel_t> dst, int src_width, int scale)
|
||||
{
|
||||
constexpr int block_size = cuda::gpu_thread.count(cuda::block, config);
|
||||
const int out_idx = blockIdx.x;
|
||||
if (out_idx >= static_cast<int>(dst.size()))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int dst_width = src_width / scale;
|
||||
const int px = out_idx % dst_width;
|
||||
const int py = out_idx / dst_width;
|
||||
const int total_elems = scale * scale;
|
||||
|
||||
// Each thread sums its share of the scale×scale source block.
|
||||
int local_sum = 0;
|
||||
const int tid = threadIdx.x;
|
||||
for (int i = tid; i < total_elems; i += block_size)
|
||||
{
|
||||
const int dy = i / scale;
|
||||
const int dx = i % scale;
|
||||
local_sum += src[static_cast<size_t>(py * scale + dy) * src_width + (px * scale + dx)];
|
||||
}
|
||||
|
||||
// cub::BlockReduce sums the per-thread partial sums into a single
|
||||
// block-wide total. Without CUB, this would be a manual shared-
|
||||
// memory tree reduction:
|
||||
//
|
||||
// __shared__ int smem[block_size];
|
||||
// smem[tid] = local_sum;
|
||||
// __syncthreads();
|
||||
// for (int s = block_size / 2; s > 0; s >>= 1)
|
||||
// {
|
||||
// if (tid < s) smem[tid] += smem[tid + s];
|
||||
// __syncthreads();
|
||||
// }
|
||||
// int block_sum = smem[0];
|
||||
using BlockReduceT = cub::BlockReduce<int, block_size>;
|
||||
__shared__ typename BlockReduceT::TempStorage temp_storage;
|
||||
const int block_sum = BlockReduceT(temp_storage).Sum(local_sum);
|
||||
|
||||
if (tid == 0)
|
||||
{
|
||||
dst[out_idx] = static_cast<pixel_t>(block_sum / total_elems);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void downscale_tile(
|
||||
cuda::stream_ref stream,
|
||||
tile_buffers& bufs,
|
||||
int slot,
|
||||
cuda::std::span<const pixel_t> dev_src,
|
||||
int row_offset,
|
||||
int tile_rows,
|
||||
cuda::std::span<pixel_t> host_preview)
|
||||
{
|
||||
const int dst_rows = tile_rows / preview_scale;
|
||||
const int dst_cols = image_width / preview_scale;
|
||||
const int dst_pixels = dst_rows * dst_cols;
|
||||
if (dst_pixels == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr int block_size = 256;
|
||||
const auto config = cuda::make_config(cuda::block_dims<block_size>(), cuda::grid_dims(dst_pixels));
|
||||
|
||||
cuda::launch(
|
||||
stream,
|
||||
config,
|
||||
downscale_kernel{},
|
||||
dev_src,
|
||||
bufs.dev_preview[slot].first(static_cast<size_t>(dst_pixels)),
|
||||
image_width,
|
||||
preview_scale);
|
||||
|
||||
const int preview_row_offset = row_offset / preview_scale;
|
||||
cuda::copy_bytes(
|
||||
stream,
|
||||
bufs.dev_preview[slot].first(static_cast<size_t>(dst_pixels)),
|
||||
host_preview.subspan(static_cast<size_t>(preview_row_offset) * dst_cols, static_cast<size_t>(dst_pixels)));
|
||||
}
|
||||
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
// Main
|
||||
// ═════════════════════════════════════════════════════════════════════
|
||||
|
||||
int main()
|
||||
try
|
||||
{
|
||||
// ── 1. Device selection and tile sizing ────────────────────────────
|
||||
const auto plan = select_device_and_plan();
|
||||
|
||||
// ── 2. Allocate all buffers ────────────────────────────────────────
|
||||
// Two streams for double-buffered tile processing. stream_a also
|
||||
// handles setup work (allocation, LUT upload, etc.) between passes.
|
||||
cuda::stream stream_a{plan.device};
|
||||
cuda::stream stream_b{plan.device};
|
||||
cuda::stream_ref streams[2] = {stream_a, stream_b};
|
||||
|
||||
auto bufs = allocate_tile_buffers(stream_a, plan.device, plan.tile_rows, plan.gpu_budget, plan.num_tiles);
|
||||
|
||||
// ── 3. Generate image and downscale input preview ──────────────────
|
||||
const int pw = image_width / preview_scale;
|
||||
const int ph = image_height / preview_scale;
|
||||
auto host_input_preview = cuda::make_pinned_buffer<pixel_t>(stream_a, static_cast<size_t>(pw) * ph, pixel_t{0});
|
||||
generate_image(stream_a, bufs, plan.num_tiles, host_input_preview.subspan(0));
|
||||
bool outputs_ok = write_bmp("input_preview.bmp", host_input_preview.subspan(0), pw, ph);
|
||||
|
||||
// ── 4. Pass 1: histogram (double-buffered) ─────────────────────────
|
||||
// stream_a: [upload tile 0] [histogram 0] [download 0] [tile 2] ...
|
||||
// stream_b: [upload tile 1] [histogram 1] [download 1] ...
|
||||
std::cout << "=== Pass 1: histogram ===\n";
|
||||
|
||||
cuda::timed_event pass1_start{stream_a};
|
||||
|
||||
for (int t = 0; t < plan.num_tiles; ++t)
|
||||
{
|
||||
const int slot = t % 2;
|
||||
const size_t count = upload_tile(streams[slot], bufs, slot, t, plan.tile_rows);
|
||||
compute_histogram(streams[slot], bufs, slot, count);
|
||||
download_tile_histogram(streams[slot], bufs, slot, t);
|
||||
}
|
||||
|
||||
stream_a.wait(stream_b);
|
||||
cuda::timed_event pass1_end{stream_a};
|
||||
stream_a.sync();
|
||||
const double pass1_ms = (pass1_end - pass1_start).count() / 1e6;
|
||||
std::cout << std::fixed << std::setprecision(1) << " Histogram pass: " << pass1_ms << " ms\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
|
||||
// ── 5. Otsu threshold + equalization LUT ───────────────────────────
|
||||
cuda::std::array<histogram_count_t, num_bins> original_hist{};
|
||||
cuda::std::span global_hist_span{original_hist};
|
||||
accumulate_histograms(bufs, plan.num_tiles, global_hist_span);
|
||||
|
||||
const float otsu = compute_otsu_threshold(global_hist_span, image_pixels);
|
||||
std::cout
|
||||
<< std::fixed << std::setprecision(4) << " Otsu threshold: " << otsu << " (" << static_cast<int>(otsu * 255)
|
||||
<< " / 255)\n"
|
||||
<< std::defaultfloat << std::setprecision(6);
|
||||
|
||||
pixel_t host_lut[num_bins];
|
||||
build_equalization_lut(global_hist_span, image_pixels, cuda::std::span<pixel_t>(host_lut, num_bins));
|
||||
|
||||
// Construct a pinned buffer from the host LUT array — the buffer
|
||||
// copies the data in stream order, no manual sync needed.
|
||||
auto pinned_lut = cuda::make_pinned_buffer<pixel_t>(stream_a, host_lut, host_lut + num_bins);
|
||||
upload_lut(stream_a, bufs, pinned_lut.subspan(0));
|
||||
std::cout << " Equalization LUT uploaded\n\n";
|
||||
|
||||
cuda::fill_bytes(stream_a, bufs.host_tile_histograms, uint8_t{0});
|
||||
auto host_eq_preview = cuda::make_pinned_buffer<pixel_t>(stream_a, static_cast<size_t>(pw) * ph, pixel_t{0});
|
||||
|
||||
// stream_b must wait for the setup work on stream_a before starting pass 2.
|
||||
stream_b.wait(stream_a);
|
||||
|
||||
// ── 6. Pass 2: equalize + threshold + stats + preview ──────────────
|
||||
std::cout << "=== Pass 2: equalize + threshold + statistics ===\n";
|
||||
cuda::timed_event pass2_start{stream_a};
|
||||
|
||||
for (int t = 0; t < plan.num_tiles; ++t)
|
||||
{
|
||||
const int slot = t % 2;
|
||||
const size_t count = upload_tile(streams[slot], bufs, slot, t, plan.tile_rows);
|
||||
process_tile(streams[slot], bufs, slot, t, count, otsu);
|
||||
const int tile_rows = static_cast<int>(count / image_width);
|
||||
const int row_offset = t * static_cast<int>(bufs.tile_pixels / image_width);
|
||||
|
||||
downscale_tile(
|
||||
streams[slot],
|
||||
bufs,
|
||||
slot,
|
||||
bufs.dev_equalized[slot].first(count),
|
||||
row_offset,
|
||||
tile_rows,
|
||||
host_eq_preview.subspan(0));
|
||||
download_tile_histogram(streams[slot], bufs, slot, t);
|
||||
}
|
||||
|
||||
// Single sync after all tiles — stats, histograms, and preview are on host.
|
||||
stream_a.wait(stream_b);
|
||||
cuda::timed_event pass2_end{stream_a};
|
||||
stream_a.sync();
|
||||
const double pass2_ms = (pass2_end - pass2_start).count() / 1e6;
|
||||
|
||||
const auto stats = accumulate_tile_stats(bufs, plan.num_tiles);
|
||||
const double mean_selected = (stats.num_selected > 0) ? static_cast<double>(stats.sum) / stats.num_selected : 0.0;
|
||||
|
||||
print_pass_stats(pass2_ms, stats.num_selected, mean_selected, stats.min_val, stats.max_val);
|
||||
print_pool_stats(bufs);
|
||||
|
||||
// ── 7. Write equalized preview ─────────────────────────────────────
|
||||
outputs_ok = write_bmp("equalized_preview.bmp", host_eq_preview.subspan(0), pw, ph) && outputs_ok;
|
||||
if (!outputs_ok)
|
||||
{
|
||||
std::cerr << "One or more preview BMP files were not written.\n";
|
||||
}
|
||||
std::cout << '\n';
|
||||
|
||||
// ── 8. Sanity check ────────────────────────────────────────────────
|
||||
const auto orig_iqr = compute_iqr(cuda::std::span{original_hist}, image_pixels);
|
||||
|
||||
cuda::std::array<histogram_count_t, num_bins> equalized_hist{};
|
||||
accumulate_histograms(bufs, plan.num_tiles, cuda::std::span{equalized_hist});
|
||||
const auto eq_iqr = compute_iqr(cuda::std::span{equalized_hist}, image_pixels);
|
||||
|
||||
print_sanity_check(orig_iqr, eq_iqr);
|
||||
|
||||
const bool ok = outputs_ok && eq_iqr.width() > orig_iqr.width();
|
||||
print_summary(plan.num_tiles, plan.tile_rows, pass1_ms, pass2_ms, ok);
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
catch (const cuda::cuda_error& e)
|
||||
{
|
||||
std::cerr << "CUDA error: " << e.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
std::cerr << "Error: " << e.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
std::cerr << "An unknown error was encountered\n";
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
# SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# 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.
|
||||
|
||||
# This demo provides an example of how to configure a project to use Thrust while selecting
|
||||
# the device system as a configuration option. The device system is selected by setting the
|
||||
# CMake option `CCCL_THRUST_DEVICE_SYSTEM={CUDA, OMP, TBB, CPP}` for CUDA, OpenMP, Intel Threading
|
||||
# Building Blocks (TBB), and serial C++, respectively. If no option is provided, the default is `CUDA`.
|
||||
#
|
||||
# See the accompanying README.md for more information and build instructions.
|
||||
|
||||
cmake_minimum_required(VERSION 3.18 FATAL_ERROR)
|
||||
|
||||
project(ThrustFlexibleDeviceSystemDemo CXX)
|
||||
|
||||
# This example uses the CMake Package Manager (CPM) to simplify fetching CCCL from GitHub
|
||||
# For more information, see https://github.com/cpm-cmake/CPM.cmake
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
# We define these as variables so they can be overridden in CI to pull from a PR instead of CCCL `main`
|
||||
# In your project, these variables are unnecessary and you can just use the values directly
|
||||
set(
|
||||
CCCL_REPOSITORY
|
||||
"https://github.com/NVIDIA/cccl"
|
||||
CACHE STRING
|
||||
"GitHub repository to fetch CCCL from"
|
||||
)
|
||||
set(CCCL_TAG "main" CACHE STRING "Git tag/branch to fetch from CCCL repository")
|
||||
|
||||
# This will automatically clone CCCL from GitHub and make the exported cmake targets available.
|
||||
# The default `CCCL::Thrust` target will be configured to use the system device defined by
|
||||
# `CCCL_THRUST_DEVICE_SYSTEM`.
|
||||
CPMAddPackage(NAME CCCL GIT_REPOSITORY "${CCCL_REPOSITORY}" GIT_TAG ${CCCL_TAG})
|
||||
|
||||
# CUDA specific setup
|
||||
if (CCCL_THRUST_DEVICE_SYSTEM STREQUAL "CUDA")
|
||||
# Need to explicitly enable the CUDA language for the project.
|
||||
# Note that the project(...) command earlier only enables CXX by default.
|
||||
enable_language(CUDA)
|
||||
|
||||
# Optional: Call find_package(CCCL) again after enabling new languages
|
||||
# to update compatibility flags for newly detected compilers.
|
||||
find_package(CCCL CONFIG REQUIRED)
|
||||
|
||||
# Compile for the native CUDA arch if not specified:
|
||||
if (NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
|
||||
set(CMAKE_CUDA_ARCHITECTURES native)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Creates a cmake executable target for the main program
|
||||
add_executable(example_program example.cpp)
|
||||
|
||||
# Thrust requires at least C++17:
|
||||
target_compile_features(example_program PUBLIC cxx_std_17)
|
||||
if (CCCL_THRUST_DEVICE_SYSTEM STREQUAL "CUDA")
|
||||
target_compile_features(example_program PUBLIC cuda_std_17)
|
||||
endif()
|
||||
|
||||
# By default, CMake inspects the source file extension to determine whether to use C++ or CUDA
|
||||
# compilers. We can override this behavior by using source file properties. Here, we tell CMake
|
||||
# to compile this C++ (.cpp) file with the CUDA compiler when using the CUDA device system:
|
||||
if (CCCL_THRUST_DEVICE_SYSTEM STREQUAL "CUDA")
|
||||
set_source_files_properties(example.cpp PROPERTIES LANGUAGE CUDA)
|
||||
endif()
|
||||
|
||||
# "Links" the CCCL Cmake target to the `example_program` executable. This configures everything needed to use
|
||||
# CCCL headers, including setting up include paths, compiler flags, Thrust host/device configuration, etc.
|
||||
target_link_libraries(example_program PRIVATE CCCL::CCCL)
|
||||
|
||||
# This is only relevant for internal testing and not needed by end users.
|
||||
include(CTest)
|
||||
enable_testing()
|
||||
add_test(NAME example_program COMMAND example_program)
|
||||
set_tests_properties(
|
||||
example_program
|
||||
PROPERTIES
|
||||
PASS_REGULAR_EXPRESSION
|
||||
"Detected device system: ${CCCL_THRUST_DEVICE_SYSTEM}"
|
||||
)
|
||||
@@ -0,0 +1,45 @@
|
||||
# Thrust Flexible Device System Example
|
||||
|
||||
This example illustrates best practices for writing generic CMake that supports user-configuration of the Thrust device system via the `CCCL_THRUST_DEVICE_SYSTEM` CMake option.
|
||||
|
||||
Valid values for this option are:
|
||||
|
||||
- `CUDA`
|
||||
- `OMP` (OpenMP)
|
||||
- `TBB` (Intel Thread Building Blocks)
|
||||
- `CPP` (Serial C++ backend)
|
||||
|
||||
The CMakeLists.txt file for this example is annotated to show how to achieve a generic build system that supports any of device system.
|
||||
|
||||
## How To Use This Example
|
||||
|
||||
Configure and build this example as follows:
|
||||
|
||||
```
|
||||
# Checkout example and prepare build directory:
|
||||
git clone https://github.com/NVIDIA/cccl.git
|
||||
cd cccl/thrust_flexible_device_system
|
||||
mkdir build
|
||||
cd build
|
||||
|
||||
# Configure:
|
||||
cmake .. -DCCCL_THRUST_DEVICE_SYSTEM=CUDA # or TBB, OMP, CPP
|
||||
|
||||
# Build:
|
||||
cmake --build .
|
||||
|
||||
# Run:
|
||||
ctest -V
|
||||
```
|
||||
|
||||
## Advanced Thrust Usecases
|
||||
|
||||
For more control over the Thrust configuration, see the Thrust CMake package's [README.md](../../lib/cmake/thrust/README.md).
|
||||
This details how to use the `thrust_create_target` function to generate Thrust interface targets in CMake.
|
||||
|
||||
If using `thrust_create_target` directly, you may also want to set the CMake option `CCCL_ENABLE_DEFAULT_THRUST_TARGET=OFF` to prevent the default `CCCL::Thrust` target from being initialized.
|
||||
This will avoid checking for any dependencies required for the default target that may be unnecessary for your project.
|
||||
|
||||
## Further Reading About CCCL + CMake
|
||||
|
||||
The `basic` example's [README.md](../basic/README.md) has additional information that you may find useful for using CCCL with CPM and CMake.
|
||||
1297
cccl_upstream/examples/thrust_flexible_device_system/cmake/CPM.cmake
Normal file
1297
cccl_upstream/examples/thrust_flexible_device_system/cmake/CPM.cmake
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include <thrust/device_vector.h>
|
||||
#include <thrust/reduce.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
constexpr std::size_t N = 1000;
|
||||
thrust::device_vector<int> data(N, 1);
|
||||
|
||||
const auto result = thrust::reduce(data.cbegin(), data.cend());
|
||||
|
||||
std::cout << "Sum: " << result << '\n';
|
||||
|
||||
if (result != N)
|
||||
{
|
||||
std::cerr << "Error: Expected sum of " << N << ", but got " << result << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::cout << "Detected device system: ";
|
||||
#if THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CUDA
|
||||
std::cout << "CUDA" << '\n';
|
||||
#elif THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_TBB
|
||||
std::cout << "TBB" << '\n';
|
||||
#elif THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_OMP
|
||||
std::cout << "OMP" << '\n';
|
||||
#elif THRUST_DEVICE_SYSTEM == THRUST_DEVICE_SYSTEM_CPP
|
||||
std::cout << "CPP" << '\n';
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user