Skip to content

feat(pt/dpa4): add nvalchemi-toolkit warpper for DPA4#5568

Draft
OutisLi wants to merge 2 commits into
deepmodeling:masterfrom
OutisLi:pr/nvtoolkit-dpa4
Draft

feat(pt/dpa4): add nvalchemi-toolkit warpper for DPA4#5568
OutisLi wants to merge 2 commits into
deepmodeling:masterfrom
OutisLi:pr/nvtoolkit-dpa4

Conversation

@OutisLi

@OutisLi OutisLi commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

Release Notes

  • New Features

    • Integrated nvalchemi-toolkit support for high-performance inference of DPA-4/SeZM models, with optional compiled execution via .pt2 packages
    • Added example scripts demonstrating single-point calculations and molecular dynamics simulations (NVE, NVT, geometry relaxation) using nvalchemi
  • Documentation

    • Added comprehensive usage guide for nvalchemi-toolkit integration with DeePMD-kit, covering model loading, batching, and performance tuning

@dosubot dosubot Bot added the new feature label Jun 21, 2026
@OutisLi OutisLi marked this pull request as draft June 21, 2026 05:03
@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3608b1f4-0fc9-4733-bd9a-8825029b6a7b

📥 Commits

Reviewing files that changed from the base of the PR and between 7ea521f and f62b887.

📒 Files selected for processing (3)
  • doc/third-party/index.rst
  • doc/third-party/nvalchemi.md
  • examples/water/dpa4/nvalchemi/README.md
💤 Files with no reviewable changes (1)
  • doc/third-party/nvalchemi.md
✅ Files skipped from review due to trivial changes (2)
  • doc/third-party/index.rst
  • examples/water/dpa4/nvalchemi/README.md

📝 Walkthrough

Walkthrough

Adds deepmd.pt.nvalchemi, a new subpackage exposing DPA4Wrapper (aliased as SeZMWrapper), which bridges DeePMD-kit SeZM/DPA-4 energy models to the nvalchemi-toolkit interface. Includes four runnable water-system example scripts, a unit test suite for wrapper correctness, and reference documentation.

Changes

DPA4Wrapper nvalchemi adapter

Layer / File(s) Summary
Package guard, scaffold, and project config
deepmd/pt/nvalchemi/__init__.py, deepmd/pt/nvalchemi/dpa4wrapper.py, pyproject.toml
__init__.py raises a user-facing ImportError with installation instructions when nvalchemi is missing and re-exports DPA4Wrapper/SeZMWrapper. The wrapper module establishes imports and __all__. pyproject.toml adds the nvalchemi optional-dependency group and Ruff T20/TID253 exemptions for example scripts.
DPA4Wrapper construction, type mapping, and input adaptation
deepmd/pt/nvalchemi/dpa4wrapper.py
__init__ and _configure build the Z-to-type lookup buffer, set rcut/descriptor dimensions, select active outputs, and construct ModelConfig. _build_z_to_type resolves element symbols to atomic numbers. _atype, _edge_schema, and adapt_input translate nvalchemi COO neighbor-list batches and atomic numbers into SeZM's edge-based lower interface. embedding_shapes exposes descriptor metadata for the .pt backend.
Output adaptation, forward dispatch, embeddings, and checkpoint loading
deepmd/pt/nvalchemi/dpa4wrapper.py
adapt_output segment-sums per-atom energies and computes optional Cauchy stress from virial divided by cell volume. forward dispatches to forward_lower (.pt) or _run_pt2 (.pt2). compute_embeddings produces node and graph embeddings via forward_common_lower. from_checkpoint/_from_pt load .pt files with multi-task head support; _from_pt2 loads frozen AOTInductor archives. SeZMWrapper is an alias for DPA4Wrapper.
Unit tests for DPA4Wrapper
source/tests/pt/model/test_sezm_nvalchemi.py
Conditionally imports nvalchemi and skips if absent. _ClearDefaultDeviceTestCase disables the test harness's sentinel CUDA device. TestSeZMNVAlchemiWrapper validates periodic/non-periodic energy-force parity, heterogeneous-batch segmentation correctness, embedding shapes and finiteness, custom atomic-number-to-type mapping equivalence, and ValueError for unknown atomic numbers.
Runnable example scripts
examples/water/dpa4/nvalchemi/single_point.py, examples/water/dpa4/nvalchemi/run_nve.py, examples/water/dpa4/nvalchemi/run_nvt.py, examples/water/dpa4/nvalchemi/relax.py
Four CLI scripts demonstrating the wrapper: single_point.py runs single-frame inference and prints energy/forces/stress; run_nve.py runs microcanonical MD and reports energy drift; run_nvt.py runs Langevin NVT MD; relax.py runs FIRE2 geometry optimization with fmax-based convergence. Each script includes a load_frame helper mapping DeePMD-kit type metadata to ASE atomic numbers.
Reference documentation and examples README
doc/third-party/index.rst, doc/third-party/nvalchemi.md, examples/water/dpa4/nvalchemi/README.md
nvalchemi.md documents installation, checkpoint loading (.pt/.pt2, multi-task heads), single-point evaluation, MD integration patterns, geometry optimization, model_config capabilities, units/conventions, and limitations. index.rst adds the page to the toctree. README.md provides prerequisites, quick-start commands, and operational notes for the bundled water examples.

Sequence Diagram(s)

sequenceDiagram
  participant User as User Script
  participant DPA4Wrapper
  participant NeighborListHook
  participant SeZMModel

  User->>DPA4Wrapper: from_checkpoint(path, device, compute_stress=True)
  DPA4Wrapper-->>User: wrapper instance

  User->>NeighborListHook: register BEFORE_COMPUTE hook
  User->>DPA4Wrapper: forward(AtomicData/Batch)
  DPA4Wrapper->>DPA4Wrapper: _atype(atomic_numbers) → type indices
  DPA4Wrapper->>DPA4Wrapper: _edge_schema(neighbors, shifts, cell) → edge_index, edge_vec
  DPA4Wrapper->>DPA4Wrapper: adapt_input() → coord, atype, edges, charge_spin
  DPA4Wrapper->>SeZMModel: forward_lower(coord, atype, edges, ...)
  SeZMModel-->>DPA4Wrapper: atom_energy, extended_force, extended_virial
  DPA4Wrapper->>DPA4Wrapper: adapt_output() → energy, forces, Cauchy stress
  DPA4Wrapper-->>User: ModelOutputs (energy, forces, stress)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • njzjz
  • iProzd
  • wanghan-iapcm
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title mentions 'nvalchemi-toolkit warpper' but the actual changes implement a DPA4Wrapper adapter with comprehensive nvalchemi integration including documentation, examples, and tests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@deepmd/pt/nvalchemi/wrapper.py`:
- Line 485: Before accessing model_params["model_dict"][head] at line 485,
validate that the head parameter exists as a key in the model_dict dictionary.
If the head is not found, raise a ValueError (instead of letting a raw KeyError
propagate) that includes a descriptive message with the list of available heads
to provide better debugging information and a cleaner API contract.
- Around line 274-283: The conditional check on line 274 that evaluates `if
neighbor_list_shifts is not None and cell is not None` allows incomplete
periodic metadata to pass through silently, causing incorrect physics when one
field is provided without the other. Add validation logic before this
conditional to check that both neighbor_list_shifts and cell are either both
present or both absent, and raise an informative error if they are inconsistent
(one is None while the other is not). This ensures that periodic boundary
condition calculations fail fast with a clear error message rather than silently
using incorrect zero shifts.
- Around line 366-367: The stress computation at the line where virial is
divided by volume does not validate whether the volume is valid before
performing the division, which can result in inf or nan values for singular or
degenerate cells. Add a validation check before computing the stress output that
ensures the volume tensor contains only valid (non-zero, non-NaN, non-inf)
values, and raise a clear and informative error if any invalid volumes are
detected. This check should occur after computing the volume using
torch.det(cell.to(virial.dtype)).abs().view(n_graph, 1, 1) but before the
division in the stress calculation.

In `@examples/water/dpa4/nvalchemi/relax.py`:
- Around line 95-96: The argument parser for --log-every lacks validation and
the optimization loop does not properly handle remaining steps. Add validation
to the argument parser around the --log-every definition to ensure the value is
greater than 0. Then examine the stepping logic around lines 141-151 where the
loop increments by log_every chunks, and modify it to ensure all remaining steps
up to --max-steps are executed even when --max-steps is not perfectly divisible
by log_every, preventing under-run of the optimization budget.

In `@examples/water/dpa4/nvalchemi/run_nve.py`:
- Around line 114-115: Add validation for the --log-every argument to ensure it
is greater than zero, preventing runtime crashes from zero or negative values.
Additionally, fix the stepping logic in the simulation loop to handle cases
where the total number of steps is not evenly divisible by log_every. Instead of
only advancing in fixed log_every chunks, explicitly run any remainder steps at
the end of the loop to ensure all requested steps are executed (e.g., if 205
steps are requested with log_every 20, ensure 5 additional steps are run after
the main loop completes). Update the argument parser to include validation for
--log-every and modify the stepping logic around the main simulation iteration
to calculate and execute remaining steps.

In `@examples/water/dpa4/nvalchemi/run_nvt.py`:
- Around line 104-105: Add validation for the log_every argument to ensure it is
a positive integer (greater than zero) in the argument parser definition.
Additionally, fix the NVT integration loop around line 168-174 to properly
handle cases where the total number of steps is not evenly divisible by
log_every. Ensure the remainder steps are executed after the main chunked loop
completes so that the simulation runs for the full requested number of steps
rather than truncating early.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8c562042-bbdf-4ddd-806e-688cf8ca0c38

📥 Commits

Reviewing files that changed from the base of the PR and between f0cf2b9 and 11ffa62.

📒 Files selected for processing (11)
  • deepmd/pt/nvalchemi/__init__.py
  • deepmd/pt/nvalchemi/wrapper.py
  • doc/inference/index.rst
  • doc/inference/nvalchemi.md
  • examples/water/dpa4/nvalchemi/README.md
  • examples/water/dpa4/nvalchemi/relax.py
  • examples/water/dpa4/nvalchemi/run_nve.py
  • examples/water/dpa4/nvalchemi/run_nvt.py
  • examples/water/dpa4/nvalchemi/single_point.py
  • pyproject.toml
  • source/tests/pt/model/test_sezm_nvalchemi.py

Comment thread deepmd/pt/nvalchemi/wrapper.py Outdated
Comment thread deepmd/pt/nvalchemi/dpa4wrapper.py
Comment thread deepmd/pt/nvalchemi/dpa4wrapper.py
Comment thread examples/water/dpa4/nvalchemi/relax.py
Comment thread examples/water/dpa4/nvalchemi/run_nve.py
Comment thread examples/water/dpa4/nvalchemi/run_nvt.py
@codecov

codecov Bot commented Jun 21, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 221 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.01%. Comparing base (c5c57d6) to head (f62b887).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/pt/nvalchemi/dpa4wrapper.py 0.00% 216 Missing ⚠️
deepmd/pt/nvalchemi/__init__.py 0.00% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5568      +/-   ##
==========================================
- Coverage   82.18%   82.01%   -0.18%     
==========================================
  Files         898      901       +3     
  Lines      103576   103799     +223     
  Branches     4434     4435       +1     
==========================================
+ Hits        85129    85130       +1     
- Misses      17056    17278     +222     
  Partials     1391     1391              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@OutisLi OutisLi force-pushed the pr/nvtoolkit-dpa4 branch from 11ffa62 to 7ea521f Compare June 21, 2026 09:12
@OutisLi OutisLi marked this pull request as ready for review June 21, 2026 09:12
@dosubot dosubot Bot added the enhancement label Jun 21, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add nvalchemi-toolkit adapter for DPA-4/SeZM PyTorch models
✨ Enhancement 📝 Documentation 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

Description

• Add DPA4Wrapper to run DeePMD SeZM/DPA-4 models inside nvalchemi-toolkit engines.
• Document installation and end-to-end workflows (inference, MD, relaxation) with runnable examples.
• Add parity-focused tests to validate energy/forces/stress equivalence to native SeZM forward.
Diagram

graph TD
  A["nvalchemi engines"] --> B["Batch / AtomicData"] --> C["compute_neighbors"] --> D["DPA4Wrapper"] --> E["SeZMModel / .pt2 runner"] --> F["Outputs: E/F/stress"]
  G["Checkpoint (.pt/.pt2)"] --> D
  subgraph Legend
    direction LR
    _ext{{"External"}} ~~~ _mod["Module"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Energy-only + nvalchemi autograd forces
  • ➕ Simpler output contract (only energy required)
  • ➕ Potentially less wrapper logic for forces/stress mapping
  • ➖ Likely slower (backprop through positions every step)
  • ➖ More fragile with respect to compiled/frozen backends and custom internal SeZM force paths
  • ➖ Stress would still require custom handling
2. Wrap as an ASE calculator and use ASE MD/optimizers
  • ➕ Leverages a widely used ecosystem and established APIs
  • ➕ Avoids nvalchemi-specific neighbor-list conventions
  • ➖ Misses nvalchemi’s GPU-batched neighbor list/integrator performance focus
  • ➖ Does not address the explicit goal of enabling nvalchemi workflows
3. Generic adapter layer for multiple DeePMD PT models
  • ➕ Could reduce future work when adding more model families
  • ➕ Centralizes neighbor-list schema translation
  • ➖ Higher up-front abstraction cost
  • ➖ Hard to unify model-specific outputs (e.g., virial/stress conventions, embeddings)

Recommendation: Keep the PR’s approach: calling SeZM’s sparse-edge lower interface and returning forces/stress directly is the most performance- and correctness-aligned integration for MD (no per-step autograd through positions, and it matches SeZM’s own conventions). The wrapper is also appropriately scoped to DPA-4/SeZM while still offering a clean extension point (checkpoint loader + input/output adaptation) if additional model families are added later.

Files changed (11) +2074 / -0

Enhancement (2) +706 / -0
__init__.pyAdd optional nvalchemi bridge package with friendly import error +41/-0

Add optional nvalchemi bridge package with friendly import error

• Introduces the new deepmd.pt.nvalchemi subpackage and guards imports so missing nvalchemi-toolkit yields an actionable installation message. Exposes DPA4Wrapper/SeZMWrapper as the public API.

deepmd/pt/nvalchemi/init.py

dpa4wrapper.pyImplement DPA4Wrapper adapting SeZM/DPA-4 models to nvalchemi BaseModelMixin +665/-0

Implement DPA4Wrapper adapting SeZM/DPA-4 models to nvalchemi BaseModelMixin

• Adds a full adapter that converts nvalchemi COO neighbor lists (with optional PBC shifts) into SeZM’s sparse edge inputs, and maps SeZM outputs into nvalchemi’s energy/forces/stress. Supports loading from training checkpoints (.pt, including multi-task head selection) and frozen AOTInductor packages (.pt2), plus optional descriptor embedding extraction for the .pt backend.

deepmd/pt/nvalchemi/dpa4wrapper.py

Tests (1) +391 / -0
test_sezm_nvalchemi.pyAdd parity and validation tests for the nvalchemi SeZM wrapper +391/-0

Add parity and validation tests for the nvalchemi SeZM wrapper

• Adds a comprehensive test suite (skipped if nvalchemi-toolkit is absent) validating wrapper parity with native SeZM forward for periodic, non-periodic, and heterogeneous batches. Tests embedding shapes, custom atomic-number mappings, and clear errors for unknown species, while working around the PT test default-device sentinel.

source/tests/pt/model/test_sezm_nvalchemi.py

Documentation (7) +972 / -0
index.rstLink nvalchemi inference docs from inference index +1/-0

Link nvalchemi inference docs from inference index

• Adds the nvalchemi documentation page to the inference documentation table of contents.

doc/inference/index.rst

nvalchemi.mdAdd nvalchemi-toolkit user guide for inference, MD, and relaxation +211/-0

Add nvalchemi-toolkit user guide for inference, MD, and relaxation

• Documents installation via the new deepmd-kit extra, model loading (.pt and .pt2), and performance knobs (DP_COMPILE_INFER/DP_TRITON_INFER). Provides end-to-end examples for single-point evaluation, MD (NVE/NVT), and FIRE geometry optimization, including units/conventions and limitations.

doc/inference/nvalchemi.md

README.mdAdd runnable nvalchemi examples README for DPA-4/SeZM +76/-0

Add runnable nvalchemi examples README for DPA-4/SeZM

• Introduces a guide for running the included scripts, prerequisites, and quick-start commands. Clarifies units, type-map behavior, and stress requirements.

examples/water/dpa4/nvalchemi/README.md

relax.pyAdd FIRE2 geometry-optimization example using DPA4Wrapper +160/-0

Add FIRE2 geometry-optimization example using DPA4Wrapper

• Adds a standalone script that loads a DeePMD dataset frame, builds an nvalchemi Batch, rebuilds neighbor lists via NeighborListHook, and runs FIRE2 until an fmax threshold is met. Reports convergence and per-atom energy change.

examples/water/dpa4/nvalchemi/relax.py

run_nve.pyAdd NVE MD example driven by DPA4Wrapper with energy-drift reporting +188/-0

Add NVE MD example driven by DPA4Wrapper with energy-drift reporting

• Adds a velocity-Verlet NVE script that seeds Maxwell–Boltzmann velocities, recomputes neighbor lists each step, and prints thermodynamic diagnostics. Computes total-energy drift per atom as an integration-quality indicator.

examples/water/dpa4/nvalchemi/run_nve.py

run_nvt.pyAdd Langevin NVT MD example driven by DPA4Wrapper +183/-0

Add Langevin NVT MD example driven by DPA4Wrapper

• Adds a canonical-ensemble MD script using NVTLangevin with configurable friction and random seed. Uses NeighborListHook for per-step neighbor rebuilds and logs potential energy, temperature, and max force.

examples/water/dpa4/nvalchemi/run_nvt.py

single_point.pyAdd single-point evaluation example (energy/forces/stress) +153/-0

Add single-point evaluation example (energy/forces/stress)

• Adds a minimal script to load a checkpoint, compute neighbors, and evaluate energy, forces, and Cauchy stress for one frame. Includes pressure reporting and stress tensor printing.

examples/water/dpa4/nvalchemi/single_point.py

Other (1) +5 / -0
pyproject.tomlAdd nvalchemi optional dependency extra and relax lint rules for examples +5/-0

Add nvalchemi optional dependency extra and relax lint rules for examples

• Defines a new optional dependency extra (deepmd-kit[nvalchemi]) pulling in nvalchemi-toolkit. Updates lint configuration to allow direct-run example scripts to use top-level imports and prints.

pyproject.toml

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Sparse edges passed as nlist 🐞 Bug ≡ Correctness
Description
DPA4Wrapper.forward passes a COO edge_index (shape (2, E)) into SeZMModel.forward_lower where
the model’s neighbor-list formatting expects a 3D nlist tensor (nf, nloc, nsel) and unpacks
nlist.shape into three values. This will raise at runtime when the model formats the neighbor
list, blocking energy/force/stress evaluation through nvalchemi.
Code

deepmd/pt/nvalchemi/dpa4wrapper.py[R428-438]

+            need_virial = "stress" in self.model_config.active_outputs
+            model_ret = self.model.forward_lower(
+                model_inputs["coord"],
+                model_inputs["atype"],
+                model_inputs["edge_index"],
+                model_inputs["edge_vec"],
+                model_inputs["edge_scatter_index"],
+                model_inputs["edge_mask"],
+                do_atomic_virial=need_virial,
+                charge_spin=model_inputs["charge_spin"],
+            )
Evidence
The wrapper constructs a 2D COO edge_index and passes it into SeZMModel.forward_lower as the
third positional argument. In the SeZM implementation, neighbor-list formatting expects a 3D nlist
and immediately unpacks nlist.shape into three values, which is incompatible with a (2, E)
tensor.

deepmd/pt/nvalchemi/dpa4wrapper.py[334-341]
deepmd/pt/nvalchemi/dpa4wrapper.py[420-439]
deepmd/pt/model/model/sezm_model.py[1441-1511]
deepmd/pt/model/model/make_model.py[408-467]
source/tests/pt/model/test_sezm_nvalchemi.py[61-77]
source/tests/pt/model/test_sezm_nvalchemi.py[103-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`DPA4Wrapper.forward()` currently forwards COO edge-list tensors to `SeZMModel.forward_lower(...)` as positional args, but the SeZM lower interface in this repo expects a DeePMD padded neighbor list (`nlist`) and related inputs. This causes a runtime failure when SeZM formats the neighbor list.

## Issue Context
- The wrapper builds `edge_index` as `(2, E)` and passes it as the third positional arg to `forward_lower`.
- SeZM’s neighbor list formatting path expects `nlist.shape` to unpack into `(nf, nloc, nnei)`.

## Fix Focus Areas
- deepmd/pt/nvalchemi/dpa4wrapper.py[334-341]
- deepmd/pt/nvalchemi/dpa4wrapper.py[420-439]
- deepmd/pt/model/model/sezm_model.py[1441-1511]
- deepmd/pt/model/model/make_model.py[408-467]

## Suggested fix approach
Choose one:
1) **Convert nvalchemi COO neighbor list -> DeePMD padded `nlist` (+ `mapping`)** and call the existing `SeZMModel.forward_lower(extended_coord, extended_atype, nlist, mapping, ...)`.
2) **Add a supported SeZM entrypoint for sparse edges** (e.g., `forward_lower_with_edges(edge_index, edge_vec, edge_mask, ...)` + an exportable equivalent for `.pt2`) and update `DPA4Wrapper` to call that API.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Unsupported embedding_only argument 🐞 Bug ≡ Correctness
Description
DPA4Wrapper.compute_embeddings passes embedding_only=True into SeZMModel.forward_common_lower,
but forward_common_lower in this repo has no embedding_only parameter. This will raise
TypeError when compute_embeddings() is used.
Code

deepmd/pt/nvalchemi/dpa4wrapper.py[R493-502]

+        ret = self.model.forward_common_lower(
+            model_inputs["coord"],
+            model_inputs["atype"],
+            model_inputs["edge_index"],
+            model_inputs["edge_vec"],
+            model_inputs["edge_scatter_index"],
+            model_inputs["edge_mask"],
+            charge_spin=model_inputs["charge_spin"],
+            embedding_only=True,
+        )
Evidence
The wrapper passes embedding_only=True into forward_common_lower, but the SeZMModel method
signature does not include that keyword, so Python will reject the call.

deepmd/pt/nvalchemi/dpa4wrapper.py[475-502]
deepmd/pt/model/model/sezm_model.py[1546-1559]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`compute_embeddings()` calls `self.model.forward_common_lower(..., embedding_only=True)`, but `SeZMModel.forward_common_lower` does not accept `embedding_only`. This breaks the documented embeddings feature.

## Issue Context
The wrapper likely intends to retrieve the descriptor (`ret["descriptor"]`) without running the full fitting/derivative stack. That needs a supported API (either on SeZMModel or by calling the descriptor module directly).

## Fix Focus Areas
- deepmd/pt/nvalchemi/dpa4wrapper.py[475-520]
- deepmd/pt/model/model/sezm_model.py[1546-1559]

## Suggested fix approach
- Remove the unsupported `embedding_only` kwarg.
- Implement embeddings by calling a supported method, e.g.:
 - call `self.model.atomic_model.descriptor.forward_with_edges(...)` using the wrapper’s `edge_index/edge_vec/edge_mask`, then segment-sum for graph embeddings; **or**
 - add a dedicated SeZMModel method (and optionally an exportable version) for returning descriptor-only outputs and use it here.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. PT2 runner arity mismatch 🐞 Bug ≡ Correctness
Description
DPA4Wrapper._run_pt2 calls the loaded AOTInductor package with 9 positional arguments matching the
wrapper’s sparse-edge schema, but this repo’s .pt2 artifacts are invoked with 7 positional inputs
(ext_coord, ext_atype, nlist, mapping, fparam, aparam, charge_spin). Using a standard DeePMD
.pt2 archive will fail at runtime due to the signature mismatch.
Code

deepmd/pt/nvalchemi/dpa4wrapper.py[R454-464]

+        ret = self._aoti_runner(
+            model_inputs["coord"],
+            model_inputs["atype"],
+            model_inputs["edge_index"],
+            model_inputs["edge_vec"],
+            model_inputs["edge_scatter_index"],
+            model_inputs["edge_mask"],
+            None,
+            None,
+            model_inputs["charge_spin"],
+        )
Evidence
The wrapper’s _aoti_runner call uses 9 positional args, while the repo’s own export test
demonstrates the .pt2 callable is used with 7 positional args, proving an interface mismatch for
standard .pt2 artifacts.

deepmd/pt/nvalchemi/dpa4wrapper.py[441-469]
source/tests/pt_expt/model/test_dpa4_export.py[109-126]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `.pt2` backend invocation in `_run_pt2()` does not match the callable interface produced/used by DeePMD’s `.pt2` export pipeline in this repo.

## Issue Context
- Wrapper calls `_aoti_runner(...)` with 9 positional args (sparse edge list inputs + two `None`s).
- The repo’s `.pt2` export test shows `aoti_load_package(...)(ext_coord, ext_atype, nlist, mapping, fparam, aparam, charge_spin)` (7 args).

## Fix Focus Areas
- deepmd/pt/nvalchemi/dpa4wrapper.py[441-469]
- source/tests/pt_expt/model/test_dpa4_export.py[109-126]

## Suggested fix approach
Choose one:
1) **Change the wrapper’s `.pt2` path to call the standard 7-arg `.pt2` interface** by constructing the required `(nlist, mapping, fparam, aparam)` inputs (likely `mapping` + padded `nlist`) from the nvalchemi batch.
2) **Export a `.pt2` package for the sparse-edge interface** (add an exportable SeZM entrypoint that consumes `edge_index/edge_vec/edge_mask/...` and freeze that), then keep the wrapper’s call shape consistent with that exported callable.

Also update the wrapper docstring/comments to reflect the actual `.pt2` callable being used.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. No zero-volume stress guard 🐞 Bug ☼ Reliability
Description
adapt_output computes stress = virial / abs(det(cell)) without validating the cell volume is
non-zero/finite. Singular or near-singular cells will yield inf/NaN stresses and can corrupt
downstream MD/barostats.
Code

deepmd/pt/nvalchemi/dpa4wrapper.py[R412-413]

+            volume = torch.det(cell.to(virial.dtype)).abs().view(n_graph, 1, 1)
+            output["stress"] = (virial.view(n_graph, 3, 3) / volume).to(out_dtype)
Evidence
The code path for stress divides by the determinant-derived volume without any finite/epsilon check.

deepmd/pt/nvalchemi/dpa4wrapper.py[402-414]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`adapt_output()` divides by `abs(det(cell))` without checking for zero/near-zero volume, producing non-finite stresses for degenerate cells.

## Issue Context
This occurs only when `"stress"` is active; the code already requires `cell` to be present but not non-degenerate.

## Fix Focus Areas
- deepmd/pt/nvalchemi/dpa4wrapper.py[402-414]

## Suggested fix approach
- Compute `volume = det(cell).abs()` and validate `torch.isfinite(volume)` and `volume > eps` (eps based on dtype, e.g. `1e-12` for float64).
- Raise a clear `ValueError` when invalid to avoid silently emitting inf/NaN stress.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread deepmd/pt/nvalchemi/dpa4wrapper.py
Comment thread deepmd/pt/nvalchemi/dpa4wrapper.py
Comment thread deepmd/pt/nvalchemi/dpa4wrapper.py
Comment thread doc/third-party/nvalchemi.md
@OutisLi OutisLi requested a review from njzjz June 21, 2026 14:02

@njzjz-bot njzjz-bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding this integration. I think this needs changes before merge: I verified the current head (f62b88775cfb00ca616bea857463c13002c48320) and several main paths in the wrapper are still incompatible with the SeZM lower/AOTI interfaces, so the nvalchemi adapter would fail at runtime rather than just needing polish.

Blocking issues:

  1. DPA4Wrapper.forward() passes sparse-edge tensors into SeZMModel.forward_lower() with the wrong interface.
    In deepmd/pt/nvalchemi/dpa4wrapper.py, the wrapper builds edge_index as a COO tensor with shape (2, E) and passes edge_index, edge_vec, edge_scatter_index, and edge_mask positionally to forward_lower. But the current SeZMModel.forward_lower() signature expects DeePMD lower-interface inputs: (extended_coord, extended_atype, nlist, mapping, fparam, aparam, ...), where nlist is a padded 3D neighbor list (nf, nloc, nsel). The model immediately routes through format_nlist() / build_edge_list_from_nlist(), so a (2, E) COO tensor will not work. Please either convert the nvalchemi COO list into the standard padded nlist + mapping, or add a supported sparse-edge SeZM entrypoint and call that explicitly.

  2. The .pt2 backend has the same callable-signature mismatch.
    _run_pt2() calls the AOTI runner with 9 sparse-edge-style arguments, but the existing export path/test for DPA-4 .pt2 artifacts uses the 7-argument forward_common_lower interface: (ext_coord, ext_atype, nlist, mapping, fparam, aparam, charge_spin). A standard dp --pt freeze package will therefore not match this wrapper call. This should be fixed consistently with item 1, and ideally covered with a .pt2 wrapper test.

  3. compute_embeddings() calls an unsupported API.
    self.model.forward_common_lower(..., embedding_only=True) is not a valid call in this repository: forward_common_lower() has no embedding_only keyword, and it does not return a descriptor key. Please implement embeddings via a supported descriptor path (for example, using atomic_model.descriptor.forward_with_edges(...) with the prepared edge tensors), or add a dedicated SeZM method for descriptor-only output.

  4. The new optional extra is unconditional but nvalchemi-toolkit is not available on all DeePMD-supported environments.
    pyproject.toml adds nvalchemi = ["nvalchemi-toolkit"], while nvalchemi-toolkit==0.1.0 currently declares Python >=3.11,<3.14; DeePMD-kit still supports Python 3.10. The existing PyTorch extra already guards nvalchemi-toolkit-ops with environment markers for Python/Linux. Please add appropriate markers for this extra and document the supported Python/platform constraints in doc/third-party/nvalchemi.md and the example README.

  5. The documented example install path misses an example dependency.
    The example scripts import ASE (from ase.data import atomic_numbers as ASE_Z), but pip install deepmd-kit[nvalchemi] only pulls nvalchemi-toolkit. A user following the docs/README will hit ModuleNotFoundError: No module named 'ase' before reaching the wrapper. Either remove the ASE dependency from these scripts by using an already-required element mapping, or include/document ASE (or an appropriate nvalchemi extra) in the prerequisites.

Smaller follow-up: the stress path divides by abs(det(cell)) without validating finite/non-zero volume. It would be safer to fail fast with a clear ValueError for singular/invalid cells, especially before using the output in barostats.

The new tests are a good direction, but because nvalchemi-toolkit is optional and absent from the current CI environment, the wrapper tests appear to be skipped and these runtime interface issues are not exercised. It would be worth adding at least one CI-covered path (or a lightweight mocked/interface test) that catches the SeZM lower-interface call shape.


Reviewed by OpenClaw 2026.6.8 (844f405)
model: custom-chat-jinzhezeng-group/gpt-5.5

@OutisLi OutisLi marked this pull request as draft June 22, 2026 04:23
@OutisLi

OutisLi commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator Author

Should be merged after PR #5571 is merged, this PR relies on that

Comment on lines +493 to +504
ret = self.model.forward_common_lower(
model_inputs["coord"],
model_inputs["atype"],
model_inputs["edge_index"],
model_inputs["edge_vec"],
model_inputs["edge_scatter_index"],
model_inputs["edge_mask"],
charge_spin=model_inputs["charge_spin"],
embedding_only=True,
)
n_node = data.num_nodes
node_embeddings = ret["descriptor"].reshape(n_node, self._descriptor_dim)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call will crash at runtime. forward_common_lower has no embedding_only parameter (and no **kwargs), and its return dict does not contain a "descriptor" key. The current signature in deepmd/pt/model/model/sezm_model.py is (coord, atype, edge_index, edge_vec, edge_scatter_index, edge_mask, fparam, aparam, comm_dict, extended_coord_corr, charge_spin, input_prec, use_compile) and it returns {energy, energy_redu, energy_derv_r, energy_derv_c, energy_derv_c_redu, mask}. The token embedding_only does not exist anywhere in deepmd/. So compute_embeddings (and embedding_shapes/_descriptor_dim, which feed the same path) raise TypeError: ... unexpected keyword argument 'embedding_only' on first use, then KeyError: 'descriptor' if that were patched. The embeddings feature needs to be wired to whatever the model actually exposes for descriptor output.

delattr(cls, "_default_device_ctx")


@unittest.skipUnless(NVALCHEMI_AVAILABLE, "nvalchemi-toolkit is not installed")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entire suite is gated on NVALCHEMI_AVAILABLE, but nvalchemi-toolkit is added only as a user-facing optional extra in pyproject.toml -- it is not in any test/CI dependency group (and is not installable from PyPI). So in upstream CI every test here silently skips and this ~665-line module gets zero executed coverage. Concrete evidence this hurts: the compute_embeddings runtime crash (forward_common_lower(embedding_only=True) / ret["descriptor"]) is exercised by test_compute_embeddings_shapes yet went uncaught, i.e. the suite was never actually run with nvalchemi installed. Please add nvalchemi-toolkit to the CI test dependencies (or otherwise ensure these tests run), so the parity tests provide real coverage.

n_graph, 9, dtype=atom_virial.dtype, device=atom_virial.device
).index_add_(0, batch_idx, atom_virial)
volume = torch.det(cell.to(virial.dtype)).abs().view(n_graph, 1, 1)
output["stress"] = (virial.view(n_graph, 3, 3) / volume).to(out_dtype)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning -- please double-check the stress sign convention. This emits stress = +virial / |det(cell)| (unsymmetrized), whereas deepmd's own ASE calculator uses the opposite sign and symmetrizes: stress = -0.5 * (v + v.T) / volume (deepmd/calculator.py:160-161, "tensile stress is positive"), where v is the same virial quantity. If nvalchemi follows the standard ASE/tensile convention, this stress is sign-inverted (and missing symmetrization), which would drive NPT/NPH barostats the wrong way. The parity test only asserts stress*volume == native_virial, which is circular and cannot catch a wrong sign. Please confirm nvalchemi's expected stress convention and add a non-circular check (e.g. a finite-difference stress, or a known-pressure reference).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants