Skip to content

Commit ed09fe7

Browse files
SunsetStandsunliang98mohanchen
authored
feat: GPU-accelerated WT KEDF multi_kernel convolution (#7448)
* checkpoint: skeleton gpu file before full implementation * feat: GPU-accelerated WT KEDF multi_kernel convolution Add GPU backend for KEDF_WT::multi_kernel() using cuFFT via PW_Basis _gpu interface. Key changes: - kedf_wt_gpu.cu: single CUDA kernel (kedf_wt_recip_multiply) for G-space element-wise kernel multiplication, plus multi_kernel_gpu() method that pipelines real2recip → kernel multiply → recip2real entirely on GPU. Persistent buffers allocated via memory_op. - kedf_wt.h: GPU method declarations and buffer members under #ifdef __CUDA guard (zero overhead when CUDA disabled). - kedf_wt.cpp: GPU dispatch at top of multi_kernel() — when pw_rho->device == "gpu", delegates to multi_kernel_gpu(). - source/CMakeLists.txt: add kedf_wt_gpu.cu to USE_CUDA block. Design follows existing ABACUS GPU patterns (memory_op for device memory, thrust::complex in kernels, CHECK_CUDA_SYNC for safety). * fix: move cufft.h include to file scope, fix memory_op type mismatch - kedf_wt.h: #include <cufft.h> was erroneously inside the class body (both in destructor and private section). This caused the cuFFT header extern "C" block to appear inside a C++ class definition, triggering "linkage specification is not allowed" and all cuFFT types undeclared. Moved the include to file scope, guarded by #ifdef __CUDA. - kedf_wt_gpu.cu: d_result_ is double* but resmem_zd_op/delmem_zd_op are typed std::complex<double>*. Changed to resmem_dd_op/delmem_dd_op (nrxx*2 doubles = nrxx complex doubles). * test: add GPU WT KEDF test case (31_OF_KE_WT_GPU) - Add test directory with INPUT (device=gpu), STRU, KPT, result.ref - Test identical to 09_OF_KE_WT but exercises GPU code path - Add CASES_GPU.txt for GPU test discovery - GPU results should match CPU reference within tolerance * refactor: move kedf_wt_gpu.cu to kernels/cuda/ for module consistency Per reviewer request (sunliang98): keep GPU kernel files organized under kernels/cuda/ subdirectory, consistent with other ABACUS modules. * fix: use full include path for kedf_wt.h in moved GPU kernel file After moving kedf_wt_gpu.cu to kernels/cuda/, the bare include #include "kedf_wt.h" no longer resolves since the header is now in the parent directory. Use full module path consistent with other CUDA kernel files (e.g., module_pwdft/kernels/cuda/*.cu). * perf: optimize WT KEDF GPU kernels — double2 + grid-stride + GPU rho^exponent Replace thrust::complex<double> with native double2 (cufftDoubleComplex) to eliminate AoS memory layout overhead (50% bandwidth waste from unused imag component). Add grid-stride loops for flexible occupancy. Move rho^exponent (std::pow) from CPU to GPU, eliminating one H→D transfer per SCF iteration. Kernel changes: - kedf_wt_rho_power (new): GPU-side pow() replaces CPU loop - kedf_wt_recip_multiply: double2 replaces thrust::complex, grid-stride - kedf_wt_real_to_complex: double2 + grid-stride - kedf_wt_complex_to_real_norm: double2 + grid-stride Benchmark (RTX 4060 Laptop, 96^3 grid): ~3.3x end-to-end speedup vs thrust::complex baseline. Kernel-only section: ~76% faster. See wt_kernel_opt/ standalone benchmark for full comparison. Thread coarsening (4x) was tested but showed regression on Ada Lovelace (SM 8.9) — fewer active warps reduced latency hiding for memory-bound kernels. Left for future architecture-specific tuning. * docs: update of_kinetic parameter to note WT GPU acceleration support * fix: pass nspin as parameter to avoid PARAM link error in CUDA unit multi_kernel_gpu in kedf_wt_gpu.cu referenced PARAM.inp.nspin, but the global PARAM symbol is not available during CUDA link in non-OFDFT test targets (dftu_core_test, dftu_operator_test). Pass nspin as a function parameter from the caller in kedf_wt.cpp. * docs: clarify GPU acceleration is enabled via device=gpu for WT KEDF * docs: remove incorrect GPU note from ext-wt KEDF ext-WT KEDF is a separate CPU-only implementation in kedf_extwt.cpp and is not modified by this PR; only WT KEDF has a GPU kernel. Drop the GPU acceleration note from the ext-wt line. --------- Co-authored-by: Liang Sun <50293369+sunliang98@users.noreply.github.com> Co-authored-by: Mohan Chen <mohanchen@pku.edu.cn>
1 parent 59dc884 commit ed09fe7

11 files changed

Lines changed: 287 additions & 2 deletions

File tree

docs/advanced/input_files/input-main.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2428,7 +2428,7 @@
24282428
- tf: Thomas-Fermi (TF) functional
24292429
- vw: von Weizsacker (vW) functional
24302430
- tf+: TF + vW functional
2431-
- wt: Wang-Teter (WT) functional
2431+
- wt: Wang-Teter (WT) functional (supports GPU acceleration when device=gpu)
24322432
- ext-wt: Extended Wang-Teter (ext-WT) functional
24332433
- xwm: Xu-Wang-Ma (XWM) functional
24342434
- lkt: Luo-Karasiev-Trickey (LKT) functional

source/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ if(USE_CUDA)
8787
source_base/kernels/cuda/math_kernel_op.cu
8888
source_base/kernels/cuda/math_kernel_op_vec.cu
8989
source_hamilt/module_xc/kernels/cuda/xc_functional_op.cu
90+
source_pw/module_ofdft/kernels/cuda/kedf_wt_gpu.cu
9091
source_pw/module_pwdft/kernels/cuda/cal_density_real_op.cu
9192
source_pw/module_pwdft/kernels/cuda/mul_potential_op.cu
9293
source_pw/module_pwdft/kernels/cuda/vec_mul_vec_complex.cu

source/source_pw/module_ofdft/kedf_wt.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,13 @@ double KEDF_WT::diff_linhard(double eta, double vw_weight)
457457
*/
458458
void KEDF_WT::multi_kernel(const double* const* prho, double** rkernel_rho, double exponent, ModulePW::PW_Basis* pw_rho)
459459
{
460+
#ifdef __CUDA
461+
if (pw_rho->get_device() == "gpu") {
462+
this->multi_kernel_gpu(prho, rkernel_rho, PARAM.inp.nspin, exponent, pw_rho);
463+
return;
464+
}
465+
#endif
466+
460467
std::complex<double>** recipkernelRho = new std::complex<double>*[PARAM.inp.nspin];
461468
for (int is = 0; is < PARAM.inp.nspin; ++is)
462469
{

source/source_pw/module_ofdft/kedf_wt.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22
#define KEDF_WT_H
33
#include <cmath>
44
#include <cstdio>
5+
#include <complex>
56

67
#include "source_base/global_function.h"
78
#include "source_base/matrix.h"
89
#include "source_base/timer.h"
910
#include "source_basis/module_pw/pw_basis.h"
1011

12+
#ifdef __CUDA
13+
#include <cufft.h>
14+
#endif
15+
1116
/**
1217
* @brief A class which calculates the kinetic energy, potential, and stress with Wang-Teter (WT) KEDF.
1318
* See Wang L W, Teter M P. Physical Review B, 1992, 45(23): 13196.
@@ -22,6 +27,9 @@ class KEDF_WT
2227
}
2328
~KEDF_WT()
2429
{
30+
#ifdef __CUDA
31+
this->free_gpu_buffers();
32+
#endif
2533
delete[] this->kernel_;
2634
}
2735

@@ -65,5 +73,20 @@ class KEDF_WT
6573
* 2; // 10/3*(3*pi^2)^{2/3}, multiply by 2 to convert unit from Hartree to Ry, finally in Ry*Bohr^(-2)
6674
double wt_coef_ = 0.; // coefficient of WT kernel
6775
double* kernel_ = nullptr;
76+
77+
#ifdef __CUDA
78+
void multi_kernel_gpu(const double* const* prho, double** rkernel_rho, int nspin,
79+
double exponent, ModulePW::PW_Basis* pw_rho);
80+
void free_gpu_buffers();
81+
82+
// Persistent GPU buffers (lazily allocated once, reused across SCF iterations)
83+
double* d_rho_ = nullptr; // real-space input (nrxx doubles)
84+
cufftHandle cufft_plan_fwd_ = 0; // cuFFT forward plan
85+
cufftHandle cufft_plan_bwd_ = 0; // cuFFT backward plan
86+
double* d_result_ = nullptr; // real-space output (nrxx doubles)
87+
double* d_kernel_ = nullptr; // WT kernel on device (npw doubles)
88+
89+
bool gpu_allocated_ = false;
90+
#endif
6891
};
69-
#endif
92+
#endif
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/**
2+
* @file kedf_wt_gpu.cu
3+
* @brief GPU-accelerated WT KEDF multi_kernel convolution (optimized).
4+
*
5+
* Offloads the rho^exponent → FFT → kernel multiply → IFFT pipeline
6+
* to GPU using cuFFT directly.
7+
*
8+
* Optimizations over v1 (thrust::complex):
9+
* - double2 (native CUDA) replaces thrust::complex, eliminating AoS overhead
10+
* - Grid-stride loops for flexible occupancy across grid sizes
11+
* - GPU rho^exponent kernel eliminates CPU work + H→D transfer
12+
*
13+
* Benchmark (RTX 4060 Laptop, 96³ grid): ~3.3× end-to-end vs original.
14+
*
15+
* Persistent GPU buffers are lazily allocated and reused across SCF.
16+
*
17+
* @author Wang Chenxi, Reze
18+
* @date 2026-06
19+
*/
20+
#include "source_pw/module_ofdft/kedf_wt.h"
21+
#include "source_base/module_device/device_check.h"
22+
#include "source_base/module_device/memory_op.h"
23+
#include "source_io/module_parameter/parameter.h"
24+
25+
#include <cuda_runtime.h>
26+
#include <cufft.h>
27+
28+
namespace {
29+
30+
constexpr int THREADS_PER_BLOCK = 256;
31+
32+
/// GPU rho^exponent: out[i] = pow(in[i], exponent)
33+
/// Eliminates the CPU-side std::pow loop + H→D transfer.
34+
__global__ void kedf_wt_rho_power(
35+
const double* __restrict__ rho,
36+
double* __restrict__ out,
37+
double exponent,
38+
int n)
39+
{
40+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
41+
int stride = blockDim.x * gridDim.x;
42+
for (int i = idx; i < n; i += stride) {
43+
out[i] = pow(rho[i], exponent);
44+
}
45+
}
46+
47+
/// Element-wise multiply: complex array *= real kernel.
48+
/// Uses double2 (native cuFFT type) instead of thrust::complex.
49+
__global__ void kedf_wt_recip_multiply(
50+
double2* __restrict__ data,
51+
const double* __restrict__ kernel,
52+
int npw)
53+
{
54+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
55+
int stride = blockDim.x * gridDim.x;
56+
for (int i = idx; i < npw; i += stride) {
57+
double2 v = data[i];
58+
double k = kernel[i];
59+
data[i] = make_double2(v.x * k, v.y * k);
60+
}
61+
}
62+
63+
/// Real → complex conversion (imag = 0).
64+
/// Uses double2 instead of thrust::complex for zero-abstraction memory access.
65+
__global__ void kedf_wt_real_to_complex(
66+
const double* __restrict__ src,
67+
double2* __restrict__ dst,
68+
int n)
69+
{
70+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
71+
int stride = blockDim.x * gridDim.x;
72+
for (int i = idx; i < n; i += stride) {
73+
dst[i] = make_double2(src[i], 0.0);
74+
}
75+
}
76+
77+
/// Complex → real with 1/N normalization.
78+
/// double2::x is the real component; y (imag) is discarded.
79+
__global__ void kedf_wt_complex_to_real_norm(
80+
const double2* __restrict__ src,
81+
double* __restrict__ dst,
82+
double inv_n,
83+
int n)
84+
{
85+
int idx = blockIdx.x * blockDim.x + threadIdx.x;
86+
int stride = blockDim.x * gridDim.x;
87+
for (int i = idx; i < n; i += stride) {
88+
dst[i] = src[i].x * inv_n;
89+
}
90+
}
91+
92+
/// cuFFT error check wrapper.
93+
inline void cufft_check(cufftResult err, const char* file, int line)
94+
{
95+
if (err != CUFFT_SUCCESS) {
96+
std::cerr << "cuFFT error " << (int)err
97+
<< " at " << file << ":" << line << std::endl;
98+
exit(1);
99+
}
100+
}
101+
#define CUFFT_CHECK(call) cufft_check(call, __FILE__, __LINE__)
102+
103+
} // anonymous namespace
104+
105+
void KEDF_WT::multi_kernel_gpu(
106+
const double* const* prho,
107+
double** rkernel_rho,
108+
int nspin,
109+
double exponent,
110+
ModulePW::PW_Basis* pw_rho)
111+
{
112+
const int nrxx = pw_rho->nrxx;
113+
const int npw = pw_rho->npw;
114+
const int nx = pw_rho->nx;
115+
const int ny = pw_rho->ny;
116+
const int nz = pw_rho->nz;
117+
const double inv_nrxx = 1.0 / nrxx;
118+
119+
// ── Lazy allocation of persistent GPU buffers ──
120+
if (!gpu_allocated_) {
121+
resmem_dd_op()(d_rho_, nrxx);
122+
resmem_dd_op()(d_result_, nrxx * 2); // complex work buffer
123+
resmem_dd_op()(d_kernel_, npw);
124+
125+
syncmem_d2d_h2d_op()(d_kernel_, this->kernel_, npw);
126+
127+
// Create cuFFT plans (3D Z2Z, in-place on d_result_)
128+
CUFFT_CHECK(cufftPlan3d(&cufft_plan_fwd_, nz, ny, nx, CUFFT_Z2Z));
129+
CUFFT_CHECK(cufftPlan3d(&cufft_plan_bwd_, nz, ny, nx, CUFFT_Z2Z));
130+
131+
gpu_allocated_ = true;
132+
}
133+
134+
const int blocks_r = std::min((nrxx + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK, 1024);
135+
const int blocks_g = std::min((npw + THREADS_PER_BLOCK - 1) / THREADS_PER_BLOCK, 1024);
136+
137+
// d_result_ is double* but aliased as cuFFT complex buffer.
138+
auto* d_fft = reinterpret_cast<double2*>(d_result_);
139+
140+
for (int is = 0; is < nspin; ++is) {
141+
// Step 1: Copy input density H→D
142+
syncmem_d2d_h2d_op()(d_rho_, prho[is], nrxx);
143+
144+
// Step 2: rho^exponent on GPU (eliminates CPU std::pow + extra H→D)
145+
kedf_wt_rho_power<<<blocks_r, THREADS_PER_BLOCK>>>(
146+
d_rho_, d_rho_, exponent, nrxx);
147+
CHECK_CUDA_SYNC();
148+
149+
// Step 3: Real → Complex (double2 out-of-place)
150+
kedf_wt_real_to_complex<<<blocks_r, THREADS_PER_BLOCK>>>(
151+
d_rho_, d_fft, nrxx);
152+
CHECK_CUDA_SYNC();
153+
154+
// Step 4: Forward FFT (in-place on d_fft)
155+
CUFFT_CHECK(cufftExecZ2Z(cufft_plan_fwd_,
156+
reinterpret_cast<cufftDoubleComplex*>(d_fft),
157+
reinterpret_cast<cufftDoubleComplex*>(d_fft),
158+
CUFFT_FORWARD));
159+
160+
// Step 5: Multiply by WT kernel in G-space (double2)
161+
kedf_wt_recip_multiply<<<blocks_g, THREADS_PER_BLOCK>>>(
162+
d_fft, d_kernel_, npw);
163+
CHECK_CUDA_SYNC();
164+
165+
// Step 6: Inverse FFT (in-place on d_fft)
166+
CUFFT_CHECK(cufftExecZ2Z(cufft_plan_bwd_,
167+
reinterpret_cast<cufftDoubleComplex*>(d_fft),
168+
reinterpret_cast<cufftDoubleComplex*>(d_fft),
169+
CUFFT_INVERSE));
170+
171+
// Step 7: Complex → Real with 1/N normalization (double2)
172+
kedf_wt_complex_to_real_norm<<<blocks_r, THREADS_PER_BLOCK>>>(
173+
d_fft, d_rho_, inv_nrxx, nrxx);
174+
CHECK_CUDA_SYNC();
175+
176+
// Step 8: D → H
177+
syncmem_d2d_d2h_op()(rkernel_rho[is], d_rho_, nrxx);
178+
}
179+
}
180+
181+
void KEDF_WT::free_gpu_buffers()
182+
{
183+
if (!gpu_allocated_) { return; }
184+
185+
if (cufft_plan_fwd_ != 0) { cufftDestroy(cufft_plan_fwd_); cufft_plan_fwd_ = 0; }
186+
if (cufft_plan_bwd_ != 0) { cufftDestroy(cufft_plan_bwd_); cufft_plan_bwd_ = 0; }
187+
188+
if (d_rho_ != nullptr) { delmem_dd_op()(d_rho_); d_rho_ = nullptr; }
189+
if (d_result_ != nullptr) { delmem_dd_op()(d_result_); d_result_ = nullptr; }
190+
if (d_kernel_ != nullptr) { delmem_dd_op()(d_kernel_); d_kernel_ = nullptr; }
191+
192+
gpu_allocated_ = false;
193+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
INPUT_PARAMETERS
2+
#Parameters (1.General)
3+
suffix autotest
4+
calculation scf
5+
esolver_type ofdft
6+
7+
device gpu
8+
9+
symmetry 1
10+
pseudo_dir ../../PP_ORB/
11+
pseudo_rcut 16
12+
nspin 1
13+
cal_force 1
14+
test_force 1
15+
cal_stress 1
16+
test_stress 1
17+
18+
#Parameters (2.Iteration)
19+
ecutwfc 20
20+
scf_nmax 50
21+
22+
#OFDFT
23+
of_kinetic wt
24+
of_method tn
25+
of_conv energy
26+
of_tole 2e-6
27+
28+
#Parameters (3.Basis)
29+
basis_type pw

tests/07_OFDFT/31_OF_KE_WT_GPU/KPT

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
K_POINTS
2+
0
3+
Gamma
4+
1 1 1 0 0 0
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Test the energy, force, and stress of Wang-Teter (WT) kinetic energy functional (of_method = wt) in OFDFT with GPU acceleration, symmetry=on
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
ATOMIC_SPECIES
2+
Al 26.98 al.lda.lps blps
3+
4+
LATTICE_CONSTANT
5+
7.50241114482312 // add lattice constant
6+
7+
LATTICE_VECTORS
8+
0.000000000000 0.500000000000 0.500000000000
9+
0.500000000000 0.000000000000 0.500000000000
10+
0.500000000000 0.500000000000 0.000000000000
11+
12+
ATOMIC_POSITIONS
13+
Direct
14+
15+
Al
16+
0
17+
1
18+
0.000000000000 0.000000000000 0.000000000000 1 1 1
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
etotref -57.9338551427919910
2+
etotperatomref -57.9338551428
3+
totalforceref 0.000000
4+
totalstressref 29.613417
5+
pointgroupref O_h
6+
spacegroupref O_h
7+
nksibzref 1
8+
totaltimeref +0.28699

0 commit comments

Comments
 (0)