Loading...
Searching...
No Matches
cuda.cpp
Go to the documentation of this file.
1/**
2 * CudaGPUFFTGrid — GPUFFTGrid implementation using NVIDIA cuFFT.
3 *
4 * Provides both FP64 (cufftExecZ2Z) and FP32 (cufftExecC2C) complex-to-complex
5 * FFT transforms. cuFFT plans are created during init() and destroyed on
6 * destruction.
7 *
8 * For the base FFTGrid interface (grid dimensions, g-vector mapping, buffers),
9 * this class composes a SerialFFTGrid internally and delegates all base-class
10 * pure virtual calls to it. This avoids diamond inheritance while reusing
11 * the complete FFTW-based implementation from SerialFFTGrid.
12 *
13 * cuFFT operates on device pointers. With unified memory (cudaMallocManaged),
14 * the same pointers are accessible from both host and device.
15 */
16
19
20#include <cuda_runtime.h>
21#include <cufft.h>
22#include <stdexcept>
23#include <string>
24
25namespace q3 {
26namespace gpu {
27
28namespace {
29
30/**
31 * Check a cuFFT return code and throw on error.
32 */
33void check_cufft_error(cufftResult result, const char* operation) {
34 if (result != CUFFT_SUCCESS) {
35 throw std::runtime_error(std::string("CudaGPUFFTGrid: ") + operation +
36 " failed with result " + std::to_string(static_cast<int>(result)));
37 }
38}
39
40} // anonymous namespace
41
42class CudaGPUFFTGrid : public GPUFFTGrid {
43 public:
44 /**
45 * Construct a CUDA FFT grid backed by the given device.
46 *
47 * cuFFT plans are not created here — they are created in init() after
48 * the grid dimensions are known.
49 */
51 std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend)
52 : GPUFFTGrid(std::move(device_backend)) {}
53
54 ~CudaGPUFFTGrid() override {
55 if (plan_fp64_ != 0) {
56 cufftDestroy(plan_fp64_);
57 }
58 if (plan_fp32_ != 0) {
59 cufftDestroy(plan_fp32_);
60 }
61 if (device_buffer_fp32_ != nullptr) {
62 cudaFree(device_buffer_fp32_);
63 }
64 if (device_buffer_fp64_ != nullptr) {
65 cudaFree(device_buffer_fp64_);
66 }
67 if (plan_fp32_batch_ != 0) {
68 cufftDestroy(plan_fp32_batch_);
69 }
70 if (device_buffer_fp32_batch_ != nullptr) {
71 cudaFree(device_buffer_fp32_batch_);
72 }
73 if (plan_fp64_batch_ != 0) {
74 cufftDestroy(plan_fp64_batch_);
75 }
76 if (device_buffer_fp64_batch_ != nullptr) {
77 cudaFree(device_buffer_fp64_batch_);
78 }
79 }
80
81 // ── FFTGrid base interface (delegated to serial_fft_grid_) ───────────
82
83 void init(int dimension_1,
84 int dimension_2,
85 int dimension_3,
86 bool use_measured_planning = false) override {
87 // Initialize the serial FFT grid (sets up dimensions and FFTW plans).
88 serial_fft_grid_.init(dimension_1, dimension_2, dimension_3, use_measured_planning);
89
90 // Create cuFFT FP64 plan (Z2Z = complex double-to-complex double).
91 check_cufft_error(
92 cufftPlan3d(&plan_fp64_, dimension_1, dimension_2, dimension_3, CUFFT_Z2Z),
93 "cufftPlan3d FP64");
94
95 // Create cuFFT FP32 plan (C2C = complex float-to-complex float).
96 check_cufft_error(
97 cufftPlan3d(&plan_fp32_, dimension_1, dimension_2, dimension_3, CUFFT_C2C),
98 "cufftPlan3d FP32");
99
100 // Allocate persistent device buffers for cuFFT.
101 total_grid_points_cached_ = serial_fft_grid_.total_grid_points();
102
103 const size_t buffer_size_fp32 =
104 static_cast<size_t>(total_grid_points_cached_) * sizeof(cufftComplex);
105 cudaMalloc(&device_buffer_fp32_, buffer_size_fp32);
106
107 const size_t buffer_size_fp64 =
108 static_cast<size_t>(total_grid_points_cached_) * sizeof(cufftDoubleComplex);
109 cudaMalloc(&device_buffer_fp64_, buffer_size_fp64);
110 }
111
112 [[nodiscard]] int grid_dimension_1() const override {
113 return serial_fft_grid_.grid_dimension_1();
114 }
115 [[nodiscard]] int grid_dimension_2() const override {
116 return serial_fft_grid_.grid_dimension_2();
117 }
118 [[nodiscard]] int grid_dimension_3() const override {
119 return serial_fft_grid_.grid_dimension_3();
120 }
121 [[nodiscard]] int total_grid_points() const override {
122 return serial_fft_grid_.total_grid_points();
123 }
124 [[nodiscard]] int local_grid_points() const override {
125 return serial_fft_grid_.local_grid_points();
126 }
127 [[nodiscard]] int local_offset() const override {
128 return serial_fft_grid_.local_offset();
129 }
130
131 void forward(Complex* data) const override {
132 serial_fft_grid_.forward(data);
133 }
134 void inverse(Complex* data) const override {
135 serial_fft_grid_.inverse(data);
136 }
137 void inverse_raw(Complex* data) const override {
138 serial_fft_grid_.inverse_raw(data);
139 }
140
141 [[nodiscard]] int
142 g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override {
143 return serial_fft_grid_.g_vector_to_fft_grid_index(miller_1, miller_2, miller_3);
144 }
145
146 void reduce_g_space(Complex* data, int number_of_elements) const override {
147 serial_fft_grid_.reduce_g_space(data, number_of_elements);
148 }
149
150 Complex* input_buffer() const override {
151 return serial_fft_grid_.input_buffer();
152 }
153 Complex* output_buffer() const override {
154 return serial_fft_grid_.output_buffer();
155 }
156
157 void execute_inverse_raw_on_buffers() const override {
158 serial_fft_grid_.execute_inverse_raw_on_buffers();
159 }
160 void execute_forward_on_buffers() const override {
161 serial_fft_grid_.execute_forward_on_buffers();
162 }
163 void prepare_forward_from_output() const override {
164 serial_fft_grid_.prepare_forward_from_output();
165 }
166
167 // ── FP32 FFT (cuFFT via persistent device buffer) ──────────────────────
168
169 /**
170 * Forward FP32 FFT via cufftExecC2C with CUFFT_FORWARD.
171 *
172 * Uses a persistent device buffer allocated in init() to avoid
173 * per-call cudaMalloc/cudaFree overhead.
174 */
175 void forward_single(ComplexSingle* data) const override {
176 const size_t buffer_size =
177 static_cast<size_t>(total_grid_points_cached_) * sizeof(cufftComplex);
178 cudaMemcpy(device_buffer_fp32_, data, buffer_size, cudaMemcpyDefault);
179
180 check_cufft_error(
181 cufftExecC2C(plan_fp32_, device_buffer_fp32_, device_buffer_fp32_, CUFFT_FORWARD),
182 "cufftExecC2C forward");
183
184 cudaMemcpy(data, device_buffer_fp32_, buffer_size, cudaMemcpyDefault);
185 }
186
187 /**
188 * Inverse FP32 FFT (raw, unnormalized) via cufftExecC2C with CUFFT_INVERSE.
189 *
190 * Uses a persistent device buffer allocated in init() to avoid
191 * per-call cudaMalloc/cudaFree overhead.
192 */
193 void inverse_raw_single(ComplexSingle* data) const override {
194 const size_t buffer_size =
195 static_cast<size_t>(total_grid_points_cached_) * sizeof(cufftComplex);
196 cudaMemcpy(device_buffer_fp32_, data, buffer_size, cudaMemcpyDefault);
197
198 check_cufft_error(
199 cufftExecC2C(plan_fp32_, device_buffer_fp32_, device_buffer_fp32_, CUFFT_INVERSE),
200 "cufftExecC2C inverse");
201
202 cudaMemcpy(data, device_buffer_fp32_, buffer_size, cudaMemcpyDefault);
203 }
204
205 // ── FP64 FFT (cuFFT via persistent device buffer) ──────────────────────
206
207 /**
208 * Forward FP64 FFT via cufftExecZ2Z with CUFFT_FORWARD.
209 *
210 * Uses a persistent device buffer allocated in init() to avoid
211 * per-call cudaMalloc/cudaFree overhead.
212 */
213 void forward_transform(Complex* data) const override {
214 const size_t buffer_size =
215 static_cast<size_t>(total_grid_points_cached_) * sizeof(cufftDoubleComplex);
216 cudaMemcpy(device_buffer_fp64_, data, buffer_size, cudaMemcpyDefault);
217
218 check_cufft_error(
219 cufftExecZ2Z(plan_fp64_, device_buffer_fp64_, device_buffer_fp64_, CUFFT_FORWARD),
220 "cufftExecZ2Z forward");
221
222 cudaMemcpy(data, device_buffer_fp64_, buffer_size, cudaMemcpyDefault);
223 }
224
225 /**
226 * Inverse FP64 FFT (raw, unnormalized) via cufftExecZ2Z with CUFFT_INVERSE.
227 *
228 * Uses a persistent device buffer allocated in init() to avoid
229 * per-call cudaMalloc/cudaFree overhead.
230 */
231 void inverse_transform(Complex* data) const override {
232 const size_t buffer_size =
233 static_cast<size_t>(total_grid_points_cached_) * sizeof(cufftDoubleComplex);
234 cudaMemcpy(device_buffer_fp64_, data, buffer_size, cudaMemcpyDefault);
235
236 check_cufft_error(
237 cufftExecZ2Z(plan_fp64_, device_buffer_fp64_, device_buffer_fp64_, CUFFT_INVERSE),
238 "cufftExecZ2Z inverse");
239
240 cudaMemcpy(data, device_buffer_fp64_, buffer_size, cudaMemcpyDefault);
241 }
242
243 /**
244 * ── Batched FP32 FFT (cufftPlanMany) ─────────────────────────────────
245 *
246 * Transforms multiple contiguous FFT grids in a single kernel launch.
247 * The batch plan and device buffer are created lazily on first use and
248 * resized if the batch size grows.
249 */
250
251 /**
252 * Batched forward FP32 FFT via cufftPlanMany + cufftExecC2C.
253 *
254 * All batch_size grids are transformed in a single cuFFT kernel launch,
255 * eliminating per-band dispatch overhead.
256 */
257 void forward_single_batch(ComplexSingle* data, int batch_size) const override {
258 ensure_batch_resources(batch_size);
259
260 const size_t buffer_size =
261 static_cast<size_t>(total_grid_points_cached_) * batch_size * sizeof(cufftComplex);
262 cudaMemcpy(device_buffer_fp32_batch_, data, buffer_size, cudaMemcpyDefault);
263
264 check_cufft_error(cufftExecC2C(plan_fp32_batch_,
265 device_buffer_fp32_batch_,
266 device_buffer_fp32_batch_,
267 CUFFT_FORWARD),
268 "cufftExecC2C batched forward");
269
270 cudaMemcpy(data, device_buffer_fp32_batch_, buffer_size, cudaMemcpyDefault);
271 }
272
273 /**
274 * Batched inverse FP32 FFT (raw, unnormalized) via cufftPlanMany + cufftExecC2C.
275 *
276 * All batch_size grids are transformed in a single cuFFT kernel launch,
277 * eliminating per-band dispatch overhead.
278 */
279 void inverse_raw_single_batch(ComplexSingle* data, int batch_size) const override {
280 ensure_batch_resources(batch_size);
281
282 const size_t buffer_size =
283 static_cast<size_t>(total_grid_points_cached_) * batch_size * sizeof(cufftComplex);
284 cudaMemcpy(device_buffer_fp32_batch_, data, buffer_size, cudaMemcpyDefault);
285
286 check_cufft_error(cufftExecC2C(plan_fp32_batch_,
287 device_buffer_fp32_batch_,
288 device_buffer_fp32_batch_,
289 CUFFT_INVERSE),
290 "cufftExecC2C batched inverse");
291
292 cudaMemcpy(data, device_buffer_fp32_batch_, buffer_size, cudaMemcpyDefault);
293 }
294
295 // ── Batched FP64 FFT (cufftPlanMany Z2Z) ─────────────────────────────
296
297 /**
298 * Batched forward FP64 FFT via cufftPlanMany + cufftExecZ2Z.
299 *
300 * All batch_size grids are transformed in a single cuFFT kernel launch.
301 */
302 void forward_transform_batch(Complex* data, int batch_size) const override {
303 ensure_batch_resources_fp64(batch_size);
304
305 const size_t buffer_size = static_cast<size_t>(total_grid_points_cached_) * batch_size *
306 sizeof(cufftDoubleComplex);
307 cudaMemcpy(device_buffer_fp64_batch_, data, buffer_size, cudaMemcpyDefault);
308
309 check_cufft_error(cufftExecZ2Z(plan_fp64_batch_,
310 device_buffer_fp64_batch_,
311 device_buffer_fp64_batch_,
312 CUFFT_FORWARD),
313 "cufftExecZ2Z batched forward");
314
315 cudaMemcpy(data, device_buffer_fp64_batch_, buffer_size, cudaMemcpyDefault);
316 }
317
318 /**
319 * Batched inverse FP64 FFT (raw, unnormalized) via cufftPlanMany + cufftExecZ2Z.
320 *
321 * All batch_size grids are transformed in a single cuFFT kernel launch.
322 */
323 void inverse_transform_batch(Complex* data, int batch_size) const override {
324 ensure_batch_resources_fp64(batch_size);
325
326 const size_t buffer_size = static_cast<size_t>(total_grid_points_cached_) * batch_size *
327 sizeof(cufftDoubleComplex);
328 cudaMemcpy(device_buffer_fp64_batch_, data, buffer_size, cudaMemcpyDefault);
329
330 check_cufft_error(cufftExecZ2Z(plan_fp64_batch_,
331 device_buffer_fp64_batch_,
332 device_buffer_fp64_batch_,
333 CUFFT_INVERSE),
334 "cufftExecZ2Z batched inverse");
335
336 cudaMemcpy(data, device_buffer_fp64_batch_, buffer_size, cudaMemcpyDefault);
337 }
338
339 private:
340 SerialFFTGrid serial_fft_grid_;
341 cufftHandle plan_fp64_ = 0;
342 cufftHandle plan_fp32_ = 0;
343
344 // Persistent device buffers for single-grid cuFFT — allocated once in init().
345 cufftComplex* device_buffer_fp32_ = nullptr;
346 cufftDoubleComplex* device_buffer_fp64_ = nullptr;
347 int total_grid_points_cached_ = 0;
348
349 // Batched FP32 cuFFT resources — allocated lazily on first batch call.
350 mutable cufftHandle plan_fp32_batch_ = 0;
351 mutable cufftComplex* device_buffer_fp32_batch_ = nullptr;
352 mutable int current_fp32_batch_size_ = 0;
353
354 // Batched FP64 cuFFT resources — allocated lazily on first batch call.
355 mutable cufftHandle plan_fp64_batch_ = 0;
356 mutable cufftDoubleComplex* device_buffer_fp64_batch_ = nullptr;
357 mutable int current_fp64_batch_size_ = 0;
358
359 /**
360 * Ensure the batched FP32 cuFFT plan and device buffer are large enough.
361 */
362 void ensure_batch_resources(int batch_size) const {
363 if (batch_size == current_fp32_batch_size_) {
364 return;
365 }
366
367 if (plan_fp32_batch_ != 0) {
368 cufftDestroy(plan_fp32_batch_);
369 plan_fp32_batch_ = 0;
370 }
371 if (device_buffer_fp32_batch_ != nullptr) {
372 cudaFree(device_buffer_fp32_batch_);
373 device_buffer_fp32_batch_ = nullptr;
374 }
375
376 int dimensions[3] = {serial_fft_grid_.grid_dimension_1(),
377 serial_fft_grid_.grid_dimension_2(),
378 serial_fft_grid_.grid_dimension_3()};
379 int distance = total_grid_points_cached_;
380
381 check_cufft_error(cufftPlanMany(&plan_fp32_batch_,
382 3, // rank (3D)
383 dimensions, // [d1, d2, d3]
384 nullptr, // inembed (null = contiguous)
385 1, // istride
386 distance, // idist
387 nullptr, // onembed (null = contiguous)
388 1, // ostride
389 distance, // odist
390 CUFFT_C2C, // type
391 batch_size), // batch
392 "cufftPlanMany FP32 batch");
393
394 const size_t buffer_size =
395 static_cast<size_t>(total_grid_points_cached_) * batch_size * sizeof(cufftComplex);
396 cudaMalloc(&device_buffer_fp32_batch_, buffer_size);
397
398 current_fp32_batch_size_ = batch_size;
399 }
400
401 /**
402 * Ensure the batched FP64 cuFFT plan and device buffer are large enough.
403 */
404 void ensure_batch_resources_fp64(int batch_size) const {
405 if (batch_size == current_fp64_batch_size_) {
406 return;
407 }
408
409 if (plan_fp64_batch_ != 0) {
410 cufftDestroy(plan_fp64_batch_);
411 plan_fp64_batch_ = 0;
412 }
413 if (device_buffer_fp64_batch_ != nullptr) {
414 cudaFree(device_buffer_fp64_batch_);
415 device_buffer_fp64_batch_ = nullptr;
416 }
417
418 int dimensions[3] = {serial_fft_grid_.grid_dimension_1(),
419 serial_fft_grid_.grid_dimension_2(),
420 serial_fft_grid_.grid_dimension_3()};
421 int distance = total_grid_points_cached_;
422
423 check_cufft_error(cufftPlanMany(&plan_fp64_batch_,
424 3, // rank (3D)
425 dimensions, // [d1, d2, d3]
426 nullptr, // inembed (null = contiguous)
427 1, // istride
428 distance, // idist
429 nullptr, // onembed (null = contiguous)
430 1, // ostride
431 distance, // odist
432 CUFFT_Z2Z, // type
433 batch_size), // batch
434 "cufftPlanMany FP64 batch");
435
436 const size_t buffer_size = static_cast<size_t>(total_grid_points_cached_) * batch_size *
437 sizeof(cufftDoubleComplex);
438 cudaMalloc(&device_buffer_fp64_batch_, buffer_size);
439
440 current_fp64_batch_size_ = batch_size;
441 }
442};
443
444std::unique_ptr<GPUFFTGrid>
445create_cuda_gpu_fft_grid(std::shared_ptr<execution::gpu::device::GPUDeviceBackend> device_backend) {
446 return std::make_unique<CudaGPUFFTGrid>(std::move(device_backend));
447}
448
449} // namespace gpu
450} // namespace q3
void inverse(Complex *data) const override
int local_grid_points() const override
int local_offset() const override
void forward(Complex *data) const override
void execute_forward_on_buffers() const override
void reduce_g_space(Complex *, int) const override
int grid_dimension_3() const override
int grid_dimension_1() const override
void init(int dimension_1, int dimension_2, int dimension_3, bool use_measured_planning=false) override
void inverse_raw(Complex *data) const override
Complex * output_buffer() const override
Complex * input_buffer() const override
void execute_inverse_raw_on_buffers() const override
int total_grid_points() const override
int grid_dimension_2() const override
int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override
void prepare_forward_from_output() const override
void reduce_g_space(Complex *data, int number_of_elements) const override
Definition cuda.cpp:146
void inverse_transform(Complex *data) const override
Definition cuda.cpp:231
int grid_dimension_2() const override
Definition cuda.cpp:115
void inverse_raw_single(ComplexSingle *data) const override
Definition cuda.cpp:193
int grid_dimension_3() const override
Definition cuda.cpp:118
void forward_single_batch(ComplexSingle *data, int batch_size) const override
Definition cuda.cpp:257
void forward(Complex *data) const override
Definition cuda.cpp:131
void execute_inverse_raw_on_buffers() const override
Definition cuda.cpp:157
~CudaGPUFFTGrid() override
Definition cuda.cpp:54
void forward_transform(Complex *data) const override
Definition cuda.cpp:213
void inverse(Complex *data) const override
Definition cuda.cpp:134
void forward_transform_batch(Complex *data, int batch_size) const override
Definition cuda.cpp:302
void init(int dimension_1, int dimension_2, int dimension_3, bool use_measured_planning=false) override
Definition cuda.cpp:83
void prepare_forward_from_output() const override
Definition cuda.cpp:163
void execute_forward_on_buffers() const override
Definition cuda.cpp:160
void forward_single(ComplexSingle *data) const override
Definition cuda.cpp:175
Complex * input_buffer() const override
Definition cuda.cpp:150
CudaGPUFFTGrid(std::shared_ptr< execution::gpu::device::GPUDeviceBackend > device_backend)
Definition cuda.cpp:50
int g_vector_to_fft_grid_index(int miller_1, int miller_2, int miller_3) const override
Definition cuda.cpp:142
int local_grid_points() const override
Definition cuda.cpp:124
void inverse_transform_batch(Complex *data, int batch_size) const override
Definition cuda.cpp:323
Complex * output_buffer() const override
Definition cuda.cpp:153
void inverse_raw(Complex *data) const override
Definition cuda.cpp:137
int local_offset() const override
Definition cuda.cpp:127
int total_grid_points() const override
Definition cuda.cpp:121
int grid_dimension_1() const override
Definition cuda.cpp:112
void inverse_raw_single_batch(ComplexSingle *data, int batch_size) const override
Definition cuda.cpp:279
execution::gpu::device::GPUDeviceBackend & device_backend()
Definition init.hpp:162
std::unique_ptr< GPUFFTGrid > create_cuda_gpu_fft_grid(std::shared_ptr< execution::gpu::device::GPUDeviceBackend > device_backend)
Definition cuda.cpp:445
std::complex< float > ComplexSingle
std::complex< double > Complex