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

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

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,189 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#pragma once
/**
* @file
* @brief Sparse conjugate gradient algorithm
*/
#include <cuda/experimental/stf.cuh>
#include "dot.cuh"
using namespace cuda::experimental::stf;
#if !_CCCL_CTK_BELOW(12, 4)
template <typename ctx_t, typename T>
void cg_solver(ctx_t& ctx, csr_matrix<T>& A, vector_t<T>& X, vector_t<T>& B, double cg_tol = 1e-10, size_t max_cg = 1000)
{
// Initial guess X = 0 (better for Newton corrections)
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
dX(i) = 0.0;
};
// Residual R initialized to B
auto R = ctx.logical_data(B.shape()).set_symbol("R");
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
dR(i) = dB(i);
};
// R = R - A*X
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
SPMV(ctx, A, X, Ax);
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
dR(i) -= dAx(i);
};
// P = R;
auto P = ctx.logical_data(R.shape()).set_symbol("P");
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
dP(i) = dR(i);
};
// RSOLD = R'*R
auto rsold = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsold");
DOT(ctx, R, R, rsold);
// CG iteration counter
auto cg_iter = ctx.logical_data(shape_of<scalar_view<int>>()).set_symbol("cg_iter");
ctx.parallel_for(box(1), cg_iter.write()).set_symbol("init_cg_iter")->*[] _CCCL_DEVICE(size_t i, auto diter) {
*diter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
// Ap = A*P
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
SPMV(ctx, A, P, Ap);
// We don't compute alpha explicitly
// alpha = rsold / (p' * Ap);
auto pAp = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("pAp");
DOT(ctx, P, Ap, pAp);
// x = x + alpha * p;
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
T alpha = (*drsold / *dpAp);
dX(i) += alpha * dP(i);
};
// r = r - alpha * Ap;
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
T alpha = (*drsold / *dpAp);
dR(i) -= alpha * dAp(i);
};
// rsnew = r' * r;
auto rsnew = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsnew");
DOT(ctx, R, R, rsnew);
while_guard.update_cond(rsnew.read(), cg_iter.rw())->*[cg_tol, max_cg] __device__(auto drsnew, auto diter) {
(*diter)++; // increment iteration counter
bool converged = (*drsnew < cg_tol * cg_tol);
// printf("CG iter %d: RES %e (tol=%e)\n", *diter, sqrt(*drsnew), cg_tol);
return !converged && (*diter < max_cg);
};
// p = r + (rsnew / rsold) * p;
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
};
// update old residual
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
*drsold = *drsnew;
};
}
}
template <typename ctx_t, typename T>
void cg_solver_no_while(
ctx_t& ctx, csr_matrix<T>& A, vector_t<T>& X, vector_t<T>& B, double cg_tol = 1e-10, size_t max_cg = 1000)
{
// Initial guess X = 0 (better for Newton corrections)
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
dX(i) = 0.0;
};
// Residual R initialized to B
auto R = ctx.logical_data(B.shape()).set_symbol("R");
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
dR(i) = dB(i);
};
// R = R - A*X
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
SPMV(ctx, A, X, Ax);
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
dR(i) -= dAx(i);
};
// P = R;
auto P = ctx.logical_data(R.shape()).set_symbol("P");
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
dP(i) = dR(i);
};
// RSOLD = R'*R
auto rsold = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsold");
DOT(ctx, R, R, rsold);
size_t iter = 0;
auto rsnew = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsnew");
do
{
// Ap = A*P
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
SPMV(ctx, A, P, Ap);
// We don't compute alpha explicitly
// alpha = rsold / (p' * Ap);
auto pAp = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("pAp");
DOT(ctx, P, Ap, pAp);
// x = x + alpha * p;
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
T alpha = (*drsold / *dpAp);
dX(i) += alpha * dP(i);
};
// r = r - alpha * Ap;
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
T alpha = (*drsold / *dpAp);
dR(i) -= alpha * dAp(i);
};
// rsnew = r' * r;
DOT(ctx, R, R, rsnew);
// p = r + (rsnew / rsold) * p;
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
};
// update old residual
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
*drsold = *drsnew;
};
} while ((++iter < max_cg) && (ctx.wait(rsnew) > cg_tol * cg_tol));
}
#endif

View File

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

View File

@@ -1,172 +0,0 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDASTF in CUDA C++ Core Libraries,
// under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
/**
* @file
* @brief Generic Newton Solver
*/
#include <cuda/experimental/stf.cuh>
#include "cg_solver.cuh"
#include "dot.cuh"
using namespace cuda::experimental::stf;
/**
* Generic Newton solver for nonlinear systems F(x) = 0
*
* @tparam ctx_t STF context type
* @tparam ResidualCallback Callback to compute residual F(x)
* @tparam JacobianCallback Callback to assemble Jacobian J = ∂F/∂x
*
* The callbacks must be callable with these signatures:
* - ResidualCallback: void fn(ctx_t&, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>&
* residual)
* - JacobianCallback: void fn(ctx_t&, const vector_t<double>& x, vector_t<double>& jacobian_values)
*/
template <typename ctx_t, typename ResidualCallback, typename JacobianCallback>
void newton_solver(
ctx_t& ctx,
vector_t<double>& U,
vector_t<double>& csr_values,
const vector_t<size_t>& csr_row_offsets,
const vector_t<size_t>& csr_col_ind,
ResidualCallback compute_residual_fn,
JacobianCallback assemble_jacobian_fn,
size_t max_newton = 20,
double newton_tol = 1e-10,
size_t max_cg = 100)
{
auto U_prev = ctx.logical_data(U.shape()).set_symbol("U_prev");
ctx.parallel_for(U.shape(), U_prev.write(), U.read()).set_symbol("init_guess")
->*[] __device__(size_t i, auto dU_prev, auto dU) {
dU_prev(i) = dU(i);
};
auto newton_norm2 = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("newton_norm2");
auto newton_iter = ctx.logical_data(shape_of<scalar_view<size_t>>()).set_symbol("newton_iter");
ctx.parallel_for(box(1), newton_iter.write()).set_symbol("init_newton_iter")->*[] _CCCL_DEVICE(size_t i, auto diter) {
*diter = 0;
};
{
auto while_guard = ctx.while_graph_scope();
auto residual = ctx.logical_data(U.shape()).set_symbol("residual");
auto delta = ctx.logical_data(U.shape()).set_symbol("delta");
// Compute residual F(U)
compute_residual_fn(ctx, U, U_prev, residual);
// Compute Newton residual norm for convergence check
DOT(ctx, residual, residual, newton_norm2);
// Assemble Jacobian J = ∂F/∂U
assemble_jacobian_fn(ctx, U, csr_values);
auto rhs = ctx.logical_data(U.shape()).set_symbol("rhs");
// Set up RHS: rhs = -F(U)
ctx.parallel_for(rhs.shape(), rhs.write(), residual.read()).set_symbol("rhs = -residual")
->*[] __device__(size_t i, auto drhs, auto dresidual) {
drhs(i) = -dresidual(i);
};
csr_matrix<double> A(csr_values, csr_row_offsets, csr_col_ind);
// Solve linear system: J * delta = -F(U)
double cg_tol = 1e-8;
cg_solver(ctx, A, delta, rhs, cg_tol, max_cg);
// Newton update: U = U + delta (no special boundary handling needed)
ctx.parallel_for(U.shape(), U.rw(), delta.read()).set_symbol("newton_update")
->*[] __device__(size_t i, auto dU, auto ddelta) {
dU(i) += ddelta(i);
};
while_guard.update_cond(newton_norm2.read(), newton_iter.rw())
->*[newton_tol, max_newton] __device__(auto dnorm2, auto diter) {
(*diter)++; // increment iteration counter
bool converged = (*dnorm2 < newton_tol * newton_tol);
return !converged && (*diter < max_newton);
};
}
}
template <typename ctx_t, typename ResidualCallback, typename JacobianCallback>
void newton_solver_no_while(
ctx_t& ctx,
vector_t<double>& U,
vector_t<double>& csr_values,
const vector_t<size_t>& csr_row_offsets,
const vector_t<size_t>& csr_col_ind,
ResidualCallback compute_residual_fn,
JacobianCallback assemble_jacobian_fn,
bool cg_use_while = false,
size_t max_newton = 20,
double newton_tol = 1e-10,
size_t max_cg = 100)
{
auto U_prev = ctx.logical_data(U.shape()).set_symbol("U_prev");
ctx.parallel_for(U.shape(), U_prev.write(), U.read()).set_symbol("init_guess")
->*[] __device__(size_t i, auto dU_prev, auto dU) {
dU_prev(i) = dU(i);
};
auto newton_norm2 = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("newton_norm2");
size_t iter = 0;
do
{
auto residual = ctx.logical_data(U.shape()).set_symbol("residual");
auto delta = ctx.logical_data(U.shape()).set_symbol("delta");
// Compute residual F(U)
compute_residual_fn(ctx, U, U_prev, residual);
// Compute Newton residual norm for convergence check
DOT(ctx, residual, residual, newton_norm2);
// Assemble Jacobian J = ∂F/∂U
assemble_jacobian_fn(ctx, U, csr_values);
auto rhs = ctx.logical_data(U.shape()).set_symbol("rhs");
// Set up RHS: rhs = -F(U)
ctx.parallel_for(rhs.shape(), rhs.write(), residual.read()).set_symbol("rhs = -residual")
->*[] __device__(size_t i, auto drhs, auto dresidual) {
drhs(i) = -dresidual(i);
};
csr_matrix<double> A(csr_values, csr_row_offsets, csr_col_ind);
// Solve linear system: J * delta = -F(U)
double cg_tol = 1e-8;
if (cg_use_while)
{
// fprintf(stderr, "NEWTON NO WHILE, CG WHILE.\n");
cg_solver(ctx, A, delta, rhs, cg_tol, max_cg);
}
else
{
// fprintf(stderr, "NEWTON NO WHILE, CG NO WHILE.\n");
cg_solver_no_while(ctx, A, delta, rhs, cg_tol, max_cg);
}
// Newton update: U = U + delta (no special boundary handling needed)
ctx.parallel_for(U.shape(), U.rw(), delta.read()).set_symbol("newton_update")
->*[] __device__(size_t i, auto dU, auto ddelta) {
dU(i) += ddelta(i);
};
} while ((++iter < max_newton) && ctx.wait(newton_norm2) > newton_tol * newton_tol);
}

View File

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