Skip to content

Commit 210cc2c

Browse files
authored
Add CPU-only build option for testing/debugging. (#162)
* Add opt-in CPU-only build Add a toolkit-free MPI-only build mode for CPU debugging and testing, including C/C++ and Fortran bindings, documentation, and backend coverage while preserving the default GPU build. Signed-off-by: romerojosh <joshr@nvidia.com> * Fix build warnings. Signed-off-by: Josh Romero <joshr@nvidia.com> --------- Signed-off-by: romerojosh <joshr@nvidia.com> Signed-off-by: Josh Romero <joshr@nvidia.com>
1 parent bef2cc6 commit 210cc2c

29 files changed

Lines changed: 962 additions & 321 deletions

‎CMakeLists.txt‎

Lines changed: 234 additions & 196 deletions
Large diffs are not rendered by default.

‎README.md‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ $ cmake -DCUDECOMP_BUILD_EXTRAS=1 -DCUDECOMP_ENABLE_NVSHMEM=1 ..
2727
```
2828

2929

30-
### Dependencies
31-
We strongly recommend building this library using NVHPC SDK compilers and libraries, as the SDK contains all required dependencies for this library and is the focus of our testing. Fortran features are only supported using NVHPC SDK compilers.
30+
### GPU dependencies
31+
We strongly recommend building this library using NVHPC SDK compilers and libraries, as the SDK contains all required dependencies for this library and is the focus of our testing. GPU Fortran bindings require NVHPC SDK compilers.
3232

3333
One exception is cuDecomp builds using NVSHMEM versions older than v3.0, which require the use of a bootstrapping layer that depends on your MPI distribution. The NVSHMEM library packaged within NVHPC SDK
3434
supports OpenMPI only. If you require usage of a different MPI implementation (e.g. Spectrum MPI or Cray MPICH), you need to either build
@@ -39,4 +39,3 @@ Additionally, this library utilizes CUDA-aware MPI and is only compatible with M
3939

4040
## License
4141
This library is released under an Apache 2.0 license, which can be found in [LICENSE](LICENSE).
42-

‎docs/cpu_only.rst‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CPU-only build
2+
==============
3+
4+
Configure with ``-DCUDECOMP_BUILD_CPU_ONLY=ON`` for a limited, toolkit-free
5+
debug/test build requiring C++17 and MPI (plus a Fortran compiler for the optional
6+
bindings). This mode is intended for debugging and testing only, not for production
7+
use. The API is unchanged except for the following differences:
8+
9+
* Library, function, and Fortran module names are unchanged. Use separate CPU/GPU
10+
build directories and installation prefixes, and only one variant per process.
11+
* Buffers and ``cudecompMalloc`` allocations reside in host memory. Operations
12+
complete synchronously; stream arguments are ignored.
13+
* Only MPI backends are supported. Set both ``pdims`` explicitly, with their product
14+
equal to the communicator size. Autotuning is unsupported; pass null autotuning
15+
options or disable tuning.
16+
* Fortran bindings use host arrays and standard C pointers instead of device
17+
arrays/pointers.
18+
* CUDA Graphs, performance reports, NCCL, NVSHMEM, and NVTX are unsupported.

‎docs/index.rst‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ Table of Contents
2727

2828
overview
2929
basic_usage
30-
cuda_graph_capture
3130
autotuning
31+
cuda_graph_capture
3232
nvshmem
33+
cpu_only
3334
api
3435
env_vars
3536

‎include/cudecomp.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,13 @@
2727
#include <stdbool.h>
2828
#include <stdint.h>
2929

30+
#include "cudecomp_config.h"
31+
#if CUDECOMP_BUILD_CPU_ONLY
32+
// Keep stream signatures compatible without requiring CUDA headers.
33+
typedef struct CUstream_st* cudaStream_t;
34+
#else
3035
#include <cuda_runtime.h>
36+
#endif
3137
#include <mpi.h>
3238

3339
#include "cudecomp_version.h"

‎include/cudecomp_config.h.in‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
#ifndef CUDECOMP_CONFIG_H
6+
#define CUDECOMP_CONFIG_H
7+
8+
#cmakedefine01 CUDECOMP_BUILD_CPU_ONLY
9+
10+
#endif

‎include/internal/checks.h‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@
2424
#include <sstream>
2525
#include <string>
2626

27+
#include "cudecomp_config.h"
28+
#if !CUDECOMP_BUILD_CPU_ONLY
2729
#include <cuda.h>
2830
#include <cuda_runtime.h>
2931
#include <cufft.h>
3032
#include <cutensor.h>
33+
#endif
3134
#include <mpi.h>
35+
#if !CUDECOMP_BUILD_CPU_ONLY
3236
#include <nccl.h>
3337
#include <nvml.h>
38+
#endif
3439

3540
#include "internal/exceptions.h"
3641

‎include/internal/comm_routines.h‎

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,15 @@
2727
#include <string>
2828
#include <vector>
2929

30+
#include "cudecomp_config.h"
31+
#if !CUDECOMP_BUILD_CPU_ONLY
3032
#include <cuda/std/complex>
3133
#include <cuda_runtime.h>
34+
#endif
3235
#include <mpi.h>
36+
#if !CUDECOMP_BUILD_CPU_ONLY
3337
#include <nccl.h>
38+
#endif
3439
#ifdef ENABLE_NVSHMEM
3540
#include <nvshmem.h>
3641
#include <nvshmemx.h>
@@ -39,6 +44,7 @@
3944
#include "internal/checks.h"
4045
#include "internal/cudecomp_kernels.h"
4146
#include "internal/nvtx.h"
47+
#include "internal/utils.h"
4248

4349
namespace cudecomp {
4450

@@ -48,8 +54,8 @@ inline constexpr uint64_t nvshmem_signal_set_value = 1; // Graph-stable source f
4854

4955
static inline MPI_Datatype getMpiDataType(float) { return MPI_FLOAT; }
5056
static inline MPI_Datatype getMpiDataType(double) { return MPI_DOUBLE; }
51-
static inline MPI_Datatype getMpiDataType(cuda::std::complex<float>) { return MPI_C_FLOAT_COMPLEX; }
52-
static inline MPI_Datatype getMpiDataType(cuda::std::complex<double>) { return MPI_C_DOUBLE_COMPLEX; }
57+
static inline MPI_Datatype getMpiDataType(cudecomp::complex<float>) { return MPI_C_FLOAT_COMPLEX; }
58+
static inline MPI_Datatype getMpiDataType(cudecomp::complex<double>) { return MPI_C_DOUBLE_COMPLEX; }
5359
template <typename T> static inline MPI_Datatype getMpiDataType() { return getMpiDataType(T(0)); }
5460

5561
static inline bool canUseMpiAlltoall(const std::vector<comm_count_t>& send_counts,
@@ -271,9 +277,11 @@ static void cudecompAlltoall(const cudecompHandle_t& handle, const cudecompGridD
271277
cudaStream_t stream, cudecompTransposePerformanceSample* current_sample = nullptr) {
272278
nvtx::rangePush("cudecompAlltoall");
273279

280+
#if !CUDECOMP_BUILD_CPU_ONLY
274281
if (handle->performance_report_enable) {
275282
CHECK_CUDA(cudaEventRecord(current_sample->alltoall_start_events[current_sample->alltoall_timing_count], stream));
276283
}
284+
#endif
277285

278286
#ifdef ENABLE_NVSHMEM
279287
if (transposeBackendRequiresMpi(grid_desc->config.transpose_comm_backend)) {
@@ -298,6 +306,7 @@ static void cudecompAlltoall(const cudecompHandle_t& handle, const cudecompGridD
298306
THROW_NOT_SUPPORTED("build does not support NVSHMEM communication backends.");
299307
#endif
300308
}
309+
#if !CUDECOMP_BUILD_CPU_ONLY
301310
case CUDECOMP_TRANSPOSE_COMM_NCCL: {
302311
auto& comm_info = (comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info : grid_desc->col_comm_info;
303312
// For fully intra-group alltoall, use distinct NCCL local comm instead of global comm as it is faster.
@@ -326,17 +335,17 @@ static void cudecompAlltoall(const cudecompHandle_t& handle, const cudecompGridD
326335
CHECK_NCCL(ncclGroupEnd());
327336
break;
328337
}
338+
#endif
329339
case CUDECOMP_TRANSPOSE_COMM_MPI_P2P: {
330340
std::vector<MPI_Request> reqs(2 * send_counts.size(), MPI_REQUEST_NULL);
331-
CHECK_CUDA(cudaStreamSynchronize(stream));
341+
streamSynchronize(stream);
332342

333343
auto comm =
334344
(comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info.mpi_comm : grid_desc->col_comm_info.mpi_comm;
335345
int self_rank = (comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info.rank : grid_desc->col_comm_info.rank;
336346

337-
// Self-copy with cudaMemcpy
338-
CHECK_CUDA(cudaMemcpyAsync(recv_buff + recv_offsets[self_rank], send_buff + send_offsets[self_rank],
339-
send_counts[self_rank] * sizeof(T), cudaMemcpyDeviceToDevice, stream));
347+
copyBuffer(recv_buff + recv_offsets[self_rank], send_buff + send_offsets[self_rank],
348+
send_counts[self_rank] * sizeof(T), stream);
340349

341350
for (int i = 1; i < recv_counts.size(); ++i) {
342351
int src_rank, dst_rank;
@@ -365,7 +374,7 @@ static void cudecompAlltoall(const cudecompHandle_t& handle, const cudecompGridD
365374
break;
366375
}
367376
case CUDECOMP_TRANSPOSE_COMM_MPI_A2A: {
368-
CHECK_CUDA(cudaStreamSynchronize(stream));
377+
streamSynchronize(stream);
369378
auto comm =
370379
(comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info.mpi_comm : grid_desc->col_comm_info.mpi_comm;
371380
int self_rank = (comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info.rank : grid_desc->col_comm_info.rank;
@@ -379,9 +388,8 @@ static void cudecompAlltoall(const cudecompHandle_t& handle, const cudecompGridD
379388
int32_t rc = static_cast<int32_t>(recv_counts[0]);
380389
CHECK_MPI(MPI_Alltoall(send_buff, sc, getMpiDataType<T>(), recv_buff, rc, getMpiDataType<T>(), comm));
381390
} else {
382-
// Self-copy with cudaMemcpy
383-
CHECK_CUDA(cudaMemcpyAsync(recv_buff + recv_offsets[self_rank], send_buff + send_offsets[self_rank],
384-
send_counts[self_rank] * sizeof(T), cudaMemcpyDeviceToDevice, stream));
391+
copyBuffer(recv_buff + recv_offsets[self_rank], send_buff + send_offsets[self_rank],
392+
send_counts[self_rank] * sizeof(T), stream);
385393

386394
// Convert count/offset args to int32
387395
std::vector<int32_t> send_counts_i32(send_counts.size());
@@ -420,10 +428,12 @@ static void cudecompAlltoall(const cudecompHandle_t& handle, const cudecompGridD
420428
}
421429
}
422430

431+
#if !CUDECOMP_BUILD_CPU_ONLY
423432
if (handle->performance_report_enable) {
424433
CHECK_CUDA(cudaEventRecord(current_sample->alltoall_end_events[current_sample->alltoall_timing_count], stream));
425434
current_sample->alltoall_timing_count++;
426435
}
436+
#endif
427437

428438
nvtx::rangePop();
429439
}
@@ -450,13 +460,15 @@ cudecompAlltoallPipelined(const cudecompHandle_t& handle, const cudecompGridDesc
450460
}
451461
nvtx::rangePush(os.str());
452462

463+
#if !CUDECOMP_BUILD_CPU_ONLY
453464
int self_rank = (comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info.rank : grid_desc->col_comm_info.rank;
454465
if (handle->performance_report_enable && src_ranks[0] != self_rank) {
455466
// Note: skipping self-copy for timing as it should be overlapped
456467
CHECK_CUDA(cudaStreamWaitEvent(handle->streams[0], grid_desc->events[dst_ranks[0]], 0));
457468
CHECK_CUDA(cudaEventRecord(current_sample->alltoall_start_events[current_sample->alltoall_timing_count],
458469
handle->streams[0]));
459470
}
471+
#endif
460472

461473
#ifdef ENABLE_NVSHMEM
462474
if (transposeBackendRequiresMpi(grid_desc->config.transpose_comm_backend)) {
@@ -537,6 +549,7 @@ cudecompAlltoallPipelined(const cudecompHandle_t& handle, const cudecompGridDesc
537549
THROW_NOT_SUPPORTED("build does not support NVSHMEM communication backends.");
538550
#endif
539551
}
552+
#if !CUDECOMP_BUILD_CPU_ONLY
540553
case CUDECOMP_TRANSPOSE_COMM_NCCL_PL: {
541554
const auto& comm_info = (comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info : grid_desc->col_comm_info;
542555
// For fully intra-group alltoall, use distinct NCCL local comm instead of global comm as it is faster.
@@ -589,6 +602,7 @@ cudecompAlltoallPipelined(const cudecompHandle_t& handle, const cudecompGridDesc
589602
}
590603
break;
591604
}
605+
#endif
592606
case CUDECOMP_TRANSPOSE_COMM_MPI_P2P_PL: {
593607
auto comm =
594608
(comm_axis == CUDECOMP_COMM_ROW) ? grid_desc->row_comm_info.mpi_comm : grid_desc->col_comm_info.mpi_comm;
@@ -599,11 +613,12 @@ cudecompAlltoallPipelined(const cudecompHandle_t& handle, const cudecompGridDesc
599613
int src_rank = src_ranks[i];
600614
int dst_rank = dst_ranks[i];
601615
if (src_rank == self_rank) {
602-
// Self-copy with cudaMemcpy
603-
CHECK_CUDA(cudaMemcpyAsync(recv_buff + recv_offsets[self_rank], send_buff + send_offsets[self_rank],
604-
send_counts[self_rank] * sizeof(T), cudaMemcpyDeviceToDevice, stream));
616+
copyBuffer(recv_buff + recv_offsets[self_rank], send_buff + send_offsets[self_rank],
617+
send_counts[self_rank] * sizeof(T), stream);
605618
} else {
619+
#if !CUDECOMP_BUILD_CPU_ONLY
606620
CHECK_CUDA(cudaEventSynchronize(grid_desc->events[dst_rank]));
621+
#endif
607622

608623
if (send_counts[dst_rank] != 0) {
609624
checkMpiInt32Limit(send_counts[dst_rank], grid_desc->config.transpose_comm_backend);
@@ -629,11 +644,13 @@ cudecompAlltoallPipelined(const cudecompHandle_t& handle, const cudecompGridDesc
629644
}
630645
}
631646

647+
#if !CUDECOMP_BUILD_CPU_ONLY
632648
if (handle->performance_report_enable && src_ranks[0] != self_rank) {
633649
CHECK_CUDA(cudaEventRecord(current_sample->alltoall_end_events[current_sample->alltoall_timing_count],
634650
handle->streams[0]));
635651
current_sample->alltoall_timing_count++;
636652
}
653+
#endif
637654
nvtx::rangePop();
638655
}
639656

@@ -647,9 +664,11 @@ static void cudecompSendRecvPair(const cudecompHandle_t& handle, const cudecompG
647664
cudecompHaloPerformanceSample* current_sample = nullptr) {
648665
nvtx::rangePush("cudecompSendRecvPair");
649666

667+
#if !CUDECOMP_BUILD_CPU_ONLY
650668
if (handle->performance_report_enable && current_sample) {
651669
CHECK_CUDA(cudaEventRecord(current_sample->sendrecv_start_event, stream));
652670
}
671+
#endif
653672

654673
#ifdef ENABLE_NVSHMEM
655674
if (haloBackendRequiresMpi(grid_desc->config.halo_comm_backend)) {
@@ -690,6 +709,7 @@ static void cudecompSendRecvPair(const cudecompHandle_t& handle, const cudecompG
690709
THROW_NOT_SUPPORTED("build does not support NVSHMEM communication backends.");
691710
#endif
692711
}
712+
#if !CUDECOMP_BUILD_CPU_ONLY
693713
case CUDECOMP_HALO_COMM_NCCL: {
694714
auto comm = *grid_desc->nccl_comm;
695715
CHECK_NCCL(ncclGroupStart());
@@ -712,15 +732,14 @@ static void cudecompSendRecvPair(const cudecompHandle_t& handle, const cudecompG
712732
CHECK_NCCL(ncclGroupEnd());
713733
break;
714734
}
735+
#endif
715736
case CUDECOMP_HALO_COMM_MPI: {
716737
auto comm = handle->mpi_comm;
717738
std::vector<MPI_Request> reqs(2 * send_counts.size(), MPI_REQUEST_NULL);
718-
CHECK_CUDA(cudaStreamSynchronize(stream));
739+
streamSynchronize(stream);
719740
for (int i = 0; i < send_counts.size(); ++i) {
720741
if (peer_ranks[i] == handle->rank) {
721-
// Self-copy with cudaMemcpy
722-
CHECK_CUDA(cudaMemcpyAsync(recv_buff + recv_offsets[i], send_buff + send_offsets[i], send_counts[i] * sizeof(T),
723-
cudaMemcpyDeviceToDevice, stream));
742+
copyBuffer(recv_buff + recv_offsets[i], send_buff + send_offsets[i], send_counts[i] * sizeof(T), stream);
724743
} else {
725744
if (recv_counts[(i + 1) % 2] != 0 && peer_ranks[(i + 1) % 2] != -1) {
726745
checkMpiInt32Limit(recv_counts[(i + 1) % 2], grid_desc->config.halo_comm_backend);
@@ -742,12 +761,10 @@ static void cudecompSendRecvPair(const cudecompHandle_t& handle, const cudecompG
742761
}
743762
case CUDECOMP_HALO_COMM_MPI_BLOCKING: {
744763
auto comm = handle->mpi_comm;
745-
CHECK_CUDA(cudaStreamSynchronize(stream));
764+
streamSynchronize(stream);
746765
for (int i = 0; i < send_counts.size(); ++i) {
747766
if (peer_ranks[i] == handle->rank) {
748-
// Self-copy with cudaMemcpy
749-
CHECK_CUDA(cudaMemcpyAsync(recv_buff + recv_offsets[i], send_buff + send_offsets[i], send_counts[i] * sizeof(T),
750-
cudaMemcpyDeviceToDevice, stream));
767+
copyBuffer(recv_buff + recv_offsets[i], send_buff + send_offsets[i], send_counts[i] * sizeof(T), stream);
751768
} else {
752769
MPI_Request r = MPI_REQUEST_NULL;
753770
if (recv_counts[(i + 1) % 2] != 0 && peer_ranks[(i + 1) % 2] != -1) {
@@ -772,9 +789,11 @@ static void cudecompSendRecvPair(const cudecompHandle_t& handle, const cudecompG
772789
}
773790
}
774791

792+
#if !CUDECOMP_BUILD_CPU_ONLY
775793
if (handle->performance_report_enable && current_sample) {
776794
CHECK_CUDA(cudaEventRecord(current_sample->sendrecv_end_event, stream));
777795
}
796+
#endif
778797

779798
nvtx::rangePop();
780799
}

0 commit comments

Comments
 (0)