Skip to content

Feature(pw): batched FFTs, small ecutexx grid and band chunking for EXX on CPU and GPU - #8018

Open
Flying-dragon-boxing wants to merge 2 commits into
deepmodeling:developfrom
Flying-dragon-boxing:260925-exx-smallgrid-main
Open

Flying-dragon-boxing wants to merge 2 commits into
deepmodeling:developfrom
Flying-dragon-boxing:260925-exx-smallgrid-main

Conversation

@Flying-dragon-boxing

Copy link
Copy Markdown
Collaborator

Reminder

  • I have read AGENTS.md and docs/developers_guide/agent_governance.md.
  • I have linked an issue or explained why this PR does not need one.
  • I have added adequate unit tests and/or case tests, or explained why not.
  • I have listed the exact verification commands run and their results.
  • I have described user-visible behavior changes, including INPUT parameter changes.
  • I have explained core-module impact for ESolver, HSolver, ElecState, Hamilt, Operator, Psi, or other source/ changes.
  • I have requested any needed governance exception below.

Linked Issue

No linked issue: this is a performance/feature PR (EXX PW acceleration) plus a physics fix for the Fock operator occupation weights, self-contained in source_pw/module_pwdft.

Unit Tests and/or Case Tests for my changes

  • Commands run: 24-case regression matrix over the PW EXX integration cases 097_PW_PBE0{,_FM,_COND} x {ACE, noACE} x {full grid, small ecutexx grid} x {CPU, GPU}, plus exx_batch_size = 1/3/5 variants of the chunked path, using build/rel (g++ MPI) and build_abacus_gnu (CUDA 13.1) executables.
  • Result summary: default settings reproduce every previous result bitwise; chunked runs (exx_batch_size > 0) are bitwise identical to unchunked ones (band blocks are disjoint and the (q,m) accumulation order is unchanged); full-grid cases match the pre-change code at 1e-14; CPU small grid matches GPU small grid to 1e-13; the metallic noACE case (k-dependent occupations) converges in 3 EXX outer iterations where the old operator never converged.
  • Checks not run, with reason: no new integration case is added to tests/ in this PR; the existing 097_PW_PBE0* cases already exercise the touched code paths, and the small-grid/chunked variants were verified locally as described above. Happy to add an ecutexx small-grid case to tests/01_PW if reviewers want one.

What's changed?

  • Unified all EXX PW entry points (act_op, act_op_kpar, cal_exx_energy_op) on one code path built on shared primitives; the batched kernels are a device specialization of the per-band operations, selected inside the primitives.
  • New kernels/exx_batch_op*: host loops + FFTW plan_many on CPU (single-precision plans only with ENABLE_FLOAT_FFTW, stubs otherwise), CUDA kernels + cuFFT as DEVICE_GPU specializations.
  • QE ecutfock-style small FFT grid from ecutexx when every |k+G|^2 fits, on CPU and GPU; falls back to the full grid with a warning otherwise.
  • New INPUT parameter exx_batch_size (Integer, default 0 = all bands, identical to previous behavior): processes bands in chunks of that width with a proportionally smaller work-buffer footprint; results are independent of the chunking.
  • Physics fix: the Fock operator weight now uses the source-state occupation f_{mq} and the source k-point weight (was the target-k wg in act_op and target-k wk in act_op_kpar). With k-dependent occupations (smearing) the old operator was inconsistent with the energy and the EXX outer loop never converged.
  • The per-(q,m) scalar MPI_Bcast of wg is replaced by one broadcast of the occupation row + wk per source k-point via Parallel_Common wrappers.
  • stress_exx G-sum truncated to the ecutexx sphere (CPU), consistent with the operator and energy.

Governance Notes

  • INPUT/docs changes: new parameter exx_batch_size registered in read_inp_other.cpp (with a >= 0 value check) and documented in docs/parameters.yaml and docs/advanced/input_files/input-main.md; the ecutexx description is updated to document the small-grid behavior and fallback. No default behavior changes.
  • Core module impact: source_pw/module_pwdft only (EXX operator + stress), plus the INPUT registration. All EXX PW paths (SCF operator action and energy) go through the new unified code path; default settings are bitwise identical to the previous results.
  • Exceptions requested: exx_band_chunk() reads PARAM.inp.exx_batch_size, the module's established style for INPUT values (same as ecutexx); the PARAM/GlobalV uses in op_pw_exx.cpp/stress_exx.cpp are migration-neutral moves — the refactor rewrites existing blocks (act_op_kpar, cal_exx_energy_op, setup) that already read PARAM.inp.nspin/ecutexx and GlobalV::MY_POOL in the same style, so no new global-dependency direction is introduced.

Unify all EXX PW entry points (act_op, act_op_kpar, cal_exx_energy_op)
on one code path built on shared primitives; the batched kernels are a
device specialization of the per-band operations, selected inside the
primitives (batch_active).

- exx_batch kernels templated on Device: host loops + FFTW plan_many on
  CPU (single-precision plans compiled only with ENABLE_FLOAT_FFTW, stubs
  otherwise), CUDA kernels + cuFFT as DEVICE_GPU specializations.
- QE ecutfock-style small FFT grid from ecutexx when every |k+G|^2 fits,
  on CPU and GPU; falls back to the full grid with a warning otherwise.
  The full-grid batched path runs on both devices when the box is local.
- Physics fix: the Fock operator weight now uses the source-state
  occupation f_{mq} and the source k-point weight (was the target-k wg in
  act_op and target-k wk in act_op_kpar). With k-dependent occupations
  (smearing) the old operator was inconsistent with the energy and the
  EXX outer loop never converged.
- The per-(q,m) scalar MPI_Bcast of wg is replaced by one broadcast of
  the occupation row + wk per source k-point via Parallel_Common wrappers.
- stress_exx G-sum truncated to the ecutexx sphere (CPU), consistent with
  the operator and energy.
- Docs: ecutexx describes the small-grid behavior and fallback.

Governance exception: the PARAM/GlobalV budget flags are migration-
neutral moves - the refactor rewrites existing blocks (act_op_kpar,
cal_exx_energy_op, setup) that already read PARAM.inp.nspin/ecutexx and
GlobalV::MY_POOL in the same style as the surrounding module.

Verified against main (4f2a397): build/rel (g++ MPI) and
build_abacus_gnu (CUDA 13.1); 19-case regression matrix
(097_PW_PBE0{,_FM,_COND} x ACE/noACE x full/small grid x CPU/GPU) -
full-grid cases match the pre-change code at 1e-14, CPU small grid
matches GPU small grid to 1e-13, metallic noACE case converges in 3 EXX
outer iterations (did not converge before the wg fix).
The small ecut_exx grid and band batching were bound in a single
predicate; they are orthogonal concerns (grid = which FFT box, chunk =
how many bands per batched round). Split them:

- exx_grid_active() answers the grid question (small grid usable, or
  the full box local); exx_band_chunk() answers the band question via
  the new exx_batch_size INPUT (default 0 = all bands, identical to the
  previous behavior; a positive value processes bands in chunks of that
  width with a proportionally smaller work-buffer footprint).
- cache_psi_nk_real, apply_exx_nbatched and the energy pair-density
  loop process bands in chunks; psi_nk_real_cache still holds all bands
  (it is the reuse floor across (iq, m)), only the work buffers shrink.
- Docs: exx_batch_size in parameters.yaml and input-main.md.

Governance exception: exx_band_chunk() reads PARAM.inp.exx_batch_size,
the module's established style for INPUT values (same as ecutexx).

Verified: 24-case regression (097_PW_PBE0{,_FM,_COND} x ACE/noACE x
full/small grid x CPU/GPU, plus exx_batch_size 1/3/5 variants) - the
default keeps every previous value bitwise, chunked runs are bitwise
identical to unchunked (band blocks are disjoint and the (q,m)
accumulation order is unchanged).
Copilot AI lite review requested due to automatic review settings September 25, 2026 02:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved critical and moderate findings remain in CUDA FFT distances, small-grid bounds validation, memory allocation, global dependencies, and documentation accuracy.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity · 2 Low severity

Open (4)
What changed in this PR

Adds batched CPU/GPU EXX processing, optional smaller ecutexx FFT grids, band chunking, and corrected occupation weighting.

Changes:

  • Adds shared CPU, CUDA, and cuFFT batching kernels.
  • Introduces and documents exx_batch_size and small-grid behavior.
  • Updates EXX operator, energy, stress, and occupation-weighting logic.
File Description
source/​source_pw/​module_pwdft/​stress_exx.cpp Applies the ecutexx cutoff to EXX stress sums.
source/​source_pw/​module_pwdft/​op_pw_exx.h Adds batching and grid-management state.
source/​source_pw/​module_pwdft/​op_pw_exx.cpp Implements unified EXX paths, chunking, grid handling, and weighting.
source/​source_pw/​module_pwdft/​kernels/​exx_batch_op.h Declares batched EXX kernel APIs.
source/​source_pw/​module_pwdft/​kernels/​exx_batch_op.cpp Implements CPU double-precision FFTW kernels.
source/​source_pw/​module_pwdft/​kernels/​exx_batch_op_float.cpp Implements CPU float FFTW plans.
source/​source_pw/​module_pwdft/​kernels/​exx_batch_op_float_stub.cpp Provides float-FFTW fallback stubs.
source/​source_pw/​module_pwdft/​kernels/​cuda/​exx_batch_op.cu Implements CUDA and cuFFT batching.
source/​source_pw/​module_pwdft/​CMakeLists.txt Registers EXX kernel sources.
source/​source_io/​module_parameter/​read_inp_other.cpp Parses and validates exx_batch_size.
source/​source_io/​module_parameter/​input_parameter.h Adds the input parameter field.
source/​CMakeLists.txt Links the CUDA EXX source.
docs/​parameters.yaml Documents EXX parameters and grid behavior.
docs/​advanced/​input_files/​input-main.md Updates user-facing input documentation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +41 to +42
nullptr, 1, 0, // input: contiguous, distance = nx*ny*nz
nullptr, 1, 0, // output: same (in-place)
Comment on lines +587 to +594
if (gridt.nx == wfcpw->nx && gridt.ny == wfcpw->ny && gridt.nz == wfcpw->nz)
{
if (user_set && GlobalV::MY_RANK == 0)
{
ModuleBase::WARNING("OperatorEXXPW",
"ecutexx gives no smaller FFT grid than ecutrho; EXX stays on the full grid");
}
return; // no gain over the current grid
item.category = "Exact Exchange (PW)";
item.type = "Real";
item.description = "The energy cutoff for EXX (Fock) exchange operator in plane wave basis calculations. Reducing ecutexx below ecutrho may significantly accelerate EXX computations. This speed improvement comes with a reduced numerical accuracy in the exchange energy calculation.";
item.description = "The energy cutoff for EXX (Fock) exchange operator in plane wave basis calculations. The pair-density G-sphere of the exchange operator, the EXX energy, and the EXX stress are all truncated at this value. If ecutexx yields a smaller FFT box and every |k+G|^2 of the wavefunctions fits inside it (i.e. ecutexx should not be smaller than ecutwfc), all EXX FFTs run on that smaller grid (QE ecutfock-style), which can significantly accelerate EXX computations. If the small grid is not usable (box not smaller, wavefunctions do not fit, or the FFT box is distributed over MPI), a warning is printed and the full grid is used. Reducing ecutexx below ecutrho reduces the numerical accuracy of the exchange contribution.";
void setup_full_grid_maps() const;
mutable bool exx_sg_init = false;
mutable bool exx_sg_ok = false;
mutable int sg_nx = 0, sg_ny = 0, sg_nz = 0, sg_nxyz = 0;

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants