Skip to content

Commit b5ff54c

Browse files
dzzz2001claude
andauthored
fix(gint): use fp64 accumulators in mixed-precision paths to avoid precision loss (#7368)
Across CPU and GPU gint paths, accumulator buffers (hr_gint, phi_dm, rho, and the vbatched GEMM C output) are now always allocated as double, even when the input phi/dm/vr_eff are fp32. Multiplies stay in fp32 (cheap), but per-block and global reductions are widened to fp64 so that summing many atom-pair contributions into the same element does not drift. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent e943b0f commit b5ff54c

17 files changed

Lines changed: 231 additions & 227 deletions
Lines changed: 8 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
#include <algorithm>
2-
#include <type_traits>
3-
41
#include "source_base/global_function.h"
52
#include "gint_rho.h"
63
#include "gint_common.h"
@@ -30,15 +27,14 @@ template<typename Real>
3027
void Gint_rho::cal_gint_impl_()
3128
{
3229
std::vector<HContainer<Real>> dm_gint_vec = init_dm_gint_<Real>();
33-
std::vector<std::vector<Real>> rho_cache(nspin_);
34-
std::vector<Real*> rho_data(nspin_);
30+
// rho_[is] is always double; phi_dot_phi accumulates into it directly.
31+
std::vector<double*> rho_data(nspin_);
3532
for (int is = 0; is < nspin_; ++is)
3633
{
37-
rho_data[is] = get_rho_data_<Real>(is, rho_cache);
34+
rho_data[is] = rho_[is];
3835
}
3936
dm_2d_to_gint(*gint_info_, dm_vec_, dm_gint_vec);
4037
cal_rho_(dm_gint_vec, rho_data);
41-
transfer_rho_cache_<Real>(rho_cache);
4238
}
4339

4440
template<typename Real>
@@ -52,64 +48,19 @@ std::vector<HContainer<Real>> Gint_rho::init_dm_gint_() const
5248
return dm_gint_vec;
5349
}
5450

55-
// Overloaded helpers (C++11-compatible alternative to if constexpr).
56-
// The double overload is preferred by overload resolution when Real=double.
57-
58-
inline double* get_rho_data(double* const* rho, int is, int /*local_mgrid_num*/,
59-
std::vector<std::vector<double>>& /*rho_cache*/)
60-
{
61-
return rho[is];
62-
}
63-
64-
template<typename Real>
65-
Real* get_rho_data(double* const* rho, int is, int local_mgrid_num,
66-
std::vector<std::vector<Real>>& rho_cache)
67-
{
68-
rho_cache[is].resize(local_mgrid_num);
69-
std::transform(rho[is], rho[is] + local_mgrid_num, rho_cache[is].begin(), [](const double value) {
70-
return static_cast<Real>(value);
71-
});
72-
return rho_cache[is].data();
73-
}
74-
75-
inline void transfer_rho_back(double* const* /*rho*/, int /*nspin*/, int /*local_mgrid_num*/,
76-
const std::vector<std::vector<double>>& /*rho_cache*/)
77-
{
78-
// Nothing to do: double rho was written directly.
79-
}
80-
81-
template<typename Real>
82-
void transfer_rho_back(double* const* rho, int nspin, int local_mgrid_num,
83-
const std::vector<std::vector<Real>>& rho_cache)
84-
{
85-
for (int is = 0; is < nspin; ++is)
86-
{
87-
for (int ir = 0; ir < local_mgrid_num; ++ir)
88-
{
89-
rho[is][ir] = static_cast<double>(rho_cache[is][ir]);
90-
}
91-
}
92-
}
93-
94-
95-
96-
template<typename Real>
97-
Real* Gint_rho::get_rho_data_(int is, std::vector<std::vector<Real>>& rho_cache) const
98-
{
99-
return get_rho_data(
100-
rho_, is, gint_info_->get_local_mgrid_num(), rho_cache);
101-
}
102-
10351
template<typename Real>
10452
void Gint_rho::cal_rho_(
10553
const std::vector<HContainer<Real>>& dm_gint_vec,
106-
const std::vector<Real*>& rho_data) const
54+
const std::vector<double*>& rho_data) const
10755
{
10856
#pragma omp parallel
10957
{
11058
PhiOperator phi_op;
11159
std::vector<Real> phi;
112-
std::vector<Real> phi_dm;
60+
// phi_dm is always double: phi_mul_dm writes the cast-to-double result
61+
// into it, and phi_dot_phi reads it as fp64 (so the rho reduction's
62+
// right-hand side is uniformly fp64 even on the fp32 path).
63+
std::vector<double> phi_dm;
11364
#pragma omp for schedule(dynamic)
11465
for (int i = 0; i < gint_info_->get_bgrids_num(); i++)
11566
{
@@ -132,11 +83,4 @@ void Gint_rho::cal_rho_(
13283
}
13384
}
13485

135-
template<typename Real>
136-
void Gint_rho::transfer_rho_cache_(const std::vector<std::vector<Real>>& rho_cache) const
137-
{
138-
transfer_rho_back(
139-
rho_, nspin_, gint_info_->get_local_mgrid_num(), rho_cache);
140-
}
141-
14286
}

source/source_lcao/module_gint/gint_rho.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,12 @@ class Gint_rho : public Gint
2828
template<typename Real>
2929
std::vector<HContainer<Real>> init_dm_gint_() const;
3030

31-
template<typename Real>
32-
Real* get_rho_data_(int is, std::vector<std::vector<Real>>& rho_cache) const;
33-
31+
// rho is always accumulated in double (see phi_dot_phi). When Real=float,
32+
// only phi and phi_dm are fp32; the per-meshgrid reduction is fp64.
3433
template<typename Real>
3534
void cal_rho_(
3635
const std::vector<HContainer<Real>>& dm_gint_vec,
37-
const std::vector<Real*>& rho_data) const;
38-
39-
template<typename Real>
40-
void transfer_rho_cache_(const std::vector<std::vector<Real>>& rho_cache) const;
36+
const std::vector<double*>& rho_data) const;
4137

4238
// input
4339
const std::vector<HContainer<double>*> dm_vec_;

source/source_lcao/module_gint/gint_rho_gpu.cpp

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
#include "kernel/phi_operator_gpu.h"
66
#include "source_base/module_device/device_check.h"
77

8-
#include <algorithm>
9-
108
namespace ModuleGint
119
{
1210

@@ -40,13 +38,14 @@ void Gint_rho_gpu::cal_gint_impl_()
4038
// 2. Transfer dm from 2D parallel distribution to gint serial distribution
4139
dm_2d_to_gint(*gint_info_, dm_vec_, dm_gint_vec);
4240

43-
// 3. Transfer dm to GPU
41+
// 3. Transfer dm to GPU. rho_d is always double — the kernel accumulates
42+
// in fp64 regardless of the input precision.
4443
std::vector<CudaMemWrapper<Real>> dm_gint_d_vec(nspin_);
45-
std::vector<CudaMemWrapper<Real>> rho_d_vec(nspin_);
44+
std::vector<CudaMemWrapper<double>> rho_d_vec(nspin_);
4645
for (int is = 0; is < nspin_; is++)
4746
{
4847
dm_gint_d_vec[is] = CudaMemWrapper<Real>(dm_gint_vec[is].get_nnr(), 0, false);
49-
rho_d_vec[is] = CudaMemWrapper<Real>(gint_info_->get_local_mgrid_num(), 0, false);
48+
rho_d_vec[is] = CudaMemWrapper<double>(gint_info_->get_local_mgrid_num(), 0, false);
5049
CHECK_CUDA(cudaMemcpy(dm_gint_d_vec[is].get_device_ptr(), dm_gint_vec[is].get_wrapper(),
5150
dm_gint_vec[is].get_nnr() * sizeof(Real), cudaMemcpyHostToDevice));
5251
}
@@ -61,7 +60,9 @@ void Gint_rho_gpu::cal_gint_impl_()
6160
CHECK_CUDA(cudaStreamCreate(&stream));
6261
PhiOperatorGpu<Real> phi_op(gint_info_->get_gpu_vars(), stream);
6362
CudaMemWrapper<Real> phi(BatchBigGrid::get_max_phi_len(), stream, false);
64-
CudaMemWrapper<Real> phi_dm(BatchBigGrid::get_max_phi_len(), stream, false);
63+
// phi_dm is always double: the gemm_nn_vbatch kernel accumulates fp32
64+
// multiplies into a fp64 register, then atomicAdd's into phi_dm.
65+
CudaMemWrapper<double> phi_dm(BatchBigGrid::get_max_phi_len(), stream, false);
6566
#pragma omp for schedule(dynamic)
6667
for (int i = 0; i < gint_info_->get_bgrid_batches_num(); ++i)
6768
{
@@ -83,18 +84,13 @@ void Gint_rho_gpu::cal_gint_impl_()
8384
CHECK_CUDA(cudaStreamDestroy(stream));
8485
}
8586

86-
// 5. Transfer rho back to CPU and convert to double if needed
87+
// 5. Transfer rho back to CPU (already double — copy straight into rho_[is])
8788
const int local_mgrid_num = gint_info_->get_local_mgrid_num();
8889
for (int is = 0; is < nspin_; is++)
8990
{
90-
std::vector<Real> rho_tmp(local_mgrid_num);
91-
CHECK_CUDA(cudaMemcpy(rho_tmp.data(), rho_d_vec[is].get_device_ptr(),
92-
local_mgrid_num * sizeof(Real), cudaMemcpyDeviceToHost));
93-
for (int ir = 0; ir < local_mgrid_num; ++ir)
94-
{
95-
rho_[is][ir] = static_cast<double>(rho_tmp[ir]);
96-
}
91+
CHECK_CUDA(cudaMemcpy(rho_[is], rho_d_vec[is].get_device_ptr(),
92+
local_mgrid_num * sizeof(double), cudaMemcpyDeviceToHost));
9793
}
9894
}
9995

100-
} // namespace ModuleGint
96+
} // namespace ModuleGint

source/source_lcao/module_gint/gint_vl.cpp

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#include <algorithm>
2-
#include <type_traits>
32

43
#include "gint_common.h"
54
#include "gint_vl.h"
@@ -30,12 +29,6 @@ void Gint_vl::cal_gint()
3029
// Private functions
3130
//========================
3231

33-
template<typename Real>
34-
HContainer<Real> Gint_vl::init_hr_gint_() const
35-
{
36-
return gint_info_->get_hr<Real>();
37-
}
38-
3932
// Overloaded helpers (C++11-compatible alternative to if constexpr).
4033
// The double overload is preferred by overload resolution when Real=double;
4134
// the template overload handles all other types (e.g. float).
@@ -57,24 +50,13 @@ const Real* get_vr_eff_data_(const double* vr_eff, int local_mgrid_num,
5750
return vr_eff_buffer.data();
5851
}
5952

60-
inline void finalize_hr_gint_(HContainer<double>& hr_gint, HContainer<double>* hR)
61-
{
62-
compose_hr_gint(hr_gint);
63-
hr_gint_to_hR(hr_gint, *hR);
64-
}
65-
66-
template<typename Real>
67-
void finalize_hr_gint_(HContainer<Real>& hr_gint, HContainer<double>* hR)
68-
{
69-
HContainer<double> hr_gint_dp = make_cast_hcontainer<double>(hr_gint);
70-
compose_hr_gint(hr_gint_dp);
71-
hr_gint_to_hR(hr_gint_dp, *hR);
72-
}
73-
7453
template<typename Real>
7554
void Gint_vl::cal_gint_impl_()
7655
{
77-
HContainer<Real> hr_gint = init_hr_gint_<Real>();
56+
// hr_gint is always allocated as HContainer<double>: when Real=float, the
57+
// fp32 multiplies feed into a fp64 accumulator inside phi_mul_phi to avoid
58+
// catastrophic precision loss in the global reduction.
59+
HContainer<double> hr_gint = gint_info_->get_hr<double>();
7860
std::vector<Real> vr_eff_buffer;
7961
const Real* vr_eff = get_vr_eff_data_(
8062
vr_eff_, gint_info_->get_local_mgrid_num(), vr_eff_buffer);
@@ -102,8 +84,8 @@ void Gint_vl::cal_gint_impl_()
10284
}
10385
}
10486

105-
finalize_hr_gint_(hr_gint, hR_);
87+
compose_hr_gint(hr_gint);
88+
hr_gint_to_hR(hr_gint, *hR_);
10689
}
10790

10891
} // namespace ModuleGint
109-

source/source_lcao/module_gint/gint_vl.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ class Gint_vl : public Gint
2424
template<typename Real>
2525
void cal_gint_impl_();
2626

27-
template<typename Real>
28-
HContainer<Real> init_hr_gint_() const;
29-
3027
// input
3128
const double* vr_eff_ = nullptr;
3229

source/source_lcao/module_gint/gint_vl_gpu.cpp

Lines changed: 13 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -28,32 +28,18 @@ void Gint_vl_gpu::cal_gint()
2828
ModuleBase::timer::end("Gint", "cal_gint_vl");
2929
}
3030

31-
// Helper: finalize hr_gint (double path — no cast needed)
32-
inline void finalize_hr_gint_gpu_(HContainer<double>& hr_gint, HContainer<double>* hR)
33-
{
34-
compose_hr_gint(hr_gint);
35-
hr_gint_to_hR(hr_gint, *hR);
36-
}
37-
38-
// Helper: finalize hr_gint (non-double path — cast to double first)
39-
template<typename Real>
40-
void finalize_hr_gint_gpu_(HContainer<Real>& hr_gint, HContainer<double>* hR)
41-
{
42-
HContainer<double> hr_gint_dp = make_cast_hcontainer<double>(hr_gint);
43-
compose_hr_gint(hr_gint_dp);
44-
hr_gint_to_hR(hr_gint_dp, *hR);
45-
}
46-
4731
template<typename Real>
4832
void Gint_vl_gpu::cal_gint_impl_()
4933
{
50-
// 1. Initialize hr_gint as HContainer<Real>
51-
HContainer<Real> hr_gint = gint_info_->get_hr<Real>();
34+
// hr_gint is always allocated as HContainer<double>: the per-atom-pair GEMM
35+
// accumulates fp32 multiplies into a fp64 register/atomicAdd inside the
36+
// kernel so that the global reduction across many biggrids stays accurate.
37+
HContainer<double> hr_gint = gint_info_->get_hr<double>();
5238

53-
// 2. Convert vr_eff to Real and transfer to GPU
39+
// 1. Convert vr_eff to Real and transfer to GPU
5440
const int local_mgrid_num = gint_info_->get_local_mgrid_num();
5541
CudaMemWrapper<Real> vr_eff_d(local_mgrid_num, 0, false);
56-
CudaMemWrapper<Real> hr_gint_d(hr_gint.get_nnr(), 0, false);
42+
CudaMemWrapper<double> hr_gint_d(hr_gint.get_nnr(), 0, false);
5743

5844
if (std::is_same<Real, double>::value)
5945
{
@@ -71,7 +57,7 @@ void Gint_vl_gpu::cal_gint_impl_()
7157
local_mgrid_num * sizeof(Real), cudaMemcpyHostToDevice));
7258
}
7359

74-
// 3. Calculate hr_gint on GPU
60+
// 2. Calculate hr_gint on GPU
7561
#pragma omp parallel num_threads(gint_info_->get_streams_num())
7662
{
7763
// 20240620 Note that it must be set again here because
@@ -101,12 +87,13 @@ void Gint_vl_gpu::cal_gint_impl_()
10187
CHECK_CUDA(cudaStreamDestroy(stream));
10288
}
10389

104-
// 4. Transfer hr_gint back to CPU
90+
// 3. Transfer hr_gint back to CPU
10591
CHECK_CUDA(cudaMemcpy(hr_gint.get_wrapper(), hr_gint_d.get_device_ptr(),
106-
hr_gint.get_nnr() * sizeof(Real), cudaMemcpyDeviceToHost));
92+
hr_gint.get_nnr() * sizeof(double), cudaMemcpyDeviceToHost));
10793

108-
// 5. Compose and transfer to hR (with cast if needed)
109-
finalize_hr_gint_gpu_(hr_gint, hR_);
94+
// 4. Compose and transfer to hR (already double, no cast needed)
95+
compose_hr_gint(hr_gint);
96+
hr_gint_to_hR(hr_gint, *hR_);
11097
}
11198

112-
}
99+
}

source/source_lcao/module_gint/kernel/dgemm_vbatch.cu

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ void gemm_nn_vbatch(
99
const int* m_d, const int* n_d, const int* k_d,
1010
const T* const* A_array_d, const int* lda_d,
1111
const T* const* B_array_d, const int* ldb_d,
12-
T** C_array_d, const int* ldc_d,
12+
double** C_array_d, const int* ldc_d,
1313
int batchCount, cudaStream_t stream,
1414
const T* alpha)
1515
{
@@ -28,7 +28,7 @@ void gemm_tn_vbatch(
2828
const int* m_d, const int* n_d, const int* k_d,
2929
const T* const* A_array_d, const int* lda_d,
3030
const T* const* B_array_d, const int* ldb_d,
31-
T** C_array_d, const int* ldc_d,
31+
double** C_array_d, const int* ldc_d,
3232
int batchCount, cudaStream_t stream,
3333
const T* alpha)
3434
{
@@ -49,7 +49,7 @@ template void gemm_nn_vbatch<double>(
4949
template void gemm_nn_vbatch<float>(
5050
int, int, int, const int*, const int*, const int*,
5151
const float* const*, const int*, const float* const*, const int*,
52-
float**, const int*, int, cudaStream_t, const float*);
52+
double**, const int*, int, cudaStream_t, const float*);
5353

5454
template void gemm_tn_vbatch<double>(
5555
int, int, int, const int*, const int*, const int*,
@@ -59,4 +59,4 @@ template void gemm_tn_vbatch<double>(
5959
template void gemm_tn_vbatch<float>(
6060
int, int, int, const int*, const int*, const int*,
6161
const float* const*, const int*, const float* const*, const int*,
62-
float**, const int*, int, cudaStream_t, const float*);
62+
double**, const int*, int, cudaStream_t, const float*);

0 commit comments

Comments
 (0)