Skip to content

Commit b95f21e

Browse files
feat(dpa4): multiple updates for dpa4 (#5734)
Since each commits relies on each other to pass the parity tests, so this is a fusion pr. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added `readout_layers` and native per-atom spin support across SeZM/DPA4 descriptors, including new `spin` runtime input and spin-aware scalar readout. * Introduced a public `SpinEmbedding` component for the native spin scheme. * Added `"fndc"` tensor-layout support and expanded optional fused Triton acceleration (including flash-attention-style aggregation) for supported configurations. * **Bug Fixes** * Improved descriptor behavior for zero-block/empty-edge cases; strengthened full-validation profile handling and relaxed spin label requirements when allowed. * Reduced evaluation logging verbosity and enabled AMP autocast during inference when opted in. * **Compatibility** * Model deserialization now recognizes `sezm_native_spin`. * Removed an older experimental CuTe rotation path. <!-- 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 33593b1 commit b95f21e

101 files changed

Lines changed: 17023 additions & 5274 deletions

File tree

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/dpa4.py

Lines changed: 337 additions & 113 deletions
Large diffs are not rendered by default.

deepmd/dpmodel/descriptor/dpa4_nn/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
EnvironmentInitialEmbedding,
4242
GeometricInitialEmbedding,
4343
SeZMTypeEmbedding,
44+
SpinEmbedding,
4445
)
4546
from .ffn import (
4647
EquivariantFFN,
@@ -158,6 +159,7 @@
158159
"ScalarRMSNorm",
159160
"SeZMInteractionBlock",
160161
"SeZMTypeEmbedding",
162+
"SpinEmbedding",
161163
"SwiGLU",
162164
"WignerDCalculator",
163165
"apply_lora_to_sezm",

deepmd/dpmodel/descriptor/dpa4_nn/activation.py

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ class GatedActivation(NativeOP):
9292
Whether to use bias in the gate linear layer.
9393
layout
9494
Tensor layout convention. ``"nfdc"`` means input shape (N, F, D, C);
95-
``"ndfc"`` means input shape (N, D, F, C).
95+
``"ndfc"`` means input shape (N, D, F, C); ``"fndc"`` means input shape
96+
(F, N, D, C), the focus-major layout used by the SO(2) mixing stack.
9697
trainable
9798
Whether parameters are trainable.
9899
seed
@@ -125,8 +126,8 @@ def __init__(
125126
self.precision = precision
126127
self.mlp_bias = bool(mlp_bias)
127128
self.layout = str(layout).lower()
128-
if self.layout not in {"nfdc", "ndfc"}:
129-
raise ValueError("`layout` must be either 'nfdc' or 'ndfc'")
129+
if self.layout not in {"nfdc", "ndfc", "fndc"}:
130+
raise ValueError("`layout` must be one of 'nfdc', 'ndfc', or 'fndc'")
130131

131132
self.activation_function = str(activation_function)
132133
self.scalar_act = get_activation_fn(activation_function)
@@ -170,7 +171,8 @@ def call(self, x: Any, gate: Any = None) -> Any:
170171
----------
171172
x
172173
Value features. Shape is (N, F, D, C) when ``layout='nfdc'``,
173-
or (N, D, F, C) when ``layout='ndfc'``.
174+
(N, D, F, C) when ``layout='ndfc'``, or (F, N, D, C) when
175+
``layout='fndc'``.
174176
gate
175177
Optional gate features with the same layout as ``x``.
176178
When provided, enables GLU mode:
@@ -184,6 +186,10 @@ def call(self, x: Any, gate: Any = None) -> Any:
184186
Gated features with the same layout as ``x``.
185187
"""
186188
xp = array_api_compat.array_namespace(x)
189+
# ``ndfc`` carries the degree axis at position 1; ``nfdc`` and the
190+
# focus-major ``fndc`` carry it at position 2. Every select/narrow/reshape
191+
# below is expressed against this single degree axis, so the three layouts
192+
# share one code path apart from the per-focus gate projection.
187193
degree_axis = 1 if self.layout == "ndfc" else 2
188194

189195
scalar_idx = tuple(
@@ -211,14 +217,17 @@ def call(self, x: Any, gate: Any = None) -> Any:
211217
return x0
212218

213219
input_dtype = gate_scalar_source.dtype
214-
gating_scalars = xp.astype(
215-
xp_sigmoid(
216-
self.gate_linear(
217-
xp.astype(gate_scalar_source, get_xp_precision(xp, self.precision))
218-
)
219-
),
220-
input_dtype,
221-
)
220+
gate_src = xp.astype(gate_scalar_source, get_xp_precision(xp, self.precision))
221+
if self.layout == "fndc":
222+
# The scalar source is focus-major (F, N, C). ``FocusLinear`` mixes
223+
# channels with the focus stream on axis 1, so present it in the shared
224+
# (N, F, C) convention and restore the focus-major orientation.
225+
gate_logits = xp.permute_dims(
226+
self.gate_linear(xp.permute_dims(gate_src, (1, 0, 2))), (1, 0, 2)
227+
)
228+
else:
229+
gate_logits = self.gate_linear(gate_src)
230+
gating_scalars = xp.astype(xp_sigmoid(gate_logits), input_dtype)
222231
gating_scalars = xp.reshape(
223232
gating_scalars,
224233
(x.shape[0], gate_scalar_source.shape[1], self.lmax, self.channels),

0 commit comments

Comments
 (0)