Skip to content

Commit 9bc4eb5

Browse files
authored
feat(dpa1): add compressible l=2 moments across PT backends (deepmodeling#5911)
## Summary - Add compressible `l=2` moment features across the DPA1 PyTorch backends. - Extend the DPA1/SeAtten descriptor, Triton and CUDA graph kernels, tabulation paths, and graph energy/force handling for the new moment basis. - Keep the regular PyTorch and `pt_expt` descriptor paths aligned with the compressed implementation. - Correct compressed DPA1/SeAtten evaluation with `exclude_types` by honoring unsorted neighbor lists throughout tabulation. - Update argument validation, documentation, serialization versioning, and backend-specific tests for the new functionality. ## Motivation The DPA1 compressed path previously lacked the `l=2` moment basis and corresponding PT-backend coverage. This change supplies the missing basis and propagates it through descriptor construction, compression, graph execution, tabulation, and force/energy evaluation so compressed and regular paths can represent the same angular information. ## Validation - All pre-commit hooks passed. - The focused pure-Python DPA1/DPModel tests passed (`12 passed`, including 6 subtests). - The selected compressed CPU custom-op tests passed (`29 passed`, 19 skipped). - The extended component-basis CPU operator tests passed (`2 passed`, including 6 subtests). - The handwritten `l=2/3/4` CUDA VJP formulas were checked numerically against PyTorch autograd with maximum absolute error below `9e-15`; CUDA compilation/runtime was not available on the macOS review host. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added configurable `lmax` (1–4) for DPA1/SE-attention, expanding the moment basis size (4/9/16/25) and enabling learnable higher-order per-degree degree weighting. * Updated CPU/CUDA/Triton fused descriptor paths to use the expanded basis (`basis_dim`) and to thread the per-degree gain through forward/backward computations. * **Bug Fixes** * Improved serialization/deserialization to preserve `lmax` and higher-order degree-gain parameters reliably across reloads. * **Documentation** * Documented `lmax`/basis-dimension behavior and clarified higher-order execution constraints and experimental high-`lmax` routing. * **Tests** * Added/extended unit and parity tests for higher `lmax`, serialization, and coordinate-derivative validation. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 15f4437 commit 9bc4eb5

47 files changed

Lines changed: 5109 additions & 2167 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

deepmd/dpmodel/descriptor/dpa1.py

Lines changed: 217 additions & 13 deletions
Large diffs are not rendered by default.

deepmd/dpmodel/descriptor/se_atten_v2.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,9 @@ class DescrptSeAttenV2(DescrptDPA1):
122122
A list of strings. Give the name to each type of atoms.
123123
seed : int, Optional
124124
Random seed for initializing the network parameters.
125+
lmax : int
126+
Maximum angular degree of the Cartesian moment basis. Supported
127+
values are 1 through 4.
125128
"""
126129

127130
def __init__(
@@ -158,6 +161,7 @@ def __init__(
158161
type_map: list[str] | None = None,
159162
# consistent with argcheck, not used though
160163
seed: int | list[int] | None = None,
164+
lmax: int = 1,
161165
) -> None:
162166
DescrptDPA1.__init__(
163167
self,
@@ -195,6 +199,7 @@ def __init__(
195199
type_map=type_map,
196200
# consistent with argcheck, not used though
197201
seed=seed,
202+
lmax=lmax,
198203
)
199204
self.compress = False
200205

@@ -204,7 +209,7 @@ def serialize(self) -> dict:
204209
data = {
205210
"@class": "Descriptor",
206211
"type": "se_atten_v2",
207-
"@version": 3 if self.compress else 2,
212+
"@version": 4 if obj.lmax != 1 else (3 if self.compress else 2),
208213
"rcut": obj.rcut,
209214
"rcut_smth": obj.rcut_smth,
210215
"sel": obj.sel,
@@ -246,6 +251,11 @@ def serialize(self) -> dict:
246251
"trainable": self.trainable,
247252
"spin": None,
248253
}
254+
if obj.lmax != 1:
255+
data["lmax"] = obj.lmax
256+
data["@variables"]["degree_gain_raw"] = to_numpy_array(
257+
obj.adam_degree_gain_raw
258+
)
249259
if self.compress:
250260
type_embd_data = (
251261
self.type_embd_data
@@ -282,7 +292,7 @@ def serialize(self) -> dict:
282292
def deserialize(cls, data: dict) -> "DescrptSeAttenV2":
283293
"""Deserialize from dict."""
284294
data = data.copy()
285-
check_version_compatibility(data.pop("@version"), 3, 1)
295+
check_version_compatibility(data.pop("@version"), 4, 1)
286296
data.pop("@class")
287297
data.pop("type")
288298
variables = data.pop("@variables")
@@ -295,10 +305,16 @@ def deserialize(cls, data: dict) -> "DescrptSeAttenV2":
295305
# compat with version 1
296306
if "use_tebd_bias" not in data:
297307
data["use_tebd_bias"] = True
308+
data.setdefault("lmax", 1)
298309
obj = cls(**data)
299310

300311
obj.se_atten["davg"] = variables["davg"]
301312
obj.se_atten["dstd"] = variables["dstd"]
313+
if obj.se_atten.lmax > 1:
314+
obj.se_atten.adam_degree_gain_raw = np.asarray(
315+
variables["degree_gain_raw"],
316+
dtype=PRECISION_DICT[obj.se_atten.precision],
317+
)
302318
obj.se_atten.embeddings = NetworkCollection.deserialize(embeddings)
303319
obj.se_atten.embeddings_strip = NetworkCollection.deserialize(embeddings_strip)
304320
obj.type_embedding = TypeEmbedNet.deserialize(type_embedding)

deepmd/kernels/cuda/dpa1/canonical.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def _forward_fake(
7070
type_embedding: torch.Tensor,
7171
average: torch.Tensor,
7272
inverse_stddev: torch.Tensor,
73+
degree_gain: torch.Tensor,
7374
table: torch.Tensor,
7475
gate_table: torch.Tensor,
7576
type_one_side: int,
@@ -86,12 +87,14 @@ def _forward_fake(
8687
rcut_smooth: float,
8788
protection: float,
8889
neighbors: float,
90+
basis_dim: int,
8991
) -> tuple[torch.Tensor, ...]:
9092
del (
9193
source,
9294
destination_row_ptr,
9395
average,
9496
inverse_stddev,
97+
degree_gain,
9598
gate_table,
9699
type_one_side,
97100
smooth,
@@ -117,7 +120,7 @@ def _forward_fake(
117120
width,
118121
3,
119122
),
120-
edge_vec.new_empty(node_count, 4, width),
123+
edge_vec.new_empty(node_count, basis_dim, width),
121124
)
122125

123126

@@ -131,6 +134,7 @@ def _backward_fake(
131134
atype: torch.Tensor,
132135
average: torch.Tensor,
133136
inverse_stddev: torch.Tensor,
137+
degree_gain: torch.Tensor,
134138
table: torch.Tensor,
135139
gate_table: torch.Tensor,
136140
type_one_side: int,
@@ -155,6 +159,7 @@ def _backward_fake(
155159
atype,
156160
average,
157161
inverse_stddev,
162+
degree_gain,
158163
table,
159164
gate_table,
160165
type_one_side,
@@ -218,9 +223,9 @@ def _cpu_forward(*args: Any) -> tuple[torch.Tensor, ...]:
218223
edge_mask,
219224
destination_order,
220225
destination_row_ptr,
221-
*tail[:11],
226+
*tail[:12],
222227
True,
223-
*tail[11:],
228+
*tail[12:],
224229
)
225230

226231

@@ -245,9 +250,9 @@ def _cpu_backward(*args: Any) -> torch.Tensor:
245250
edge_mask,
246251
destination_order,
247252
destination_row_ptr,
248-
*tail[:8],
253+
*tail[:9],
249254
True,
250-
*tail[8:],
255+
*tail[9:],
251256
)
252257

253258

@@ -329,6 +334,11 @@ def dpa1_canonical_compress_energy_force(
329334
compress_data = desc.compress_data[0].contiguous()
330335
gate_table = desc.type_embd_data.contiguous()
331336
inverse_stddev = torch.reciprocal(se.stddev[:, 0, :]).contiguous()
337+
degree_gain = (
338+
se.adam_degree_gain_raw.to(torch.float32).contiguous()
339+
if se.adam_degree_gain_raw is not None
340+
else compress_data.new_empty(0)
341+
)
332342
from torch.fx.experimental.proxy_tensor import (
333343
disable_proxy_modes_tracing,
334344
)
@@ -346,6 +356,7 @@ def dpa1_canonical_compress_energy_force(
346356
type_embedding,
347357
se.mean[:, 0, :].contiguous(),
348358
inverse_stddev,
359+
degree_gain,
349360
compress_data,
350361
gate_table,
351362
int(se.type_one_side),
@@ -362,6 +373,7 @@ def dpa1_canonical_compress_energy_force(
362373
float(se.rcut_smth),
363374
float(se.env_protection),
364375
float(se.nnei),
376+
(int(se.lmax) + 1) ** 2,
365377
)
366378

367379
*hidden, head = fit.nets[0].layers
@@ -426,6 +438,7 @@ def dpa1_canonical_compress_energy_force(
426438
atype,
427439
se.mean[:, 0, :].contiguous(),
428440
inverse_stddev,
441+
degree_gain,
429442
compress_data,
430443
gate_table,
431444
int(se.type_one_side),

0 commit comments

Comments
 (0)