5555# ---------------------------------------------------------------------------
5656PT2_EXTRA_PREFIX = "model/extra/"
5757
58+ # Backend conversion supplies the source artifact's lower ABI. Concrete target
59+ # schemas pass through unchanged. PT SeZM's ``edge_vec`` identifies an edge-list
60+ # source contract rather than a pt_expt schema; the target model capabilities
61+ # determine whether that contract is materialized as NeighborGraph or dense
62+ # nlist input.
63+ _LOWER_INPUT_KINDS = frozenset (
64+ {
65+ "nlist" ,
66+ "graph" ,
67+ "dpa1_canonical" ,
68+ "dpa4c_canonical" ,
69+ "edge_vec" ,
70+ }
71+ )
72+
5873
5974def _strip_shape_assertions (graph_module : torch .nn .Module ) -> None :
6075 """Neutralise deferred shape-guard assertion nodes in an exported graph.
@@ -1284,7 +1299,8 @@ def serialize_from_file(model_file: str) -> dict:
12841299 dict
12851300 The serialized model data. If the archive contains
12861301 ``model_def_script.json`` (training config), it is included
1287- under the ``"model_def_script"`` key.
1302+ under the ``"model_def_script"`` key. ``lower_input_kind`` records
1303+ the concrete lower ABI from the artifact metadata.
12881304 """
12891305 if model_file .endswith (".pt2" ):
12901306 return _serialize_from_file_pt2 (model_file )
@@ -1294,10 +1310,20 @@ def serialize_from_file(model_file: str) -> dict:
12941310
12951311def _serialize_from_file_pte (model_file : str ) -> dict :
12961312 """Serialize a .pte model file to a dictionary."""
1297- extra_files = {"model.json" : "" , "model_def_script.json" : "" }
1313+ extra_files = {
1314+ "model.json" : "" ,
1315+ "model_def_script.json" : "" ,
1316+ "metadata.json" : "" ,
1317+ }
12981318 torch .export .load (model_file , extra_files = extra_files )
12991319 model_dict = json .loads (extra_files ["model.json" ])
13001320 model_dict = _json_to_numpy (model_dict )
1321+ metadata = (
1322+ json .loads (extra_files ["metadata.json" ]) if extra_files ["metadata.json" ] else {}
1323+ )
1324+ model_dict ["lower_input_kind" ] = metadata .get (
1325+ "lower_input_kind" , model_dict .get ("lower_input_kind" , "nlist" )
1326+ )
13011327 if extra_files ["model_def_script.json" ]:
13021328 model_dict ["model_def_script" ] = json .loads (
13031329 extra_files ["model_def_script.json" ]
@@ -1315,6 +1341,7 @@ def _serialize_from_file_pt2(model_file: str) -> dict:
13151341
13161342 model_json_entry = PT2_EXTRA_PREFIX + "model.json"
13171343 model_def_script_entry = PT2_EXTRA_PREFIX + "model_def_script.json"
1344+ metadata_entry = PT2_EXTRA_PREFIX + "metadata.json"
13181345 with zipfile .ZipFile (model_file , "r" ) as zf :
13191346 names = zf .namelist ()
13201347 if model_json_entry not in names :
@@ -1325,8 +1352,15 @@ def _serialize_from_file_pt2(model_file: str) -> dict:
13251352 model_def_script_json = ""
13261353 if model_def_script_entry in names :
13271354 model_def_script_json = zf .read (model_def_script_entry ).decode ("utf-8" )
1355+ metadata_json = ""
1356+ if metadata_entry in names :
1357+ metadata_json = zf .read (metadata_entry ).decode ("utf-8" )
13281358 model_dict = json .loads (model_json )
13291359 model_dict = _json_to_numpy (model_dict )
1360+ metadata = json .loads (metadata_json ) if metadata_json else {}
1361+ model_dict ["lower_input_kind" ] = metadata .get (
1362+ "lower_input_kind" , model_dict .get ("lower_input_kind" , "nlist" )
1363+ )
13301364 if model_def_script_json :
13311365 model_dict ["model_def_script" ] = json .loads (model_def_script_json )
13321366 return model_dict
@@ -1506,19 +1540,22 @@ def _dpa4_kernel_levels_for_target(
15061540 os .environ [name ] = value
15071541
15081542
1509- def _resolve_lower_kind ( model_file : str , data : dict , lower_kind : str ) -> str :
1510- """Resolve ``lower_kind="auto"`` to a concrete lower-forward schema .
1543+ def _select_graph_lower_kind ( data : dict , * , allow_canonical : bool ) -> str | None :
1544+ """Select the graph schema supported by the target model .
15111545
1512- ``"auto"`` selects the graph lower for a graph-lower model whose graph
1513- implementation is exportable to ``.pt2`` and the dense nlist lower for
1514- everything else. Eligible compressed DPA1 and DPA4C energy models select
1515- their compact canonical graph schemas. Any explicit lower kind is returned
1516- unchanged.
1546+ Parameters
1547+ ----------
1548+ data : dict
1549+ Serialized model data.
1550+ allow_canonical : bool
1551+ Whether an eligible compact canonical schema may replace NeighborGraph.
1552+
1553+ Returns
1554+ -------
1555+ str or None
1556+ The supported graph schema, or ``None`` when the model uses the dense
1557+ lower.
15171558 """
1518- if lower_kind != "auto" :
1519- return lower_kind
1520- if not model_file .endswith (".pt2" ) or data ["model" ].get ("type" ) == "spin_ener" :
1521- return "nlist"
15221559 from deepmd .pt_expt .model .graph_lower import (
15231560 model_uses_graph_lower ,
15241561 )
@@ -1527,7 +1564,9 @@ def _resolve_lower_kind(model_file: str, data: dict, lower_kind: str) -> str:
15271564 )
15281565
15291566 model = BaseModel .deserialize (data ["model" ])
1530- if model_uses_graph_lower (model ) and _supports_graph_export (model ):
1567+ if not (model_uses_graph_lower (model ) and _supports_graph_export (model )):
1568+ return None
1569+ if allow_canonical :
15311570 from deepmd .pt_expt .kernels .cuda .dpa1 .canonical import (
15321571 canonical_model_eligible as dpa1_canonical_eligible ,
15331572 )
@@ -1539,8 +1578,62 @@ def _resolve_lower_kind(model_file: str, data: dict, lower_kind: str) -> str:
15391578 return "dpa4c_canonical"
15401579 if dpa1_canonical_eligible (model ):
15411580 return "dpa1_canonical"
1542- return "graph"
1543- return "nlist"
1581+ return "graph"
1582+
1583+
1584+ def _resolve_lower_kind (model_file : str , data : dict , lower_kind : str ) -> str :
1585+ """Resolve ``lower_kind="auto"`` to a concrete lower-forward schema.
1586+
1587+ ``"auto"`` selects the graph lower for a graph-lower model whose graph
1588+ implementation is exportable to ``.pt2`` and the dense nlist lower for
1589+ everything else. Eligible compressed DPA1 and DPA4C energy models select
1590+ their compact canonical graph schemas. Any explicit lower kind is returned
1591+ unchanged.
1592+ """
1593+ if lower_kind != "auto" :
1594+ return lower_kind
1595+ if not model_file .endswith (".pt2" ) or data ["model" ].get ("type" ) == "spin_ener" :
1596+ return "nlist"
1597+ return _select_graph_lower_kind (data , allow_canonical = True ) or "nlist"
1598+
1599+
1600+ def _resolve_target_lower_kind (model_file : str , data : dict , lower_kind : str ) -> str :
1601+ """Resolve a source lower ABI to a concrete pt_expt export schema."""
1602+ source_lower_kind = _resolve_lower_kind (model_file , data , lower_kind )
1603+ if source_lower_kind not in _LOWER_INPUT_KINDS :
1604+ raise ValueError (
1605+ f"Unsupported lower_kind { source_lower_kind !r} ; expected one of "
1606+ f"{ sorted (_LOWER_INPUT_KINDS )} ."
1607+ )
1608+ target_lower_kind = source_lower_kind
1609+ if source_lower_kind == "edge_vec" :
1610+ target_lower_kind = (
1611+ _select_graph_lower_kind (data , allow_canonical = False ) or "nlist"
1612+ )
1613+
1614+ if data ["model" ].get ("type" ) == "native_spin" and target_lower_kind not in (
1615+ "graph" ,
1616+ "dpa4c_canonical" ,
1617+ ):
1618+ if lower_kind == "auto" :
1619+ if not model_file .endswith (".pt2" ):
1620+ raise ValueError (
1621+ "automatic lower selection for native-spin models requires "
1622+ "a .pt2 output because native-spin models do not implement "
1623+ "the dense nlist lower"
1624+ )
1625+ raise ValueError (
1626+ "automatic lower selection found no exportable graph lower for "
1627+ "this native-spin model, which does not implement the dense "
1628+ "nlist lower"
1629+ )
1630+ raise ValueError (
1631+ "native-spin models implement only the NeighborGraph and compact "
1632+ f"canonical lowers (got lower_kind={ target_lower_kind !r} ); use "
1633+ "lower_kind='graph', or lower_kind='dpa4c_canonical' for an "
1634+ "eligible compressed DPA4C model, with a .pt2 output."
1635+ )
1636+ return target_lower_kind
15441637
15451638
15461639def deserialize_to_file (
@@ -1581,34 +1674,19 @@ def deserialize_to_file(
15811674 (``atype``/``n_node``/``edge_index``/``edge_vec``/``edge_mask`` and
15821675 the destination/source CSR views) with a DYNAMIC edge axis ``E``
15831676 (``Dim("nedge", min=2)``), so the artifact accepts any system size.
1584- ``"auto"`` (used by ``convert-backend``) resolves to ``"graph"`` for an
1585- exportable graph-lower ``.pt2`` and ``"nlist"`` otherwise (see
1586- :func:`_resolve_lower_kind`). A graph lower preserves the selected
1587- inference operators and always includes the per-atom virial. DPA1 and
1588- DPA4C graph pipelines use ``DP_CUDA_INFER >= 2``; DPA4 ``.pt2`` follows
1589- its PT freeze defaults unless the environment explicitly selects other
1590- levels.
1677+ ``"auto"`` resolves to ``"graph"`` for an exportable graph-lower
1678+ ``.pt2`` and ``"nlist"`` otherwise (see :func:`_resolve_lower_kind`).
1679+ Backend conversion passes the source artifact's concrete lower kind;
1680+ compatible source ABIs are mapped to the target's native schema while
1681+ preserving their execution semantics. A graph lower preserves the
1682+ selected inference operators and always includes the per-atom virial.
1683+ DPA1 and DPA4C graph pipelines use ``DP_CUDA_INFER >= 2``; DPA4
1684+ ``.pt2`` follows its PT freeze defaults unless the environment
1685+ explicitly selects other levels.
15911686 The selected schema is recorded as ``lower_input_kind`` in
15921687 ``metadata.json``.
15931688 """
1594- lower_kind = _resolve_lower_kind (model_file , data , lower_kind )
1595- if data ["model" ].get ("type" ) == "native_spin" and lower_kind not in (
1596- "graph" ,
1597- "dpa4c_canonical" ,
1598- ):
1599- # Native-spin models implement the NeighborGraph lower and, for an
1600- # eligible compressed DPA4C, the compact canonical one; the dense/nlist
1601- # trace branch does not exist for them. The public freeze layer
1602- # resolves this before calling here (see
1603- # deepmd.pt_expt.entrypoints.main.freeze); this guard pins the
1604- # contract for direct programmatic callers with a clear error instead
1605- # of an opaque trace-time failure.
1606- raise ValueError (
1607- "native-spin models implement only the NeighborGraph and compact "
1608- f"canonical lowers (got lower_kind={ lower_kind !r} ); use "
1609- "lower_kind='graph', or lower_kind='dpa4c_canonical' for an "
1610- "eligible compressed DPA4C model, with a .pt2 output."
1611- )
1689+ lower_kind = _resolve_target_lower_kind (model_file , data , lower_kind )
16121690 uses_dpa4_defaults = model_file .endswith (".pt2" ) and _uses_dpa4_kernel_defaults (
16131691 data ["model" ]
16141692 )
0 commit comments