Skip to content

Commit eedcddd

Browse files
authored
Merge branch 'master' into pr/dpa1
2 parents 6b9825c + cf3e6f1 commit eedcddd

11 files changed

Lines changed: 1273 additions & 160 deletions

File tree

deepmd/backend/pt_expt.py

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
from deepmd.backend.backend import (
1414
Backend,
1515
)
16+
from deepmd.utils.pt_checkpoint import (
17+
detect_pt_checkpoint_backend,
18+
)
1619

1720
if TYPE_CHECKING:
1821
from argparse import (
@@ -51,11 +54,8 @@ def match_filename(cls, filename: str) -> int:
5154
Returns
5255
-------
5356
- 1 for the regular `.pte` / `.pt2` suffixes (default behaviour).
54-
- 2 for `.pt` files whose state-dict uses pt_expt's dpmodel
55-
parameter naming (`.w`/`.b`); this outranks the legacy pt
56-
backend's default suffix score (1) so pt_expt-trained `.pt`
57-
checkpoints route here, while genuine pt-trained `.pt` files
58-
(which use `.matrix`/`.bias`) keep going to the pt backend.
57+
- 2 for `.pt` files whose state dictionary uses the pt_expt parameter
58+
dialect. This outranks the pt backend's default suffix score (1).
5959
- 0 otherwise.
6060
"""
6161
score = super().match_filename(filename)
@@ -69,21 +69,14 @@ def match_filename(cls, filename: str) -> int:
6969

7070
# weights_only=True avoids unpickling arbitrary code from an
7171
# untrusted .pt — sniffing only needs the dict keys.
72-
sd = torch.load(filename, map_location="cpu", weights_only=True)
72+
checkpoint = torch.load(filename, map_location="cpu", weights_only=True)
7373
except Exception:
7474
# Not a valid torch archive (corrupt file, wrong format, or a
7575
# weights_only=True restriction trip). Surrender the claim so
7676
# the dispatcher falls back to the default suffix match — pt's
7777
# default score (1) will pick up the file under `dp --pt`.
7878
return 0
79-
if isinstance(sd, dict) and "model" in sd:
80-
sd = sd["model"]
81-
keys = list(sd.keys()) if hasattr(sd, "keys") else []
82-
has_pt_expt = any(k.endswith(".w") or k.endswith(".b") for k in keys)
83-
has_pt = any(k.endswith(".matrix") or k.endswith(".bias") for k in keys)
84-
if has_pt_expt and not has_pt:
85-
return 2
86-
return 0
79+
return 2 if detect_pt_checkpoint_backend(checkpoint) == "pt-expt" else 0
8780

8881
def is_available(self) -> bool:
8982
"""Check if the backend is available.

deepmd/pt_expt/infer/deep_eval.py

Lines changed: 284 additions & 116 deletions
Large diffs are not rendered by default.

deepmd/pt_expt/model/graph_lower.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,38 @@
55
Any,
66
)
77

8+
import torch
9+
10+
11+
def graph_edge_dtype(model: Any, lower_kind: str) -> str:
12+
"""Return the graph edge-vector dtype encoded by a deployment artifact.
13+
14+
Parameters
15+
----------
16+
model : Any
17+
Model exposing an atomic-model descriptor.
18+
lower_kind : str
19+
Concrete lower-forward schema.
20+
21+
Returns
22+
-------
23+
str
24+
``"float32"`` for eligible geometrically compressed DPA1 graph
25+
lowers, otherwise ``"float64"``.
26+
"""
27+
atomic_model = getattr(model, "atomic_model", None)
28+
descriptor = getattr(atomic_model, "descriptor", None)
29+
descriptor_block = getattr(descriptor, "se_atten", None)
30+
statistics = getattr(descriptor_block, "mean", None)
31+
if (
32+
lower_kind in ("graph", "dpa1_canonical")
33+
and bool(getattr(descriptor, "geo_compress", False))
34+
and isinstance(statistics, torch.Tensor)
35+
and statistics.dtype == torch.float32
36+
):
37+
return "float32"
38+
return "float64"
39+
840

941
def model_uses_graph_lower(model: Any) -> bool:
1042
"""Return whether a model's default lower uses ``NeighborGraph``.

deepmd/pt_expt/utils/serialization.py

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
from deepmd.dpmodel.utils.serialization import (
2727
traverse_model_dict,
2828
)
29+
from deepmd.pt_expt.model.graph_lower import (
30+
graph_edge_dtype,
31+
)
2932

3033
# ---------------------------------------------------------------------------
3134
# AOTInductor ``.pt2`` archive layout.
@@ -1015,28 +1018,6 @@ def _build_dynamic_shapes(
10151018
return (*base, None, None, None, None, None, None, None, None)
10161019

10171020

1018-
def _graph_edge_dtype(model: torch.nn.Module, lower_kind: str) -> str:
1019-
"""Return the graph edge-vector dtype encoded by the deployment artifact.
1020-
1021-
Geometrically compressed DPA1 with float32 descriptor statistics evaluates
1022-
both descriptor directions in float32 and therefore accepts float32
1023-
geometry directly. Other graph descriptors retain the model-agnostic
1024-
float64 geometry ABI.
1025-
"""
1026-
atomic_model = getattr(model, "atomic_model", None)
1027-
descriptor = getattr(atomic_model, "descriptor", None)
1028-
descriptor_block = getattr(descriptor, "se_atten", None)
1029-
statistics = getattr(descriptor_block, "mean", None)
1030-
if (
1031-
lower_kind in ("graph", "dpa1_canonical")
1032-
and bool(getattr(descriptor, "geo_compress", False))
1033-
and isinstance(statistics, torch.Tensor)
1034-
and statistics.dtype == torch.float32
1035-
):
1036-
return "float32"
1037-
return "float64"
1038-
1039-
10401021
def _supports_graph_export(model: torch.nn.Module) -> bool:
10411022
"""Whether the model has an exportable graph-lower implementation.
10421023
@@ -1173,7 +1154,7 @@ def _probe_has_message_passing(obj: object) -> bool | None:
11731154
# "graph" → NeighborGraph (atype, n_node, edge_index, edge_vec, edge_mask)
11741155
# The C++ loader branches on this to build the matching inputs.
11751156
meta["lower_input_kind"] = lower_kind
1176-
meta["graph_edge_dtype"] = _graph_edge_dtype(model, lower_kind)
1157+
meta["graph_edge_dtype"] = graph_edge_dtype(model, lower_kind)
11771158

11781159
# Model-level pair-type exclusion (``pair_exclude_types``): a list of
11791160
# ``[ti, tj]`` type pairs whose interaction is dropped. Exclusion is a

deepmd/utils/pt_checkpoint.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# SPDX-License-Identifier: LGPL-3.0-or-later
2+
"""Utilities shared by PyTorch checkpoint backends."""
3+
4+
from collections.abc import (
5+
Mapping,
6+
)
7+
from typing import (
8+
Any,
9+
)
10+
11+
12+
def detect_pt_checkpoint_backend(checkpoint: Any) -> str | None:
13+
"""Detect the parameter dialect of a raw PyTorch checkpoint.
14+
15+
Parameters
16+
----------
17+
checkpoint : Any
18+
A checkpoint payload or its unwrapped model state dictionary.
19+
20+
Returns
21+
-------
22+
str or None
23+
``"pt-expt"`` or ``"pt"`` when the parameter names identify one
24+
backend unambiguously, otherwise ``None``.
25+
"""
26+
state_dict = checkpoint
27+
if isinstance(state_dict, Mapping) and "model" in state_dict:
28+
state_dict = state_dict["model"]
29+
if not isinstance(state_dict, Mapping):
30+
return None
31+
32+
keys = tuple(key for key in state_dict if isinstance(key, str))
33+
34+
# Weight names are decisive. pt_expt DPA4 also contains ordinary
35+
# torch-native ``.bias`` parameters, so bias names cannot override a
36+
# clear ``.w`` versus ``.matrix`` distinction.
37+
has_pt_expt_weight = any(key.endswith(".w") for key in keys)
38+
has_pt_weight = any(key.endswith(".matrix") for key in keys)
39+
if has_pt_expt_weight or has_pt_weight:
40+
if has_pt_expt_weight == has_pt_weight:
41+
return None
42+
return "pt-expt" if has_pt_expt_weight else "pt"
43+
44+
# A lone ``.b`` is specific to pt_expt's NativeLayer. A lone ``.bias``
45+
# is not specific to pt because pt_expt models can contain torch-native
46+
# modules with that suffix, so it remains deliberately unclassified.
47+
has_pt_expt_bias = any(key.endswith(".b") for key in keys)
48+
has_pt_bias = any(key.endswith(".bias") for key in keys)
49+
if has_pt_expt_bias and not has_pt_bias:
50+
return "pt-expt"
51+
return None

doc/conf.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@
2727
)
2828

2929
sys.path.append(os.path.dirname(__file__))
30+
import github_linkcode
3031
import sphinx_contrib_exhale_multiproject # noqa: F401
3132

33+
linkcode_resolve = github_linkcode.linkcode_resolve
34+
3235
# -- Project information -----------------------------------------------------
3336

3437
project = "DeePMD-kit"
@@ -59,7 +62,7 @@
5962
"myst_nb",
6063
"sphinx.ext.autosummary",
6164
"sphinx.ext.mathjax",
62-
"sphinx.ext.viewcode",
65+
"sphinx.ext.linkcode",
6366
"sphinx.ext.imgconverter",
6467
"sphinx.ext.intersphinx",
6568
"sphinx.ext.napoleon",
@@ -277,5 +280,13 @@ def _cap_cli_secnumbers(app: Sphinx, doctree: nodes.document, docname: str) -> N
277280

278281

279282
def setup(app: Sphinx) -> dict[str, bool]:
283+
# AutoAPI records exact source locations without importing backend modules.
284+
# Reuse that metadata for commit-pinned GitHub links after AutoAPI's default
285+
# priority-500 ``builder-inited`` callback has populated the environment.
286+
app.connect(
287+
"builder-inited",
288+
github_linkcode.collect_autoapi_source_locations,
289+
priority=600,
290+
)
280291
app.connect("doctree-resolved", _cap_cli_secnumbers)
281292
return {"parallel_read_safe": True, "parallel_write_safe": True}

0 commit comments

Comments
 (0)