Skip to content

Commit 8475f60

Browse files
authored
Improve DF task granularity for large fitting bases (#970)
* df: bound CUDA tasks for integral builds, gradients and Hessians Before this change, the three-center builders and derivative contractions allow up to 4B orbital pairs per task, where B is the kernel capacity with s-type auxiliary shells. Each task also spans an auxiliary-shell group. Large groups and expensive angular channels produce long indivisible tasks, leaving workers idle as a launch finishes. This commit changes pair limits to min(4B, max(B//4, 16)) in CDERI builds, the shared first-derivative evaluator, restricted/unrestricted ground-state and TD J/K gradients, and restricted/unrestricted DF Hessian energy contractions. The smaller pair ranges expose more independently schedulable work. Each path uses its own kernel capacity; pair ordering and packed auxiliary layouts remain unchanged. Whole-auxiliary CDERI builds previously assign an entire homogeneous auxiliary group to each task, potentially spanning hundreds of shells. This commit also subdivides those groups into at most 16 shells when aux_batch_size is None, following df.j_engine_3c2e.Int3c2eOpt.contract_auxvec. The subdivisions are carried through auxiliary sorting and multi-device clone metadata. Explicit auxiliary batches and derivative auxiliary grouping retain their existing layouts. B/4 is a heuristic balancing parallel work against task setup. For B=128, 32 pairs times 16 auxiliary shells give 512 shell triples. Kernel capacities of 8 to 128 process that task in 64 to 4 passes. Pair subdivision also shortens derivative tasks that retain whole auxiliary groups. Further subdivision increases queue claims and may increase repeated setup and partially filled passes. The 16-pair floor is a separate heuristic limiting small-task setup cost, with the original 4B cap preserved when it is below 16. For the g-g pair in aug-cc-pVQZ, B=32: the floor raises B/4 from 8 to 16 pairs. Sweeps of floors 4, 8, 16 and 32 show low sensitivity in complete builds and gradients, supporting 16 as an intermediate choice. The 16-shell auxiliary limit follows the existing direct-J implementation. * df: subdivide whole auxiliary groups in Coulomb-only contractions Before this change, the Coulomb-only derivative and Rys potential contractions assign each CUDA block an orbital-pair range and a whole homogeneous auxiliary-shell group. Large fitting bases lengthen the serial auxiliary loop inside each block. A long auxiliary group stays assigned to one block even when other SMs become idle. Subdividing that group allows its work to run across multiple SMs. This commit splits each auxiliary group into ranges of at most 16 shells with _split_l_ctr_groups, following Int3c2eOpt.contract_auxvec in df/j_engine_3c2e.py. It applies this subdivision to ground-state and TD Coulomb gradients, pure-J Hessians and multi-density direct SCF J builds.
1 parent b1c36c1 commit 8475f60

6 files changed

Lines changed: 60 additions & 17 deletions

File tree

‎gpu4pyscf/df/grad/rhf.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
SHM_SIZE, LMAX, L_AUX_MAX, THREADS, libvhf_rys, Int3c2eOpt, int2c2e)
2929
from gpu4pyscf.df import df
3030
from gpu4pyscf.df.df_jk import factorize_dm, _DFHF
31-
from gpu4pyscf.gto.mole import SortedMole
31+
from gpu4pyscf.gto.mole import SortedMole, _split_l_ctr_groups
3232
from gpu4pyscf.scf.jk import _check_rsh_factors
3333

3434
__all__ = ['Gradients']
@@ -149,8 +149,10 @@ def _jk_energy_per_atom(int3c2e_opt, dm, j_factor=1, k_factor=1, hermi=0,
149149
laux = auxmol.uniq_l_ctr[:,0].max()
150150
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
151151

152+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
153+
np.maximum(nsp_per_block[0]//4, 16))
152154
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
153-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
155+
int3c2e_opt.bas_ij_cache, pair_batch_size)
154156
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
155157
aux_loc = auxmol.ao_loc
156158

@@ -286,7 +288,10 @@ def _j_energy_per_atom(int3c2e_opt, dm, hermi=0, auxbasis_response=True, verbose
286288
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
287289
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
288290
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*16)
289-
ksh_offsets_cpu = np.append(0, np.cumsum(auxmol.l_ctr_counts))
291+
# Split auxbasis into small batches for load balance
292+
_, aux_counts = _split_l_ctr_groups(
293+
auxmol.uniq_l_ctr, auxmol.l_ctr_counts, [16]*(laux+1))
294+
ksh_offsets_cpu = np.append(0, np.cumsum(aux_counts))
290295
ksh_offsets_gpu = cp.asarray(ksh_offsets_cpu+mol.nbas, dtype=np.int32)
291296

292297
int3c2e_envs = int3c2e_opt.int3c2e_envs

‎gpu4pyscf/df/grad/tdrhf.py‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
from gpu4pyscf.df import df
3030
from gpu4pyscf.tdscf import rhf as tdrhf
3131
from gpu4pyscf.grad import tdrhf as tdrhf_grad
32-
from gpu4pyscf.gto.mole import SortedMole
32+
from gpu4pyscf.gto.mole import SortedMole, _split_l_ctr_groups
3333
from gpu4pyscf.scf.jk import _check_rsh_factors
3434

3535
__all__ = ['Gradients']
@@ -135,8 +135,10 @@ def _jk_energy_per_atom(int3c2e_opt, dms, j_factor=None, k_factor=None, hermi=0,
135135
laux = auxmol.uniq_l_ctr[:,0].max()
136136
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
137137

138+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
139+
np.maximum(nsp_per_block[0]//4, 16))
138140
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
139-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
141+
int3c2e_opt.bas_ij_cache, pair_batch_size)
140142
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
141143
aux_loc = auxmol.ao_loc
142144

@@ -271,7 +273,10 @@ def _j_energy_per_atom(int3c2e_opt, dms, j_factor, hermi=0, verbose=None):
271273
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
272274
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
273275
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*16)
274-
ksh_offsets_cpu = np.append(0, np.cumsum(auxmol.l_ctr_counts))
276+
# Split auxbasis into small batches for load balance
277+
_, aux_counts = _split_l_ctr_groups(
278+
auxmol.uniq_l_ctr, auxmol.l_ctr_counts, [16]*(laux+1))
279+
ksh_offsets_cpu = np.append(0, np.cumsum(aux_counts))
275280
ksh_offsets_gpu = cp.asarray(ksh_offsets_cpu+mol.nbas, dtype=np.int32)
276281

277282
int3c2e_envs = int3c2e_opt.int3c2e_envs
@@ -562,8 +567,10 @@ def _jk_energies_by_dm_factors(int3c2e_opt, dm_factors, j_factor, k_factor,
562567
laux = auxmol.uniq_l_ctr[:,0].max()
563568
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
564569

570+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
571+
np.maximum(nsp_per_block[0]//4, 16))
565572
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
566-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
573+
int3c2e_opt.bas_ij_cache, pair_batch_size)
567574
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
568575
aux_loc = auxmol.ao_loc
569576

@@ -710,7 +717,10 @@ def _j_energies_per_atom(int3c2e_opt, dm_pairs, j_factor,
710717
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
711718
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
712719
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*16)
713-
ksh_offsets_cpu = np.append(0, np.cumsum(auxmol.l_ctr_counts))
720+
# Split auxbasis into small batches for load balance
721+
_, aux_counts = _split_l_ctr_groups(
722+
auxmol.uniq_l_ctr, auxmol.l_ctr_counts, [16]*(laux+1))
723+
ksh_offsets_cpu = np.append(0, np.cumsum(aux_counts))
714724
ksh_offsets_gpu = cp.asarray(ksh_offsets_cpu+mol.nbas, dtype=np.int32)
715725

716726
int3c2e_envs = int3c2e_opt.int3c2e_envs

‎gpu4pyscf/df/grad/uhf.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ def _jk_energy_per_atom(int3c2e_opt, dm, j_factor=1, k_factor=1, hermi=0,
126126
laux = auxmol.uniq_l_ctr[:,0].max()
127127
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
128128

129+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
130+
np.maximum(nsp_per_block[0]//4, 16))
129131
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
130-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
132+
int3c2e_opt.bas_ij_cache, pair_batch_size)
131133
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
132134
aux_loc = auxmol.ao_loc
133135

‎gpu4pyscf/df/hessian/rhf.py‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from gpu4pyscf.hessian import rhf as rhf_hess
4242
from gpu4pyscf.hessian.rhf import _hcore_energy, _aggregate_to_atoms
4343
from gpu4pyscf.lib import multi_gpu
44+
from gpu4pyscf.gto.mole import _split_l_ctr_groups
4445

4546
num_devices = multi_gpu.num_devices
4647

@@ -189,8 +190,11 @@ def proc(aux_batch_iter):
189190
laux = auxmol.uniq_l_ctr[:,0].max()
190191
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
191192

193+
# Bound serial work in each derivative-integral task.
194+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
195+
np.maximum(nsp_per_block[0]//4, 16))
192196
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
193-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
197+
int3c2e_opt.bas_ij_cache, pair_batch_size)
194198
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
195199

196200
l_ctr_aux_offsets = np.append(0, np.cumsum(auxmol.l_ctr_counts))
@@ -594,10 +598,16 @@ def proc():
594598
laux = auxmol.uniq_l_ctr[:,0].max()
595599
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
596600

601+
# Bound serial work in each derivative-integral task.
602+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
603+
np.maximum(nsp_per_block[0]//4, 16))
597604
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
598-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
605+
int3c2e_opt.bas_ij_cache, pair_batch_size)
599606
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
600-
ksh_offsets_cpu = np.append(0, np.cumsum(auxmol.l_ctr_counts))
607+
# Split auxbasis into small batches for load balance
608+
_, aux_counts = _split_l_ctr_groups(
609+
auxmol.uniq_l_ctr, auxmol.l_ctr_counts, [16]*(laux+1))
610+
ksh_offsets_cpu = np.append(0, np.cumsum(aux_counts))
601611
ksh_offsets_gpu = cp.asarray(ksh_offsets_cpu+mol.nbas, dtype=np.int32)
602612

603613
int3c2e_envs = int3c2e_opt.int3c2e_envs

‎gpu4pyscf/df/hessian/uhf.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,11 @@ def proc():
164164
laux = auxmol.uniq_l_ctr[:,0].max()
165165
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
166166

167+
# Bound serial work in each derivative-integral task.
168+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
169+
np.maximum(nsp_per_block[0]//4, 16))
167170
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
168-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
171+
int3c2e_opt.bas_ij_cache, pair_batch_size)
169172
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
170173

171174
l_ctr_aux_offsets = np.append(0, np.cumsum(auxmol.l_ctr_counts))

‎gpu4pyscf/df/int3c2e_bdiv.py‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from gpu4pyscf.lib import multi_gpu
3535
from gpu4pyscf.gto.mole import (
3636
PTR_BAS_COORD, SortedMole, RysIntEnvVars, PBCIntEnvVars,
37-
extract_pgto_params, groupby)
37+
extract_pgto_params, groupby, _split_l_ctr_groups)
3838
from gpu4pyscf.scf.jk import (
3939
_nearest_power2, _scale_sp_ctr_coeff, _cache_q_cond_and_non0pairs,
4040
_check_rsh_factors, SHM_SIZE, libvhf_rys)
@@ -175,8 +175,11 @@ def int3c2e_evaluator(self, ao_pair_batch_size=None, aux_batch_size=None,
175175
bas_ij_cache = {k: v for k, v in mol.iter_pair_by_l(self.bas_ij_cache)}
176176
else:
177177
bas_ij_cache = self.bas_ij_cache
178+
# Bound pair work alongside the auxiliary-shell task subdivision.
179+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
180+
np.maximum(nsp_per_block[0]//4, 16))
178181
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
179-
bas_ij_cache, nsp_per_block[0]*4)
182+
bas_ij_cache, pair_batch_size)
180183
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], bas_ij_cache, cart)
181184
nao_pair = ao_pair_loc[-1].get()
182185

@@ -210,6 +213,10 @@ def int3c2e_evaluator(self, ao_pair_batch_size=None, aux_batch_size=None,
210213
uniq_l_ctr_aux = auxmol.uniq_l_ctr
211214
aux_loc = auxmol.ao_loc
212215
if aux_batch_size is None:
216+
# Follow df.j_engine_3c2e.Int3c2eOpt.contract_auxvec's 16-shell tasks.
217+
uniq_l_ctr_aux, aux_counts = _split_l_ctr_groups(
218+
auxmol.uniq_l_ctr, auxmol.l_ctr_counts, [16]*(laux+1))
219+
l_ctr_aux_offsets = np.append(0, np.cumsum(aux_counts))
213220
ksh_offsets_cpu = l_ctr_aux_offsets
214221
aux_splits = [0, len(ksh_offsets_cpu)-1]
215222
else:
@@ -410,7 +417,10 @@ def contract_auxvec(self, auxvec):
410417
self.bas_ij_cache, nsp_per_block[0]*4)
411418
gout_stride = cp.asarray(gout_stride, dtype=np.int32)
412419

413-
l_ctr_aux_offsets = np.append(0, np.cumsum(auxmol.l_ctr_counts))
420+
# Split auxbasis into small batches for load balance
421+
_, aux_counts = _split_l_ctr_groups(
422+
auxmol.uniq_l_ctr, auxmol.l_ctr_counts, [16]*(laux+1))
423+
l_ctr_aux_offsets = np.append(0, np.cumsum(aux_counts))
414424
ksh_offsets = cp.asarray(l_ctr_aux_offsets + mol.nbas, dtype=np.int32)
415425
log.debug1('sp_blocks = %d, ksh_blocks = %d, shm_size = %d B',
416426
len(shl_pair_offsets)-1, len(ksh_offsets)-1, shm_size_max)
@@ -785,8 +795,11 @@ def _int3c2e_ip1_evaluator(int3c2e_opt, scheme, batch_size,
785795
laux = auxmol.uniq_l_ctr[:,0].max()
786796
shm_size_max = shm_size[:laux+1,:lmax+1,:lmax+1].max()
787797

798+
# Bound serial work in each derivative-integral task.
799+
pair_batch_size = np.minimum(nsp_per_block[0]*4,
800+
np.maximum(nsp_per_block[0]//4, 16))
788801
bas_ij_idx, shl_pair_offsets = mol.aggregate_shl_pairs(
789-
int3c2e_opt.bas_ij_cache, nsp_per_block[0]*4)
802+
int3c2e_opt.bas_ij_cache, pair_batch_size)
790803
ao_pair_loc = get_ao_pair_loc(mol.uniq_l_ctr[:,0], int3c2e_opt.bas_ij_cache)
791804
nao_pair = int(ao_pair_loc[-1].get())
792805

0 commit comments

Comments
 (0)