Skip to content

Commit 562625e

Browse files
Merge branch 'master' into pr/dpa4
2 parents e33fcbc + 3c44661 commit 562625e

7 files changed

Lines changed: 171 additions & 11 deletions

File tree

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ repos:
3030
exclude: ^source/3rdparty
3131
- repo: https://github.com/astral-sh/ruff-pre-commit
3232
# Ruff version.
33-
rev: v0.15.15
33+
rev: v0.15.16
3434
hooks:
3535
- id: ruff
3636
args: ["--fix"]

deepmd/loggers/training.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def format_training_message(
3838
wall_time: float,
3939
eta: int | None = None,
4040
current_time: datetime.datetime | None = None,
41+
step_time: float | None = None,
4142
) -> str:
4243
"""Format the summary message for one training interval.
4344
@@ -52,13 +53,18 @@ def format_training_message(
5253
current_time : datetime.datetime | None, optional
5354
Current local time used to estimate the finish timestamp. This is only
5455
used when ``eta`` is provided.
56+
step_time : float | None, optional
57+
Average wall-clock time per training step over this interval, in
58+
seconds. Shown only when provided.
5559
5660
Returns
5761
-------
5862
str
5963
The formatted training message.
6064
"""
6165
msg = f"Batch {batch:7d}: total wall time = {wall_time:.2f} s"
66+
if step_time is not None:
67+
msg += f", avg = {step_time:.4f} s/step"
6268
if isinstance(eta, int):
6369
eta_seconds = int(eta)
6470
msg += (

deepmd/pt_expt/train/training.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,7 @@ def run(self) -> None:
14521452
self.wrapper.train()
14531453
wall_start = time.time()
14541454
last_log_time = wall_start
1455+
last_log_step = self.start_step
14551456

14561457
for step_id in range(self.start_step, self.num_steps):
14571458
cur_lr = float(self.lr_schedule.value(step_id))
@@ -1465,9 +1466,6 @@ def run(self) -> None:
14651466
)
14661467
task_key = self.model_keys[model_index]
14671468

1468-
if self.timing_in_training:
1469-
t_start = time.time()
1470-
14711469
# --- forward / backward ---
14721470
self.optimizer.zero_grad(set_to_none=True)
14731471
input_dict, label_dict = self.get_data(is_train=True, task_key=task_key)
@@ -1488,9 +1486,6 @@ def run(self) -> None:
14881486

14891487
self._optimizer_step()
14901488

1491-
if self.timing_in_training:
1492-
t_end = time.time()
1493-
14941489
# --- display ---
14951490
display_step_id = step_id + 1
14961491
if self.display_in_training and (
@@ -1598,9 +1593,14 @@ def _to_float(v: Any) -> float:
15981593
current_time = time.time()
15991594
wall_elapsed = current_time - wall_start
16001595
interval_wall_time = current_time - last_log_time
1596+
# average wall time per step over the interval since the
1597+
# last log (number of steps counted exactly once across
1598+
# intervals via last_log_step)
1599+
interval_steps = max(1, display_step_id - last_log_step)
1600+
step_time = interval_wall_time / interval_steps
16011601
last_log_time = current_time
1602+
last_log_step = display_step_id
16021603
if self.timing_in_training:
1603-
step_time = t_end - t_start
16041604
steps_completed_since_restart = max(
16051605
1,
16061606
display_step_id - self.start_step,
@@ -1619,9 +1619,9 @@ def _to_float(v: Any) -> float:
16191619
current_time,
16201620
tz=datetime.timezone.utc,
16211621
).astimezone(),
1622+
step_time=step_time,
16221623
)
16231624
)
1624-
log.info("step=%d step_time=%.4fs", display_step_id, step_time)
16251625
else:
16261626
log.info(
16271627
format_training_message(

doc/agent-skills.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Agent Skills
2+
3+
DeePMD-kit provides official [Agent Skills](https://agentskills.io/what-are-skills) that help AI agents run
4+
DeePMD-kit workflows in a reproducible way. These skills capture
5+
project-specific operating knowledge—such as training inputs, model
6+
deployment, LAMMPS integration, and Python inference patterns—so an agent can
7+
turn a high-level request into concrete files, commands, and validation steps.
8+
9+
The DeePMD-kit skills were initially developed in the
10+
[Computational Chemistry Agent Skills](https://github.com/jinzhezenggroup/computational-chemistry-agent-skills)
11+
project as part of the work described below. They are now maintained directly
12+
in the DeePMD-kit repository under `skills/`.
13+
14+
## List of skills
15+
16+
- `deepmd-train-dpa3`: Train DeePMD-kit models with the DPA3 descriptor and the
17+
PyTorch backend, including input generation, neighbor-selection choices,
18+
training, freezing, and testing.
19+
- `deepmd-finetune-dpa3`: Fine-tune DPA3 models from self-trained checkpoints,
20+
multi-task pretrained models, or built-in models downloaded by `dp pretrained download`.
21+
- `deepmd-train-se-e2-a`: Train classical Deep Potential models with the
22+
`se_e2_a` descriptor, including preparation of training JSON files and
23+
post-training validation.
24+
- `deepmd-python-inference`: Run Python and CLI inference with trained or
25+
frozen DeePMD-kit models, including energy, force, virial, descriptor, and
26+
model-deviation workflows.
27+
- `lammps-deepmd`: Prepare, explain, and run LAMMPS simulations with DeePMD-kit
28+
potentials, including common NVE, NVT, and NPT setups.
29+
30+
## Related reference
31+
32+
The DeePMD-kit skills were originally developed as part of the following
33+
paper:
34+
35+
- Mingwei Ding, Chen Huang, Yibo Hu, Yifan Li, Zitian Lu, Xingtai Yu, Duo
36+
Zhang, Wenxi Zhai, Tong Zhu, Qiangqiang Gu, and Jinzhe Zeng. [Automating
37+
Computational Chemistry Workflows via OpenClaw and Domain-Specific
38+
Skills](https://doi.org/10.1021/acs.jctc.6c00622). *Journal
39+
of Chemical Theory and Computation*, 2026.
40+
41+
## Install skills
42+
43+
### If you are a user
44+
45+
The easiest way is to send this page to your agent and ask it to install the
46+
skills for you. Users usually do not need to perform manual installation.
47+
48+
### If you are an agent
49+
50+
If you already have a DeePMD-kit checkout, run this command from the repository
51+
root:
52+
53+
```bash
54+
npx -y skills add ./skills -a openclaw -y
55+
```
56+
57+
If you do not have a checkout, the same skills can also be installed directly
58+
from GitHub:
59+
60+
```bash
61+
npx -y skills add https://github.com/deepmodeling/deepmd-kit/tree/master/skills \
62+
-a openclaw -y
63+
```
64+
65+
The examples above require Node.js/npm so that `npx` is available, and they
66+
install the skills for OpenClaw. Replace `openclaw` with the target agent name
67+
when installing for another agent. The GitHub command lets the skill CLI fetch
68+
the repository for you. For large repositories or slow networks, this can take
69+
longer than installing from an existing local checkout. Refresh or restart the
70+
session afterward so the installed skills are reloaded.
71+
72+
## Minimal verification
73+
74+
Ask the agent to perform a small task that exercises the installed skill
75+
without launching an expensive calculation. For example:
76+
77+
- “Use the `deepmd-python-inference` skill to write a minimal Python snippet
78+
for loading a frozen DeePMD-kit model and evaluating one frame.”
79+
- “Use the `deepmd-train-dpa3` skill to draft a small DPA3 training input for a
80+
water dataset, but do not start training.”
81+
- “Use the `lammps-deepmd` skill to prepare an NVT LAMMPS input file for a
82+
DeePMD-kit model, and explain each command.”

doc/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ DeePMD-kit is a package written in Python/C++, designed to minimize the effort r
4545
inference/index
4646
cli
4747
third-party/index
48+
agent-skills
4849
nvnmd/index
4950
env
5051
troubleshooting/index

source/tests/pt_expt/test_training.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
4. Loss decreases over those steps
99
"""
1010

11+
import datetime
1112
import os
1213
import shutil
1314
import tempfile
@@ -18,6 +19,9 @@
1819

1920
import torch
2021

22+
from deepmd.loggers.training import (
23+
format_training_message,
24+
)
2125
from deepmd.pt_expt.entrypoints.main import (
2226
get_trainer,
2327
)
@@ -1684,5 +1688,60 @@ def test_compiled_matches_eager_per_task(self) -> None:
16841688
shutil.rmtree(tmpdir, ignore_errors=True)
16851689

16861690

1691+
class TestFormatTrainingMessageStepTime(unittest.TestCase):
1692+
"""The pt_expt trainer reports the average wall time per step over each
1693+
display interval by passing ``step_time`` to ``format_training_message``
1694+
(replacing the former standalone ``step=... step_time=...`` debug line).
1695+
These tests cover both branches of the optional ``step_time``/``eta``
1696+
arguments so the "avg = ... s/step" segment is rendered only when requested.
1697+
"""
1698+
1699+
def test_without_step_time(self) -> None:
1700+
"""``step_time=None`` (default) omits the step-time segment."""
1701+
msg = format_training_message(batch=100, wall_time=18.41)
1702+
self.assertEqual(msg, "Batch 100: total wall time = 18.41 s")
1703+
self.assertNotIn("s/step", msg)
1704+
1705+
def test_with_step_time(self) -> None:
1706+
"""``step_time`` is rendered with 4 decimals after the wall time."""
1707+
msg = format_training_message(batch=100, wall_time=18.41, step_time=0.1841)
1708+
self.assertEqual(
1709+
msg,
1710+
"Batch 100: total wall time = 18.41 s, avg = 0.1841 s/step",
1711+
)
1712+
1713+
def test_step_time_zero_is_shown(self) -> None:
1714+
"""A literal ``0.0`` step time is still shown (not treated as absent)."""
1715+
msg = format_training_message(batch=1, wall_time=0.5, step_time=0.0)
1716+
self.assertIn("avg = 0.0000 s/step", msg)
1717+
1718+
def test_with_step_time_and_eta(self) -> None:
1719+
"""Step time appears before the eta segment."""
1720+
current_time = datetime.datetime(
1721+
2026, 6, 7, 5, 21, 29, tzinfo=datetime.timezone.utc
1722+
)
1723+
msg = format_training_message(
1724+
batch=100,
1725+
wall_time=18.41,
1726+
eta=100,
1727+
current_time=current_time,
1728+
step_time=0.1841,
1729+
)
1730+
self.assertIn("total wall time = 18.41 s, avg = 0.1841 s/step, eta = ", msg)
1731+
# ordering: wall time -> step time -> eta
1732+
self.assertLess(msg.index("s/step"), msg.index("eta ="))
1733+
1734+
def test_eta_without_step_time(self) -> None:
1735+
"""Eta still works when no step time is supplied."""
1736+
current_time = datetime.datetime(
1737+
2026, 6, 7, 5, 21, 29, tzinfo=datetime.timezone.utc
1738+
)
1739+
msg = format_training_message(
1740+
batch=100, wall_time=18.41, eta=100, current_time=current_time
1741+
)
1742+
self.assertNotIn("s/step", msg)
1743+
self.assertIn("eta = ", msg)
1744+
1745+
16871746
if __name__ == "__main__":
16881747
unittest.main()

source/tests/pt_expt/utils/test_neighbor_list.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -476,7 +476,17 @@ def test_pt_expt_multiframe_equivalence(name: str) -> None:
476476

477477
@pytest.mark.parametrize("name", list(ALL_MODELS)) # descriptor family
478478
def test_default_fallback(name: str) -> None:
479-
"""``neighbor_list=None`` equals an explicit DefaultNeighborList byte-for-byte."""
479+
"""``neighbor_list=None`` dispatches to the same DefaultNeighborList builder.
480+
481+
``None`` and an explicit ``DefaultNeighborList()`` are the identical builder
482+
(``call_common`` does ``builder = nl if nl is not None else DefaultNeighborList()``),
483+
so the two forward passes are the *same computation*; on CPU they are
484+
bit-identical. We compare with a tight tolerance rather than exact equality
485+
because the two passes are independent forward evaluations, and on CUDA the
486+
GNN message-passing scatter (atomic adds) is not bit-reproducible run-to-run,
487+
so the virial can differ by ~1 ULP between the passes (a real dispatch bug
488+
would differ by orders of magnitude more).
489+
"""
480490
coord_np, atype_np, box_np = _system()
481491
md = get_model(copy.deepcopy(ALL_MODELS[name])).to(env.DEVICE)
482492
md.eval()
@@ -492,8 +502,10 @@ def test_default_fallback(name: str) -> None:
492502
).requires_grad_(True)
493503
outs[tag] = md.forward(coord_t, atype_t, box=box_t, do_atomic_virial=True, **kw)
494504
for k in ("energy", "force", "virial"):
495-
np.testing.assert_array_equal(
505+
np.testing.assert_allclose(
496506
outs["none"][k].detach().cpu().numpy(),
497507
outs["explicit"][k].detach().cpu().numpy(),
508+
rtol=1e-10,
509+
atol=1e-12,
498510
err_msg=f"{name} {k}",
499511
)

0 commit comments

Comments
 (0)