[CCCL] 瘦身 + 补全: 移除 cudax/python/libcudacxx-tests 冗余文件, 新增 c2h 测试助手 + cmake 构建系统 + 8 个 CUDA thrust examples
变更摘要:
- 删除: cudax/ (783 files, 7.2M) — 实验性组件,竞赛不需要
- 删除: python/ (226 files, 2.0M) — Python 绑定,竞赛不需要
- 删除: libcudacxx/{test,benchmarks,codegen,cmake,share} (4432 files, 31M)
保留: libcudacxx/include/ (1463 headers, cuda::std 编译依赖)
- 新增: c2h/ (27 files) — CUB Catch2 测试辅助头文件,编译 243 个测试必需
- 新增: cmake/ (29 files) — CCCL 原生 CMake 构建系统
- 新增: thrust/examples/cuda/ (7 files) + cpp_integration/ (1 file)
async_reduce, custom_temporary_allocation, explicit_cuda_stream,
global_device_vector, range_view, unwrap_pointer, wrap_pointer, device
结果: cccl_upstream 从 74M→35M (瘦身 53%), 核心内容 100% 保留:
27/27 tuning headers, 78 benchmarks, 243 tests,
60 thrust examples, 18 CUB examples, 全部编译头文件
This commit is contained in:
@@ -1,484 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief An example that implements a tiled matrix product over multiple devices using CUBLAS
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "cuda/experimental/stf.cuh"
|
||||
#include <nvtx3/nvToolsExt.h>
|
||||
|
||||
#define TILED
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
static std::unordered_map<int, cublasHandle_t> cublas_handles;
|
||||
|
||||
/* Get a CUBLAS handle valid on the current device, or initialize it lazily */
|
||||
cublasHandle_t get_cublas_handle()
|
||||
{
|
||||
int dev;
|
||||
cuda_safe_call(cudaGetDevice(&dev));
|
||||
|
||||
auto& result = cublas_handles[dev];
|
||||
if (result == cublasHandle_t())
|
||||
{ // not found, default value inserted
|
||||
// Lazy initialization, and save the handle for future use
|
||||
cuda_safe_call(cublasCreate(&result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class matrix
|
||||
{
|
||||
public:
|
||||
matrix(stackable_ctx& ctx,
|
||||
size_t NROWS,
|
||||
size_t NCOLS,
|
||||
size_t BLOCKSIZE_ROWS,
|
||||
size_t BLOCKSIZE_COLS,
|
||||
const char* _symbol = "matrix")
|
||||
: h_array(nullptr)
|
||||
, m(NROWS)
|
||||
, n(NCOLS)
|
||||
, mb(BLOCKSIZE_ROWS)
|
||||
, nb(BLOCKSIZE_COLS)
|
||||
, mt(0)
|
||||
, nt(0)
|
||||
, symbol(_symbol)
|
||||
, ndevs(0)
|
||||
, grid_p(0)
|
||||
, grid_q(0)
|
||||
{
|
||||
assert(m % mb == 0);
|
||||
assert(n % nb == 0);
|
||||
|
||||
const size_t s = m * n * sizeof(T);
|
||||
// cuda_safe_call(cudaMallocHost(&h_array, m*n*sizeof(T)));
|
||||
// fprintf(stderr, "Allocating %ld x %ld x %ld = %ld bytes (%f GB) on host for %s\n", m, n, sizeof(T), s,
|
||||
// s / (1024.0 * 1024.0 * 1024.0), _symbol);
|
||||
h_array = static_cast<T*>(malloc(s));
|
||||
assert(h_array);
|
||||
cuda_safe_call(cudaHostRegister(h_array, s, cudaHostRegisterPortable));
|
||||
|
||||
// Compute the number of blocks
|
||||
mt = m / mb;
|
||||
nt = n / nb;
|
||||
|
||||
handles.resize(mt * nt);
|
||||
|
||||
for (size_t colb = 0; colb < nt; colb++)
|
||||
{
|
||||
for (size_t rowb = 0; rowb < mt; rowb++)
|
||||
{
|
||||
T* addr_h = get_block_h(rowb, colb);
|
||||
|
||||
#ifdef TILED
|
||||
// tiles are stored contiguously
|
||||
const size_t ld = mb;
|
||||
#else
|
||||
const size_t ld = m;
|
||||
#endif
|
||||
|
||||
std::ignore = ld; // avoid warning #177-D: variable "ld" was declared but never referenced
|
||||
auto s = make_slice(addr_h, std::tuple{mb, nb}, ld);
|
||||
auto tile = ctx.logical_data(s);
|
||||
tile.set_write_back(false);
|
||||
|
||||
tile.set_symbol(std::string(symbol) + "_" + std::to_string(rowb) + "_" + std::to_string(colb));
|
||||
|
||||
handles[rowb + colb * mt] = std::move(tile);
|
||||
}
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
for (int a = 1; a * a <= ndevs; a++)
|
||||
{
|
||||
if (ndevs % a == 0)
|
||||
{
|
||||
grid_p = a;
|
||||
grid_q = ndevs / a;
|
||||
}
|
||||
}
|
||||
|
||||
assert(grid_p * grid_q == ndevs);
|
||||
|
||||
// std::cout << "FOUND " << ndevs << " DEVICES "
|
||||
// << "p=" << grid_p << " q=" << grid_q << '\n';
|
||||
}
|
||||
|
||||
~matrix()
|
||||
{
|
||||
if (h_array)
|
||||
{
|
||||
cuda_safe_call(cudaHostUnregister(h_array));
|
||||
free(h_array);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable copy and move operations - this is a resource-owning class used locally
|
||||
matrix(const matrix&) = delete;
|
||||
matrix& operator=(const matrix&) = delete;
|
||||
matrix(matrix&&) = delete;
|
||||
matrix& operator=(matrix&&) = delete;
|
||||
|
||||
void push(access_mode mode)
|
||||
{
|
||||
for (auto& h : handles)
|
||||
{
|
||||
h.push(mode);
|
||||
}
|
||||
}
|
||||
|
||||
int get_preferred_devid(int row, int col) const
|
||||
{
|
||||
return (row % grid_p) + (col % grid_q) * grid_p;
|
||||
}
|
||||
|
||||
auto& get_handle(int row, int col)
|
||||
{
|
||||
return handles[row + col * mt];
|
||||
}
|
||||
|
||||
auto& get_handle(int row, int col) const
|
||||
{
|
||||
return handles[row + col * mt];
|
||||
}
|
||||
|
||||
size_t get_index(size_t row, size_t col) const
|
||||
{
|
||||
#ifdef TILED
|
||||
// Find which tile contains this element
|
||||
const int tile_row = static_cast<int>(row / mb);
|
||||
const int tile_col = static_cast<int>(col / nb);
|
||||
|
||||
const size_t tile_size = mb * nb;
|
||||
|
||||
// Look for the index of the beginning of the tile
|
||||
const size_t tile_start = (tile_row + mt * tile_col) * tile_size;
|
||||
|
||||
// Offset within the tile
|
||||
const size_t offset = (row % mb) + (col % nb) * mb;
|
||||
|
||||
return tile_start + offset;
|
||||
#else
|
||||
return row + col * m;
|
||||
#endif
|
||||
}
|
||||
|
||||
T* get_block_h(int brow, int bcol)
|
||||
{
|
||||
const size_t index = get_index(brow * mb, bcol * nb);
|
||||
return &h_array[index];
|
||||
}
|
||||
|
||||
// Fill with func(Matrix*,row, col)
|
||||
template <typename Fun>
|
||||
void fill(stackable_ctx& ctx, Fun&& fun)
|
||||
{
|
||||
nvtxRangePushA("FILL");
|
||||
// Fill blocks by blocks
|
||||
for (size_t colb = 0; colb < nt; colb++)
|
||||
{
|
||||
for (size_t rowb = 0; rowb < mt; rowb++)
|
||||
{
|
||||
// Each task fills a block
|
||||
auto& h = get_handle(rowb, colb);
|
||||
int devid = get_preferred_devid(rowb, colb);
|
||||
|
||||
ctx.parallel_for(exec_place::device(devid), h.shape(), h.write()).set_symbol("INIT")->*
|
||||
[=] _CCCL_DEVICE(size_t lrow, size_t lcol, auto sA) {
|
||||
const size_t row = lrow + rowb * sA.extent(0);
|
||||
const size_t col = lcol + colb * sA.extent(1);
|
||||
sA(lrow, lcol) = fun(row, col);
|
||||
};
|
||||
}
|
||||
}
|
||||
nvtxRangePop();
|
||||
}
|
||||
|
||||
T* h_array;
|
||||
size_t m; // nrows
|
||||
size_t n; // ncols
|
||||
|
||||
size_t mb; // block size (rows)
|
||||
size_t nb; // block size (cols)
|
||||
|
||||
size_t mt; // numter of column blocks
|
||||
size_t nt; // numter of row blocks
|
||||
|
||||
// abstract data handles
|
||||
std::vector<stackable_logical_data<slice<T, 2>>> handles;
|
||||
|
||||
const char* symbol;
|
||||
|
||||
// for the mapping
|
||||
int ndevs;
|
||||
int grid_p, grid_q;
|
||||
};
|
||||
|
||||
void DGEMM(
|
||||
stackable_ctx& ctx,
|
||||
cublasOperation_t transa,
|
||||
cublasOperation_t transb,
|
||||
double alpha,
|
||||
const matrix<double>& A,
|
||||
int A_row,
|
||||
int A_col,
|
||||
const matrix<double>& B,
|
||||
int B_row,
|
||||
int B_col,
|
||||
double beta,
|
||||
matrix<double>& C,
|
||||
int C_row,
|
||||
int C_col)
|
||||
{
|
||||
const auto dev = exec_place::device(C.get_preferred_devid(C_row, C_col));
|
||||
|
||||
auto t = ctx.task(
|
||||
dev, A.get_handle(A_row, A_col).read(), B.get_handle(B_row, B_col).read(), C.get_handle(C_row, C_col).rw());
|
||||
t.set_symbol("DGEMM");
|
||||
|
||||
t->*[&](cudaStream_t stream, auto tA, auto tB, auto tC) {
|
||||
cuda_safe_call(cublasSetStream(get_cublas_handle(), stream));
|
||||
int k = tA.extent(transa == CUBLAS_OP_N ? 1 : 0);
|
||||
cuda_safe_call(cublasDgemm(
|
||||
get_cublas_handle(),
|
||||
transa,
|
||||
transb,
|
||||
tC.extent(0),
|
||||
tC.extent(1),
|
||||
k,
|
||||
&alpha,
|
||||
tA.data_handle(),
|
||||
tA.stride(1),
|
||||
tB.data_handle(),
|
||||
tB.stride(1),
|
||||
&beta,
|
||||
tC.data_handle(),
|
||||
tC.stride(1)));
|
||||
};
|
||||
}
|
||||
|
||||
void PDGEMM(stackable_ctx& ctx,
|
||||
cublasOperation_t transa,
|
||||
cublasOperation_t transb,
|
||||
double alpha,
|
||||
const matrix<double>& A,
|
||||
const matrix<double>& B,
|
||||
double beta,
|
||||
matrix<double>& C)
|
||||
{
|
||||
for (size_t m = 0; m < C.mt; m++)
|
||||
{
|
||||
for (size_t n = 0; n < C.nt; n++)
|
||||
{
|
||||
//=========================================
|
||||
// alpha*A*B does not contribute; scale C
|
||||
//=========================================
|
||||
const size_t inner_k = transa == CUBLAS_OP_N ? A.n : A.m;
|
||||
if (alpha == 0.0 || inner_k == 0)
|
||||
{
|
||||
DGEMM(ctx, transa, transb, alpha, A, 0, 0, B, 0, 0, beta, C, static_cast<int>(m), static_cast<int>(n));
|
||||
}
|
||||
else if (transa == CUBLAS_OP_N)
|
||||
{
|
||||
//================================
|
||||
// CUBLAS_OP_N / CUBLAS_OP_N
|
||||
//================================
|
||||
if (transb == CUBLAS_OP_N)
|
||||
{
|
||||
assert(A.nt == B.mt);
|
||||
for (size_t k = 0; k < A.nt; k++)
|
||||
{
|
||||
const double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx,
|
||||
transa,
|
||||
transb,
|
||||
alpha,
|
||||
A,
|
||||
static_cast<int>(m),
|
||||
static_cast<int>(k),
|
||||
B,
|
||||
static_cast<int>(k),
|
||||
static_cast<int>(n),
|
||||
zbeta,
|
||||
C,
|
||||
static_cast<int>(m),
|
||||
static_cast<int>(n));
|
||||
}
|
||||
}
|
||||
//=====================================
|
||||
// CUBLAS_OP_N / CUBLAS_OP_T
|
||||
//=====================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < A.nt; k++)
|
||||
{
|
||||
const double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx,
|
||||
transa,
|
||||
transb,
|
||||
alpha,
|
||||
A,
|
||||
static_cast<int>(m),
|
||||
static_cast<int>(k),
|
||||
B,
|
||||
static_cast<int>(n),
|
||||
static_cast<int>(k),
|
||||
zbeta,
|
||||
C,
|
||||
static_cast<int>(m),
|
||||
static_cast<int>(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//=====================================
|
||||
// CUBLAS_OP_T / CUBLAS_OP_N
|
||||
//=====================================
|
||||
if (transb == CUBLAS_OP_N)
|
||||
{
|
||||
for (size_t k = 0; k < A.mt; k++)
|
||||
{
|
||||
const double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx,
|
||||
transa,
|
||||
transb,
|
||||
alpha,
|
||||
A,
|
||||
static_cast<int>(k),
|
||||
static_cast<int>(m),
|
||||
B,
|
||||
static_cast<int>(k),
|
||||
static_cast<int>(n),
|
||||
zbeta,
|
||||
C,
|
||||
static_cast<int>(m),
|
||||
static_cast<int>(n));
|
||||
}
|
||||
}
|
||||
//==========================================
|
||||
// CUBLAS_OP_T / CUBLAS_OP_T
|
||||
//==========================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < A.mt; k++)
|
||||
{
|
||||
const double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx,
|
||||
transa,
|
||||
transb,
|
||||
alpha,
|
||||
A,
|
||||
static_cast<int>(k),
|
||||
static_cast<int>(m),
|
||||
B,
|
||||
static_cast<int>(n),
|
||||
static_cast<int>(k),
|
||||
zbeta,
|
||||
C,
|
||||
static_cast<int>(m),
|
||||
static_cast<int>(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run(stackable_ctx& ctx, size_t N, size_t NB)
|
||||
{
|
||||
/// auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, NB * NB * sizeof(double));
|
||||
// ctx.set_allocator(fixed_alloc);
|
||||
|
||||
// Set up CUBLAS and CUSOLVER
|
||||
int ndevs;
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
|
||||
/* Warm up allocators */
|
||||
for (int d = 0; d < ndevs; d++)
|
||||
{
|
||||
auto lX = ctx.logical_data(shape_of<slice<double>>(1));
|
||||
ctx.parallel_for(exec_place::device(d), lX.shape(), lX.write())->*[] _CCCL_DEVICE(size_t, auto) {};
|
||||
}
|
||||
|
||||
/* Initializes CUBLAS on all devices */
|
||||
for (int d = 0; d < ndevs; d++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(d));
|
||||
get_cublas_handle();
|
||||
}
|
||||
|
||||
matrix<double> A(ctx, N, N, NB, NB, "A");
|
||||
matrix<double> B(ctx, N, N, NB, NB, "B");
|
||||
matrix<double> C(ctx, N, N, NB, NB, "C");
|
||||
|
||||
// (Hilbert matrix + 2*N*Id)
|
||||
auto hilbert = [=] _CCCL_HOST_DEVICE(size_t row, size_t col) {
|
||||
return 1.0 / (col + row + 1.0) + 2.0 * N * (col == row);
|
||||
};
|
||||
|
||||
A.fill(ctx, hilbert);
|
||||
B.fill(ctx, hilbert);
|
||||
C.fill(ctx, hilbert);
|
||||
|
||||
cudaEvent_t startEvent, stopEvent;
|
||||
|
||||
cuda_safe_call(cudaEventCreate(&startEvent));
|
||||
cuda_safe_call(cudaEventCreate(&stopEvent));
|
||||
|
||||
cuda_safe_call(cudaEventRecord(startEvent, ctx.fence()));
|
||||
|
||||
ctx.push();
|
||||
A.push(access_mode::read);
|
||||
B.push(access_mode::read);
|
||||
C.push(access_mode::rw);
|
||||
PDGEMM(ctx, CUBLAS_OP_N, CUBLAS_OP_N, 1.0, A, B, -2.0, C);
|
||||
ctx.pop();
|
||||
|
||||
cuda_safe_call(cudaEventRecord(stopEvent, ctx.fence()));
|
||||
|
||||
ctx.finalize();
|
||||
|
||||
float milliseconds;
|
||||
cuda_safe_call(cudaEventElapsedTime(&milliseconds, startEvent, stopEvent));
|
||||
|
||||
const double gflops_pdgemm =
|
||||
2.0 * (static_cast<double>(N) * static_cast<double>(N) * static_cast<double>(N)) / 1000000000.0;
|
||||
::std::cout
|
||||
<< "[PDDGEMM] ELAPSED: " << milliseconds << " ms, GFLOPS: " << gflops_pdgemm / (milliseconds / 1000.0) << '\n';
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
size_t N = 4096;
|
||||
size_t NB = 512;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = static_cast<size_t>(::std::atoi(argv[1]));
|
||||
}
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
NB = static_cast<size_t>(::std::atoi(argv[2]));
|
||||
}
|
||||
|
||||
assert(N % NB == 0);
|
||||
|
||||
stackable_ctx ctx;
|
||||
run(ctx, N, NB);
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief An example that implements a tiled matrix product over multiple devices using CUBLAS
|
||||
*
|
||||
* This also illustrates how the same code base can be used both with a
|
||||
* stream_ctx and a graph_ctx backend.
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/__stf/utility/nvtx.cuh>
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#define TILED
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
static std::unordered_map<exec_place, cublasHandle_t, hash<exec_place>> cublas_handles;
|
||||
|
||||
/* Get a CUBLAS handle valid on the current execution place, or initialize it lazily */
|
||||
cublasHandle_t get_cublas_handle(const exec_place& ep = exec_place::current_device())
|
||||
{
|
||||
auto& result = cublas_handles[ep];
|
||||
if (result == cublasHandle_t())
|
||||
{ // not found, default value inserted
|
||||
// Lazy initialization, and save the handle for future use
|
||||
cuda_safe_call(cublasCreate(&result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class matrix
|
||||
{
|
||||
public:
|
||||
matrix(stream_ctx& ctx,
|
||||
size_t NROWS,
|
||||
size_t NCOLS,
|
||||
size_t BLOCKSIZE_ROWS,
|
||||
size_t BLOCKSIZE_COLS,
|
||||
const char* _symbol = "matrix")
|
||||
{
|
||||
symbol = _symbol;
|
||||
|
||||
m = NROWS;
|
||||
mb = BLOCKSIZE_ROWS;
|
||||
|
||||
n = NCOLS;
|
||||
nb = BLOCKSIZE_COLS;
|
||||
|
||||
assert(m % mb == 0);
|
||||
assert(n % nb == 0);
|
||||
|
||||
size_t s = ((size_t) m) * ((size_t) n) * sizeof(T);
|
||||
// cuda_safe_call(cudaMallocHost(&h_array, m*n*sizeof(T)));
|
||||
// fprintf(stderr, "Allocating %ld x %ld x %ld = %ld bytes (%f GB) on host for %s\n", m, n, sizeof(T), s,
|
||||
// s / (1024.0 * 1024.0 * 1024.0), _symbol);
|
||||
h_array = (T*) malloc(s);
|
||||
assert(h_array);
|
||||
cuda_safe_call(cudaHostRegister(h_array, s, cudaHostRegisterPortable));
|
||||
|
||||
// Compute the number of blocks
|
||||
mt = m / mb;
|
||||
nt = n / nb;
|
||||
|
||||
handles.resize(mt * nt);
|
||||
|
||||
for (size_t colb = 0; colb < nt; colb++)
|
||||
{
|
||||
for (size_t rowb = 0; rowb < mt; rowb++)
|
||||
{
|
||||
T* addr_h = get_block_h(rowb, colb);
|
||||
|
||||
#ifdef TILED
|
||||
// tiles are stored contiguously
|
||||
const size_t ld = mb;
|
||||
#else
|
||||
const size_t ld = m;
|
||||
#endif
|
||||
|
||||
std::ignore = ld; // avoid warning #177-D: variable "ld" was declared but never referenced
|
||||
auto s = make_slice(addr_h, std::tuple{mb, nb}, ld);
|
||||
auto tile = ctx.logical_data(s);
|
||||
tile.set_write_back(false);
|
||||
|
||||
tile.set_symbol(std::string(symbol) + "_" + std::to_string(rowb) + "_" + std::to_string(colb));
|
||||
|
||||
handles[rowb + colb * mt] = std::move(tile);
|
||||
}
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
for (int a = 1; a * a <= ndevs; a++)
|
||||
{
|
||||
if (ndevs % a == 0)
|
||||
{
|
||||
grid_p = a;
|
||||
grid_q = ndevs / a;
|
||||
}
|
||||
}
|
||||
|
||||
assert(grid_p * grid_q == ndevs);
|
||||
|
||||
// std::cout << "FOUND " << ndevs << " DEVICES "
|
||||
// << "p=" << grid_p << " q=" << grid_q << '\n';
|
||||
}
|
||||
|
||||
int get_preferred_devid(int row, int col)
|
||||
{
|
||||
return (row % grid_p) + (col % grid_q) * grid_p;
|
||||
}
|
||||
|
||||
logical_data<slice<T, 2>>& get_handle(int row, int col)
|
||||
{
|
||||
return handles[row + col * mt];
|
||||
}
|
||||
|
||||
size_t get_index(size_t row, size_t col)
|
||||
{
|
||||
#ifdef TILED
|
||||
// Find which tile contains this element
|
||||
int tile_row = row / mb;
|
||||
int tile_col = col / nb;
|
||||
|
||||
size_t tile_size = mb * nb;
|
||||
|
||||
// Look for the index of the beginning of the tile
|
||||
size_t tile_start = (tile_row + mt * tile_col) * tile_size;
|
||||
|
||||
// Offset within the tile
|
||||
size_t offset = (row % mb) + (col % nb) * mb;
|
||||
|
||||
return tile_start + offset;
|
||||
#else
|
||||
return row + col * m;
|
||||
#endif
|
||||
}
|
||||
|
||||
T* get_block_h(int brow, int bcol)
|
||||
{
|
||||
size_t index = get_index(brow * mb, bcol * nb);
|
||||
return &h_array[index];
|
||||
}
|
||||
|
||||
// Fill with func(Matrix*,row, col)
|
||||
template <typename Fun>
|
||||
void fill(stream_ctx& ctx, Fun&& fun)
|
||||
{
|
||||
nvtx_range r("fill");
|
||||
|
||||
// Fill blocks by blocks
|
||||
for (size_t colb = 0; colb < nt; colb++)
|
||||
{
|
||||
for (size_t rowb = 0; rowb < mt; rowb++)
|
||||
{
|
||||
// Each task fills a block
|
||||
auto& h = get_handle(rowb, colb);
|
||||
int devid = get_preferred_devid(rowb, colb);
|
||||
|
||||
ctx.parallel_for(exec_place::device(devid), h.shape(), h.write()).set_symbol("INIT")->*
|
||||
[=] _CCCL_DEVICE(size_t lrow, size_t lcol, auto sA) {
|
||||
size_t row = lrow + rowb * sA.extent(0);
|
||||
size_t col = lcol + colb * sA.extent(1);
|
||||
sA(lrow, lcol) = fun(row, col);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
T* h_array;
|
||||
size_t m; // nrows
|
||||
size_t n; // ncols
|
||||
|
||||
size_t mb; // block size (rows)
|
||||
size_t nb; // block size (cols)
|
||||
|
||||
size_t mt; // numter of column blocks
|
||||
size_t nt; // numter of row blocks
|
||||
|
||||
// abstract data handles
|
||||
std::vector<logical_data<slice<T, 2>>> handles;
|
||||
|
||||
const char* symbol;
|
||||
|
||||
// for the mapping
|
||||
int ndevs;
|
||||
int grid_p, grid_q;
|
||||
};
|
||||
|
||||
void DGEMM(
|
||||
stream_ctx& ctx,
|
||||
cublasOperation_t transa,
|
||||
cublasOperation_t transb,
|
||||
double alpha,
|
||||
matrix<double>& A,
|
||||
int A_row,
|
||||
int A_col,
|
||||
matrix<double>& B,
|
||||
int B_row,
|
||||
int B_col,
|
||||
double beta,
|
||||
matrix<double>& C,
|
||||
int C_row,
|
||||
int C_col)
|
||||
{
|
||||
auto dev = exec_place::device(C.get_preferred_devid(C_row, C_col));
|
||||
|
||||
auto t = ctx.task(
|
||||
dev, A.get_handle(A_row, A_col).read(), B.get_handle(B_row, B_col).read(), C.get_handle(C_row, C_col).rw());
|
||||
t.set_symbol("DGEMM");
|
||||
|
||||
t->*[&](cudaStream_t stream, auto tA, auto tB, auto tC) {
|
||||
cuda_safe_call(cublasSetStream(get_cublas_handle(), stream));
|
||||
int k = tA.extent(transa == CUBLAS_OP_N ? 1 : 0);
|
||||
cuda_safe_call(cublasDgemm(
|
||||
get_cublas_handle(),
|
||||
transa,
|
||||
transb,
|
||||
tC.extent(0),
|
||||
tC.extent(1),
|
||||
k,
|
||||
&alpha,
|
||||
tA.data_handle(),
|
||||
tA.stride(1),
|
||||
tB.data_handle(),
|
||||
tB.stride(1),
|
||||
&beta,
|
||||
tC.data_handle(),
|
||||
tC.stride(1)));
|
||||
};
|
||||
}
|
||||
|
||||
void PDGEMM(stream_ctx& ctx,
|
||||
cublasOperation_t transa,
|
||||
cublasOperation_t transb,
|
||||
double alpha,
|
||||
matrix<double>& A,
|
||||
matrix<double>& B,
|
||||
double beta,
|
||||
matrix<double>& C)
|
||||
{
|
||||
nvtx_range r("PDGEMM");
|
||||
|
||||
for (size_t m = 0; m < C.mt; m++)
|
||||
{
|
||||
for (size_t n = 0; n < C.nt; n++)
|
||||
{
|
||||
//=========================================
|
||||
// alpha*A*B does not contribute; scale C
|
||||
//=========================================
|
||||
int inner_k = transa == CUBLAS_OP_N ? A.n : A.m;
|
||||
if (alpha == 0.0 || inner_k == 0)
|
||||
{
|
||||
DGEMM(ctx, transa, transb, alpha, A, 0, 0, B, 0, 0, beta, C, m, n);
|
||||
}
|
||||
else if (transa == CUBLAS_OP_N)
|
||||
{
|
||||
//================================
|
||||
// CUBLAS_OP_N / CUBLAS_OP_N
|
||||
//================================
|
||||
if (transb == CUBLAS_OP_N)
|
||||
{
|
||||
assert(A.nt == B.mt);
|
||||
for (size_t k = 0; k < A.nt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx, transa, transb, alpha, A, m, k, B, k, n, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
//=====================================
|
||||
// CUBLAS_OP_N / CUBLAS_OP_T
|
||||
//=====================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < A.nt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx, transa, transb, alpha, A, m, k, B, n, k, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//=====================================
|
||||
// CUBLAS_OP_T / CUBLAS_OP_N
|
||||
//=====================================
|
||||
if (transb == CUBLAS_OP_N)
|
||||
{
|
||||
for (size_t k = 0; k < A.mt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx, transa, transb, alpha, A, k, m, B, k, n, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
//==========================================
|
||||
// CUBLAS_OP_T / CUBLAS_OP_T
|
||||
//==========================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < A.mt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(ctx, transa, transb, alpha, A, k, m, B, n, k, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void run(stream_ctx& ctx, size_t N, size_t NB)
|
||||
{
|
||||
auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, NB * NB * sizeof(double));
|
||||
ctx.set_allocator(fixed_alloc);
|
||||
|
||||
// Set up CUBLAS and CUSOLVER
|
||||
int ndevs;
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
|
||||
/* Warm up allocators */
|
||||
for (int d = 0; d < ndevs; d++)
|
||||
{
|
||||
auto lX = ctx.logical_data(shape_of<slice<double>>(1));
|
||||
ctx.parallel_for(exec_place::device(d), lX.shape(), lX.write())->*[] _CCCL_DEVICE(size_t, auto) {};
|
||||
}
|
||||
|
||||
/* Initializes CUBLAS on all devices */
|
||||
for (int d = 0; d < ndevs; d++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(d));
|
||||
get_cublas_handle();
|
||||
}
|
||||
|
||||
matrix<double> A(ctx, N, N, NB, NB, "A");
|
||||
matrix<double> B(ctx, N, N, NB, NB, "B");
|
||||
matrix<double> C(ctx, N, N, NB, NB, "C");
|
||||
|
||||
// (Hilbert matrix + 2*N*Id)
|
||||
auto hilbert = [=] _CCCL_HOST_DEVICE(size_t row, size_t col) {
|
||||
return 1.0 / (col + row + 1.0) + 2.0 * N * (col == row);
|
||||
};
|
||||
|
||||
A.fill(ctx, hilbert);
|
||||
B.fill(ctx, hilbert);
|
||||
C.fill(ctx, hilbert);
|
||||
|
||||
cudaEvent_t startEvent, stopEvent;
|
||||
|
||||
cuda_safe_call(cudaEventCreate(&startEvent));
|
||||
cuda_safe_call(cudaEventCreate(&stopEvent));
|
||||
|
||||
cuda_safe_call(cudaEventRecord(startEvent, ctx.fence()));
|
||||
|
||||
PDGEMM(ctx, CUBLAS_OP_N, CUBLAS_OP_N, 1.0, A, B, -2.0, C);
|
||||
|
||||
cuda_safe_call(cudaEventRecord(stopEvent, ctx.fence()));
|
||||
|
||||
ctx.finalize();
|
||||
|
||||
float milliseconds;
|
||||
cuda_safe_call(cudaEventElapsedTime(&milliseconds, startEvent, stopEvent));
|
||||
|
||||
double gflops_pdgemm = 2.0 * ((double) N * (double) N * (double) N) / (1000000000.0);
|
||||
std::cout
|
||||
<< "[PDDGEMM] ELAPSED: " << milliseconds << " ms, GFLOPS: " << gflops_pdgemm / (milliseconds / 1000.0) << '\n';
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
size_t N = 4096;
|
||||
size_t NB = 512;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
}
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
NB = atoi(argv[2]);
|
||||
}
|
||||
|
||||
assert(N % NB == 0);
|
||||
|
||||
stream_ctx ctx;
|
||||
run(ctx, N, NB);
|
||||
|
||||
// // Also run using a graph context.
|
||||
// ctx = graph_ctx();
|
||||
// run(ctx, N, NB);
|
||||
}
|
||||
@@ -1,748 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
*
|
||||
* @brief This example implements a Cholesky decomposition over multiple devices using CUBLAS and CUSOLVER
|
||||
*
|
||||
* It also illustrates how we can use CUDASTF to allocate temporary data for CUSOLVER in CUDASTF tasks
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/__stf/utility/nvtx.cuh>
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <cublas_v2.h>
|
||||
|
||||
#define TILED
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
// Global for the sake of simplicity !
|
||||
stream_ctx ctx;
|
||||
|
||||
/* Get a CUBLAS handle valid on the current execution place, or initialize it lazily */
|
||||
cublasHandle_t& get_cublas_handle(const exec_place& ep = exec_place::current_device())
|
||||
{
|
||||
static std::unordered_map<exec_place, cublasHandle_t, hash<exec_place>> cublas_handles;
|
||||
auto& result = cublas_handles[ep];
|
||||
if (result == cublasHandle_t())
|
||||
{ // not found, default value inserted
|
||||
// Lazy initialization, and save the handle for future use
|
||||
cuda_safe_call(cublasCreate(&result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Get a CUSOLVER handle valid on the current execution place, or initialize it lazily */
|
||||
cusolverDnHandle_t& get_cusolver_handle(const exec_place& ep = exec_place::current_device())
|
||||
{
|
||||
static std::unordered_map<exec_place, cusolverDnHandle_t, hash<exec_place>> cusolver_handles;
|
||||
auto& result = cusolver_handles[ep];
|
||||
if (result == cusolverDnHandle_t())
|
||||
{ // not found, default value inserted
|
||||
// Lazy initialization, and save the handle for future use
|
||||
cuda_safe_call(cusolverDnCreate(&result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
class matrix
|
||||
{
|
||||
public:
|
||||
matrix(int NROWS, int NCOLS, int BLOCKSIZE_ROWS, int BLOCKSIZE_COLS, bool is_sym, const char* _symbol = "matrix")
|
||||
{
|
||||
symbol = _symbol;
|
||||
|
||||
sym_matrix = is_sym;
|
||||
|
||||
m = NROWS;
|
||||
mb = BLOCKSIZE_ROWS;
|
||||
|
||||
n = NCOLS;
|
||||
nb = BLOCKSIZE_COLS;
|
||||
|
||||
assert(m % mb == 0);
|
||||
assert(n % nb == 0);
|
||||
|
||||
// cuda_safe_call(cudaMallocHost(&h_array, m*n*sizeof(T)));
|
||||
// fprintf(stderr, "Allocating %ld x %ld x %ld = %ld bytes (%f GB) on host for %s\n", m, n, sizeof(T), s,
|
||||
// s / (1024.0 * 1024.0 * 1024.0), _symbol);
|
||||
h_array.resize(m * n);
|
||||
cuda_safe_call(cudaHostRegister(&h_array[0], h_array.size() * sizeof(T), cudaHostRegisterPortable));
|
||||
|
||||
// Compute the number of blocks
|
||||
mt = m / mb;
|
||||
nt = n / nb;
|
||||
|
||||
handles.resize(mt * nt);
|
||||
|
||||
for (size_t colb = 0; colb < nt; colb++)
|
||||
{
|
||||
int low_rowb = sym_matrix ? colb : 0;
|
||||
for (size_t rowb = low_rowb; rowb < mt; rowb++)
|
||||
{
|
||||
T* addr_h = get_block_h(rowb, colb);
|
||||
auto& h = handle(rowb, colb);
|
||||
|
||||
#ifdef TILED
|
||||
// tiles are stored contiguously
|
||||
size_t ld = mb;
|
||||
#else
|
||||
size_t ld = m;
|
||||
#endif
|
||||
std::ignore = ld; // work around bug in compiler
|
||||
h = ctx.logical_data(make_slice(addr_h, std::tuple{mb, nb}, ld));
|
||||
h.set_symbol(std::string(symbol) + "_" + std::to_string(rowb) + "_" + std::to_string(colb));
|
||||
h.set_write_back(false);
|
||||
}
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
for (int a = 1; a * a <= ndevs; a++)
|
||||
{
|
||||
if (ndevs % a == 0)
|
||||
{
|
||||
grid_p = a;
|
||||
grid_q = ndevs / a;
|
||||
}
|
||||
}
|
||||
|
||||
assert(grid_p * grid_q == ndevs);
|
||||
|
||||
// std::cout << "FOUND " << ndevs << " DEVICES "
|
||||
// << "p=" << grid_p << " q=" << grid_q << '\n';
|
||||
}
|
||||
|
||||
int get_preferred_devid(int row, int col)
|
||||
{
|
||||
return (row % grid_p) + (col % grid_q) * grid_p;
|
||||
}
|
||||
|
||||
auto& handle(int row, int col)
|
||||
{
|
||||
return handles[row + col * mt];
|
||||
}
|
||||
|
||||
size_t get_index(size_t row, size_t col)
|
||||
{
|
||||
#ifdef TILED
|
||||
// Find which tile contains this element
|
||||
int tile_row = row / mb;
|
||||
int tile_col = col / nb;
|
||||
|
||||
size_t tile_size = mb * nb;
|
||||
|
||||
// Look for the index of the beginning of the tile
|
||||
size_t tile_start = (tile_row + mt * tile_col) * tile_size;
|
||||
|
||||
// Offset within the tile
|
||||
size_t offset = (row % mb) + (col % nb) * mb;
|
||||
|
||||
return tile_start + offset;
|
||||
#else
|
||||
return row + col * m;
|
||||
#endif
|
||||
}
|
||||
|
||||
T* get_block_h(int brow, int bcol)
|
||||
{
|
||||
size_t index = get_index(brow * mb, bcol * nb);
|
||||
return &h_array[index];
|
||||
}
|
||||
|
||||
// Fill with func(Matrix*,row, col)
|
||||
template <typename Fun>
|
||||
void fill(Fun&& fun)
|
||||
{
|
||||
nvtx_range r("fill");
|
||||
|
||||
// Fill blocks by blocks
|
||||
for (size_t colb = 0; colb < nt; colb++)
|
||||
{
|
||||
size_t low_rowb = sym_matrix ? colb : 0;
|
||||
for (size_t rowb = low_rowb; rowb < mt; rowb++)
|
||||
{
|
||||
// Each task fills a block
|
||||
auto& h = handle(rowb, colb);
|
||||
int devid = get_preferred_devid(rowb, colb);
|
||||
|
||||
ctx.parallel_for(exec_place::device(devid), h.shape(), h.write()).set_symbol("INIT")->*
|
||||
[=] _CCCL_DEVICE(size_t lrow, size_t lcol, auto sA) {
|
||||
size_t row = lrow + rowb * sA.extent(0);
|
||||
size_t col = lcol + colb * sA.extent(1);
|
||||
sA(lrow, lcol) = fun(row, col);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<T> h_array;
|
||||
size_t m; // nrows
|
||||
size_t n; // ncols
|
||||
|
||||
// Is this a sym matrix ? (lower assumed)
|
||||
bool sym_matrix;
|
||||
|
||||
size_t mb; // block size (rows)
|
||||
size_t nb; // block size (cols)
|
||||
|
||||
size_t mt; // number of column blocks
|
||||
size_t nt; // number of row blocks
|
||||
|
||||
// abstract data handles
|
||||
std::vector<logical_data<slice<double, 2>>> handles;
|
||||
|
||||
const char* symbol;
|
||||
|
||||
// for the mapping
|
||||
int ndevs;
|
||||
int grid_p, grid_q;
|
||||
};
|
||||
|
||||
void DPOTRF(cublasFillMode_t uplo, class matrix<double>& A, int A_row, int A_col)
|
||||
{
|
||||
auto& Akk = A.handle(A_row, A_col);
|
||||
size_t m_akk = Akk.shape().extent(0);
|
||||
// Note that the handle may be different from the actual handle...
|
||||
int Lwork_expected;
|
||||
cuda_safe_call(cusolverDnDpotrf_bufferSize(get_cusolver_handle(), uplo, m_akk, nullptr, 0, &Lwork_expected));
|
||||
|
||||
auto potrf_buffer = ctx.logical_data<double>(size_t(Lwork_expected));
|
||||
potrf_buffer.set_allocator(ctx.get_default_allocator());
|
||||
|
||||
auto devInfo = ctx.logical_data(shape_of<slice<int>>(1));
|
||||
|
||||
auto t =
|
||||
ctx.task(exec_place::device(A.get_preferred_devid(A_row, A_col)), Akk.rw(), potrf_buffer.write(), devInfo.write());
|
||||
t.set_symbol("DPOTRF");
|
||||
t->*[uplo](cudaStream_t s, auto sAkk, auto buffer, auto info) {
|
||||
auto& h = get_cusolver_handle();
|
||||
cuda_safe_call(cusolverDnSetStream(h, s));
|
||||
|
||||
cuda_safe_call(cusolverDnDpotrf(
|
||||
h,
|
||||
uplo,
|
||||
sAkk.extent(0),
|
||||
sAkk.data_handle(),
|
||||
sAkk.stride(1),
|
||||
buffer.data_handle(),
|
||||
buffer.extent(0),
|
||||
info.data_handle()));
|
||||
};
|
||||
}
|
||||
|
||||
void DGEMM(
|
||||
cublasOperation_t transa,
|
||||
cublasOperation_t transb,
|
||||
double alpha,
|
||||
class matrix<double>& A,
|
||||
int A_row,
|
||||
int A_col,
|
||||
class matrix<double>& B,
|
||||
int B_row,
|
||||
int B_col,
|
||||
double beta,
|
||||
class matrix<double>& C,
|
||||
int C_row,
|
||||
int C_col)
|
||||
{
|
||||
auto t = ctx.task(exec_place::device(A.get_preferred_devid(C_row, C_col)),
|
||||
A.handle(A_row, A_col).read(),
|
||||
B.handle(B_row, B_col).read(),
|
||||
C.handle(C_row, C_col).rw());
|
||||
t.set_symbol("DGEMM");
|
||||
t->*[transa, transb, alpha, beta](cudaStream_t s, auto sA, auto sB, auto sC) {
|
||||
auto& h = get_cublas_handle();
|
||||
cuda_safe_call(cublasSetStream(h, s));
|
||||
|
||||
auto k = (transa == CUBLAS_OP_N) ? sA.extent(1) : sA.extent(0);
|
||||
cuda_safe_call(cublasDgemm(
|
||||
h,
|
||||
transa,
|
||||
transb,
|
||||
sC.extent(0),
|
||||
sC.extent(1),
|
||||
k,
|
||||
&alpha,
|
||||
sA.data_handle(),
|
||||
sA.stride(1),
|
||||
sB.data_handle(),
|
||||
sB.stride(1),
|
||||
&beta,
|
||||
sC.data_handle(),
|
||||
sC.stride(1)));
|
||||
};
|
||||
}
|
||||
|
||||
void DSYRK(
|
||||
cublasFillMode_t uplo,
|
||||
cublasOperation_t trans,
|
||||
double alpha,
|
||||
class matrix<double>& A,
|
||||
int A_row,
|
||||
int A_col,
|
||||
double beta,
|
||||
class matrix<double>& C,
|
||||
int C_row,
|
||||
int C_col)
|
||||
{
|
||||
auto t = ctx.task(exec_place::device(A.get_preferred_devid(C_row, C_col)),
|
||||
A.handle(A_row, A_col).read(),
|
||||
C.handle(C_row, C_col).rw());
|
||||
t.set_symbol("DSYRK");
|
||||
t->*[uplo, trans, alpha, beta](cudaStream_t s, auto sA, auto sC) {
|
||||
auto& h = get_cublas_handle();
|
||||
cuda_safe_call(cublasSetStream(h, s));
|
||||
|
||||
// number of rows of matrix op(A) and C
|
||||
auto n = sC.extent(0);
|
||||
|
||||
// number of columns of matrix op(A)
|
||||
auto k = (trans == CUBLAS_OP_N) ? sA.extent(1) : sA.extent(0);
|
||||
|
||||
cuda_safe_call(
|
||||
cublasDsyrk(h, uplo, trans, n, k, &alpha, sA.data_handle(), sA.stride(1), &beta, sC.data_handle(), sC.stride(1)));
|
||||
};
|
||||
}
|
||||
|
||||
void DTRSM(
|
||||
cublasSideMode_t side,
|
||||
cublasFillMode_t uplo,
|
||||
cublasOperation_t transa,
|
||||
cublasDiagType_t diag,
|
||||
double alpha,
|
||||
class matrix<double>& A,
|
||||
int A_row,
|
||||
int A_col,
|
||||
class matrix<double>& B,
|
||||
int B_row,
|
||||
int B_col)
|
||||
{
|
||||
auto t = ctx.task(exec_place::device(A.get_preferred_devid(B_row, B_col)),
|
||||
A.handle(A_row, A_col).read(),
|
||||
B.handle(B_row, B_col).rw());
|
||||
t.set_symbol("DTRSM");
|
||||
t->*[side, uplo, transa, diag, alpha](cudaStream_t s, auto sA, auto sB) {
|
||||
auto& h = get_cublas_handle();
|
||||
cuda_safe_call(cublasSetStream(h, s));
|
||||
|
||||
cuda_safe_call(cublasDtrsm(
|
||||
h,
|
||||
side,
|
||||
uplo,
|
||||
transa,
|
||||
diag,
|
||||
sB.extent(0),
|
||||
sB.extent(1),
|
||||
&alpha,
|
||||
sA.data_handle(),
|
||||
sA.stride(1),
|
||||
sB.data_handle(),
|
||||
sB.stride(1)));
|
||||
};
|
||||
}
|
||||
|
||||
void PDNRM2_HOST(matrix<double>* A, double* result)
|
||||
{
|
||||
#ifdef HAVE_DOT
|
||||
reserved::dot::set_current_color("red");
|
||||
#endif
|
||||
|
||||
for (size_t rowb = 0; rowb < A->mt; rowb++)
|
||||
{
|
||||
for (size_t colb = 0; colb < A->nt; colb++)
|
||||
{
|
||||
ctx.host_launch(A->handle(rowb, colb).read())->*[=](auto sA) {
|
||||
double res2 = 0.0;
|
||||
for (size_t col = 0; col < sA.extent(1); col++)
|
||||
{
|
||||
for (size_t row = 0; row < sA.extent(0); row++)
|
||||
{
|
||||
double v = sA(row, col);
|
||||
res2 += v * v;
|
||||
}
|
||||
}
|
||||
*result += res2;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PDPOTRF(matrix<double>& A)
|
||||
{
|
||||
auto guard = ctx.dot_section("PDPOTRF");
|
||||
|
||||
#ifdef HAVE_DOT
|
||||
reserved::dot::set_current_color("yellow");
|
||||
#endif
|
||||
|
||||
assert(A.m == A.n);
|
||||
assert(A.mt == A.nt);
|
||||
|
||||
int NBLOCKS = A.mt;
|
||||
assert(A.mb == A.nb);
|
||||
|
||||
cuda_safe_call(cudaSetDevice(0));
|
||||
|
||||
for (int K = 0; K < NBLOCKS; K++)
|
||||
{
|
||||
int dev_akk = A.get_preferred_devid(K, K);
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(K, K)));
|
||||
DPOTRF(CUBLAS_FILL_MODE_LOWER, A, K, K);
|
||||
|
||||
for (int row = K + 1; row < NBLOCKS; row++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, K)));
|
||||
DTRSM(CUBLAS_SIDE_RIGHT, CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_T, CUBLAS_DIAG_NON_UNIT, 1.0, A, K, K, A, row, K);
|
||||
|
||||
for (int col = K + 1; col < row; col++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, col)));
|
||||
DGEMM(CUBLAS_OP_N, CUBLAS_OP_T, -1.0, A, row, K, A, col, K, 1.0, A, row, col);
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, row)));
|
||||
DSYRK(CUBLAS_FILL_MODE_LOWER, CUBLAS_OP_N, -1.0, A, row, K, 1.0, A, row, row);
|
||||
}
|
||||
}
|
||||
cuda_safe_call(cudaSetDevice(0));
|
||||
}
|
||||
|
||||
// Algorithm from PLASMA
|
||||
void PDTRSM(cublasSideMode_t side,
|
||||
cublasFillMode_t uplo,
|
||||
cublasOperation_t trans,
|
||||
cublasDiagType_t diag,
|
||||
double alpha,
|
||||
class matrix<double>& A,
|
||||
class matrix<double>& B)
|
||||
{
|
||||
auto guard = ctx.dot_section("PDTRSM");
|
||||
|
||||
// std::cout << "[PDTRSM] START B MT " << B.mt << " NT " << B.nt << '\n';
|
||||
|
||||
if (side == CUBLAS_SIDE_LEFT)
|
||||
{
|
||||
if (uplo == CUBLAS_FILL_MODE_UPPER)
|
||||
{
|
||||
// TODO
|
||||
assert(0);
|
||||
abort();
|
||||
}
|
||||
else
|
||||
{
|
||||
//===========================================
|
||||
// CUBLAS_SIDE_LEFT / CUBLAS_FILL_MODE_LOWER / CUBLAS_OP_N
|
||||
//===========================================
|
||||
if (trans == CUBLAS_OP_N)
|
||||
{
|
||||
for (size_t k = 0; k < B.mt; k++)
|
||||
{
|
||||
double lalpha = k == 0 ? alpha : 1.0;
|
||||
for (size_t n = 0; n < B.nt; n++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(k, k)));
|
||||
DTRSM(side, uplo, trans, diag, lalpha, A, k, k, B, k, n);
|
||||
}
|
||||
for (size_t m = k + 1; m < B.mt; m++)
|
||||
{
|
||||
for (size_t n = 0; n < B.nt; n++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(m, k)));
|
||||
DGEMM(CUBLAS_OP_N, CUBLAS_OP_N, -1.0, A, m, k, B, k, n, lalpha, B, m, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//================================================
|
||||
// CUBLAS_SIDE_LEFT / CUBLAS_FILL_MODE_LOWER / CUBLAS_OP_[C|T]
|
||||
//================================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < B.mt; k++)
|
||||
{
|
||||
double lalpha = k == 0 ? alpha : 1.0;
|
||||
for (size_t n = 0; n < B.nt; n++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(B.mt - k - 1, B.mt - k - 1)));
|
||||
DTRSM(side, uplo, trans, diag, lalpha, A, B.mt - k - 1, B.mt - k - 1, B, B.mt - k - 1, n);
|
||||
}
|
||||
for (size_t m = k + 1; m < B.mt; m++)
|
||||
{
|
||||
for (size_t n = 0; n < B.nt; n++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(A.get_preferred_devid(B.mt - k - 1, B.mt - 1 - m)));
|
||||
DGEMM(
|
||||
trans, CUBLAS_OP_N, -1.0, A, B.mt - k - 1, B.mt - 1 - m, B, B.mt - k - 1, n, lalpha, B, B.mt - 1 - m, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
abort();
|
||||
}
|
||||
cuda_safe_call(cudaSetDevice(0));
|
||||
// std::cout << "[PDTRSM] END" << '\n';
|
||||
}
|
||||
|
||||
void PDPOTRS(matrix<double>& A, class matrix<double>& B, cublasFillMode_t uplo)
|
||||
{
|
||||
auto guard = ctx.dot_section("PDPOTRS");
|
||||
#ifdef HAVE_DOT
|
||||
reserved::dot::set_current_color("green");
|
||||
#endif
|
||||
|
||||
// std::cout << "[PDPOTRS] START" << '\n';
|
||||
// Call the parallel functions.
|
||||
PDTRSM(
|
||||
CUBLAS_SIDE_LEFT, uplo, uplo == CUBLAS_FILL_MODE_UPPER ? CUBLAS_OP_T : CUBLAS_OP_N, CUBLAS_DIAG_NON_UNIT, 1.0, A, B);
|
||||
|
||||
#ifdef HAVE_DOT
|
||||
reserved::dot::set_current_color("darkgreen");
|
||||
#endif
|
||||
|
||||
PDTRSM(
|
||||
CUBLAS_SIDE_LEFT, uplo, uplo == CUBLAS_FILL_MODE_UPPER ? CUBLAS_OP_N : CUBLAS_OP_T, CUBLAS_DIAG_NON_UNIT, 1.0, A, B);
|
||||
// std::cout << "[PDPOTRS] END" << '\n';
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Parallel tile matrix-matrix
|
||||
*multiplication.
|
||||
* @see plasma_omp_dgemm
|
||||
******************************************************************************/
|
||||
void PDGEMM(cublasOperation_t transa,
|
||||
cublasOperation_t transb,
|
||||
double alpha,
|
||||
class matrix<double>& A,
|
||||
class matrix<double>& B,
|
||||
double beta,
|
||||
class matrix<double>& C)
|
||||
{
|
||||
auto guard = ctx.dot_section("PDGEMM");
|
||||
|
||||
#ifdef HAVE_DOT
|
||||
reserved::dot::set_current_color("blue");
|
||||
#endif
|
||||
|
||||
for (size_t m = 0; m < C.mt; m++)
|
||||
{
|
||||
for (size_t n = 0; n < C.nt; n++)
|
||||
{
|
||||
//=========================================
|
||||
// alpha*A*B does not contribute; scale C
|
||||
//=========================================
|
||||
int inner_k = transa == CUBLAS_OP_N ? A.n : A.m;
|
||||
if (alpha == 0.0 || inner_k == 0)
|
||||
{
|
||||
DGEMM(transa, transb, alpha, A, 0, 0, B, 0, 0, beta, C, m, n);
|
||||
}
|
||||
else if (transa == CUBLAS_OP_N)
|
||||
{
|
||||
//================================
|
||||
// CUBLAS_OP_N / CUBLAS_OP_N
|
||||
//================================
|
||||
if (transb == CUBLAS_OP_N)
|
||||
{
|
||||
for (size_t k = 0; k < A.nt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(transa, transb, alpha, A, m, k, B, k, n, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
//=====================================
|
||||
// CUBLAS_OP_N / CUBLAS_OP_T
|
||||
//=====================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < A.nt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(transa, transb, alpha, A, m, k, B, n, k, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//=====================================
|
||||
// CUBLAS_OP_T / CUBLAS_OP_N
|
||||
//=====================================
|
||||
if (transb == CUBLAS_OP_N)
|
||||
{
|
||||
for (size_t k = 0; k < A.mt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(transa, transb, alpha, A, k, m, B, k, n, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
//==========================================
|
||||
// CUBLAS_OP_T / CUBLAS_OP_T
|
||||
//==========================================
|
||||
else
|
||||
{
|
||||
for (size_t k = 0; k < A.mt; k++)
|
||||
{
|
||||
double zbeta = k == 0 ? beta : 1.0;
|
||||
DGEMM(transa, transb, alpha, A, k, m, B, n, k, zbeta, C, m, n);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
int N = 1024;
|
||||
int NB = 128;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
}
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
NB = atoi(argv[2]);
|
||||
}
|
||||
|
||||
int check_result = 1;
|
||||
if (getenv("CHECK_RESULT"))
|
||||
{
|
||||
check_result = atoi(getenv("CHECK_RESULT"));
|
||||
}
|
||||
|
||||
assert(N % NB == 0);
|
||||
|
||||
// Use pools of preallocated blocks
|
||||
auto fixed_alloc = block_allocator<fixed_size_allocator>(ctx, NB * NB * sizeof(double));
|
||||
ctx.set_allocator(fixed_alloc);
|
||||
|
||||
// Set up CUBLAS and CUSOLVER
|
||||
int ndevs;
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
|
||||
for (int d = 0; d < ndevs; d++)
|
||||
{
|
||||
auto lX = ctx.logical_data(shape_of<slice<double>>(1));
|
||||
ctx.parallel_for(exec_place::device(d), lX.shape(), lX.write())->*[] _CCCL_DEVICE(size_t, auto) {};
|
||||
cuda_safe_call(cudaSetDevice(d));
|
||||
get_cublas_handle();
|
||||
get_cusolver_handle();
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaSetDevice(0));
|
||||
|
||||
matrix<double> A(N, N, NB, NB, true, "A");
|
||||
matrix<double> Aref(N, N, NB, NB, false, "Aref");
|
||||
|
||||
// (Hilbert matrix + 2*N*Id) to have a diagonal dominant matrix
|
||||
auto hilbert = [=] _CCCL_HOST_DEVICE(size_t row, size_t col) {
|
||||
return 1.0 / (col + row + 1.0) + 2.0 * N * (col == row);
|
||||
};
|
||||
|
||||
auto s = ctx.dot_section("fillA");
|
||||
if (check_result)
|
||||
{
|
||||
Aref.fill(hilbert);
|
||||
}
|
||||
|
||||
A.fill(hilbert);
|
||||
s.end();
|
||||
|
||||
/* Right-hand side */
|
||||
matrix<double> B_potrs(N, 1, NB, 1, false, "B");
|
||||
matrix<double> Bref_potrs(N, 1, NB, 1, false, "Bref");
|
||||
|
||||
if (check_result)
|
||||
{
|
||||
auto rhs_vals = [] _CCCL_HOST_DEVICE(size_t row, size_t /*unused*/) {
|
||||
return 1.0 * (row + 1);
|
||||
};
|
||||
B_potrs.fill(rhs_vals);
|
||||
Bref_potrs.fill(rhs_vals);
|
||||
}
|
||||
|
||||
// // Compute ||Bref||
|
||||
double Bref_nrm2 = 0.0;
|
||||
double res_nrm2 = 0.0;
|
||||
|
||||
if (check_result)
|
||||
{
|
||||
PDNRM2_HOST(&Bref_potrs, &Bref_nrm2);
|
||||
}
|
||||
|
||||
cudaEvent_t startEvent_pdpotrf, stopEvent_pdpotrf;
|
||||
float milliseconds_pdpotrf = 0;
|
||||
|
||||
// for (size_t row = 0; row < A.mt; row++)
|
||||
// {
|
||||
// for (size_t col = 0; col <= row; col++)
|
||||
// {
|
||||
// cuda_safe_call(cudaSetDevice(A.get_preferred_devid(row, col)));
|
||||
// NOOP(A, row, col);
|
||||
// }
|
||||
// }
|
||||
|
||||
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
|
||||
|
||||
cuda_safe_call(cudaEventCreate(&startEvent_pdpotrf));
|
||||
cuda_safe_call(cudaEventCreate(&stopEvent_pdpotrf));
|
||||
|
||||
cuda_safe_call(cudaEventRecord(startEvent_pdpotrf, ctx.fence()));
|
||||
|
||||
PDPOTRF(A);
|
||||
|
||||
cuda_safe_call(cudaEventRecord(stopEvent_pdpotrf, ctx.fence()));
|
||||
|
||||
/*
|
||||
* POTRS
|
||||
*/
|
||||
|
||||
if (check_result)
|
||||
{
|
||||
// Solve AX = B and put the result in B
|
||||
PDPOTRS(A, B_potrs, CUBLAS_FILL_MODE_LOWER);
|
||||
|
||||
// Compute (AX - B)
|
||||
// Bref = (Aref*B - Bref)
|
||||
PDGEMM(CUBLAS_OP_N, CUBLAS_OP_N, 1.0, Aref, B_potrs, -1.0, Bref_potrs);
|
||||
|
||||
// Compute ||AX - B|| = ||Bref||
|
||||
PDNRM2_HOST(&Bref_potrs, &res_nrm2);
|
||||
}
|
||||
|
||||
ctx.finalize();
|
||||
|
||||
cuda_safe_call(cudaEventElapsedTime(&milliseconds_pdpotrf, startEvent_pdpotrf, stopEvent_pdpotrf));
|
||||
|
||||
double gflops_pdpotrf = 1.0 / 3.0 * ((double) N * (double) N * (double) N) / (1000000000.0);
|
||||
std::cout << "[PDPOTRF] ELAPSED: " << milliseconds_pdpotrf
|
||||
<< " ms, GFLOPS: " << gflops_pdpotrf / (milliseconds_pdpotrf / 1000.0) << '\n';
|
||||
|
||||
if (check_result)
|
||||
{
|
||||
if (double residual = sqrt(res_nrm2) / sqrt(Bref_nrm2); residual >= 0.01)
|
||||
{
|
||||
std::cerr << "[POTRS] ||AX - B|| : " << sqrt(res_nrm2) << '\n';
|
||||
std::cerr << "[POTRS] ||B|| : " << sqrt(Bref_nrm2) << '\n';
|
||||
std::cerr << "[POTRS] RESIDUAL (||AX - B||/||B||) : " << residual << '\n';
|
||||
assert(!"Algorithm did not converge.");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,372 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Sparse conjugate gradient algorithm
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "dot.cuh"
|
||||
#include "newton_solver.cuh"
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
#if !_CCCL_CTK_BELOW(12, 4)
|
||||
|
||||
void build_full_csr_structure(size_t* row_offsets, size_t* col_indices, size_t N)
|
||||
{
|
||||
size_t nnz = 0;
|
||||
row_offsets[0] = 0;
|
||||
|
||||
for (size_t row = 0; row < N; row++)
|
||||
{
|
||||
if (row == 0 || row == N - 1)
|
||||
{
|
||||
// Boundary rows: only diagonal entry (identity for BC: u[i] = prescribed_value)
|
||||
col_indices[nnz++] = row;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior rows: tridiagonal structure (left, center, right)
|
||||
col_indices[nnz++] = row - 1; // left neighbor
|
||||
col_indices[nnz++] = row; // center (diagonal)
|
||||
col_indices[nnz++] = row + 1; // right neighbor
|
||||
}
|
||||
row_offsets[row + 1] = nnz;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ctx_t>
|
||||
void assemble_jacobian_full(
|
||||
ctx_t& ctx, vector_t<double> U, vector_t<double> values, size_t N, double h, double dt, double nu)
|
||||
{
|
||||
ctx.parallel_for(box(N), U.read(), values.write()).set_symbol("assemble_jacobian_full")
|
||||
->*[N, h, dt, nu] __device__(size_t row, auto dU, auto dvalues) {
|
||||
if (row == 0)
|
||||
{
|
||||
// Left boundary: u[0] = 0 (homogeneous Dirichlet)
|
||||
// Jacobian row: [1, 0, 0, ..., 0]
|
||||
size_t val_idx = 0; // First entry in CSR values array
|
||||
dvalues[val_idx] = 1.0;
|
||||
}
|
||||
else if (row == N - 1)
|
||||
{
|
||||
// Right boundary: u[N-1] = 0 (homogeneous Dirichlet)
|
||||
// Jacobian row: [0, ..., 0, 1]
|
||||
size_t val_idx = 1 + 3 * (N - 2); // Last entry in CSR values array
|
||||
dvalues[val_idx] = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior point: Burger's equation discretization
|
||||
double u_i = dU[row];
|
||||
double u_ip1 = dU[row + 1];
|
||||
double u_im1 = dU[row - 1];
|
||||
|
||||
// Jacobian entries: ∂F_i/∂u_{i-1}, ∂F_i/∂u_i, ∂F_i/∂u_{i+1}
|
||||
double left = -u_i / (2 * h) - nu / (h * h);
|
||||
double center = 1.0 / dt + (u_ip1 - u_im1) / (2 * h) + 2.0 * nu / (h * h);
|
||||
double right = u_i / (2 * h) - nu / (h * h);
|
||||
|
||||
// CSR indexing for interior row i: starts at 1 + 3*(i-1)
|
||||
size_t val_idx = 1 + 3 * (row - 1);
|
||||
dvalues[val_idx] = left; // ∂F_i/∂u_{i-1}
|
||||
dvalues[val_idx + 1] = center; // ∂F_i/∂u_i
|
||||
dvalues[val_idx + 2] = right; // ∂F_i/∂u_{i+1}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// residual: length N (full system including boundaries)
|
||||
template <typename ctx_t, typename T>
|
||||
void compute_residual_full(
|
||||
ctx_t& ctx, vector_t<T> U, vector_t<T> U_prev, vector_t<T> residual, size_t N, double h, double dt, double nu)
|
||||
{
|
||||
ctx.parallel_for(box(N), residual.write(), U.read(), U_prev.read()).set_symbol("compute_residual_full")
|
||||
->*[N, h, dt, nu] __device__(size_t i, auto dresidual, auto dU, auto dU_prev) {
|
||||
if (i == 0)
|
||||
{
|
||||
// Left boundary condition: u[0] = 0
|
||||
dresidual(i) = dU(i) - 0.0;
|
||||
}
|
||||
else if (i == N - 1)
|
||||
{
|
||||
// Right boundary condition: u[N-1] = 0
|
||||
dresidual(i) = dU(i) - 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior point: Burger's equation F_i = ∂u/∂t + u*∂u/∂x - nu*∂²u/∂x²
|
||||
double u_i = dU(i);
|
||||
double u_ip1 = dU(i + 1);
|
||||
double u_im1 = dU(i - 1);
|
||||
|
||||
double term_time = (u_i - dU_prev(i)) / dt; // ∂u/∂t
|
||||
double term_conv = u_i * (u_ip1 - u_im1) / (2 * h); // u * ∂u/∂x (nonlinear convection)
|
||||
double term_diff = -nu * (u_im1 - 2 * u_i + u_ip1) / (h * h); // -nu * ∂²u/∂x²
|
||||
|
||||
dresidual(i) = term_time + term_conv + term_diff;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Callback function objects for Burger's equation
|
||||
struct BurgerResidualCallback
|
||||
{
|
||||
size_t N;
|
||||
double h, dt, nu;
|
||||
|
||||
template <typename ctx_t>
|
||||
void
|
||||
operator()(ctx_t& ctx, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>& residual) const
|
||||
{
|
||||
compute_residual_full(ctx, x, x_prev, residual, N, h, dt, nu);
|
||||
}
|
||||
};
|
||||
|
||||
struct BurgerJacobianCallback
|
||||
{
|
||||
size_t N;
|
||||
double h, dt, nu;
|
||||
|
||||
template <typename ctx_t>
|
||||
void operator()(ctx_t& ctx, const vector_t<double>& x, vector_t<double>& jacobian_values) const
|
||||
{
|
||||
assemble_jacobian_full(ctx, x, jacobian_values, N, h, dt, nu);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize the solution output file (call once at simulation start)
|
||||
void initialize_solution_file(const char* filename, size_t N, double h)
|
||||
{
|
||||
FILE* fp = fopen(filename, "w");
|
||||
if (fp)
|
||||
{
|
||||
fprintf(fp, "# Burger equation solution - block format\n");
|
||||
fprintf(fp, "# Each timestep is a separate block, separated by blank lines\n");
|
||||
fprintf(fp, "# Format: x_coordinate u(x,t)\n");
|
||||
fprintf(fp, "# Grid points: %zu, h=%.6e\n", N, h);
|
||||
fprintf(fp,
|
||||
"# Use in gnuplot: plot for [i=0:*] 'solution.dat' index i with lines title sprintf('step %%d', i*10)\n");
|
||||
fprintf(fp, "#\n");
|
||||
fclose(fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Error: Could not create %s for writing\n", filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to append timestep block to solution file (simple and reliable)
|
||||
template <typename ctx_t>
|
||||
void dump_solution(
|
||||
ctx_t& ctx, vector_t<double>& U, size_t timestep, size_t N, double h, double dt, const char* filename = "solution.dat")
|
||||
{
|
||||
ctx.host_launch(U.read()).set_symbol("dump solution")->*[timestep, h, N, dt, filename](auto hU) {
|
||||
FILE* fp = fopen(filename, "a"); // Simple append - no read/modify/write
|
||||
if (fp)
|
||||
{
|
||||
fprintf(fp, "# Timestep %zu, t=%.6e\n", timestep, timestep * dt);
|
||||
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
double x = i * h;
|
||||
fprintf(fp, "%.10e %.10e\n", x, hU(i));
|
||||
}
|
||||
|
||||
fprintf(fp, "\n"); // Blank line to separate datasets
|
||||
fclose(fp);
|
||||
|
||||
printf("Appended timestep %zu (t=%.4e) to %s\n", timestep, timestep * dt, filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Error: Could not open %s for appending\n", filename);
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
|
||||
{
|
||||
#if _CCCL_CTK_BELOW(12, 4)
|
||||
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
|
||||
return 0;
|
||||
#else
|
||||
// Usage: ./burger [N] [nsteps] [nu]
|
||||
// N = Grid points (default: 100000)
|
||||
// nsteps = Time steps (default: 10000)
|
||||
// nu = Viscosity (default: 0.05, try 0.001 for shocks)
|
||||
|
||||
stackable_ctx ctx;
|
||||
|
||||
size_t N = 2560;
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
fprintf(stderr, "N = %zu\n", N);
|
||||
}
|
||||
|
||||
size_t nsteps = 200;
|
||||
if (argc > 2)
|
||||
{
|
||||
nsteps = atol(argv[2]);
|
||||
fprintf(stderr, "nsteps = %ld\n", nsteps);
|
||||
}
|
||||
|
||||
// Set reasonable parameters - implicit method allows larger time steps
|
||||
double nu = 0.05; // Default viscosity
|
||||
if (argc > 3)
|
||||
{
|
||||
nu = atof(argv[3]);
|
||||
fprintf(stderr, "nu = %e\n", nu);
|
||||
}
|
||||
|
||||
ssize_t output_freq = -1;
|
||||
if (argc > 4)
|
||||
{
|
||||
output_freq = atoi(argv[4]);
|
||||
fprintf(stderr, "output_freq %ld\n", output_freq);
|
||||
}
|
||||
|
||||
// use_while = 0 => no while; 1 => while in CG, 2 => while in Newton and CG
|
||||
int use_while = 2;
|
||||
if (argc > 5)
|
||||
{
|
||||
use_while = atoi(argv[5]);
|
||||
fprintf(stderr, "use_while = %d\n", use_while);
|
||||
}
|
||||
|
||||
double h = 1.0 / (N - 1);
|
||||
|
||||
double dt_diffusion = 0.5 * h * h / nu; // Diffusion-limited time step
|
||||
double dt_fixed = 0.001; // Fixed reasonable time step
|
||||
double dt = std::max(dt_diffusion, dt_fixed); // Use larger of the two
|
||||
|
||||
// For very fine grids, cap the time step to prevent tiny steps
|
||||
if (N > 10000)
|
||||
{
|
||||
dt = std::min(dt, 0.01); // Cap at 0.01 for large grids
|
||||
}
|
||||
|
||||
double total_time = nsteps * dt;
|
||||
|
||||
fprintf(stderr, "=== Simulation Parameters ===\n");
|
||||
fprintf(stderr, "Grid: N=%zu, h=%e\n", N, h);
|
||||
fprintf(stderr, "Time: dt=%e, nsteps=%zu, total_time=%e\n", dt, nsteps, total_time);
|
||||
fprintf(stderr, "Physics: nu=%e (viscosity)\n", nu);
|
||||
fprintf(stderr, "Diffusion number: nu*dt/h^2 = %e\n", nu * dt / (h * h));
|
||||
fprintf(stderr, "=============================\n");
|
||||
|
||||
// Full N×N system: boundary rows have 1 entry each, interior rows have 3 entries each
|
||||
// Total: 2*1 + (N-2)*3 = 3*N - 4 non-zeros
|
||||
size_t nz = 3 * N - 4;
|
||||
|
||||
size_t* row_offsets;
|
||||
size_t* col_indices;
|
||||
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
|
||||
cuda_safe_call(cudaHostAlloc(&col_indices, nz * sizeof(size_t), cudaHostAllocMapped));
|
||||
|
||||
build_full_csr_structure(row_offsets, col_indices, N);
|
||||
|
||||
auto csr_row_offsets = ctx.logical_data(make_slice(row_offsets, N + 1)).set_symbol("csr_row");
|
||||
auto csr_col_ind = ctx.logical_data(make_slice(col_indices, nz)).set_symbol("csr_col");
|
||||
auto csr_values = ctx.logical_data(shape_of<slice<double>>(nz)).set_symbol("csr_val");
|
||||
|
||||
auto U = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("U");
|
||||
|
||||
// This will prevent erroneous modifications and may allow access from concurrent graphs
|
||||
csr_row_offsets.set_read_only();
|
||||
csr_col_ind.set_read_only();
|
||||
|
||||
// Initial condition
|
||||
ctx.parallel_for(U.shape(), U.write()).set_symbol("init conditions")->*[h, N] __device__(size_t i, auto dU) {
|
||||
double x = i * h;
|
||||
if (i == 0 || i == N - 1)
|
||||
{
|
||||
dU(i) = 0.0; // Homogeneous Dirichlet boundary conditions
|
||||
}
|
||||
else
|
||||
{
|
||||
dU(i) = sin(M_PI * x);
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize solution output file
|
||||
initialize_solution_file("solution.dat", N, h);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
|
||||
|
||||
// Parameters are now set above with auto-scaling
|
||||
size_t substeps = (output_freq > 0) ? output_freq : nsteps;
|
||||
size_t outer_iterations = nsteps / substeps;
|
||||
|
||||
if (use_while == 2)
|
||||
{
|
||||
for (size_t outer = 0; outer < outer_iterations; outer++)
|
||||
{
|
||||
auto g = ctx.graph_scope();
|
||||
|
||||
// Repeat substeps inner iterations using STF repeat block
|
||||
{
|
||||
auto repeat_guard = ctx.repeat_graph_scope(substeps);
|
||||
|
||||
// Create callback function objects for Burger's equation
|
||||
BurgerResidualCallback residual_callback{N, h, dt, nu};
|
||||
BurgerJacobianCallback jacobian_callback{N, h, dt, nu};
|
||||
|
||||
// Solve the nonlinear system using generic Newton solver
|
||||
newton_solver(ctx, U, csr_values, csr_row_offsets, csr_col_ind, residual_callback, jacobian_callback);
|
||||
} // repeat_guard automatically manages the loop condition
|
||||
|
||||
// Dump solution after each substep block
|
||||
size_t current_timestep = (outer + 1) * substeps;
|
||||
dump_solution(ctx, U, current_timestep, N, h, dt);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (size_t outer = 0; outer < outer_iterations; outer++)
|
||||
{
|
||||
// Repeat substeps inner iterations using STF repeat block
|
||||
for (size_t substep = 0; substep < substeps; substep++)
|
||||
{
|
||||
// Create callback function objects for Burger's equation
|
||||
BurgerResidualCallback residual_callback{N, h, dt, nu};
|
||||
BurgerJacobianCallback jacobian_callback{N, h, dt, nu};
|
||||
|
||||
// Solve the nonlinear system using generic Newton solver
|
||||
newton_solver_no_while(
|
||||
ctx, U, csr_values, csr_row_offsets, csr_col_ind, residual_callback, jacobian_callback, use_while == 1);
|
||||
} // repeat_guard automatically manages the loop condition
|
||||
|
||||
// Dump solution after each substep block
|
||||
size_t current_timestep = (outer + 1) * substeps;
|
||||
dump_solution(ctx, U, current_timestep, N, h, dt);
|
||||
}
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaStreamSynchronize(ctx.fence()));
|
||||
auto end = std::chrono::high_resolution_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
|
||||
std::cout << "Duration: " << duration << " milliseconds" << '\n';
|
||||
|
||||
ctx.finalize();
|
||||
#endif
|
||||
}
|
||||
@@ -1,479 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Sensitivity analysis for Burger equation - analyze shock formation vs viscosity
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "cg_solver.cuh"
|
||||
#include "dot.cuh"
|
||||
#include "newton_solver.cuh"
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
#if !_CCCL_CTK_BELOW(12, 4)
|
||||
|
||||
void build_tridiagonal_csr_structure(size_t* row_offsets, size_t* col_indices, size_t N)
|
||||
{
|
||||
size_t nnz = 0;
|
||||
row_offsets[0] = 0;
|
||||
|
||||
for (size_t row = 0; row < N; row++)
|
||||
{
|
||||
if (row == 0 || row == N - 1)
|
||||
{
|
||||
// Boundary rows: only diagonal entry (identity for BC: u[i] = prescribed_value)
|
||||
col_indices[nnz++] = row;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior rows: tridiagonal structure (left, center, right)
|
||||
col_indices[nnz++] = row - 1; // left neighbor
|
||||
col_indices[nnz++] = row; // center (diagonal)
|
||||
col_indices[nnz++] = row + 1; // right neighbor
|
||||
}
|
||||
row_offsets[row + 1] = nnz;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ctx_t>
|
||||
void assemble_jacobian_full(
|
||||
ctx_t& ctx, vector_t<double> U, vector_t<double> values, size_t N, double h, double dt, double nu)
|
||||
{
|
||||
ctx.parallel_for(box(N), U.read(), values.write()).set_symbol("assemble_jacobian_full")
|
||||
->*[N, h, dt, nu] __device__(size_t row, auto dU, auto dvalues) {
|
||||
if (row == 0)
|
||||
{
|
||||
// Left boundary: u[0] = 0 (homogeneous Dirichlet)
|
||||
size_t val_idx = 0;
|
||||
dvalues[val_idx] = 1.0;
|
||||
}
|
||||
else if (row == N - 1)
|
||||
{
|
||||
// Right boundary: u[N-1] = 0 (homogeneous Dirichlet)
|
||||
size_t val_idx = 1 + 3 * (N - 2);
|
||||
dvalues[val_idx] = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior point: Burger's equation discretization
|
||||
double u_i = dU[row];
|
||||
double u_ip1 = dU[row + 1];
|
||||
double u_im1 = dU[row - 1];
|
||||
|
||||
// Jacobian entries: ∂F_i/∂u_{i-1}, ∂F_i/∂u_i, ∂F_i/∂u_{i+1}
|
||||
double left = -u_i / (2 * h) - nu / (h * h);
|
||||
double center = 1.0 / dt + (u_ip1 - u_im1) / (2 * h) + 2.0 * nu / (h * h);
|
||||
double right = u_i / (2 * h) - nu / (h * h);
|
||||
|
||||
size_t val_idx = 1 + 3 * (row - 1);
|
||||
dvalues[val_idx] = left;
|
||||
dvalues[val_idx + 1] = center;
|
||||
dvalues[val_idx + 2] = right;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename ctx_t, typename T>
|
||||
void compute_residual_full(
|
||||
ctx_t& ctx, vector_t<T> U, vector_t<T> U_prev, vector_t<T> residual, size_t N, double h, double dt, double nu)
|
||||
{
|
||||
ctx.parallel_for(box(N), residual.write(), U.read(), U_prev.read()).set_symbol("compute_residual_full")
|
||||
->*[N, h, dt, nu] __device__(size_t i, auto dresidual, auto dU, auto dU_prev) {
|
||||
if (i == 0)
|
||||
{
|
||||
dresidual(i) = dU(i) - 0.0;
|
||||
}
|
||||
else if (i == N - 1)
|
||||
{
|
||||
dresidual(i) = dU(i) - 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Interior point: Burger's equation F_i = ∂u/∂t + u*∂u/∂x - nu*∂²u/∂x²
|
||||
double u_i = dU(i);
|
||||
double u_ip1 = dU(i + 1);
|
||||
double u_im1 = dU(i - 1);
|
||||
|
||||
double term_time = (u_i - dU_prev(i)) / dt;
|
||||
double term_conv = u_i * (u_ip1 - u_im1) / (2 * h);
|
||||
double term_diff = -nu * (u_im1 - 2 * u_i + u_ip1) / (h * h);
|
||||
|
||||
dresidual(i) = term_time + term_conv + term_diff;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Shock detection: compute maximum gradient magnitude
|
||||
template <typename ctx_t>
|
||||
void detect_shock(
|
||||
ctx_t& ctx, vector_t<double>& U, stackable_logical_data<scalar_view<double>>& max_gradient, size_t N, double h)
|
||||
{
|
||||
ctx.parallel_for(box(N - 1), U.read(), max_gradient.reduce(reducer::maxval<double>{})).set_symbol("detect_shock")
|
||||
->*[h] __device__(size_t i, auto dU, double& dmax_grad) {
|
||||
double gradient = fabs(dU(i + 1) - dU(i)) / h;
|
||||
dmax_grad = fmax(dmax_grad, gradient);
|
||||
};
|
||||
}
|
||||
|
||||
// Callback function objects for Burger's equation
|
||||
struct BurgerResidualCallback
|
||||
{
|
||||
size_t N;
|
||||
double h, dt, nu;
|
||||
|
||||
template <typename ctx_t>
|
||||
void
|
||||
operator()(ctx_t& ctx, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>& residual) const
|
||||
{
|
||||
compute_residual_full(ctx, x, x_prev, residual, N, h, dt, nu);
|
||||
}
|
||||
};
|
||||
|
||||
struct BurgerJacobianCallback
|
||||
{
|
||||
size_t N;
|
||||
double h, dt, nu;
|
||||
|
||||
template <typename ctx_t>
|
||||
void operator()(ctx_t& ctx, const vector_t<double>& x, vector_t<double>& jacobian_values) const
|
||||
{
|
||||
assemble_jacobian_full(ctx, x, jacobian_values, N, h, dt, nu);
|
||||
}
|
||||
};
|
||||
|
||||
// Generate nu values around target with given distribution
|
||||
std::vector<double> generate_nu_samples(double nu_target, double nu_std, size_t num_samples)
|
||||
{
|
||||
std::vector<double> nu_values;
|
||||
std::random_device rd;
|
||||
std::mt19937 gen(rd());
|
||||
std::normal_distribution<double> dist(nu_target, nu_std);
|
||||
|
||||
// Generate samples and ensure they are positive
|
||||
for (size_t i = 0; i < num_samples; ++i)
|
||||
{
|
||||
double nu_sample = dist(gen);
|
||||
// Ensure nu > 0 for physical validity
|
||||
if (nu_sample > 1e-6)
|
||||
{
|
||||
nu_values.push_back(nu_sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Retry if we get non-physical values
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort for better output organization
|
||||
std::sort(nu_values.begin(), nu_values.end());
|
||||
return nu_values;
|
||||
}
|
||||
|
||||
// Initialize sensitivity analysis output file
|
||||
void initialize_sensitivity_file(const char* filename, double nu_target, double nu_std, size_t num_samples)
|
||||
{
|
||||
FILE* fp = fopen(filename, "w");
|
||||
if (fp)
|
||||
{
|
||||
fprintf(fp, "# Burger equation sensitivity analysis\n");
|
||||
fprintf(fp, "# Target nu: %.6e, std: %.6e, samples: %zu\n", nu_target, nu_std, num_samples);
|
||||
fprintf(fp, "# Format: nu_value shock_time max_gradient final_time\n");
|
||||
fprintf(fp, "# shock_time: time when max gradient exceeds threshold (or -1 if no shock)\n");
|
||||
fprintf(fp, "# max_gradient: maximum gradient achieved\n");
|
||||
fprintf(fp, "# final_time: total simulation time reached\n");
|
||||
fprintf(fp, "#\n");
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize shock solutions output file
|
||||
void initialize_shock_file(const char* filename, double shock_threshold)
|
||||
{
|
||||
FILE* fp = fopen(filename, "w");
|
||||
if (fp)
|
||||
{
|
||||
fprintf(fp, "# Burger equation shock solutions\n");
|
||||
fprintf(fp, "# Solutions dumped when gradient exceeds threshold: %.1f\n", shock_threshold);
|
||||
fprintf(fp, "# Each shock is a separate data block, separated by blank lines\n");
|
||||
fprintf(fp, "# Block header: Sample ID, nu value, shock time, max gradient\n");
|
||||
fprintf(fp, "# Block format: x_coordinate u(x,t_shock)\n");
|
||||
fprintf(fp, "#\n");
|
||||
fprintf(fp,
|
||||
"# Use in gnuplot: plot for [i=0:*] 'shock_solutions.dat' index i with lines title sprintf('Sample %%d', "
|
||||
"i+1)\n");
|
||||
fprintf(fp, "#\n");
|
||||
fclose(fp);
|
||||
printf("Initialized shock solutions file: %s\n", filename);
|
||||
}
|
||||
}
|
||||
|
||||
// Dump solution when shock is detected
|
||||
template <typename ctx_t>
|
||||
void dump_shock_solution(
|
||||
ctx_t& ctx,
|
||||
vector_t<double>& U,
|
||||
double nu,
|
||||
double shock_time,
|
||||
double max_gradient,
|
||||
size_t sample_id,
|
||||
size_t N,
|
||||
double h,
|
||||
const char* filename = "shock_solutions.dat")
|
||||
{
|
||||
ctx.host_launch(U.read()).set_symbol("dump shock solution")
|
||||
->*
|
||||
[nu, shock_time, max_gradient, sample_id, h, N, filename](auto hU) {
|
||||
FILE* fp = fopen(filename, "a"); // Append to file
|
||||
if (fp)
|
||||
{
|
||||
fprintf(
|
||||
fp, "# Sample %zu: nu=%.6e, shock_time=%.6e, max_gradient=%.2f\n", sample_id, nu, shock_time, max_gradient);
|
||||
fprintf(fp, "# Format: x_coordinate u(x,t_shock)\n");
|
||||
|
||||
for (size_t i = 0; i < N; i++)
|
||||
{
|
||||
double x = i * h;
|
||||
fprintf(fp, "%.10e %.10e\n", x, hU(i));
|
||||
}
|
||||
|
||||
fprintf(fp, "\n"); // Blank line to separate datasets
|
||||
fclose(fp);
|
||||
|
||||
printf(" -> Solution saved to %s", filename);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf(" -> Error: Could not save solution to %s", filename);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename ctx_t>
|
||||
void run_single_nu_simulation(
|
||||
ctx_t& ctx,
|
||||
double nu,
|
||||
vector_t<double>& U,
|
||||
vector_t<double>& csr_values,
|
||||
const vector_t<size_t>& csr_row_offsets,
|
||||
const vector_t<size_t>& csr_col_ind,
|
||||
size_t N,
|
||||
double h,
|
||||
double dt,
|
||||
double max_time,
|
||||
double shock_threshold,
|
||||
size_t sample_id,
|
||||
double& shock_time,
|
||||
double& max_gradient,
|
||||
double& final_time)
|
||||
{
|
||||
// Reset solution to initial condition
|
||||
ctx.parallel_for(U.shape(), U.write()).set_symbol("reset_initial_condition")->*[h, N] __device__(size_t i, auto dU) {
|
||||
double x = i * h;
|
||||
dU(i) = (i == 0 || i == N - 1) ? 0.0 : sin(M_PI * x);
|
||||
};
|
||||
|
||||
auto current_time = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("current_time");
|
||||
auto max_grad_global = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("max_grad_global");
|
||||
auto shock_detected = ctx.logical_data(shape_of<scalar_view<int>>()).set_symbol("shock_detected");
|
||||
|
||||
// Initialize tracking variables
|
||||
ctx.parallel_for(box(1), current_time.write(), max_grad_global.write(), shock_detected.write())
|
||||
.set_symbol("init_tracking")
|
||||
->*[] __device__(size_t i, auto dtime, auto dmax_grad, auto dshock) {
|
||||
*dtime = 0.0;
|
||||
*dmax_grad = 0.0;
|
||||
*dshock = 0; // 0 = no shock, 1 = shock detected
|
||||
};
|
||||
|
||||
// Time evolution loop with shock detection
|
||||
{
|
||||
auto while_guard = ctx.while_graph_scope();
|
||||
|
||||
// Create callback function objects
|
||||
BurgerResidualCallback residual_callback{N, h, dt, nu};
|
||||
BurgerJacobianCallback jacobian_callback{N, h, dt, nu};
|
||||
|
||||
// Solve the nonlinear system
|
||||
newton_solver(ctx, U, csr_values, csr_row_offsets, csr_col_ind, residual_callback, jacobian_callback);
|
||||
|
||||
// Update time
|
||||
ctx.parallel_for(box(1), current_time.rw()).set_symbol("update_time")->*[dt] __device__(size_t i, auto dtime) {
|
||||
*dtime += dt;
|
||||
};
|
||||
|
||||
// Detect shock by computing maximum gradient
|
||||
auto current_grad = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("current_grad");
|
||||
detect_shock(ctx, U, current_grad, N, h);
|
||||
|
||||
// Update global maximum gradient and check for shock
|
||||
ctx.parallel_for(box(1), max_grad_global.rw(), current_grad.read(), shock_detected.rw())
|
||||
.set_symbol("update_shock_detection")
|
||||
->*[shock_threshold] __device__(size_t i, auto dmax_grad, auto dcurrent_grad, auto dshock) {
|
||||
double grad = *dcurrent_grad;
|
||||
if (grad > *dmax_grad)
|
||||
{
|
||||
*dmax_grad = grad;
|
||||
}
|
||||
if (grad > shock_threshold && *dshock == 0)
|
||||
{
|
||||
*dshock = 1; // First time shock threshold is exceeded
|
||||
}
|
||||
};
|
||||
|
||||
// Continue while time < max_time and no shock detected
|
||||
while_guard.update_cond(current_time.read(), shock_detected.read())->*[max_time] __device__(auto dtime, auto dshock) {
|
||||
return (*dtime < max_time) && (*dshock == 0);
|
||||
};
|
||||
}
|
||||
|
||||
// Extract results to host variables
|
||||
ctx.host_launch(current_time.read(), max_grad_global.read(), shock_detected.read()).set_symbol("extract_results")
|
||||
->*[&shock_time, &max_gradient, &final_time](auto htime, auto hmax_grad, auto hshock) {
|
||||
final_time = *htime;
|
||||
max_gradient = *hmax_grad;
|
||||
shock_time = (*hshock == 1) ? *htime : -1.0; // -1 indicates no shock
|
||||
|
||||
if (shock_time > 0)
|
||||
{
|
||||
printf("shock at t=%.4f, max_grad=%.1f\n", shock_time, max_gradient);
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("no shock, max_grad=%.1f\n", max_gradient);
|
||||
}
|
||||
};
|
||||
|
||||
// Dump solution if shock was detected
|
||||
if (shock_time > 0)
|
||||
{
|
||||
dump_shock_solution(ctx, U, nu, shock_time, max_gradient, sample_id, N, h);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
|
||||
{
|
||||
#if _CCCL_CTK_BELOW(12, 4)
|
||||
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
|
||||
return 0;
|
||||
#else
|
||||
// Usage: ./burger_sensitivity [N] [nu_target] [nu_std] [num_samples] [shock_threshold]
|
||||
|
||||
size_t N = 1000; // Smaller grid for sensitivity analysis
|
||||
double nu_target = 0.02; // Target viscosity
|
||||
double nu_std = 0.01; // Standard deviation for nu distribution
|
||||
size_t num_samples = 20; // Number of nu samples to test
|
||||
|
||||
double shock_threshold = 15.0; // Gradient threshold to detect shock (du/dx magnitude)
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
}
|
||||
if (argc > 2)
|
||||
{
|
||||
nu_target = atof(argv[2]);
|
||||
}
|
||||
if (argc > 3)
|
||||
{
|
||||
nu_std = atof(argv[3]);
|
||||
}
|
||||
if (argc > 4)
|
||||
{
|
||||
num_samples = atoi(argv[4]);
|
||||
}
|
||||
if (argc > 5)
|
||||
{
|
||||
shock_threshold = atof(argv[5]);
|
||||
}
|
||||
|
||||
double h = 1.0 / (N - 1);
|
||||
double dt = 0.001; // Fixed time step
|
||||
double max_time = 2.0; // Maximum simulation time per sample
|
||||
|
||||
fprintf(stderr, "=== Sensitivity Analysis Parameters ===\n");
|
||||
fprintf(stderr, "Grid: N=%zu, h=%e\n", N, h);
|
||||
fprintf(stderr, "Viscosity: target=%e, std=%e, samples=%zu\n", nu_target, nu_std, num_samples);
|
||||
fprintf(stderr, "Time: dt=%e, max_time=%e\n", dt, max_time);
|
||||
fprintf(stderr, "Shock threshold: %.1f (gradient magnitude)\n", shock_threshold);
|
||||
fprintf(stderr, "======================================\n");
|
||||
|
||||
stackable_ctx ctx;
|
||||
|
||||
// Generate nu samples
|
||||
auto nu_values = generate_nu_samples(nu_target, nu_std, num_samples);
|
||||
|
||||
// Set up CSR structure
|
||||
size_t nz = 3 * N - 4;
|
||||
size_t* row_offsets;
|
||||
size_t* col_indices;
|
||||
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
|
||||
cuda_safe_call(cudaHostAlloc(&col_indices, nz * sizeof(size_t), cudaHostAllocMapped));
|
||||
build_tridiagonal_csr_structure(row_offsets, col_indices, N);
|
||||
|
||||
auto csr_row_offsets = ctx.logical_data(make_slice(row_offsets, N + 1)).set_symbol("csr_row");
|
||||
auto csr_col_ind = ctx.logical_data(make_slice(col_indices, nz)).set_symbol("csr_col");
|
||||
csr_row_offsets.set_read_only();
|
||||
csr_col_ind.set_read_only();
|
||||
|
||||
// Initialize output files
|
||||
initialize_sensitivity_file("sensitivity_results.dat", nu_target, nu_std, num_samples);
|
||||
initialize_shock_file("shock_solutions.dat", shock_threshold);
|
||||
|
||||
// Run sensitivity analysis
|
||||
printf("Running sensitivity analysis with %zu samples...\n", num_samples);
|
||||
|
||||
{
|
||||
auto g = ctx.graph_scope();
|
||||
for (size_t i = 0; i < nu_values.size(); ++i)
|
||||
{
|
||||
double nu = nu_values[i];
|
||||
double shock_time, max_gradient, final_time;
|
||||
|
||||
printf("Sample %zu/%zu: nu=%.6e... ", i + 1, nu_values.size(), nu);
|
||||
fflush(stdout);
|
||||
|
||||
auto csr_values = ctx.logical_data(shape_of<slice<double>>(nz)).set_symbol("csr_val");
|
||||
auto U = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("U");
|
||||
|
||||
run_single_nu_simulation(
|
||||
ctx,
|
||||
nu,
|
||||
U,
|
||||
csr_values,
|
||||
csr_row_offsets,
|
||||
csr_col_ind,
|
||||
N,
|
||||
h,
|
||||
dt,
|
||||
max_time,
|
||||
shock_threshold,
|
||||
i + 1,
|
||||
shock_time,
|
||||
max_gradient,
|
||||
final_time);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.finalize();
|
||||
#endif
|
||||
}
|
||||
@@ -1,232 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Sparse conjugate gradient algorithm
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
using vector_t = logical_data<slice<double>>;
|
||||
using scalar_t = logical_data<scalar_view<double>>;
|
||||
using context_t = context;
|
||||
|
||||
struct csr_matrix
|
||||
{
|
||||
csr_matrix(
|
||||
context_t& ctx, size_t num_rows, size_t num_nonzeros, double* values, size_t* row_offsets, size_t* column_indices)
|
||||
{
|
||||
val_handle = ctx.logical_data(make_slice(values, num_nonzeros));
|
||||
col_handle = ctx.logical_data(make_slice(column_indices, num_nonzeros));
|
||||
row_handle = ctx.logical_data(make_slice(row_offsets, num_rows + 1));
|
||||
}
|
||||
|
||||
/* Description of the CSR */
|
||||
mutable logical_data<slice<double>> val_handle;
|
||||
mutable logical_data<slice<size_t>> row_handle;
|
||||
mutable logical_data<slice<size_t>> col_handle;
|
||||
};
|
||||
|
||||
// Note that a and b might be the same logical data
|
||||
void DOT(context_t& ctx, vector_t& a, vector_t& b, scalar_t& res)
|
||||
{
|
||||
ctx.parallel_for(a.shape(), a.read(), b.read(), res.reduce(reducer::sum<double>{}))
|
||||
->*[] __device__(size_t i, auto da, auto db, double& dres) {
|
||||
dres += da(i) * db(i);
|
||||
};
|
||||
};
|
||||
|
||||
void SPMV(context_t& ctx, csr_matrix& a, vector_t& x, vector_t& y)
|
||||
{
|
||||
ctx.parallel_for(y.shape(), a.val_handle.read(), a.col_handle.read(), a.row_handle.read(), x.read(), y.write())
|
||||
->*[] _CCCL_DEVICE(size_t row, auto da_val, auto da_col, auto da_row, auto dx, auto dy) {
|
||||
int row_start = da_row(row);
|
||||
int row_end = da_row(row + 1);
|
||||
|
||||
double sum = 0.0;
|
||||
for (int elt = row_start; elt < row_end; elt++)
|
||||
{
|
||||
sum += da_val(elt) * dx(da_col(elt));
|
||||
}
|
||||
|
||||
dy(row) = sum;
|
||||
};
|
||||
}
|
||||
|
||||
/* genTridiag: generate a random tridiagonal symmetric matrix
|
||||
from :
|
||||
https://github.com/NVIDIA/cuda-samples/blob/master/Samples/4_CUDA_Libraries/conjugateGradientCudaGraphs/conjugateGradientCudaGraphs.cu
|
||||
*/
|
||||
void genTridiag(size_t* I, size_t* J, double* val, size_t N, size_t nz)
|
||||
{
|
||||
const double d = 2.0;
|
||||
|
||||
I[0] = 0, J[0] = 0, J[1] = 1;
|
||||
val[0] = drand48() + d;
|
||||
val[1] = drand48();
|
||||
int start;
|
||||
|
||||
for (size_t i = 1; i < N; i++)
|
||||
{
|
||||
if (i > 1)
|
||||
{
|
||||
I[i] = I[i - 1] + 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
I[1] = 2;
|
||||
}
|
||||
|
||||
start = (i - 1) * 3 + 2;
|
||||
J[start] = i - 1;
|
||||
J[start + 1] = i;
|
||||
|
||||
if (i < N - 1)
|
||||
{
|
||||
J[start + 2] = i + 1;
|
||||
}
|
||||
|
||||
val[start] = val[start - 1];
|
||||
val[start + 1] = drand48() + d;
|
||||
|
||||
if (i < N - 1)
|
||||
{
|
||||
val[start + 2] = drand48();
|
||||
}
|
||||
}
|
||||
|
||||
I[N] = nz;
|
||||
}
|
||||
|
||||
void cg_solver(context_t& ctx, csr_matrix& A, vector_t& X, vector_t& B)
|
||||
{
|
||||
// Initial guess X = 1
|
||||
ctx.parallel_for(X.shape(), X.write())->*[] _CCCL_DEVICE(size_t i, auto dX) {
|
||||
dX(i) = 1.0;
|
||||
};
|
||||
|
||||
// Residual R initialized to B
|
||||
auto R = ctx.logical_data(B.shape());
|
||||
ctx.parallel_for(R.shape(), R.write(), B.read())->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
|
||||
dR(i) = dB(i);
|
||||
};
|
||||
|
||||
// R = R - A*X
|
||||
auto Ax = ctx.logical_data(X.shape());
|
||||
SPMV(ctx, A, X, Ax);
|
||||
ctx.parallel_for(R.shape(), R.rw(), Ax.read())->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
|
||||
dR(i) -= dAx(i);
|
||||
};
|
||||
|
||||
// P = R;
|
||||
auto P = ctx.logical_data(R.shape());
|
||||
ctx.parallel_for(P.shape(), P.write(), R.read())->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
|
||||
dP(i) = dR(i);
|
||||
};
|
||||
|
||||
// RSOLD = R'*R
|
||||
auto rsold = ctx.logical_data(shape_of<scalar_view<double>>());
|
||||
DOT(ctx, R, R, rsold);
|
||||
|
||||
const int MAXITER = X.shape().size();
|
||||
for (int k = 0; k < MAXITER; k++)
|
||||
{
|
||||
// Ap = A*P
|
||||
auto Ap = ctx.logical_data(P.shape());
|
||||
SPMV(ctx, A, P, Ap);
|
||||
|
||||
// We don't compute alpha explicitly
|
||||
// alpha = rsold / (p' * Ap);
|
||||
auto pAp = ctx.logical_data(shape_of<scalar_view<double>>());
|
||||
DOT(ctx, P, Ap, pAp);
|
||||
|
||||
// x = x + alpha * p;
|
||||
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read())
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
|
||||
double alpha = (*drsold / *dpAp);
|
||||
dX(i) += alpha * dP(i);
|
||||
};
|
||||
|
||||
// r = r - alpha * Ap;
|
||||
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read())
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
|
||||
double alpha = (*drsold / *dpAp);
|
||||
dR(i) -= alpha * dAp(i);
|
||||
};
|
||||
|
||||
// rsnew = r' * r;
|
||||
auto rsnew = ctx.logical_data(shape_of<scalar_view<double>>());
|
||||
DOT(ctx, R, R, rsnew);
|
||||
|
||||
// Read the residual on the CPU, and halt the iterative process if we have converged
|
||||
// (note that this will block the submission of tasks)
|
||||
double err = ctx.wait(rsnew);
|
||||
fprintf(stderr, "iter %d : residual %e\n", k, err);
|
||||
if (err < 1e-10)
|
||||
{
|
||||
// We have converged
|
||||
fprintf(stderr, "Successfully converged (err = %le)\n", err);
|
||||
break;
|
||||
}
|
||||
|
||||
// p = r + (rsnew / rsold) * p;
|
||||
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read())
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
|
||||
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
|
||||
};
|
||||
|
||||
// update old residual
|
||||
ctx.parallel_for(box(1), rsold.write(), rsnew.read())->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
|
||||
*drsold = *drsnew;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
size_t N = 10485760;
|
||||
|
||||
context_t ctx;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
fprintf(stderr, "N = %zu\n", N);
|
||||
}
|
||||
|
||||
size_t nz = (N - 2) * 3 + 4;
|
||||
|
||||
size_t* row_offsets;
|
||||
size_t* column_indices;
|
||||
double* values;
|
||||
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
|
||||
cuda_safe_call(cudaHostAlloc(&column_indices, nz * sizeof(size_t), cudaHostAllocMapped));
|
||||
cuda_safe_call(cudaHostAlloc(&values, nz * sizeof(double), cudaHostAllocMapped));
|
||||
|
||||
// Generate a random matrix that is supposed to be invertible
|
||||
genTridiag(row_offsets, column_indices, values, N, nz);
|
||||
|
||||
csr_matrix A(ctx, N, nz, values, row_offsets, column_indices);
|
||||
|
||||
auto X = ctx.logical_data(shape_of<slice<double>>(N));
|
||||
auto B = ctx.logical_data(shape_of<slice<double>>(N));
|
||||
|
||||
// RHS
|
||||
ctx.parallel_for(B.shape(), B.write())->*[] __device__(size_t i, auto dB) {
|
||||
dB(i) = 1.0;
|
||||
};
|
||||
|
||||
cg_solver(ctx, A, X, B);
|
||||
|
||||
ctx.finalize();
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Sparse conjugate gradient algorithm
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
#if !_CCCL_CTK_BELOW(12, 4)
|
||||
using vector_t = stackable_logical_data<slice<double>>;
|
||||
using scalar_t = stackable_logical_data<scalar_view<double>>;
|
||||
using context_t = stackable_ctx;
|
||||
|
||||
struct csr_matrix
|
||||
{
|
||||
csr_matrix(
|
||||
context_t& ctx, size_t num_rows, size_t num_nonzeros, double* values, size_t* row_offsets, size_t* column_indices)
|
||||
{
|
||||
val_handle = ctx.logical_data(make_slice(values, num_nonzeros));
|
||||
col_handle = ctx.logical_data(make_slice(column_indices, num_nonzeros));
|
||||
row_handle = ctx.logical_data(make_slice(row_offsets, num_rows + 1));
|
||||
|
||||
val_handle.set_symbol("csr_val");
|
||||
col_handle.set_symbol("csr_col");
|
||||
row_handle.set_symbol("csr_row");
|
||||
}
|
||||
|
||||
/* Description of the CSR */
|
||||
mutable stackable_logical_data<slice<double>> val_handle;
|
||||
mutable stackable_logical_data<slice<size_t>> row_handle;
|
||||
mutable stackable_logical_data<slice<size_t>> col_handle;
|
||||
};
|
||||
|
||||
// Note that a and b might be the same logical data
|
||||
void DOT(context_t& ctx, vector_t& a, vector_t& b, scalar_t& res)
|
||||
{
|
||||
ctx.parallel_for(a.shape(), a.read(), b.read(), res.reduce(reducer::sum<double>{})).set_symbol("DOT")->*
|
||||
[] __device__(size_t i, auto da, auto db, double& dres) {
|
||||
dres += da(i) * db(i);
|
||||
};
|
||||
};
|
||||
|
||||
void SPMV(context_t& ctx, csr_matrix& a, vector_t& x, vector_t& y)
|
||||
{
|
||||
ctx.parallel_for(y.shape(), a.val_handle.read(), a.col_handle.read(), a.row_handle.read(), x.read(), y.write())
|
||||
.set_symbol("SPMV")
|
||||
->*[] _CCCL_DEVICE(size_t row, auto da_val, auto da_col, auto da_row, auto dx, auto dy) {
|
||||
int row_start = da_row(row);
|
||||
int row_end = da_row(row + 1);
|
||||
|
||||
double sum = 0.0;
|
||||
for (int elt = row_start; elt < row_end; elt++)
|
||||
{
|
||||
sum += da_val(elt) * dx(da_col(elt));
|
||||
}
|
||||
|
||||
dy(row) = sum;
|
||||
};
|
||||
}
|
||||
|
||||
/* genTridiag: generate a random tridiagonal symmetric matrix
|
||||
from :
|
||||
https://github.com/NVIDIA/cuda-samples/blob/master/Samples/4_CUDA_Libraries/conjugateGradientCudaGraphs/conjugateGradientCudaGraphs.cu
|
||||
*/
|
||||
void genTridiag(size_t* I, size_t* J, double* val, size_t N, size_t nz)
|
||||
{
|
||||
const double d = 2.0;
|
||||
|
||||
I[0] = 0, J[0] = 0, J[1] = 1;
|
||||
val[0] = drand48() + d;
|
||||
val[1] = drand48();
|
||||
int start;
|
||||
|
||||
for (size_t i = 1; i < N; i++)
|
||||
{
|
||||
if (i > 1)
|
||||
{
|
||||
I[i] = I[i - 1] + 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
I[1] = 2;
|
||||
}
|
||||
|
||||
start = (i - 1) * 3 + 2;
|
||||
J[start] = i - 1;
|
||||
J[start + 1] = i;
|
||||
|
||||
if (i < N - 1)
|
||||
{
|
||||
J[start + 2] = i + 1;
|
||||
}
|
||||
|
||||
val[start] = val[start - 1];
|
||||
val[start + 1] = drand48() + d;
|
||||
|
||||
if (i < N - 1)
|
||||
{
|
||||
val[start + 2] = drand48();
|
||||
}
|
||||
}
|
||||
|
||||
I[N] = nz;
|
||||
}
|
||||
|
||||
void cg_solver(context_t& ctx, csr_matrix& A, vector_t& X, vector_t& B)
|
||||
{
|
||||
// Initial guess X = 1
|
||||
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
|
||||
dX(i) = 1.0;
|
||||
};
|
||||
|
||||
// Residual R initialized to B
|
||||
auto R = ctx.logical_data(B.shape());
|
||||
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
|
||||
dR(i) = dB(i);
|
||||
};
|
||||
|
||||
// R = R - A*X
|
||||
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
|
||||
SPMV(ctx, A, X, Ax);
|
||||
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
|
||||
dR(i) -= dAx(i);
|
||||
};
|
||||
|
||||
// P = R;
|
||||
auto P = ctx.logical_data(R.shape()).set_symbol("P");
|
||||
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
|
||||
dP(i) = dR(i);
|
||||
};
|
||||
|
||||
// RSOLD = R'*R
|
||||
auto rsold = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("rsold");
|
||||
DOT(ctx, R, R, rsold);
|
||||
|
||||
{
|
||||
auto while_guard = ctx.while_graph_scope();
|
||||
|
||||
// Ap = A*P
|
||||
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
|
||||
SPMV(ctx, A, P, Ap);
|
||||
|
||||
// We don't compute alpha explicitly
|
||||
// alpha = rsold / (p' * Ap);
|
||||
auto pAp = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("pAp");
|
||||
DOT(ctx, P, Ap, pAp);
|
||||
|
||||
// x = x + alpha * p;
|
||||
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
|
||||
double alpha = (*drsold / *dpAp);
|
||||
dX(i) += alpha * dP(i);
|
||||
};
|
||||
|
||||
// r = r - alpha * Ap;
|
||||
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
|
||||
double alpha = (*drsold / *dpAp);
|
||||
dR(i) -= alpha * dAp(i);
|
||||
};
|
||||
|
||||
// rsnew = r' * r;
|
||||
auto rsnew = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("rsnew");
|
||||
DOT(ctx, R, R, rsnew);
|
||||
|
||||
while_guard.update_cond(rsnew.read())->*[] __device__(auto drsnew) {
|
||||
printf("RES %e\n", *drsnew);
|
||||
bool converged = (*drsnew < 1e-13);
|
||||
return !converged;
|
||||
};
|
||||
|
||||
// p = r + (rsnew / rsold) * p;
|
||||
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
|
||||
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
|
||||
};
|
||||
|
||||
// update old residual
|
||||
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
|
||||
*drsold = *drsnew;
|
||||
};
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
|
||||
{
|
||||
#if _CCCL_CTK_BELOW(12, 4)
|
||||
fprintf(stderr, "Waiving test: conditional nodes are only available since CUDA 12.4.\n");
|
||||
return 0;
|
||||
#else
|
||||
size_t N = 10485760;
|
||||
|
||||
context_t ctx;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
fprintf(stderr, "N = %zu\n", N);
|
||||
}
|
||||
|
||||
size_t nz = (N - 2) * 3 + 4;
|
||||
|
||||
size_t* row_offsets;
|
||||
size_t* column_indices;
|
||||
double* values;
|
||||
cuda_safe_call(cudaHostAlloc(&row_offsets, (N + 1) * sizeof(size_t), cudaHostAllocMapped));
|
||||
cuda_safe_call(cudaHostAlloc(&column_indices, nz * sizeof(size_t), cudaHostAllocMapped));
|
||||
cuda_safe_call(cudaHostAlloc(&values, nz * sizeof(double), cudaHostAllocMapped));
|
||||
|
||||
// Generate a random matrix that is supposed to be invertible
|
||||
genTridiag(row_offsets, column_indices, values, N, nz);
|
||||
|
||||
csr_matrix A(ctx, N, nz, values, row_offsets, column_indices);
|
||||
|
||||
auto X = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("X");
|
||||
auto B = ctx.logical_data(shape_of<slice<double>>(N)).set_symbol("B");
|
||||
|
||||
// RHS
|
||||
ctx.parallel_for(B.shape(), B.write()).set_symbol("B assembly")->*[] __device__(size_t i, auto dB) {
|
||||
dB(i) = 1.0;
|
||||
};
|
||||
|
||||
cg_solver(ctx, A, X, B);
|
||||
|
||||
ctx.finalize();
|
||||
#endif
|
||||
}
|
||||
@@ -1,462 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Conjugate gradient for a tiled dense matrix
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
static cublasHandle_t cublas_handle;
|
||||
|
||||
stream_ctx ctx;
|
||||
|
||||
class matrix
|
||||
{
|
||||
public:
|
||||
matrix(size_t N)
|
||||
: N(N)
|
||||
{
|
||||
h_addr.reset(new double[N * N]);
|
||||
cuda_safe_call(cudaHostRegister(h_addr.get(), N * N * sizeof(double), cudaHostRegisterPortable));
|
||||
handle = ::std::make_shared<logical_data<slice<double, 2>>>(
|
||||
ctx.logical_data(make_slice(h_addr.get(), std::tuple{N, N}, N)));
|
||||
}
|
||||
|
||||
void fill(const std::function<double(int, int)>& f)
|
||||
{
|
||||
ctx.task(exec_place::host(), handle->write())->*[&f](cudaStream_t stream, auto ds) {
|
||||
cuda_safe_call(cudaStreamSynchronize(stream));
|
||||
|
||||
for (size_t col = 0; col < ds.extent(1); col++)
|
||||
{
|
||||
for (size_t row = 0; row < ds.extent(0); row++)
|
||||
{
|
||||
ds(row, col) = f(row, col);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
size_t N;
|
||||
std::unique_ptr<double[]> h_addr;
|
||||
std::shared_ptr<logical_data<slice<double, 2>>> handle;
|
||||
};
|
||||
|
||||
class vector
|
||||
{
|
||||
public:
|
||||
vector(size_t N, size_t _block_size, bool is_tmp = false)
|
||||
: N(N)
|
||||
, block_size(_block_size)
|
||||
, nblocks((N + block_size - 1) / block_size)
|
||||
{
|
||||
handles.resize(nblocks);
|
||||
|
||||
if (is_tmp)
|
||||
{
|
||||
// There is no physical backing for this temporary vector
|
||||
for (size_t b = 0; b < nblocks; b++)
|
||||
{
|
||||
size_t bs = std::min(N - block_size * b, block_size);
|
||||
handles[b] = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(bs)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
h_addr.reset(new double[N]);
|
||||
cuda_safe_call(cudaHostRegister(h_addr.get(), N * sizeof(double), cudaHostRegisterPortable));
|
||||
for (size_t b = 0; b < nblocks; b++)
|
||||
{
|
||||
size_t bs = std::min(N - block_size * b, block_size);
|
||||
handles[b] =
|
||||
::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(make_slice(&h_addr[block_size * b], bs)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
vector(const vector& a)
|
||||
: N(a.N)
|
||||
, block_size(a.block_size)
|
||||
, nblocks(a.nblocks)
|
||||
{
|
||||
handles.resize(nblocks);
|
||||
|
||||
for (size_t b = 0; b < nblocks; b++)
|
||||
{
|
||||
size_t bs = std::min(N - block_size * b, block_size);
|
||||
handles[b] = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(bs)));
|
||||
|
||||
ctx.task(handles[b]->write(), a.handles[b]->read())->*[bs](cudaStream_t stream, auto dthis, auto da) {
|
||||
// There are likely much more efficient ways.
|
||||
cuda_safe_call(cudaMemcpyAsync(
|
||||
dthis.data_handle(), da.data_handle(), bs * sizeof(double), cudaMemcpyDeviceToDevice, stream));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void fill(const std::function<double(int)>& f)
|
||||
{
|
||||
size_t bs = block_size;
|
||||
for (size_t b = 0; b < nblocks; b++)
|
||||
{
|
||||
ctx.task(exec_place::host(), handles[b]->write())->*[&f, b, bs](cudaStream_t stream, auto ds) {
|
||||
cuda_safe_call(cudaStreamSynchronize(stream));
|
||||
|
||||
for (size_t local_row = 0; local_row < ds.extent(0); local_row++)
|
||||
{
|
||||
ds(local_row) = f(local_row + b * bs);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
size_t N;
|
||||
size_t block_size;
|
||||
size_t nblocks;
|
||||
|
||||
mutable std::vector<std::shared_ptr<logical_data<slice<double>>>> handles;
|
||||
std::unique_ptr<double[]> h_addr;
|
||||
};
|
||||
|
||||
__global__ void scalar_div(const double* a, const double* b, double* c)
|
||||
{
|
||||
*c = *a / *b;
|
||||
}
|
||||
|
||||
// A += B
|
||||
__global__ void scalar_add(double* a, const double* b)
|
||||
{
|
||||
*a = *a + *b;
|
||||
}
|
||||
|
||||
__global__ void scalar_minus(const double* a, double* res)
|
||||
{
|
||||
*res = -(*a);
|
||||
}
|
||||
|
||||
class scalar
|
||||
{
|
||||
public:
|
||||
scalar(bool is_tmp = false)
|
||||
{
|
||||
size_t s = sizeof(double);
|
||||
|
||||
if (is_tmp)
|
||||
{
|
||||
// There is no physical backing for this temporary vector
|
||||
handle = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
h_addr.reset(new double);
|
||||
cuda_safe_call(cudaHostRegister(h_addr.get(), s, cudaHostRegisterPortable));
|
||||
handle = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(make_slice(h_addr.get(), 1)));
|
||||
}
|
||||
}
|
||||
|
||||
scalar(scalar&&) = default;
|
||||
scalar& operator=(scalar&&) = default;
|
||||
|
||||
// Copy constructor
|
||||
scalar(const scalar& a)
|
||||
{
|
||||
handle = ::std::make_shared<logical_data<slice<double>>>(ctx.logical_data(shape_of<slice<double>>(1)));
|
||||
|
||||
ctx.task(handle->write(), a.handle->read())->*[](cudaStream_t stream, auto dthis, auto da) {
|
||||
// There are likely much more efficient ways.
|
||||
cuda_safe_call(
|
||||
cudaMemcpyAsync(dthis.data_handle(), da.data_handle(), sizeof(double), cudaMemcpyDeviceToDevice, stream));
|
||||
};
|
||||
}
|
||||
|
||||
scalar operator/(scalar const& rhs) const
|
||||
{
|
||||
// Submit a task that computes this/rhs
|
||||
scalar res(true);
|
||||
ctx.task(handle->read(), rhs.handle->read(), res.handle->write())
|
||||
->*[](cudaStream_t stream, auto da, auto db, auto dres) {
|
||||
scalar_div<<<1, 1, 0, stream>>>(da.data_handle(), db.data_handle(), dres.data_handle());
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// this += rhs
|
||||
scalar& operator+=(const scalar& rhs)
|
||||
{
|
||||
ctx.task(handle->rw(), rhs.handle->read())->*[](cudaStream_t stream, auto dthis, auto drhs) {
|
||||
scalar_add<<<1, 1, 0, stream>>>(dthis.data_handle(), drhs.data_handle());
|
||||
};
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
scalar operator-() const
|
||||
{
|
||||
// Submit a task that computes -s
|
||||
scalar res(true);
|
||||
ctx.task(handle->read(), res.handle->write())->*[](cudaStream_t stream, auto dthis, auto dres) {
|
||||
scalar_minus<<<1, 1, 0, stream>>>(dthis.data_handle(), dres.data_handle());
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Get value on the host
|
||||
double get_value()
|
||||
{
|
||||
double val;
|
||||
ctx.task(exec_place::host(), handle->read())->*[&val](cudaStream_t stream, auto ds) {
|
||||
cuda_safe_call(cudaStreamSynchronize(stream));
|
||||
val = ds(0);
|
||||
};
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
mutable std::shared_ptr<logical_data<slice<double>>> handle;
|
||||
std::unique_ptr<double> h_addr;
|
||||
};
|
||||
|
||||
class scalar DOT(vector& a, class vector& b)
|
||||
{
|
||||
assert(a.nblocks == b.nblocks);
|
||||
scalar global_res(true);
|
||||
|
||||
// Loop over all blocks,
|
||||
for (size_t bid = 0; bid < a.nblocks; bid++)
|
||||
{
|
||||
scalar res(true);
|
||||
|
||||
// Note that it works even if a.handle == b.handle because they have the same access mode
|
||||
ctx.task(a.handles[bid]->read(), b.handles[bid]->read(), res.handle->write())
|
||||
->*[](cudaStream_t stream, auto da, auto db, auto dres) {
|
||||
cuda_safe_call(cublasSetStream(cublas_handle, stream));
|
||||
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_DEVICE));
|
||||
cuda_safe_call(
|
||||
cublasDdot(cublas_handle, da.extent(0), da.data_handle(), 1, db.data_handle(), 1, dres.data_handle()));
|
||||
};
|
||||
|
||||
if (bid == 0)
|
||||
{
|
||||
// First access requires an assignment because it was not initialized
|
||||
global_res = std::move(res);
|
||||
}
|
||||
else
|
||||
{
|
||||
global_res += res;
|
||||
}
|
||||
}
|
||||
|
||||
return global_res;
|
||||
};
|
||||
|
||||
// Y = Y + alpha * X
|
||||
void AXPY(const class scalar& alpha, class vector& x, class vector& y)
|
||||
{
|
||||
assert(x.N == y.N);
|
||||
assert(x.nblocks == y.nblocks);
|
||||
|
||||
for (size_t b = 0; b < x.nblocks; b++)
|
||||
{
|
||||
ctx.task(alpha.handle->read(), x.handles[b]->read(), y.handles[b]->rw())
|
||||
->*
|
||||
[](cudaStream_t stream, auto dalpha, auto dx, auto dy) {
|
||||
auto nx = dx.extent(0);
|
||||
cuda_safe_call(cublasSetStream(cublas_handle, stream));
|
||||
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_DEVICE));
|
||||
cuda_safe_call(cublasDaxpy(cublas_handle, nx, dalpha.data_handle(), dx.data_handle(), 1, dy.data_handle(), 1));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Y = alpha*Y + X
|
||||
void SCALE_AXPY(const scalar& alpha, const class vector& x, class vector& y)
|
||||
{
|
||||
assert(x.N == y.N);
|
||||
assert(x.nblocks == y.nblocks);
|
||||
|
||||
for (size_t b = 0; b < x.nblocks; b++)
|
||||
{
|
||||
ctx.task(alpha.handle->read(), x.handles[b]->read(), y.handles[b]->rw())
|
||||
->*[](cudaStream_t stream, auto dalpha, auto dx, auto dy) {
|
||||
cuda_safe_call(cublasSetStream(cublas_handle, stream));
|
||||
|
||||
auto nx = dx.extent(0);
|
||||
|
||||
// Y = alpha Y
|
||||
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_DEVICE));
|
||||
cuda_safe_call(cublasDscal(cublas_handle, nx, dalpha.data_handle(), dy.data_handle(), 1));
|
||||
|
||||
// Y = Y + X
|
||||
const double one = 1.0;
|
||||
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_HOST));
|
||||
cuda_safe_call(cublasDaxpy(cublas_handle, nx, &one, dx.data_handle(), 1, dy.data_handle(), 1));
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// y = alpha Ax + beta y
|
||||
void GEMV(double alpha, class matrix& a, class vector& x, double beta, class vector& y)
|
||||
{
|
||||
assert(a.N == x.N);
|
||||
assert(x.N == y.N);
|
||||
|
||||
size_t block_size = x.block_size;
|
||||
assert(block_size == y.block_size);
|
||||
|
||||
for (size_t row_y = 0; row_y < y.nblocks; row_y++)
|
||||
{
|
||||
for (size_t row_x = 0; row_x < x.nblocks; row_x++)
|
||||
{
|
||||
double local_beta = (row_x == 0) ? beta : 1.0;
|
||||
|
||||
// If beta is null, then this is a write only mode
|
||||
auto y_mode = local_beta == 0.0 ? access_mode::write : access_mode::rw;
|
||||
|
||||
ctx.task(a.handle->read(), x.handles[row_x]->read(), task_dep<slice<double>>(*(y.handles[row_y].get()), y_mode))
|
||||
->*[alpha, local_beta, row_x, row_y, block_size](cudaStream_t stream, auto da, auto dx, auto dy) {
|
||||
auto nx = dx.extent(0);
|
||||
auto ny = dy.extent(0);
|
||||
auto ldA = da.stride(1);
|
||||
const double* Ablock = &da(row_y * block_size, row_x * block_size);
|
||||
|
||||
cuda_safe_call(cublasSetStream(cublas_handle, stream));
|
||||
cuda_safe_call(cublasSetPointerMode(cublas_handle, CUBLAS_POINTER_MODE_HOST));
|
||||
cuda_safe_call(cublasDgemv(
|
||||
cublas_handle,
|
||||
CUBLAS_OP_N,
|
||||
ny,
|
||||
nx,
|
||||
&alpha,
|
||||
Ablock,
|
||||
ldA,
|
||||
dx.data_handle(),
|
||||
1,
|
||||
&local_beta,
|
||||
dy.data_handle(),
|
||||
1));
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cg(matrix& A, vector& X, vector& B)
|
||||
{
|
||||
int N = A.N;
|
||||
|
||||
assert(N == X.N);
|
||||
assert(N == B.N);
|
||||
|
||||
vector R = B;
|
||||
|
||||
// R = R - A*X
|
||||
GEMV(-1.0, A, X, 1.0, R);
|
||||
|
||||
vector P = R;
|
||||
|
||||
// RSOLD = R'*R
|
||||
scalar rsold = DOT(R, R);
|
||||
|
||||
int MAXITER = N;
|
||||
|
||||
if (getenv("MAXITER"))
|
||||
{
|
||||
MAXITER = atoi(getenv("MAXITER"));
|
||||
}
|
||||
|
||||
for (int k = 0; k < MAXITER; k++)
|
||||
{
|
||||
vector Ap(N, P.block_size, true);
|
||||
|
||||
// Ap = A*P
|
||||
GEMV(1.0, A, P, 0.0, Ap);
|
||||
|
||||
// alpha = rsold / (p' * Ap);
|
||||
scalar alpha = rsold / DOT(P, Ap);
|
||||
|
||||
// x = x + alpha * p;
|
||||
AXPY(alpha, P, X);
|
||||
|
||||
// r = r - alpha * Ap;
|
||||
AXPY(-alpha, Ap, R);
|
||||
|
||||
// rsnew = r' * r;
|
||||
scalar rsnew = DOT(R, R);
|
||||
|
||||
// Read the residual on the CPU, and halt the iterative process if we have converged
|
||||
{
|
||||
double err;
|
||||
ctx.task(exec_place::host(), rsnew.handle->read())->*[&err](cudaStream_t stream, auto dres) {
|
||||
cuda_safe_call(cudaStreamSynchronize(stream));
|
||||
err = sqrt(dres(0));
|
||||
};
|
||||
|
||||
if (err < 1e-10)
|
||||
{
|
||||
// We have converged
|
||||
// fprintf(stderr, "Successfully converged (err = %le)\n", err);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// p = r + (rsnew / rsold) * p;
|
||||
SCALE_AXPY(rsnew / rsold, R, P);
|
||||
|
||||
rsold = std::move(rsnew);
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
size_t N = 1024;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
fprintf(stderr, "N = %zu\n", N);
|
||||
}
|
||||
|
||||
size_t block_size = N / 4;
|
||||
|
||||
if (argc > 2)
|
||||
{
|
||||
block_size = atoi(argv[2]);
|
||||
fprintf(stderr, "block_size = %zu\n", block_size);
|
||||
}
|
||||
|
||||
// Do this lazily ?
|
||||
cuda_safe_call(cublasCreate(&cublas_handle));
|
||||
|
||||
matrix A(N);
|
||||
A.fill([&](int row, int col) {
|
||||
return (1.0 / (row + col + 1) + (row == col ? 0.1 : 0.0));
|
||||
});
|
||||
|
||||
vector B(N, block_size);
|
||||
vector X(N, block_size);
|
||||
|
||||
B.fill([&](int /*unused*/) {
|
||||
return 1.0;
|
||||
});
|
||||
|
||||
X.fill([&](int /*unused*/) {
|
||||
return 0.0;
|
||||
});
|
||||
|
||||
cg(A, X, B);
|
||||
|
||||
ctx.finalize();
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
/**
|
||||
* @file
|
||||
* @brief Sparse conjugate gradient algorithm
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#include "dot.cuh"
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
#if !_CCCL_CTK_BELOW(12, 4)
|
||||
template <typename ctx_t, typename T>
|
||||
void cg_solver(ctx_t& ctx, csr_matrix<T>& A, vector_t<T>& X, vector_t<T>& B, double cg_tol = 1e-10, size_t max_cg = 1000)
|
||||
{
|
||||
// Initial guess X = 0 (better for Newton corrections)
|
||||
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
|
||||
dX(i) = 0.0;
|
||||
};
|
||||
|
||||
// Residual R initialized to B
|
||||
auto R = ctx.logical_data(B.shape()).set_symbol("R");
|
||||
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
|
||||
dR(i) = dB(i);
|
||||
};
|
||||
|
||||
// R = R - A*X
|
||||
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
|
||||
SPMV(ctx, A, X, Ax);
|
||||
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
|
||||
dR(i) -= dAx(i);
|
||||
};
|
||||
|
||||
// P = R;
|
||||
auto P = ctx.logical_data(R.shape()).set_symbol("P");
|
||||
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
|
||||
dP(i) = dR(i);
|
||||
};
|
||||
|
||||
// RSOLD = R'*R
|
||||
auto rsold = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsold");
|
||||
DOT(ctx, R, R, rsold);
|
||||
|
||||
// CG iteration counter
|
||||
auto cg_iter = ctx.logical_data(shape_of<scalar_view<int>>()).set_symbol("cg_iter");
|
||||
ctx.parallel_for(box(1), cg_iter.write()).set_symbol("init_cg_iter")->*[] _CCCL_DEVICE(size_t i, auto diter) {
|
||||
*diter = 0;
|
||||
};
|
||||
|
||||
{
|
||||
auto while_guard = ctx.while_graph_scope();
|
||||
|
||||
// Ap = A*P
|
||||
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
|
||||
SPMV(ctx, A, P, Ap);
|
||||
|
||||
// We don't compute alpha explicitly
|
||||
// alpha = rsold / (p' * Ap);
|
||||
auto pAp = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("pAp");
|
||||
DOT(ctx, P, Ap, pAp);
|
||||
|
||||
// x = x + alpha * p;
|
||||
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
|
||||
T alpha = (*drsold / *dpAp);
|
||||
dX(i) += alpha * dP(i);
|
||||
};
|
||||
|
||||
// r = r - alpha * Ap;
|
||||
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
|
||||
T alpha = (*drsold / *dpAp);
|
||||
dR(i) -= alpha * dAp(i);
|
||||
};
|
||||
|
||||
// rsnew = r' * r;
|
||||
auto rsnew = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsnew");
|
||||
DOT(ctx, R, R, rsnew);
|
||||
|
||||
while_guard.update_cond(rsnew.read(), cg_iter.rw())->*[cg_tol, max_cg] __device__(auto drsnew, auto diter) {
|
||||
(*diter)++; // increment iteration counter
|
||||
bool converged = (*drsnew < cg_tol * cg_tol);
|
||||
// printf("CG iter %d: RES %e (tol=%e)\n", *diter, sqrt(*drsnew), cg_tol);
|
||||
return !converged && (*diter < max_cg);
|
||||
};
|
||||
|
||||
// p = r + (rsnew / rsold) * p;
|
||||
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
|
||||
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
|
||||
};
|
||||
|
||||
// update old residual
|
||||
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
|
||||
*drsold = *drsnew;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ctx_t, typename T>
|
||||
void cg_solver_no_while(
|
||||
ctx_t& ctx, csr_matrix<T>& A, vector_t<T>& X, vector_t<T>& B, double cg_tol = 1e-10, size_t max_cg = 1000)
|
||||
{
|
||||
// Initial guess X = 0 (better for Newton corrections)
|
||||
ctx.parallel_for(X.shape(), X.write()).set_symbol("init_guess")->*[] _CCCL_DEVICE(size_t i, auto dX) {
|
||||
dX(i) = 0.0;
|
||||
};
|
||||
|
||||
// Residual R initialized to B
|
||||
auto R = ctx.logical_data(B.shape()).set_symbol("R");
|
||||
ctx.parallel_for(R.shape(), R.write(), B.read()).set_symbol("R=B")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dB) {
|
||||
dR(i) = dB(i);
|
||||
};
|
||||
|
||||
// R = R - A*X
|
||||
auto Ax = ctx.logical_data(X.shape()).set_symbol("Ax");
|
||||
SPMV(ctx, A, X, Ax);
|
||||
ctx.parallel_for(R.shape(), R.rw(), Ax.read()).set_symbol("R -= Ax")->*[] _CCCL_DEVICE(size_t i, auto dR, auto dAx) {
|
||||
dR(i) -= dAx(i);
|
||||
};
|
||||
|
||||
// P = R;
|
||||
auto P = ctx.logical_data(R.shape()).set_symbol("P");
|
||||
ctx.parallel_for(P.shape(), P.write(), R.read()).set_symbol("P=R")->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR) {
|
||||
dP(i) = dR(i);
|
||||
};
|
||||
|
||||
// RSOLD = R'*R
|
||||
auto rsold = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsold");
|
||||
DOT(ctx, R, R, rsold);
|
||||
|
||||
size_t iter = 0;
|
||||
auto rsnew = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("rsnew");
|
||||
|
||||
do
|
||||
{
|
||||
// Ap = A*P
|
||||
auto Ap = ctx.logical_data(P.shape()).set_symbol("Ap");
|
||||
SPMV(ctx, A, P, Ap);
|
||||
|
||||
// We don't compute alpha explicitly
|
||||
// alpha = rsold / (p' * Ap);
|
||||
auto pAp = ctx.logical_data(shape_of<scalar_view<T>>()).set_symbol("pAp");
|
||||
DOT(ctx, P, Ap, pAp);
|
||||
|
||||
// x = x + alpha * p;
|
||||
ctx.parallel_for(X.shape(), X.rw(), rsold.read(), pAp.read(), P.read()).set_symbol("X+=alpha*P")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dX, auto drsold, auto dpAp, auto dP) {
|
||||
T alpha = (*drsold / *dpAp);
|
||||
dX(i) += alpha * dP(i);
|
||||
};
|
||||
|
||||
// r = r - alpha * Ap;
|
||||
ctx.parallel_for(R.shape(), R.rw(), rsold.read(), pAp.read(), Ap.read()).set_symbol("R-=alpha*Ap")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dR, auto drsold, auto dpAp, auto dAp) {
|
||||
T alpha = (*drsold / *dpAp);
|
||||
dR(i) -= alpha * dAp(i);
|
||||
};
|
||||
|
||||
// rsnew = r' * r;
|
||||
DOT(ctx, R, R, rsnew);
|
||||
|
||||
// p = r + (rsnew / rsold) * p;
|
||||
ctx.parallel_for(P.shape(), P.rw(), R.read(), rsnew.read(), rsold.read()).set_symbol("P=r+(rsnew/rsold)*P")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto dP, auto dR, auto drsnew, auto drsold) {
|
||||
dP(i) = dR(i) + (*drsnew / *drsold) * dP(i);
|
||||
};
|
||||
|
||||
// update old residual
|
||||
ctx.parallel_for(box(1), rsold.write(), rsnew.read()).set_symbol("update_rsold")
|
||||
->*[] _CCCL_DEVICE(size_t i, auto drsold, auto drsnew) {
|
||||
*drsold = *drsnew;
|
||||
};
|
||||
} while ((++iter < max_cg) && (ctx.wait(rsnew) > cg_tol * cg_tol));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,70 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#pragma once
|
||||
|
||||
//! \file
|
||||
//! \brief DOT algorithm
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
template <typename T>
|
||||
using vector_t = stackable_logical_data<slice<T>>;
|
||||
|
||||
template <typename T>
|
||||
using scalar_t = stackable_logical_data<scalar_view<T>>;
|
||||
|
||||
template <typename T = double>
|
||||
struct csr_matrix
|
||||
{
|
||||
csr_matrix(stackable_logical_data<slice<T>> _val_handle,
|
||||
stackable_logical_data<slice<size_t>> _row_handle,
|
||||
stackable_logical_data<slice<size_t>> _col_handle)
|
||||
: val_handle(mv(_val_handle))
|
||||
, row_handle(mv(_row_handle))
|
||||
, col_handle(mv(_col_handle))
|
||||
{}
|
||||
|
||||
/* Description of the CSR */
|
||||
mutable stackable_logical_data<slice<T>> val_handle;
|
||||
mutable stackable_logical_data<slice<size_t>> row_handle;
|
||||
mutable stackable_logical_data<slice<size_t>> col_handle;
|
||||
};
|
||||
|
||||
// Note that a and b might be the same logical data
|
||||
template <typename ctx_t, typename T>
|
||||
void DOT(ctx_t& ctx, vector_t<T>& a, vector_t<T>& b, scalar_t<T>& res)
|
||||
{
|
||||
ctx.parallel_for(a.shape(), a.read(), b.read(), res.reduce(reducer::sum<T>{})).set_symbol("DOT")->*
|
||||
[] __device__(size_t i, auto da, auto db, T& dres) {
|
||||
dres += da(i) * db(i);
|
||||
};
|
||||
};
|
||||
|
||||
template <typename ctx_t, typename T>
|
||||
void SPMV(ctx_t& ctx, csr_matrix<T>& a, vector_t<T>& x, vector_t<T>& y)
|
||||
{
|
||||
ctx.parallel_for(y.shape(), a.val_handle.read(), a.col_handle.read(), a.row_handle.read(), x.read(), y.write())
|
||||
.set_symbol("SPMV")
|
||||
->*[] _CCCL_DEVICE(size_t row, auto da_val, auto da_col, auto da_row, auto dx, auto dy) {
|
||||
int row_start = da_row(row);
|
||||
int row_end = da_row(row + 1);
|
||||
|
||||
double sum = 0.0;
|
||||
for (int elt = row_start; elt < row_end; elt++)
|
||||
{
|
||||
sum += da_val(elt) * dx(da_col(elt));
|
||||
}
|
||||
|
||||
dy(row) = sum;
|
||||
};
|
||||
}
|
||||
@@ -1,172 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Generic Newton Solver
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
#include "cg_solver.cuh"
|
||||
#include "dot.cuh"
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
/**
|
||||
* Generic Newton solver for nonlinear systems F(x) = 0
|
||||
*
|
||||
* @tparam ctx_t STF context type
|
||||
* @tparam ResidualCallback Callback to compute residual F(x)
|
||||
* @tparam JacobianCallback Callback to assemble Jacobian J = ∂F/∂x
|
||||
*
|
||||
* The callbacks must be callable with these signatures:
|
||||
* - ResidualCallback: void fn(ctx_t&, const vector_t<double>& x, const vector_t<double>& x_prev, vector_t<double>&
|
||||
* residual)
|
||||
* - JacobianCallback: void fn(ctx_t&, const vector_t<double>& x, vector_t<double>& jacobian_values)
|
||||
*/
|
||||
template <typename ctx_t, typename ResidualCallback, typename JacobianCallback>
|
||||
void newton_solver(
|
||||
ctx_t& ctx,
|
||||
vector_t<double>& U,
|
||||
vector_t<double>& csr_values,
|
||||
const vector_t<size_t>& csr_row_offsets,
|
||||
const vector_t<size_t>& csr_col_ind,
|
||||
ResidualCallback compute_residual_fn,
|
||||
JacobianCallback assemble_jacobian_fn,
|
||||
size_t max_newton = 20,
|
||||
double newton_tol = 1e-10,
|
||||
size_t max_cg = 100)
|
||||
{
|
||||
auto U_prev = ctx.logical_data(U.shape()).set_symbol("U_prev");
|
||||
|
||||
ctx.parallel_for(U.shape(), U_prev.write(), U.read()).set_symbol("init_guess")
|
||||
->*[] __device__(size_t i, auto dU_prev, auto dU) {
|
||||
dU_prev(i) = dU(i);
|
||||
};
|
||||
|
||||
auto newton_norm2 = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("newton_norm2");
|
||||
auto newton_iter = ctx.logical_data(shape_of<scalar_view<size_t>>()).set_symbol("newton_iter");
|
||||
ctx.parallel_for(box(1), newton_iter.write()).set_symbol("init_newton_iter")->*[] _CCCL_DEVICE(size_t i, auto diter) {
|
||||
*diter = 0;
|
||||
};
|
||||
|
||||
{
|
||||
auto while_guard = ctx.while_graph_scope();
|
||||
|
||||
auto residual = ctx.logical_data(U.shape()).set_symbol("residual");
|
||||
auto delta = ctx.logical_data(U.shape()).set_symbol("delta");
|
||||
|
||||
// Compute residual F(U)
|
||||
compute_residual_fn(ctx, U, U_prev, residual);
|
||||
|
||||
// Compute Newton residual norm for convergence check
|
||||
DOT(ctx, residual, residual, newton_norm2);
|
||||
|
||||
// Assemble Jacobian J = ∂F/∂U
|
||||
assemble_jacobian_fn(ctx, U, csr_values);
|
||||
|
||||
auto rhs = ctx.logical_data(U.shape()).set_symbol("rhs");
|
||||
|
||||
// Set up RHS: rhs = -F(U)
|
||||
ctx.parallel_for(rhs.shape(), rhs.write(), residual.read()).set_symbol("rhs = -residual")
|
||||
->*[] __device__(size_t i, auto drhs, auto dresidual) {
|
||||
drhs(i) = -dresidual(i);
|
||||
};
|
||||
|
||||
csr_matrix<double> A(csr_values, csr_row_offsets, csr_col_ind);
|
||||
|
||||
// Solve linear system: J * delta = -F(U)
|
||||
double cg_tol = 1e-8;
|
||||
cg_solver(ctx, A, delta, rhs, cg_tol, max_cg);
|
||||
|
||||
// Newton update: U = U + delta (no special boundary handling needed)
|
||||
ctx.parallel_for(U.shape(), U.rw(), delta.read()).set_symbol("newton_update")
|
||||
->*[] __device__(size_t i, auto dU, auto ddelta) {
|
||||
dU(i) += ddelta(i);
|
||||
};
|
||||
|
||||
while_guard.update_cond(newton_norm2.read(), newton_iter.rw())
|
||||
->*[newton_tol, max_newton] __device__(auto dnorm2, auto diter) {
|
||||
(*diter)++; // increment iteration counter
|
||||
bool converged = (*dnorm2 < newton_tol * newton_tol);
|
||||
return !converged && (*diter < max_newton);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <typename ctx_t, typename ResidualCallback, typename JacobianCallback>
|
||||
void newton_solver_no_while(
|
||||
ctx_t& ctx,
|
||||
vector_t<double>& U,
|
||||
vector_t<double>& csr_values,
|
||||
const vector_t<size_t>& csr_row_offsets,
|
||||
const vector_t<size_t>& csr_col_ind,
|
||||
ResidualCallback compute_residual_fn,
|
||||
JacobianCallback assemble_jacobian_fn,
|
||||
bool cg_use_while = false,
|
||||
size_t max_newton = 20,
|
||||
double newton_tol = 1e-10,
|
||||
size_t max_cg = 100)
|
||||
{
|
||||
auto U_prev = ctx.logical_data(U.shape()).set_symbol("U_prev");
|
||||
|
||||
ctx.parallel_for(U.shape(), U_prev.write(), U.read()).set_symbol("init_guess")
|
||||
->*[] __device__(size_t i, auto dU_prev, auto dU) {
|
||||
dU_prev(i) = dU(i);
|
||||
};
|
||||
|
||||
auto newton_norm2 = ctx.logical_data(shape_of<scalar_view<double>>()).set_symbol("newton_norm2");
|
||||
|
||||
size_t iter = 0;
|
||||
do
|
||||
{
|
||||
auto residual = ctx.logical_data(U.shape()).set_symbol("residual");
|
||||
auto delta = ctx.logical_data(U.shape()).set_symbol("delta");
|
||||
|
||||
// Compute residual F(U)
|
||||
compute_residual_fn(ctx, U, U_prev, residual);
|
||||
|
||||
// Compute Newton residual norm for convergence check
|
||||
DOT(ctx, residual, residual, newton_norm2);
|
||||
|
||||
// Assemble Jacobian J = ∂F/∂U
|
||||
assemble_jacobian_fn(ctx, U, csr_values);
|
||||
|
||||
auto rhs = ctx.logical_data(U.shape()).set_symbol("rhs");
|
||||
|
||||
// Set up RHS: rhs = -F(U)
|
||||
ctx.parallel_for(rhs.shape(), rhs.write(), residual.read()).set_symbol("rhs = -residual")
|
||||
->*[] __device__(size_t i, auto drhs, auto dresidual) {
|
||||
drhs(i) = -dresidual(i);
|
||||
};
|
||||
|
||||
csr_matrix<double> A(csr_values, csr_row_offsets, csr_col_ind);
|
||||
|
||||
// Solve linear system: J * delta = -F(U)
|
||||
double cg_tol = 1e-8;
|
||||
if (cg_use_while)
|
||||
{
|
||||
// fprintf(stderr, "NEWTON NO WHILE, CG WHILE.\n");
|
||||
cg_solver(ctx, A, delta, rhs, cg_tol, max_cg);
|
||||
}
|
||||
else
|
||||
{
|
||||
// fprintf(stderr, "NEWTON NO WHILE, CG NO WHILE.\n");
|
||||
cg_solver_no_while(ctx, A, delta, rhs, cg_tol, max_cg);
|
||||
}
|
||||
|
||||
// Newton update: U = U + delta (no special boundary handling needed)
|
||||
ctx.parallel_for(U.shape(), U.rw(), delta.read()).set_symbol("newton_update")
|
||||
->*[] __device__(size_t i, auto dU, auto ddelta) {
|
||||
dU(i) += ddelta(i);
|
||||
};
|
||||
} while ((++iter < max_newton) && ctx.wait(newton_norm2) > newton_tol * newton_tol);
|
||||
}
|
||||
@@ -1,489 +0,0 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
//
|
||||
// Part of CUDASTF in CUDA C++ Core Libraries,
|
||||
// under the Apache License v2.0 with LLVM Exceptions.
|
||||
// See https://llvm.org/LICENSE.txt for license information.
|
||||
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2022-2024 NVIDIA CORPORATION & AFFILIATES.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
/**
|
||||
* @file
|
||||
* @brief Strassen matrix multiplication algorithm
|
||||
*
|
||||
* This demonstrates how CUDASTF helps combining many interdependent tasks and
|
||||
* deal with temporary data.
|
||||
*/
|
||||
|
||||
#include <cuda/experimental/stf.cuh>
|
||||
|
||||
static const size_t BLOCKSIZE = 1024;
|
||||
|
||||
using namespace cuda::experimental::stf;
|
||||
|
||||
using logical_matrix = logical_data<slice<double, 2>>;
|
||||
|
||||
inline size_t get_m(logical_matrix& s)
|
||||
{
|
||||
return s.shape().extent(0);
|
||||
}
|
||||
|
||||
inline size_t get_n(logical_matrix& s)
|
||||
{
|
||||
return s.shape().extent(1);
|
||||
}
|
||||
|
||||
// XXX global for the sake of simplicity, yet ...
|
||||
static std::vector<cublasHandle_t> cublas_handle;
|
||||
|
||||
cublasHandle_t get_cublas_handle()
|
||||
{
|
||||
int dev;
|
||||
cuda_safe_call(cudaGetDevice(&dev));
|
||||
return cublas_handle[dev];
|
||||
}
|
||||
|
||||
// C = AB
|
||||
void MULT_CLASSIC(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
|
||||
{
|
||||
ctx.task(A.read(), B.read(), C.write()).set_symbol("MULT")->*[](cudaStream_t s, auto a, auto b, auto c) {
|
||||
cuda_safe_call(cublasSetStream(get_cublas_handle(), s));
|
||||
|
||||
size_t N = a.extent(0);
|
||||
|
||||
const double zero = 0.0;
|
||||
const double one = 1.0;
|
||||
cuda_safe_call(cublasDgemm(
|
||||
get_cublas_handle(),
|
||||
CUBLAS_OP_N,
|
||||
CUBLAS_OP_N,
|
||||
N,
|
||||
N,
|
||||
N,
|
||||
&one,
|
||||
a.data_handle(),
|
||||
a.stride(1),
|
||||
b.data_handle(),
|
||||
b.stride(1),
|
||||
&zero,
|
||||
c.data_handle(),
|
||||
c.stride(1)));
|
||||
};
|
||||
}
|
||||
|
||||
// A = A + alpha B
|
||||
template <typename T>
|
||||
__global__ void add_kernel(int m, int n, T* A, int ld_A, T alpha, const T* B, int ld_B)
|
||||
{
|
||||
for (int idx = threadIdx.x + blockIdx.x * blockDim.x; idx < n; idx += blockDim.x * gridDim.x)
|
||||
{
|
||||
for (int idy = threadIdx.y + blockIdx.y * blockDim.y; idy < m; idy += blockDim.y * gridDim.y)
|
||||
{
|
||||
A[idy + idx * ld_A] += alpha * B[idy + idx * ld_B];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute A = A + B
|
||||
template <typename T>
|
||||
void ADD(context& ctx, logical_matrix& A, T alpha, logical_matrix& B)
|
||||
{
|
||||
ctx.task(A.rw(), B.read()).set_symbol("ADD")->*[&](cudaStream_t s, auto a, auto b) {
|
||||
int m_A = a.extent(0);
|
||||
int n_A = a.extent(1);
|
||||
|
||||
int ld_A = a.stride(1);
|
||||
int ld_B = b.stride(1);
|
||||
|
||||
T* addr_A = a.data_handle();
|
||||
const T* addr_B = b.data_handle();
|
||||
|
||||
add_kernel<<<16, 16, 0, s>>>(m_A, n_A, addr_A, ld_A, alpha, addr_B, ld_B);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
__global__ void copy_kernel(int m, int n, const T* src, int ld_src, T* dst, int ld_dst)
|
||||
{
|
||||
for (int idx = threadIdx.x + blockIdx.x * blockDim.x; idx < n; idx += blockDim.x * gridDim.x)
|
||||
{
|
||||
for (int idy = threadIdx.y + blockIdx.y * blockDim.y; idy < m; idy += blockDim.y * gridDim.y)
|
||||
{
|
||||
dst[idy + idx * ld_dst] = src[idy + idx * ld_src];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// row and col = 0 or 1
|
||||
template <typename T>
|
||||
void COPY_TO_SUBMATRIX(context& ctx, logical_data<slice<T, 2>>& A, logical_data<slice<T, 2>>& subA, int row, int col)
|
||||
{
|
||||
// To copy to a subset, this is a write only access, so that we did not need a valid copy for subA before ...
|
||||
ctx.task(A.read(), subA.write()).set_symbol("COPY_TO")->*[&](cudaStream_t s, auto a, auto subA) {
|
||||
int ld_A = a.stride(1);
|
||||
int ld_subA = subA.stride(1);
|
||||
int m_subA = subA.extent(0);
|
||||
int n_subA = subA.extent(1);
|
||||
T* addr_subA = subA.data_handle();
|
||||
const T* addr_A_base = a.data_handle();
|
||||
const T* addr_A = addr_A_base + row * m_subA + col * n_subA * ld_A;
|
||||
|
||||
// subA = A_row,col
|
||||
copy_kernel<<<16, 16, 0, s>>>(m_subA, n_subA, addr_A, ld_A, addr_subA, ld_subA);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void COPY_FROM_SUBMATRICES(context& ctx, logical_data<slice<T, 2>>& A, logical_data<slice<T, 2>> subA[2][2])
|
||||
{
|
||||
// To copy to a subset, this is a write only access, so that we did not need a valid copy for subA before ...
|
||||
// When copying from a subset to the whole matrix, we need a RW because we only modify a part of the matrix
|
||||
ctx.task(A.write(), subA[0][0].read(), subA[0][1].read(), subA[1][0].read(), subA[1][1].read()).set_symbol("COPY_FROM")
|
||||
->*[&](cudaStream_t s, auto a, auto a00, auto a01, auto a10, auto a11) {
|
||||
int ld_A = a.stride(1);
|
||||
T* addr_A_base = a.data_handle();
|
||||
|
||||
for (int col = 0; col < 2; col++)
|
||||
{
|
||||
for (int row = 0; row < 2; row++)
|
||||
{
|
||||
auto& subA = col == 0 ? (row == 0 ? a00 : a10) : (row == 0 ? a01 : a11);
|
||||
int m_subA = subA.extent(0);
|
||||
int n_subA = subA.extent(1);
|
||||
int ld_subA = subA.stride(1);
|
||||
const T* addr_subA = subA.data_handle();
|
||||
T* addr_A = addr_A_base + row * m_subA + col * n_subA * ld_A;
|
||||
|
||||
// A_row,col= subA
|
||||
copy_kernel<<<16, 16, 0, s>>>(m_subA, n_subA, addr_subA, ld_subA, addr_A, ld_A);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void COPY_MATRIX(context& ctx, logical_data<slice<T, 2>>& dst, logical_data<slice<T, 2>>& src)
|
||||
{
|
||||
// This is a write only access, so that we did not need a valid copy for subA before ...
|
||||
ctx.task(dst.write(), src.read()).set_symbol("COPY")->*[&](cudaStream_t s, auto d_dst, auto d_src) {
|
||||
int ld_src = d_dst.stride(1);
|
||||
int ld_dst = d_src.stride(1);
|
||||
|
||||
auto m = d_src.extent(0);
|
||||
assert(m == d_dst.extent(0));
|
||||
|
||||
auto n = d_src.extent(1);
|
||||
assert(n == d_dst.extent(1));
|
||||
|
||||
const T* addr_src = d_src.data_handle();
|
||||
T* addr_dst = d_dst.data_handle();
|
||||
|
||||
copy_kernel<<<16, 16, 0, s>>>(m, n, addr_src, ld_src, addr_dst, ld_dst);
|
||||
};
|
||||
}
|
||||
|
||||
void MULT(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C);
|
||||
|
||||
void MULT_REC_NAIVE(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
|
||||
{
|
||||
logical_matrix subA[2][2], subB[2][2], subC[2][2];
|
||||
|
||||
size_t N = get_m(A);
|
||||
|
||||
assert(get_m(A) == get_n(A));
|
||||
assert(get_m(B) == get_n(B));
|
||||
assert(get_m(C) == get_n(C));
|
||||
|
||||
assert(N % 2 == 0);
|
||||
|
||||
size_t half_N = N / 2;
|
||||
|
||||
// These are TMP data which don't have a valid copy yet
|
||||
for (int col = 0; col < 2; col++)
|
||||
{
|
||||
for (int row = 0; row < 2; row++)
|
||||
{
|
||||
subA[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
subB[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
subC[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_TO_SUBMATRIX(ctx, A, subA[row][col], row, col);
|
||||
COPY_TO_SUBMATRIX(ctx, B, subB[row][col], row, col);
|
||||
}
|
||||
}
|
||||
|
||||
for (int col = 0; col < 2; col++)
|
||||
{
|
||||
for (int row = 0; row < 2; row++)
|
||||
{
|
||||
for (int k = 0; k < 2; k++)
|
||||
{
|
||||
auto Ck = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
MULT(ctx, subA[row][k], subB[k][col], Ck);
|
||||
|
||||
ADD(ctx, subC[row][col], 1.0, Ck);
|
||||
}
|
||||
|
||||
// C_row,col = subC[row][col]
|
||||
COPY_FROM_SUBMATRICES(ctx, C, subC);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MULT_STRASSEN(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
|
||||
{
|
||||
/*
|
||||
* STRASSEN ALGORITHM
|
||||
*
|
||||
* M1 = (A00 + A11)(B00 + B11)
|
||||
* M2 = (A10 + A11)B00
|
||||
* M3 = A00(B01 - B11)
|
||||
* M4 = A11(B10 - B00)
|
||||
* M5 = (A00 + A01)B11
|
||||
* M6 = (A10 - A00)(B00 + B01)
|
||||
* M7 = (A01 - A11)(B10 + B11)
|
||||
*
|
||||
* C00 = M1 + M4 - M5 + M7
|
||||
* C01 = M3 + M5
|
||||
* C10 = M2 + M4
|
||||
* C11 = M1 - M2 + M3 + M6
|
||||
*
|
||||
*/
|
||||
size_t N = get_m(A);
|
||||
assert(N % 2 == 0);
|
||||
size_t half_N = N / 2;
|
||||
|
||||
logical_matrix subA[2][2], subB[2][2], subC[2][2];
|
||||
auto M1 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto M2 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto M3 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto M4 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto M5 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto M6 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto M7 = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
assert(get_m(A) == get_n(A));
|
||||
assert(get_m(B) == get_n(B));
|
||||
assert(get_m(C) == get_n(C));
|
||||
|
||||
// These are TMP data which don't have a valid copy yet
|
||||
for (int col = 0; col < 2; col++)
|
||||
{
|
||||
for (int row = 0; row < 2; row++)
|
||||
{
|
||||
subA[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
subB[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
subC[row][col] = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_TO_SUBMATRIX(ctx, A, subA[row][col], row, col);
|
||||
COPY_TO_SUBMATRIX(ctx, B, subB[row][col], row, col);
|
||||
}
|
||||
}
|
||||
|
||||
// M1 = (A00 + A11)(B00 + B11)
|
||||
{
|
||||
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N)),
|
||||
right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, left, subA[0][0]);
|
||||
ADD(ctx, left, 1.0, subA[1][1]);
|
||||
|
||||
COPY_MATRIX(ctx, right, subB[0][0]);
|
||||
ADD(ctx, right, 1.0, subB[1][1]);
|
||||
|
||||
MULT(ctx, left, right, M1);
|
||||
}
|
||||
|
||||
// M2 = (A10 + A11)B00
|
||||
{
|
||||
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, left, subA[1][0]);
|
||||
ADD(ctx, left, 1.0, subA[1][1]);
|
||||
|
||||
MULT(ctx, left, subB[0][0], M2);
|
||||
}
|
||||
|
||||
// M3 = A00(B01 - B11)
|
||||
{
|
||||
auto right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, right, subB[0][1]);
|
||||
ADD(ctx, right, -1.0, subB[1][1]);
|
||||
|
||||
MULT(ctx, subA[0][0], right, M3);
|
||||
}
|
||||
|
||||
// M4 = A11(B10 - B00)
|
||||
{
|
||||
auto right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, right, subB[1][0]);
|
||||
ADD(ctx, right, -1.0, subB[0][0]);
|
||||
|
||||
MULT(ctx, subA[1][1], right, M4);
|
||||
}
|
||||
|
||||
// M5 = (A00 + A01)B11
|
||||
{
|
||||
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, left, subA[0][0]);
|
||||
ADD(ctx, left, 1.0, subA[0][1]);
|
||||
|
||||
MULT(ctx, left, subB[1][1], M5);
|
||||
}
|
||||
|
||||
// M6 = (A10 - A00)(B00 + B01)
|
||||
{
|
||||
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N)),
|
||||
right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, left, subA[1][0]);
|
||||
ADD(ctx, left, -1.0, subA[1][1]);
|
||||
|
||||
COPY_MATRIX(ctx, right, subB[0][0]);
|
||||
ADD(ctx, right, 1.0, subB[0][1]);
|
||||
|
||||
MULT(ctx, left, right, M6);
|
||||
}
|
||||
|
||||
// M7 = (A01 - A11)(B10 + B11)
|
||||
{
|
||||
auto left = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
auto right = ctx.logical_data(shape_of<slice<double, 2>>(half_N, half_N));
|
||||
|
||||
COPY_MATRIX(ctx, left, subA[0][1]);
|
||||
ADD(ctx, left, -1.0, subA[1][1]);
|
||||
|
||||
COPY_MATRIX(ctx, right, subB[1][0]);
|
||||
ADD(ctx, right, 1.0, subB[1][1]);
|
||||
|
||||
MULT(ctx, left, right, M7);
|
||||
}
|
||||
|
||||
// C00 = M1 + M4 - M5 + M7
|
||||
COPY_MATRIX(ctx, subC[0][0], M1);
|
||||
ADD(ctx, subC[0][0], 1.0, M4);
|
||||
ADD(ctx, subC[0][0], -1.0, M5);
|
||||
ADD(ctx, subC[0][0], -1.0, M5);
|
||||
ADD(ctx, subC[0][0], 1.0, M7);
|
||||
|
||||
// C01 = M3 + M5
|
||||
COPY_MATRIX(ctx, subC[0][1], M3);
|
||||
ADD(ctx, subC[0][1], 1.0, M5);
|
||||
|
||||
// C10 = M2 + M4
|
||||
COPY_MATRIX(ctx, subC[1][0], M2);
|
||||
ADD(ctx, subC[1][0], 1.0, M4);
|
||||
|
||||
// C11 = M1 - M2 + M3 + M6
|
||||
COPY_MATRIX(ctx, subC[1][1], M1);
|
||||
ADD(ctx, subC[1][1], -1.0, M2);
|
||||
ADD(ctx, subC[1][1], 1.0, M3);
|
||||
ADD(ctx, subC[1][1], 1.0, M6);
|
||||
|
||||
// Write back subsets of C to C
|
||||
COPY_FROM_SUBMATRICES(ctx, C, subC);
|
||||
}
|
||||
|
||||
void MULT(context& ctx, logical_matrix& A, logical_matrix& B, logical_matrix& C)
|
||||
{
|
||||
size_t N = get_m(A);
|
||||
|
||||
if (N <= BLOCKSIZE)
|
||||
{
|
||||
MULT_CLASSIC(ctx, A, B, C);
|
||||
}
|
||||
else
|
||||
{
|
||||
// MULT_REC_NAIVE(ctx, A, B, C);
|
||||
MULT_STRASSEN(ctx, A, B, C);
|
||||
}
|
||||
}
|
||||
|
||||
void strassen_test(context& ctx, size_t N)
|
||||
{
|
||||
double* A = new double[N * N];
|
||||
double* B = new double[N * N];
|
||||
double* C = new double[N * N];
|
||||
|
||||
int ldA = N;
|
||||
int ldB = N;
|
||||
int ldC = N;
|
||||
|
||||
cuda_safe_call(cudaHostRegister(A, N * N * sizeof(double), cudaHostRegisterPortable));
|
||||
cuda_safe_call(cudaHostRegister(B, N * N * sizeof(double), cudaHostRegisterPortable));
|
||||
cuda_safe_call(cudaHostRegister(C, N * N * sizeof(double), cudaHostRegisterPortable));
|
||||
|
||||
for (size_t col = 0; col < N; col++)
|
||||
{
|
||||
for (size_t row = 0; row < N; row++)
|
||||
{
|
||||
A[row + N * col] = 1.0;
|
||||
B[row + N * col] = -1.0;
|
||||
C[row + N * col] = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
auto descA = ctx.logical_data(make_slice(A, std::tuple{N, N}, ldA)),
|
||||
descB = ctx.logical_data(make_slice(B, std::tuple{N, N}, ldB)),
|
||||
descC = ctx.logical_data(make_slice(C, std::tuple{N, N}, ldC));
|
||||
descA.set_symbol("A");
|
||||
descB.set_symbol("B");
|
||||
descC.set_symbol("C");
|
||||
|
||||
std::chrono::steady_clock::time_point start, stop;
|
||||
|
||||
ctx.host_launch(descC.read())->*[&](auto /* ignored */) {
|
||||
start = std::chrono::steady_clock::now();
|
||||
};
|
||||
|
||||
MULT(ctx, descA, descB, descC);
|
||||
|
||||
ctx.host_launch(descC.read())->*[&](auto /* ignored */) {
|
||||
stop = std::chrono::steady_clock::now();
|
||||
};
|
||||
|
||||
ctx.finalize();
|
||||
|
||||
std::chrono::duration<double> duration = stop - start;
|
||||
fprintf(stderr, "Elapsed: %.2lf ms\n", duration.count() * 1000.0);
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
long N = 2 * BLOCKSIZE;
|
||||
|
||||
if (argc > 1)
|
||||
{
|
||||
N = atoi(argv[1]);
|
||||
}
|
||||
|
||||
bool use_graphs = false;
|
||||
if (argc > 2)
|
||||
{
|
||||
use_graphs = (atoi(argv[2]) > 0);
|
||||
}
|
||||
|
||||
// Set up CUBLAS
|
||||
int ndevs;
|
||||
cuda_safe_call(cudaGetDeviceCount(&ndevs));
|
||||
cublas_handle.resize(ndevs);
|
||||
for (int d = 0; d < ndevs; d++)
|
||||
{
|
||||
cuda_safe_call(cudaSetDevice(d));
|
||||
cuda_safe_call(cublasCreate(&cublas_handle[d]));
|
||||
}
|
||||
|
||||
cuda_safe_call(cudaSetDevice(0));
|
||||
|
||||
context ctx;
|
||||
if (use_graphs)
|
||||
{
|
||||
ctx = graph_ctx();
|
||||
}
|
||||
|
||||
strassen_test(ctx, N);
|
||||
}
|
||||
Reference in New Issue
Block a user