Skip to content

Commit 95ca4ad

Browse files
feat(pt/dp): add dynamic sel for DPA3 (#4754)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced dynamic neighbor selection for the DPA3 descriptor, enabling handling of variable neighbor counts without fixed padding. - Added configuration options to enable dynamic selection (`use_dynamic_sel`) and adjust normalization scaling (`sel_reduce_factor`). - Added new utility functions for aggregation and graph index computation to support dynamic neighbor processing. - **Bug Fixes** - Corrected tensor split order in angle update logic for improved accuracy. - **Documentation** - Enhanced argument documentation for new dynamic selection options. - **Tests** - Added comprehensive tests for dynamic selection mode, verifying consistency across configurations. - Updated existing test parameterizations to include dynamic selection options. - Integrated new descriptor and parameters into model test suites. - **Chores** - Extended public APIs to include new utility functions supporting dynamic neighbor selection. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent adfa8ad commit 95ca4ad

17 files changed

Lines changed: 1654 additions & 191 deletions

File tree

deepmd/dpmodel/array_api.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,37 @@ def xp_scatter_sum(input, dim, index: np.ndarray, src: np.ndarray) -> np.ndarray
9292
)
9393
else:
9494
raise NotImplementedError("Only JAX arrays are supported.")
95+
96+
97+
def xp_add_at(x, indices, values):
98+
"""Adds values to the specified indices of x in place or returns new x (for JAX)."""
99+
xp = array_api_compat.array_namespace(x, indices, values)
100+
if array_api_compat.is_numpy_array(x):
101+
# NumPy: supports np.add.at (in-place)
102+
xp.add.at(x, indices, values)
103+
return x
104+
105+
elif array_api_compat.is_jax_array(x):
106+
# JAX: functional update, not in-place
107+
return x.at[indices].add(values)
108+
else:
109+
# Fallback for array_api_strict: use basic indexing only
110+
# may need a more efficient way to do this
111+
n = indices.shape[0]
112+
for i in range(n):
113+
idx = int(indices[i])
114+
x[idx, ...] = x[idx, ...] + values[i, ...]
115+
return x
116+
117+
118+
def xp_bincount(x, weights=None, minlength=0):
119+
"""Counts the number of occurrences of each value in x."""
120+
xp = array_api_compat.array_namespace(x)
121+
if array_api_compat.is_numpy_array(x) or array_api_compat.is_jax_array(x):
122+
result = xp.bincount(x, weights=weights, minlength=minlength)
123+
else:
124+
if weights is None:
125+
weights = xp.ones_like(x)
126+
result = xp.zeros((max(minlength, int(xp.max(x)) + 1),), dtype=weights.dtype)
127+
result = xp_add_at(result, x, weights)
128+
return result

deepmd/dpmodel/descriptor/dpa3.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,18 @@ class RepFlowArgs:
123123
smooth_edge_update : bool, optional
124124
Whether to make edge update smooth.
125125
If True, the edge update from angle message will not use self as padding.
126+
use_dynamic_sel : bool, optional
127+
Whether to dynamically select neighbors within the cutoff radius.
128+
If True, the exact number of neighbors within the cutoff radius is used
129+
without padding to a fixed selection numbers.
130+
When enabled, users can safely set larger values for `e_sel` or `a_sel` (e.g., 1200 or 300, respectively)
131+
to guarantee capturing all neighbors within the cutoff radius.
132+
Note that when using dynamic selection, the `smooth_edge_update` must be True.
133+
sel_reduce_factor : float, optional
134+
Reduction factor applied to neighbor-scale normalization when `use_dynamic_sel` is True.
135+
In the dynamic selection case, neighbor-scale normalization will use `e_sel / sel_reduce_factor`
136+
or `a_sel / sel_reduce_factor` instead of the raw `e_sel` or `a_sel` values,
137+
accommodating larger selection numbers.
126138
"""
127139

128140
def __init__(
@@ -150,6 +162,8 @@ def __init__(
150162
skip_stat: bool = False,
151163
optim_update: bool = True,
152164
smooth_edge_update: bool = False,
165+
use_dynamic_sel: bool = False,
166+
sel_reduce_factor: float = 10.0,
153167
) -> None:
154168
self.n_dim = n_dim
155169
self.e_dim = e_dim
@@ -176,6 +190,8 @@ def __init__(
176190
self.a_compress_use_split = a_compress_use_split
177191
self.optim_update = optim_update
178192
self.smooth_edge_update = smooth_edge_update
193+
self.use_dynamic_sel = use_dynamic_sel
194+
self.sel_reduce_factor = sel_reduce_factor
179195

180196
def __getitem__(self, key):
181197
if hasattr(self, key):
@@ -207,6 +223,8 @@ def serialize(self) -> dict:
207223
"fix_stat_std": self.fix_stat_std,
208224
"optim_update": self.optim_update,
209225
"smooth_edge_update": self.smooth_edge_update,
226+
"use_dynamic_sel": self.use_dynamic_sel,
227+
"sel_reduce_factor": self.sel_reduce_factor,
210228
}
211229

212230
@classmethod
@@ -303,6 +321,8 @@ def init_subclass_params(sub_data, sub_class):
303321
fix_stat_std=self.repflow_args.fix_stat_std,
304322
optim_update=self.repflow_args.optim_update,
305323
smooth_edge_update=self.repflow_args.smooth_edge_update,
324+
use_dynamic_sel=self.repflow_args.use_dynamic_sel,
325+
sel_reduce_factor=self.repflow_args.sel_reduce_factor,
306326
exclude_types=exclude_types,
307327
env_protection=env_protection,
308328
precision=precision,

0 commit comments

Comments
 (0)