Loading...
Searching...
No Matches
main_mpi_gpu_cuda.cpp
Go to the documentation of this file.
1/**
2 * main_mpi_gpu_cuda.cpp — CUDA GPU entry point that also speaks MPI.
3 *
4 * q3_gpu_cuda is one process on one device: main_gpu_cuda.cpp delegates to
5 * run_gpu_entry_point(), which hard-codes PlanewaveSerialExecutionPolicy. So
6 * `srun -n 8 q3_gpu_cuda` runs eight independent duplicate calculations rather
7 * than one calculation across eight devices, and there is no way to spend more
8 * than one GPU on a single system.
9 *
10 * This binary is the other composition: the MPI setup from main_mpi.cpp --
11 * execution policy, topology, MPI iterator -- with the CUDA backends and the
12 * GPU Davidson injected the way the GPU entry point injects them. Each rank
13 * drives its own device and owns a k-point pool, which is the decomposition
14 * that already scales on CPU.
15 *
16 * Device selection. cuda.cpp calls cudaSetDevice(0), so ranks sharing a node
17 * must be given different devices from outside:
18 *
19 * srun -N 2 -n 8 --ntasks-per-node=4 --gpus-per-node=4 \
20 * bash -c 'export CUDA_VISIBLE_DEVICES=$SLURM_LOCALID; q3_mpi_gpu_cuda --pools 8 deck.yml'
21 *
22 * Without that every rank on a node opens GPU 0 and the run measures four
23 * processes contending for one card.
24 *
25 * Scope. Like q3_mpi it runs every engine flavor -- the SCF and the
26 * non-self-consistent pass -- on the same ranks, devices and precision: the
27 * flavor is handed an iterator factory carrying all three.
28 */
29
31#include <memory>
32#include <stdexcept>
33#include <string>
34
36#include "io/cli.hpp"
38#include "io/logger_mpi.hpp"
48#include "utils/time.hpp"
49
50using namespace q3;
51
52// CUDA backend factories, defined in the cuda.cpp translation units.
54std::unique_ptr<GPUBLAS>
55create_cuda_gpu_blas(std::shared_ptr<device::GPUDeviceBackend> device_backend);
56} // namespace q3::execution::gpu::blas
57
59std::unique_ptr<GPUEigensolver>
60create_cuda_gpu_eigensolver(std::shared_ptr<device::GPUDeviceBackend> device_backend);
61} // namespace q3::execution::gpu::lapack
62
63namespace q3::gpu {
64std::unique_ptr<GPUFFTGrid>
65create_cuda_gpu_fft_grid(std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend);
66} // namespace q3::gpu
67
68int main(int argc, char* argv[]) {
70 auto options = cli::parse_options(argc, argv, "q3_mpi_gpu_cuda");
71
72 auto policy = std::make_unique<execution::PlanewaveMPIExecutionPolicy>();
73 log::init_mpi(options.log_file);
74
75 if (options.print_version) {
76 cli::print_version("q3_mpi_gpu_cuda");
77 return 0;
78 }
79 if (!options.log_level.empty()) {
80 log::set_level(options.log_level);
81 }
82
83 bool use_mixed_precision = false;
84 if (options.precision == "mixed") {
85 use_mixed_precision = true;
86 } else if (!options.precision.empty() && options.precision != "double") {
87 log::error("Unknown precision: {}. Use 'double' or 'mixed'.", options.precision);
88 return 1;
89 }
90
91 cli::print_timestamped_banner("BEGIN (CUDA GPU + MPI): ");
92
93 int exit_code = 0;
94 try {
95 auto input = cli::read_input(options, "q3_mpi_gpu_cuda");
96 if (!input.hubbard.empty()) {
97 log::error("DFT+U (hubbard) is not yet supported on GPU. Run on CPU instead.");
98 return 1;
99 }
100 // ── GPU backends, one device per rank ─────────────────────────────
102 auto gpu_blas = execution::gpu::blas::create_cuda_gpu_blas(device_backend);
103 auto gpu_eigensolver = execution::gpu::lapack::create_cuda_gpu_eigensolver(device_backend);
104 auto gpu_fft_grid = gpu::create_cuda_gpu_fft_grid(device_backend);
105
106 // ── MPI topology, as q3_mpi builds it ─────────────────────────────
107 int requested_number_of_pools = options.number_of_kpoint_pools_was_set
108 ? options.number_of_kpoint_pools
109 : input.number_of_kpoint_pools;
110 policy->configure_topology(requested_number_of_pools);
111 if (options.distribute_bands) {
112 policy->enable_band_distribution();
113 }
114 int ranks_per_pool = policy->world_size() / std::max(policy->number_of_pools(), 1);
115 log::info("MPI topology: {} rank(s), {} k-point pool(s), {} rank(s) per pool, "
116 "one CUDA device each",
117 policy->world_size(),
118 policy->number_of_pools(),
119 ranks_per_pool);
120
121 /**
122 * The Davidson takes the execution policy by base reference, so the MPI
123 * policy goes in where the serial one does for q3_gpu_cuda. The policy
124 * stays owned here, outliving every iterator built on it, and finalizes
125 * MPI from its destructor at the end of main.
126 */
127 bool is_root = policy->is_root();
128 IteratorFactory iterator_factory = [&](const InputData& run_input) {
129 auto iterator = create_kohn_sham_iterator(run_input, *policy);
130 const execution::ExecutionPolicy& policy_reference = *policy;
131 auto gpu_davidson = std::make_unique<PlanewaveDavidsonDiagonalizer>(
132 policy_reference, *gpu_blas, gpu_fft_grid.get(), *gpu_eigensolver, device_backend);
133 if (use_mixed_precision) {
134 gpu_davidson->set_use_mixed_precision(true);
135 }
136 iterator->set_diagonalizer(std::move(gpu_davidson));
137 return iterator;
138 };
139 auto flavor = create_flavor(input.calculation, std::move(iterator_factory));
141 log::info(" Calculation type: {}", input.calculation);
142
143 generated::Results results = flavor->run(input);
144 if (is_root) {
145 write_results_json(results,
146 input.output_directory + "/" + input.prefix + "_results.json");
147 write_companion_outputs(input, results);
150 }
151 } catch (const std::exception& error) {
152 log::error("Error: {}", error.what());
153 exit_code = 1;
154 }
155
156 cli::print_timestamped_banner("END (CUDA GPU + MPI): ");
157 return exit_code;
158}
int main(int argc, char *argv[])
void register_all()
No add-on is compiled in, so there is nothing to register.
Options parse_options(int argc, char *argv[], const std::string &binary_name)
Definition cli.hpp:175
void print_version(const std::string &binary_name)
Definition cli.hpp:45
InputData read_input(Options &options, const std::string &binary_name)
Definition cli.hpp:239
void print_footer()
Definition cli.hpp:278
void print_timestamped_banner(const std::string &label_prefix)
Definition cli.hpp:261
void print_header()
Definition cli.hpp:274
std::unique_ptr< GPUBLAS > create_cuda_gpu_blas(std::shared_ptr< device::GPUDeviceBackend > device_backend)
Definition cuda.cpp:297
std::shared_ptr< GPUDeviceBackend > create_gpu_device_backend()
Definition cuda.cpp:103
std::unique_ptr< GPUEigensolver > create_cuda_gpu_eigensolver(std::shared_ptr< device::GPUDeviceBackend > device_backend)
Definition cuda.cpp:203
std::unique_ptr< GPUFFTGrid > create_cuda_gpu_fft_grid(std::shared_ptr< execution::gpu::device::GPUDeviceBackend > device_backend)
Definition cuda.cpp:445
void init_mpi(const std::string &log_file="")
void error(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:192
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
void set_level(const std::string &level)
Set log level: "trace", "debug", "info", "warn", "error", "off".
Definition logger.hpp:94
void write_results_json(const generated::Results &results, const std::string &output_path)
Write the results to output_path as results.json.
Definition results.hpp:191
std::unique_ptr< Flavor > create_flavor(const std::string &calculation_type, IteratorFactory iterator_factory={})
Definition factory.hpp:52
std::function< std::unique_ptr< PlanewaveKohnShamSCFIterator >(const InputData &)> IteratorFactory
Definition factory.hpp:59
void write_companion_outputs(const InputData &input, const generated::Results &results)
Write the companion outputs of the format input was read from, if it has any.
std::unique_ptr< PlanewaveKohnShamSCFIterator > create_kohn_sham_iterator(const InputData &input, const execution::PlanewaveExecutionPolicy &execution_policy)
Definition factory.hpp:64
Parsed input data — holds raw values read from input files.
Definition data.hpp:27
static ProfilingCounters & instance()
Definition counters.hpp:220
DavidsonTimingAccumulator davidson_timing
Definition counters.hpp:124