Skip to content

Commit f802202

Browse files
author
dyzheng
committed
Fix(deltaspin,psi): disable subspace for nspin=4 and fix PAGED_GPU memory risks
- lambda_loop.cpp: add nspin_==2 guard to subspace acceleration, since nspin=4 P_I_sub loses spin-channel separation from I_spin pre_hr - psi.cpp: fix copy constructor crash when source has PAGED_GPU flag but psi_cpu_ not allocated (set_storage_mode edge case) - psi.cpp: fix_k() auto-routes to psi_cpu_ when GPU lacks k-point data, covering psiToRho/cal_becsum/cal_occupations without per-caller changes - psi.cpp: operator= supports PAGED_GPU (copy psi_cpu_, skip GPU buffer) - psi.cpp: zero_out() handles PAGED_GPU (memset CPU + zero GPU buffer) - psi.cpp: operator()(ik,ib,ibasis) reads from psi_cpu_ in PAGED_GPU - psi.cpp: fix_kb/fix_b add PAGED_GPU guard - psi.h/psi_paging.cpp: add get_cpu_pointer_safe() for null-safe access - setup_psi_pw.cpp: copy_d2h_impl reads from psi_cpu_ in PAGED_GPU GPU memory invariant preserved: psi->psi holds only single k-point.
1 parent 1f02f16 commit f802202

5 files changed

Lines changed: 135 additions & 53 deletions

File tree

source/source_lcao/module_deltaspin/lambda_loop.cpp

Lines changed: 9 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -691,49 +691,21 @@ void spinconstrain::SpinConstrain<std::complex<double>>::run_lambda_loop(int out
691691
mean_error = sum_2d(temp_1) / nat;
692692
rms_error = std::sqrt(mean_error);
693693

694+
// =============================================================
694695
// =============================================================
695696
// ACCELERATION ACTIVATION CHECK (LCAO nspin=2 only)
696697
// =============================================================
697698
//
698-
// PURPOSE: When the BFGS optimizer is near convergence (RMS error
699-
// drops below sc_acceleration_rms_thr), lambda changes become small.
700-
// At this point, subspace methods can approximate the response much
701-
// faster than full diagonalization while maintaining accuracy.
702-
//
703-
// ACTIVATION CONDITIONS (all must be true):
704-
// 1. PARAM.inp.basis_type == "lcao" (only LCAO basis supported)
705-
// 2. this->nspin_ == 2 (only collinear spin supported;
706-
// nspin=4 requires complex-type operator template instantiation)
707-
// 3. sc_acceleration_mode != "off" (user must explicitly enable;
708-
// valid values: "first_order", "subspace")
709-
// 4. sc_acceleration_rms_thr > 0 (threshold must be set)
710-
// 5. rms_error < sc_acceleration_rms_thr (current error is small enough)
711-
//
712-
// ONCE-ONLY ACTIVATION:
713-
// The check uses `!this->acceleration_active_` to ensure subspace
714-
// is built only ONCE per SCF iteration. After activation, all
715-
// subsequent cal_mw_from_lambda calls use the accelerated path
716-
// until the next SCF iteration resets the flag.
717-
//
718-
// WHY build subspace at current lambda?
719-
// The subspace approximation is only valid for small perturbations
720-
// around the reference lambda. Building it at the current (near-converged)
721-
// lambda ensures the reference point is close to the optimal solution.
722-
// If we built it at lambda=0, the approximation would be poor for
723-
// large lambda values.
724-
//
725-
// WHY use i_step=-2?
726-
// The main BFGS loop uses i_step = -1, 0, 1, ..., nsc-1.
727-
// Using -2 ensures this call is handled by a special branch in
728-
// cal_mw_from_lambda that does full diagonalization + cache build
729-
// WITHOUT affecting the main loop state (psi, delta_lambda, etc.).
730-
// This is a clean separation of concerns.
731-
//
732-
// WHY free_lcao_subspace_cache() first?
733-
// To prevent memory leaks from previous SCF iterations. Each SCF
734-
// iteration should build a fresh subspace at its own converged lambda.
699+
// nspin=4: subspace acceleration is disabled. The pre_hr matrix
700+
// stores I_spin structure (same value in all 4 spin blocks),
701+
// causing pre-computed P_I_sub to lose spin-channel separation.
702+
// Full diagonalization is always used for nspin=4.
735703
// =============================================================
704+
705+
const bool nspin_ok = (this->nspin_ == 2);
706+
736707
const bool accel_enabled = (PARAM.inp.basis_type == "lcao") &&
708+
nspin_ok &&
737709
(this->sc_acceleration_mode_ != "off") &&
738710
(this->sc_acceleration_rms_thr_ > 0.0) &&
739711
(rms_error < this->sc_acceleration_rms_thr_);

source/source_psi/psi.cpp

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,21 @@ Psi<T, Device>::Psi(const Psi& psi_in)
206206
const size_t total_size = static_cast<size_t>(this->nk) * this->nbands * this->nbasis;
207207
const T* src_ptr = psi_in.get_pointer() - psi_in.get_psi_bias();
208208

209-
if (psi_in.get_storage_mode() == PsiStorageMode::PAGED_GPU)
209+
if (psi_in.get_storage_mode() == PsiStorageMode::PAGED_GPU)
210210
{
211-
std::memcpy(this->psi_cpu_, psi_in.get_cpu_pointer(), sizeof(T) * total_size);
211+
// Source may have PAGED_GPU flag set but psi_cpu_ not yet allocated
212+
// (e.g. after set_storage_mode() in setup_psi_pw). In that case,
213+
// copy from the source's main buffer (psi) into our psi_cpu_.
214+
const T* cp_ptr = psi_in.get_cpu_pointer_safe();
215+
if (cp_ptr != nullptr)
216+
{
217+
std::memcpy(this->psi_cpu_, cp_ptr, sizeof(T) * total_size);
218+
}
219+
else
220+
{
221+
// Fallback: source data is in psi buffer (ALL_GPU layout)
222+
std::memcpy(this->psi_cpu_, src_ptr, sizeof(T) * total_size);
223+
}
212224
}
213225
else if (std::is_same<Device, base_device::DEVICE_CPU>::value)
214226
{
@@ -255,10 +267,23 @@ Psi<T, Device>::Psi(const Psi<T_in, Device_in>& psi_in)
255267
{
256268
const size_t total_size = static_cast<size_t>(this->nk) * this->nbands * this->nbasis;
257269

258-
if (psi_in.get_storage_mode() == PsiStorageMode::PAGED_GPU)
270+
if (psi_in.get_storage_mode() == PsiStorageMode::PAGED_GPU)
259271
{
260-
const T_in* src_ptr = psi_in.get_cpu_pointer();
261-
detail::TypeCopy<T, T_in, std::is_same<T, T_in>::value>::copy(this->psi_cpu_, src_ptr, total_size);
272+
// Source may have PAGED_GPU flag set but psi_cpu_ not yet allocated.
273+
const T_in* cp_ptr = psi_in.get_cpu_pointer_safe();
274+
if (cp_ptr != nullptr)
275+
{
276+
detail::TypeCopy<T, T_in, std::is_same<T, T_in>::value>::copy(this->psi_cpu_, cp_ptr, total_size);
277+
}
278+
else
279+
{
280+
// Fallback: source data is in main psi buffer (ALL_GPU layout)
281+
auto* arr = (T*)malloc(sizeof(T) * total_size);
282+
base_device::memory::cast_memory_op<T, T_in, Device_in, Device_in>()(
283+
arr, psi_in.get_pointer() - psi_in.get_psi_bias(), total_size);
284+
std::memcpy(this->psi_cpu_, arr, sizeof(T) * total_size);
285+
free(arr);
286+
}
262287
}
263288
else
264289
{
@@ -317,13 +342,30 @@ Psi<T, Device>& Psi<T, Device>::operator=(const Psi<T, Device>& psi_in)
317342
this->k_first = psi_in.get_k_first();
318343
// this function will copy psi_in.psi to this->psi no matter the device types of each other.
319344

345+
this->storage_mode_ = psi_in.get_storage_mode();
320346
this->resize(psi_in.get_nk(), psi_in.get_nbands(), psi_in.get_nbasis());
321-
base_device::memory::synchronize_memory_op<T, Device, Device>()(this->psi,
322-
psi_in.psi,
323-
psi_in.size());
324-
this->psi_bias = psi_in.get_psi_bias();
347+
348+
if (this->storage_mode_ == PsiStorageMode::PAGED_GPU)
349+
{
350+
// PAGED_GPU: copy the full CPU buffer, GPU buffer stays single-k
351+
if (psi_cpu_ != nullptr && psi_in.psi_cpu_ != nullptr)
352+
{
353+
const size_t total_size = static_cast<size_t>(this->nk) * this->nbands * this->nbasis;
354+
std::memcpy(this->psi_cpu_, psi_in.psi_cpu_, sizeof(T) * total_size);
355+
}
356+
this->current_k_gpu_ = -1;
357+
this->psi_bias = 0;
358+
this->psi_current = this->psi;
359+
}
360+
else
361+
{
362+
base_device::memory::synchronize_memory_op<T, Device, Device>()(this->psi,
363+
psi_in.psi,
364+
psi_in.size());
365+
this->psi_bias = psi_in.get_psi_bias();
366+
this->psi_current = this->psi + psi_in.get_psi_bias();
367+
}
325368
this->current_nbasis = psi_in.get_current_nbas();
326-
this->psi_current = this->psi + psi_in.get_psi_bias();
327369

328370
return *this;
329371
}
@@ -485,7 +527,17 @@ void Psi<T, Device>::fix_k(const int ik) const
485527
if (storage_mode_ == PsiStorageMode::PAGED_GPU)
486528
{
487529
this->psi_bias = 0;
488-
this->psi_current = const_cast<T*>(this->psi);
530+
if (this->current_k_gpu_ == ik)
531+
{
532+
// This k-point is loaded on GPU by load_k_to_gpu
533+
this->psi_current = const_cast<T*>(this->psi);
534+
}
535+
else
536+
{
537+
// Read from CPU full buffer (psi_cpu_ has all k-points)
538+
this->psi_current = const_cast<T*>(psi_cpu_
539+
+ static_cast<size_t>(ik) * this->nbands * this->nbasis);
540+
}
489541
return;
490542
}
491543

@@ -508,6 +560,13 @@ void Psi<T, Device>::fix_b(const int ib) const
508560
assert(ib >= 0);
509561
this->current_b = ib;
510562

563+
if (storage_mode_ == PsiStorageMode::PAGED_GPU)
564+
{
565+
this->psi_bias = 0;
566+
this->psi_current = const_cast<T*>(this->psi);
567+
return;
568+
}
569+
511570
if (!this->k_first)
512571
{
513572
this->current_k = 0;
@@ -532,6 +591,14 @@ void Psi<T, Device>::fix_kb(const int ik, const int ib) const
532591
assert(ik >= 0 && ib >= 0);
533592
this->current_k = ik;
534593
this->current_b = ib;
594+
595+
if (storage_mode_ == PsiStorageMode::PAGED_GPU)
596+
{
597+
this->psi_bias = 0;
598+
this->psi_current = const_cast<T*>(this->psi);
599+
return;
600+
}
601+
535602
if (ik >= this->nk || ib >= this->nbands)
536603
{ // fix to 0
537604
this->psi_bias = 0;
@@ -549,6 +616,14 @@ T& Psi<T, Device>::operator()(const int ikb1, const int ikb2, const int ibasis)
549616
{
550617
assert(ikb1 >= 0 && ikb2 >= 0 && ibasis >= 0);
551618
assert(this->k_first ? ikb1 < this->nk && ikb2 < this->nbands : ikb1 < this->nbands && ikb2 < this->nk);
619+
620+
if (storage_mode_ == PsiStorageMode::PAGED_GPU)
621+
{
622+
// PAGED_GPU: GPU buffer is single-k, read from CPU full buffer (k_first layout)
623+
assert(psi_cpu_ != nullptr);
624+
return const_cast<T*>(psi_cpu_)[(ikb1 * this->nbands + ikb2) * this->nbasis + ibasis];
625+
}
626+
552627
return this->k_first ? this->psi[(ikb1 * this->nbands + ikb2) * this->nbasis + ibasis]
553628
: this->psi[(ikb1 * this->nk + ikb2) * this->nbasis + ibasis];
554629
}
@@ -597,8 +672,21 @@ const int& Psi<T, Device>::get_ngk(const int ik_in) const
597672
template <typename T, typename Device>
598673
void Psi<T, Device>::zero_out()
599674
{
600-
// this->psi.assign(this->psi.size(), T(0));
601-
set_memory_op()(this->psi, 0, this->size());
675+
if (storage_mode_ == PsiStorageMode::PAGED_GPU)
676+
{
677+
// Zero full CPU buffer and single-k GPU buffer
678+
if (psi_cpu_ != nullptr)
679+
{
680+
const size_t total_size = static_cast<size_t>(this->nk) * this->nbands * this->nbasis;
681+
std::memset(psi_cpu_, 0, sizeof(T) * total_size);
682+
}
683+
const size_t k_size = static_cast<size_t>(this->nbands) * this->nbasis;
684+
set_memory_op()(this->psi, 0, k_size);
685+
}
686+
else
687+
{
688+
set_memory_op()(this->psi, 0, this->size());
689+
}
602690
}
603691

604692
template <typename T, typename Device>

source/source_psi/psi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class Psi
158158
int get_current_k_gpu() const { return current_k_gpu_; }
159159
T* get_cpu_pointer(int ik = 0);
160160
const T* get_cpu_pointer(int ik = 0) const;
161+
const T* get_cpu_pointer_safe(int ik = 0) const;
161162

162163
private:
163164
T* psi = nullptr; // avoid using C++ STL

source/source_psi/psi_paging.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,17 @@ const T* Psi<T, Device>::get_cpu_pointer(int ik) const
114114
}
115115
}
116116

117+
template <typename T, typename Device>
118+
const T* Psi<T, Device>::get_cpu_pointer_safe(int ik) const
119+
{
120+
if (storage_mode_ == PsiStorageMode::PAGED_GPU && psi_cpu_ != nullptr)
121+
{
122+
if (ik < 0 || ik >= this->nk) return nullptr;
123+
return psi_cpu_ + static_cast<size_t>(ik) * this->nbands * this->nbasis;
124+
}
125+
return nullptr;
126+
}
127+
117128
template <typename T, typename Device>
118129
void Psi<T, Device>::load_k_to_gpu(int ik)
119130
{
@@ -199,6 +210,7 @@ void Psi<T, Device>::set_psi_cpu_external(T* ext_cpu_buf)
199210
template void Psi<T, Device>::set_storage_mode(PsiStorageMode); \
200211
template T* Psi<T, Device>::get_cpu_pointer(int); \
201212
template const T* Psi<T, Device>::get_cpu_pointer(int) const; \
213+
template const T* Psi<T, Device>::get_cpu_pointer_safe(int) const; \
202214
template void Psi<T, Device>::load_k_to_gpu(int); \
203215
template void Psi<T, Device>::store_k_from_gpu(int); \
204216
template void Psi<T, Device>::ensure_k_on_gpu(int); \

source/source_psi/setup_psi_pw.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,18 @@ template <typename T, typename Device>
200200
void Setup_Psi_pw::copy_d2h_impl()
201201
{
202202
auto* psi_t = this->get_psi_t<T, Device>();
203-
this->castmem_d2h_impl<T, Device>(this->psi_cpu[0].get_pointer() - this->psi_cpu[0].get_psi_bias(),
204-
psi_t->get_pointer() - psi_t->get_psi_bias(),
205-
this->psi_cpu[0].size());
203+
if (psi_t->get_storage_mode() == psi::PsiStorageMode::PAGED_GPU)
204+
{
205+
// PAGED_GPU: full data is on CPU in psi_cpu_, just memcpy
206+
const size_t total_size = sizeof(T) * psi_t->get_nk() * psi_t->get_nbands() * psi_t->get_nbasis();
207+
std::memcpy(this->psi_cpu[0].get_pointer(), psi_t->get_cpu_pointer(), total_size);
208+
}
209+
else
210+
{
211+
this->castmem_d2h_impl<T, Device>(this->psi_cpu[0].get_pointer() - this->psi_cpu[0].get_psi_bias(),
212+
psi_t->get_pointer() - psi_t->get_psi_bias(),
213+
this->psi_cpu[0].size());
214+
}
206215
}
207216

208217
void Setup_Psi_pw::copy_d2h()

0 commit comments

Comments
 (0)