-
Notifications
You must be signed in to change notification settings - Fork 643
fix(pt-expt): preserve lower semantics in backend conversion #5975
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ | |
| ) | ||
|
|
||
|
|
||
| def test_convert_backend_automatically_selects_lower_kind( | ||
| def test_convert_backend_preserves_default_dense_lower_kind( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| captured: dict[str, object] = {} | ||
|
|
@@ -46,5 +46,65 @@ def detect_backend(path: str) -> type[InputBackend] | type[OutputBackend]: | |
|
|
||
| convert_backend(INPUT="model.input", OUTPUT="model.output") | ||
|
|
||
| assert captured["lower_kind"] == "auto" | ||
| assert captured["lower_kind"] == "nlist" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Blocking. The test that would prevent this bug from coming back has not been committed. The bug's essence is "a dense-trained model came out of the conversion as graph-native". Reproducing it requires the intersection of three conditions: going through The old test crossed the first condition only, and because it used stub backends The PR description reports exactly the right check ("real nonzero- Two smaller coverage notes while you are in here, not blocking on their own: the no-op path of the new |
||
| assert captured["do_atomic_virial"] is False | ||
|
|
||
|
|
||
| def test_convert_backend_preserves_graph_lower_kind( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| captured: dict[str, object] = {} | ||
|
|
||
| class InputBackend: | ||
| name = "input" | ||
|
|
||
| @staticmethod | ||
| def serialize_hook(path: str) -> dict[str, str]: | ||
| return {"path": path, "lower_input_kind": "graph"} | ||
|
|
||
| class OutputBackend: | ||
| name = "output" | ||
|
|
||
| @staticmethod | ||
| def deserialize_hook( | ||
| path: str, | ||
| data: dict[str, str], | ||
| *, | ||
| lower_kind: str = "nlist", | ||
| ) -> None: | ||
| captured.update(path=path, data=data, lower_kind=lower_kind) | ||
|
|
||
| def detect_backend(path: str) -> type[InputBackend] | type[OutputBackend]: | ||
| return InputBackend if path.endswith(".input") else OutputBackend | ||
|
|
||
| monkeypatch.setattr(Backend, "detect_backend_by_model", detect_backend) | ||
|
|
||
| convert_backend(INPUT="model.input", OUTPUT="model.output") | ||
|
|
||
| assert captured["lower_kind"] == "graph" | ||
|
|
||
|
|
||
| def test_convert_backend_rejects_graph_for_dense_only_output( | ||
| monkeypatch: pytest.MonkeyPatch, | ||
| ) -> None: | ||
| class InputBackend: | ||
| name = "input" | ||
|
|
||
| @staticmethod | ||
| def serialize_hook(path: str) -> dict[str, str]: | ||
| return {"path": path, "lower_input_kind": "graph"} | ||
|
|
||
| class OutputBackend: | ||
| name = "output" | ||
|
|
||
| @staticmethod | ||
| def deserialize_hook(path: str, data: dict[str, str]) -> None: | ||
| raise AssertionError("dense-only output hook must not be called") | ||
|
|
||
| def detect_backend(path: str) -> type[InputBackend] | type[OutputBackend]: | ||
| return InputBackend if path.endswith(".input") else OutputBackend | ||
|
|
||
| monkeypatch.setattr(Backend, "detect_backend_by_model", detect_backend) | ||
|
|
||
| with pytest.raises(ValueError, match="Cannot preserve graph lower"): | ||
| convert_backend(INPUT="model.input", OUTPUT="model.output") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking. Defaulting to
"nlist"for every source that does not carrylower_input_kindregresses two cases.(a) The automatic graph selection is lost for all non-pt_expt sources. Only pt_expt
.pt2/.ptepopulate this key, so.pth,.pb,.dp, jax and paddle sources are now hard-pinned to"nlist". The"auto"path did not consult artifact metadata at all --_resolve_lower_kinddeserializesdata["model"]and asksmodel_uses_graph_lower(model) and _supports_graph_export(model), i.e. a property of the model that is available regardless of source format. That is why.pth/.dp->.pt2of a graph-eligible model produced a graph ordpa1_canonicalartifact, which is what 2aee81c (#5758) added it for. The docstring's premise that "Formats without explicit lower metadata are dense neighbor-list models" does not hold for.pth/.dp.The genuinely broken case in #5973 is narrower: a dense-trained model whose reconstructed dpmodel happens to advertise graph support. For a graph-native descriptor (DPA4C has no dense lower at all --
disable_graph_lower()raises) the old resolution was the only correct answer, and it is now unreachable.(b) Native-spin models can no longer be converted to
.pt2at all.deepmd/dpmodel/model/native_spin_model.py:261stampstype="native_spin", anddeepmd/pt_expt/utils/serialization.py:1344raises unconditionally whendata["model"]["type"] == "native_spin"andlower_kind != "graph"._resolve_lower_kindshort-circuits only on"auto", so the new concrete"nlist"passes straight through into that guard.dp convert-backend model.dp model.pt2for a model trained withspin.scheme == "native"now aborts withValueError: native-spin models implement only the NeighborGraph lower, where it previously succeeded. Native spin is graph-lower-only by construction, so"nlist"is never a valid default for it.Suggestion: keep the model-derived resolution as the fallback when the source dict carries no
lower_input_kind(i.e. pass"auto"through in that case) and use the metadata value only when it is actually present. That preserves the source's semantics where the source states them, and keeps the model-property answer where it does not.