Skip to content

Commit b755ae4

Browse files
Merge pull request #35 from AsymmetryChou/rgf_acc
RGF_ACC: Reduce dead memory in RGF and Improve energy chunk size handling
2 parents a7beb27 + a8e970d commit b755ae4

5 files changed

Lines changed: 44 additions & 8 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ examples/hBN/negf_output_k20/self_energy/self_energy_leadL.h5
3434
examples/hBN/negf_output_k20/self_energy/self_energy_leadR.h5
3535
examples/hBN/negf_output_k50/self_energy/*
3636
examples/hBN/negf_output_k70/self_energy/*
37-
examples/CNT/output/*
37+
examples/CNT/negf_profiling
38+
examples/CNT/output*
39+
examples/CNT/*long*
3840
examples/long_cnt/*
3941
CLAUDE*
4042
ai_docs/*

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ RUN \
4141
conda run -n dpnegf pip install torch==2.5.1 --index-url https://download.pytorch.org/whl/cpu && \
4242
# [2] Pull torch-scatter from the PyG wheel index and use --only-binary=torch-scatter to fully disable source builds.
4343
# If no matching wheel is found it fails immediately instead of spending ~10 minutes compiling a package that would crash at runtime.
44-
conda run -n dpnegf pip install torch-scatter -f https://data.pyg.org/whl/torch-2.5.0+cpu.html --only-binary=torch-scatter && \
44+
conda run -n dpnegf pip install torch-scatter -f https://data.pyg.org/whl/torch-2.5.1+cpu.html --only-binary=torch-scatter && \
4545
# [3] Guard the local-repo installs with the CPU index so hidden dependencies can't replace the CPU torch with a CUDA build.
4646
conda run -n dpnegf pip install ./DeePTB torch==2.5.1 --extra-index-url https://download.pytorch.org/whl/cpu && \
4747
conda run -n dpnegf pip install ./ torch==2.5.1 --extra-index-url https://download.pytorch.org/whl/cpu && \

dpnegf/negf/recursive_green_cal.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,10 +176,17 @@ def recursive_gf_cal(energy, mat_l_list, mat_d_list, mat_u_list,
176176
# In-place: mat_d_list is a fresh tensor (wrapper's `* 1.` copy on D),
177177
# so we can fuse the energy shift without the e_bcast*sd transient.
178178
mat_d_list[jj].addcmul_(sd[jj], e_bcast, value=-1)
179+
# sd[jj] is dead after this — it's only read here in the non-uniform
180+
# kernel. Drop the wrapper-side view so the addcmul_ transient slab
181+
# can be coalesced by the caching allocator inside the loop instead
182+
# of waiting for the wrapper's `sd_b` Python name to leave scope.
183+
sd[jj] = None
179184
for jj in range(len(mat_l_list)):
180185
mat_l_list[jj] = mat_l_list[jj] - e_bcast * sl[jj]
186+
sl[jj] = None
181187
for jj in range(len(mat_u_list)):
182188
mat_u_list[jj] = mat_u_list[jj] - e_bcast * su[jj]
189+
su[jj] = None
183190

184191
num_of_matrices = len(mat_d_list)
185192
mat_shapes = [item.shape for item in mat_d_list] # [B, n_q, n_q]
@@ -454,6 +461,13 @@ def _to_batch(t):
454461
Sd = torch.stack(sd_b, dim=0) # [K, B, n, n]
455462
Sl = torch.stack(sl_b, dim=0) # [K-1, B, n, n]
456463
Su = torch.stack(su_b, dim=0) # [K-1, B, n, n]
464+
# torch.stack on the wrapper's `*1.` D copies and the L/U/sd/sl/su
465+
# expanded views produces six owned 4-D tensors. The wrapper-side
466+
# lists are dead from here on — drop them now so the per-slot
467+
# `[B, n_q, n_q]` storage (≈ K × B × n² × 16 B for D) can be freed
468+
# before the kernel allocates gr_left/grl/gru.
469+
del temp_mat_d_list, temp_mat_l_list, temp_mat_u_list
470+
del sd_b, sl_b, su_b
457471
ans = recursive_gf_cal(shift_energy, L, D, U, Sd, Su, Sl,
458472
s_in=s_in_b, s_out=s_out_b, eta=eta,
459473
need_lesser=need_lesser,
@@ -470,6 +484,12 @@ def _to_batch(t):
470484
need_gr_lc=need_gr_lc,
471485
stacked=False,
472486
keep_gr_left=keep_gr_left)
487+
# Non-uniform kernel consumed the lists by reference and nulled
488+
# individual slots as it went. Drop the wrapper-side names so the
489+
# Python list objects (and any straggler refs) are gone before
490+
# _squeeze_ans/return.
491+
temp_mat_d_list = temp_mat_l_list = temp_mat_u_list = None
492+
sd_b = sl_b = su_b = None
473493

474494
if squeezed:
475495
ans = _squeeze_ans(ans)

dpnegf/runner/NEGF.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -607,9 +607,12 @@ def _auto_chunk_size(self, n_grid):
607607
Per-energy peak (post per-slot-release, complex128) approximated as
608608
bytes_per_E ~= C * K * n_max**2 * 16
609609
with C bundling the live tensors in the worst backward-sweep slot
610-
(grd full + grl + gru full + decaying gr_left tail + gU + transients).
611-
C=10 with a 0.7x free-memory budget; deliberately conservative because
612-
without expandable_segments the allocator can't defragment on demand.
610+
(grd full + grl + gru full + decaying gr_left tail + gU + transients
611+
+ the K-resident H/S diagonal & off-diagonal lists that survive across
612+
chunks). C=14 with a 0.5x free-memory budget; deliberately conservative
613+
because without expandable_segments the allocator can't defragment on
614+
demand, and on real workloads (CNT10/long6) the previous 10x / 0.7
615+
combination still picked a chunk that OOM'd on a 15.77 GiB V100.
613616
"""
614617
rgf_dev = self.rgf_device
615618
if not (isinstance(rgf_dev, torch.device) and rgf_dev.type == "cuda"):
@@ -620,10 +623,10 @@ def _auto_chunk_size(self, n_grid):
620623
K = len(self.deviceprop.hd)
621624
except Exception:
622625
return n_grid
623-
per_e = 10 * K * (n_max ** 2) * 16
626+
per_e = 14 * K * (n_max ** 2) * 16
624627
if per_e <= 0:
625628
return n_grid
626-
b = max(1, min(n_grid, int(0.7 * free_bytes) // per_e))
629+
b = max(1, min(n_grid, int(0.5 * free_bytes) // per_e))
627630
log.info(
628631
f"auto e_batch_size={b} (free={free_bytes/2**30:.2f} GiB, "
629632
f"per_E~={per_e/2**20:.1f} MiB, K={K}, n_max={n_max})"
@@ -751,8 +754,19 @@ def negf_compute(self,scf_require=False,Vbias=None):
751754
# Non-SCF: solve a whole chunk of energies in one batched recursive_gf call.
752755
if self.e_batch_size is not None:
753756
chunk = self.e_batch_size
757+
# The user-supplied value bypasses the auto-budget.
758+
rgf_dev = self.rgf_device
759+
if isinstance(rgf_dev, torch.device) and rgf_dev.type == "cuda":
760+
cap = self._auto_chunk_size(len(self.uni_grid))
761+
if chunk > cap:
762+
log.warning(
763+
f"user e_batch_size={chunk} exceeds the "
764+
f"CUDA auto-cap={cap} on {rgf_dev}; "
765+
)
754766
else:
755767
chunk = self._auto_chunk_size(len(self.uni_grid))
768+
log.info(f"Using e_batch_size={chunk} for energy loop with {len(self.uni_grid)} points")
769+
756770
for e_chunk in torch.split(self.uni_grid, chunk):
757771
e_batch_size = len(e_chunk)
758772
log.info(

ut.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ echo "--- Installing/updating package from PR in editable mode ---"
1919
# Docker image.
2020
# 2. `pytest ./tests/`: After the package is installed, we run the tests.
2121

22-
conda run -n dpnegf bash -c "pip install -e . 'torch==2.1.1' --extra-index-url https://download.pytorch.org/whl/cpu -f https://data.pyg.org/whl/torch-2.1.1+cpu.html --only-binary=torch-scatter && pytest dpnegf/tests/"
22+
conda run -n dpnegf bash -c "pip install -e . 'torch==2.5.1' --extra-index-url https://download.pytorch.org/whl/cpu -f https://data.pyg.org/whl/torch-2.5.1+cpu.html --only-binary=torch-scatter && pytest dpnegf/tests/"
2323

2424
echo "--- Unit Tests Passed Successfully ---"
2525

0 commit comments

Comments
 (0)