Skip to content

feat(pt_expt): align the training runtime with pt - #5958

Open
OutisLi wants to merge 6 commits into
deepmodeling:masterfrom
OutisLi:pr/pt-expt-training
Open

feat(pt_expt): align the training runtime with pt#5958
OutisLi wants to merge 6 commits into
deepmodeling:masterfrom
OutisLi:pr/pt-expt-training

Conversation

@OutisLi

@OutisLi OutisLi commented Aug 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • move checkpoint layout, retention, training timing, and sharding policy into backend-independent training utilities shared by pt and pt_expt
  • add pt_expt support for checkpoint directories and retention ratios, EMA training/checkpoints, EMA full validation, consistent training reports, and restart-safe state restoration
  • support the same zero_stage strategies as pt, including DDP, ZeRO-1, and FSDP2, with collective checkpoint assembly and optimizer-state restoration
  • reuse overflow-safe gradient norm reduction and defer the non-finite verdict to checkpoint boundaries so a diverged model is not saved
  • make EMA checkpoint retention inherit max_ckpt_keep by default while preserving an explicit ema_ckpt_keep override

Motivation

The pt_expt trainer currently lacks several operational guarantees available in pt: equivalent checkpoint retention and restart behavior, EMA support, distributed state sharding, stable gradient checks, and consistent progress reporting. Implementing these separately would leave two training runtimes with duplicated policies that can drift.

This PR keeps backend-specific serialization and execution in each trainer, while centralizing the policies that are independent of a backend. It also relocates the EMA, validation, and gradient helpers under pt_expt, which is their continuing owner as the legacy pt trainer is retired.

Notable fixes

  • rerunning in a directory from a longer run no longer lets stale future checkpoints evict the newly written checkpoint
  • max_ckpt_keep < 1 retains all checkpoints instead of deleting the current checkpoint
  • ckpt_keep_ratio works when periodic saving is disabled and overrides both regular and EMA windows
  • restarting from a checkpoint without optimizer state resumes the learning-rate schedule from the recorded step
  • sharded checkpoints are assembled collectively, avoiding rank desynchronization at the next barrier
  • regular and EMA checkpoint families are pruned independently; absent ema_ckpt_keep now gives both families the max_ckpt_keep window

Validation

  • OMP_NUM_THREADS=1 DP_INTER_OP_PARALLELISM_THREADS=0 DP_INTRA_OP_PARALLELISM_THREADS=0 /Users/outisli/Software/miniforge3/envs/dpmd/bin/python -m pytest source/tests/common/dpmodel/test_train_checkpoint.py source/tests/common/test_argcheck_training.py -q (14 passed)
  • a five-step pt_expt training run with EMA, save_freq=1, and only max_ckpt_keep=2 retained regular steps 4, 5 and EMA steps 4, 5
  • repository pre-commit checks passed

Summary by CodeRabbit

  • New Features

    • Added configurable checkpoint retention, restart-safe cleanup, and separate EMA checkpoints.
    • Added distributed training with multiple sharding strategies and improved checkpoint restoration.
    • Added EMA full-validation workflows with independent schedules and best-checkpoint tracking.
    • Added parameter-count reporting and improved training progress timing, averages, and ETA estimates.
    • Added safer handling of non-finite gradients.
    • Added support for checkpoint retention and EMA features across supported PyTorch backends.
  • Documentation

    • Expanded guidance for checkpoint retention, EMA, distributed training, and inference options.
  • Bug Fixes

    • Improved validation compatibility and handling of shared or relocated save directories.

OutisLi added 3 commits August 4, 2026 22:46
…ith pt

A pt_expt run could not be operated under the same conventions as a pt
run: it ignored the checkpoint directory and retention options, kept no
EMA, never reported its parameter count, and printed progress in its own
format with a systematically inflated estimate of the remaining time.
Four features are brought over, and everything about them that is not
specific to a backend is described once so that both backends execute the
same implementation instead of two drifting copies.

Checkpointing. `save_dir` and `ckpt_keep_ratio` are honoured, and the
on-disk layout -- naming, prefix symlinks, pointer file and retention --
moves into `CheckpointStore` (deepmd/dpmodel/train/checkpoint.py), which
both backends now use; pt loses four hand-rolled copies of the publish
sequence. The store drops checkpoints numbered above the one being
written before it applies the retention window. Those are remnants of a
longer earlier run over the same directory, and leaving them in place let
the window discard the checkpoint that was just written, so restarting a
run in a finished directory kept no result at all. A disabled window
(`max_ckpt_keep < 1`) now retains every checkpoint, as the jax and tf2
backends already do, rather than deleting all of them including the
current one. `resolve_keep_ckpt_count` also handles `save_freq <= 0`,
which previously raised `ZeroDivisionError` when combined with a
retention ratio.

EMA. `enable_ema`, `ema_decay` and `ema_ckpt_keep` are honoured. The
shadow weights are updated after every optimizer step, written as a
separate family of checkpoints carrying neither optimizer nor EMA state,
and restored on restart. `deepmd/pt/train/ema.py` is reused as is rather
than copied.

Full validation. `build_full_validators` configures the live-weight and
the EMA-weight flow together, since they differ only in the weights they
read, the log they write and the prefix of the checkpoints they select.
pt_expt thereby gains `ema_full_validation`, and the per-flow eligibility
check stays with the backend that knows what it supports.
`compiled_infer` and `amp_infer` reach the models through the shared
`infer_env_defaults` translation. `tf32_infer` remains unimplemented in
pt_expt, which has no TF32 path yet.

Training report. The display now prints the losses before the wall-clock
line and omits the per-step average, matching pt, and the run ends with
the average step time over the representative intervals. The remaining
time is extrapolated from the interval that just ended rather than from
the average since the run began: the latter carries the one-off cost of
the first steps, such as graph compilation, and therefore never stops
overestimating. This accounting moves into `TrainingTimer`
(deepmd/dpmodel/train/timing.py), replacing pt's three loose counters.

The parameter-count report moves to `deepmd/loggers/training.py`, the
home of the other training log messages, and reads counts a backend
supplies.

Relocation. `deepmd/pt/train/{utils,validation,ema}.py` move under
`deepmd/pt_expt/train/`. pt is being retired, so the shared training code
belongs with the backend that outlives it and the dependency arrow is
reversed. While moving, the validator recognizes its validation data by
the surface it exposes rather than by pt's dataset types, and reads the
environment constants from pt_expt; only `AutoBatchSize` and
`to_torch_tensor` still come from `deepmd/pt/utils`, which is the next
unit to migrate.
Two gaps separated pt_expt from pt in distributed training: a run was
always plain data parallel, and a step was taken without ever inspecting
its gradient. Both are closed here, and the part that is not specific to
PyTorch is shared with pt rather than duplicated.

`training.zero_stage` now selects the same four strategies as in pt:
plain DDP, DDP over a redundancy-sharded optimizer, and FSDP2 sharding
the gradients or the parameters as well.

What a stage implies -- which wrapper holds the model, how the optimizer
is built, how a checkpoint is assembled, whether a gradient norm may be
reduced locally -- follows from the stage alone, so `ShardingPolicy` in
the backend-independent train layer states it once and both backends
query it, rather than each comparing the stage against numbers wherever a
decision is due; pt sheds twenty such comparisons. A single-process run
drops the requested stage instead of failing, so one configuration stays
usable whether or not it is launched across ranks.

Assembling a checkpoint out of shards is a collective operation, which
the shared training loop had no notion of: it called `save_checkpoint` on
the chief alone, which would leave the other ranks waiting at the next
barrier. The trainer gained `checkpoint_is_collective`, false by default
so that tf2 and jax are unaffected; a backend that opts in is called on
every rank and gates the write itself.

A run is restored before the model is distributed. A checkpoint records
whole tensors, and those cannot be copied into parameters that FSDP2 has
already cut into shards. The optimizer is still built after distribution,
so its state is restored separately, through the distributed-checkpoint
API when the stage calls for it.

That reorder also removes a second construction of the learning-rate
schedule, and with it a defect it was covering: the schedule was built
before the resumed step was known and rebuilt with the true value only
when optimizer state was present, so restarting from a checkpoint that
carried a step but no optimizer state -- a frozen model, or one saved
without it -- resumed at the wrong learning rate.

Sharding is rejected alongside multi-task training, EMA from stage two,
and `change_bias_after_training`, as in pt. pt_expt additionally rejects
`enable_compile`, whose graph is traced from the parameters that FSDP2
replaces with DTensors. Display-time validation is skipped once the
parameters are sharded, because its forward gathers them while the
display runs on the chief alone; the full-validation flow, which every
rank enters together, stays available.

pt_expt clipped gradients with the stock `torch.nn.utils.clip_grad_norm_`
and never inspected the resulting norm, so a run could write a checkpoint
of a model that had already diverged, and a gradient that was large but
still representable could be misread as infinite when the sum of squares
of the naive reduction overflowed. pt has carried safeguards against both
for a while; they now serve pt_expt as well.

The two safeguards move out of the training utilities, which had accreted
four unrelated concerns, into `deepmd/pt_expt/train/gradient.py`. They
share one rationale -- keep the reduction overflow-safe, and keep the
verdict off the host until it is needed -- which the module can now state
once. `deepmd/pt_expt/train/utils.py` keeps the trainer setup helpers.

pt_expt feeds the norm to the guard on every step and consults the guard
at the checkpoint boundary. The verdict is deliberately not read anywhere
else: the check resets the accumulated state, so a second caller between
two boundaries would consume a divergence that the checkpoint about to be
written should have seen. Reading it once per boundary also keeps the
step free of host synchronization, which is why the state is accumulated
on device in the first place. The reduction itself is overflow-safe
except where the parameters are sharded, since that reduction has to
propagate DTensor sharding instead.

Verified on two gloo ranks: every stage trains, checkpoints and restarts,
recording whole tensors and restoring the optimizer state. One test pins
a defect the sharded path invites, in that a redundancy-sharded optimizer
turns each constructor keyword into a param-group default and would
therefore record a second copy of the model in every checkpoint of a
name-routed optimizer.
Copilot AI lite review requested due to automatic review settings August 4, 2026 15:03

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@OutisLi
OutisLi marked this pull request as ready for review August 4, 2026 15:03
@coderabbitai

coderabbitai Bot commented Aug 4, 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 Plus

Run ID: d1c1ea86-cd7f-4760-b588-9e16998dd90e

📥 Commits

Reviewing files that changed from the base of the PR and between c814ddd and a425d1e.

📒 Files selected for processing (4)
  • deepmd/pt/train/training.py
  • deepmd/pt_expt/train/training.py
  • deepmd/utils/argcheck.py
  • doc/train/parallel-training.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • deepmd/utils/argcheck.py
  • deepmd/pt_expt/train/training.py
  • deepmd/pt/train/training.py

📝 Walkthrough

Walkthrough

The PR adds shared checkpoint, sharding, and timing APIs. It integrates ZeRO and FSDP2 training, EMA checkpoints and validation, guarded gradient handling, parameter logging, generalized validation, and updated configuration support.

Changes

Training infrastructure

Layer / File(s) Summary
Shared training contracts and stores
deepmd/dpmodel/train/*, deepmd/dpmodel/train/trainer.py, deepmd/loggers/training.py, source/tests/common/*
Adds checkpoint stores, sharding policies, training timers, collective checkpoint participation, average timing logs, parameter-count logging, and tests.
Distributed training and checkpoint execution
deepmd/pt_expt/train/*, deepmd/pt/train/training.py, source/tests/pt_expt/test_training_ddp.py
Adds ZeRO stages 1–3, FSDP2 setup, optimizer restoration, guarded gradient clipping, EMA updates, sharded checkpoint serialization, and restart coverage.
EMA validation and configuration support
deepmd/pt_expt/train/validation.py, deepmd/utils/argcheck.py, doc/train/*, source/tests/pt_expt/test_training.py
Adds separate live and EMA validators, EMA checkpoints, generalized validation data handling, retention configuration, and related tests.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related issues

Possibly related PRs

Suggested labels: bug

Suggested reviewers: njzjz, copilot

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.38% 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 clearly summarizes the main change: aligning the pt_expt training runtime with the pt runtime.
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.
✨ 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.

@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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
deepmd/utils/argcheck.py (1)

6028-6033: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep compiled_infer behind model.use_compile in the pt_expt docs.

The pt_expt backend exports DP_COMPILE_INFER, but DPA4 models still sample it only as a descriptor activation-checkpoint switch, and pt_expt raises on model.use_compile. The Argument("compiled_infer", ..., doc=supported_backends("pt", "pt_expt") + doc_compiled_infer) makes compiled_infer look available for the eval torch.compile path in pt_expt, which users cannot enable. Label it pt only, or add the actual pt_expt torch_compile path.

🤖 Prompt for 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.

In `@deepmd/utils/argcheck.py` around lines 6028 - 6033, Update the compiled_infer
Argument declaration so its documentation advertises the option only for the pt
backend, unless a real pt_expt torch.compile implementation is added; do not
expose it as an eval torch.compile option for pt_expt.
🤖 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/dpmodel/train/timing.py`:
- Around line 71-117: Update the timing implementation around __init__ and
record to use time.monotonic() for _interval_start and elapsed wall_time
calculations, preventing clock adjustments from producing negative durations or
forecasts. Keep the displayed timestamp based on a separate time.time() reading
converted to the local timezone.

In `@deepmd/pt_expt/train/ema.py`:
- Around line 76-90: Update apply_shadow to snapshot every parameter’s original
data before performing any EMA copy, including aliased parameters returned by
_named_model_parameters. Separate backup collection from shadow-value
application, then restore all originals in finally so shared parameters retain
their pre-EMA training values after the context exits.

In `@deepmd/pt/train/training.py`:
- Around line 1227-1229: The checkpoint_dir parameter passed to
resolve_best_checkpoint_dir uses Path(self.save_ckpt).parent as a default, but
this may differ from the active checkpoint store directory
(self.ckpt_store.directory) when training.save_dir is set and
validating.save_best_dir is unset. Update the resolve_best_checkpoint_dir call
to pass self.save_dir alongside validating_params and self.save_ckpt, so it can
construct the default validation checkpoint directory consistently from
self.ckpt_store.directory rather than inferring the parent directory from the
checkpoint file path alone.
- Line 463: Move the pretrained_model construction into the
scoped_env_defaults(eval_env_defaults) context manager block. Currently,
pretrained_model is built outside this context, which causes
get_model_for_wrapper to sample environment flags like DP_COMPILE_INFER,
DP_TF32_INFER, and DP_AMP_INFER using ambient settings rather than the intended
eval defaults. Ensure the pretrained_model is fully constructed and available
within the scoped_env_defaults context so that all downstream model building
operations use consistent environment configuration.

In `@source/tests/pt_expt/test_training.py`:
- Line 2253: Update the assertion for model_ema.ckpt.pt in the affected training
test to first verify the file exists, then check os.path.islink only when
platform.system() is not Windows. Add the platform import if needed, matching
the equivalent logic in the PT training test.

---

Outside diff comments:
In `@deepmd/utils/argcheck.py`:
- Around line 6028-6033: Update the compiled_infer Argument declaration so its
documentation advertises the option only for the pt backend, unless a real
pt_expt torch.compile implementation is added; do not expose it as an eval
torch.compile option for pt_expt.
🪄 Autofix

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 Plus

Run ID: 50fdceaf-7e0f-4d86-a25b-a86afa80e3f5

📥 Commits

Reviewing files that changed from the base of the PR and between c0c1f0c and 775b68f.

📒 Files selected for processing (26)
  • deepmd/dpmodel/train/__init__.py
  • deepmd/dpmodel/train/checkpoint.py
  • deepmd/dpmodel/train/sharding.py
  • deepmd/dpmodel/train/timing.py
  • deepmd/dpmodel/train/trainer.py
  • deepmd/loggers/training.py
  • deepmd/pt/train/training.py
  • deepmd/pt_expt/train/ema.py
  • deepmd/pt_expt/train/gradient.py
  • deepmd/pt_expt/train/training.py
  • deepmd/pt_expt/train/utils.py
  • deepmd/pt_expt/train/validation.py
  • deepmd/utils/argcheck.py
  • doc/train/training-advanced.md
  • source/tests/common/dpmodel/test_train_abstract_trainer.py
  • source/tests/common/dpmodel/test_train_checkpoint.py
  • source/tests/common/dpmodel/test_train_sharding.py
  • source/tests/common/dpmodel/test_train_timing.py
  • source/tests/common/test_argcheck_training.py
  • source/tests/common/test_loggers_training.py
  • source/tests/pt/test_training.py
  • source/tests/pt/test_validation.py
  • source/tests/pt_expt/test_entrypoint.py
  • source/tests/pt_expt/test_train_gradient.py
  • source/tests/pt_expt/test_training.py
  • source/tests/pt_expt/test_training_ddp.py

Comment thread deepmd/dpmodel/train/timing.py
Comment thread deepmd/pt/train/training.py
Comment thread deepmd/pt/train/training.py
Comment thread source/tests/pt_expt/test_training.py Outdated

@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.

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
deepmd/utils/argcheck.py (1)

6028-6033: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep compiled_infer behind model.use_compile in the pt_expt docs.

The pt_expt backend exports DP_COMPILE_INFER, but DPA4 models still sample it only as a descriptor activation-checkpoint switch, and pt_expt raises on model.use_compile. The Argument("compiled_infer", ..., doc=supported_backends("pt", "pt_expt") + doc_compiled_infer) makes compiled_infer look available for the eval torch.compile path in pt_expt, which users cannot enable. Label it pt only, or add the actual pt_expt torch_compile path.

🤖 Prompt for 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.

In `@deepmd/utils/argcheck.py` around lines 6028 - 6033, Update the compiled_infer
Argument declaration so its documentation advertises the option only for the pt
backend, unless a real pt_expt torch.compile implementation is added; do not
expose it as an eval torch.compile option for pt_expt.
🤖 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/dpmodel/train/timing.py`:
- Around line 71-117: Update the timing implementation around __init__ and
record to use time.monotonic() for _interval_start and elapsed wall_time
calculations, preventing clock adjustments from producing negative durations or
forecasts. Keep the displayed timestamp based on a separate time.time() reading
converted to the local timezone.

In `@deepmd/pt_expt/train/ema.py`:
- Around line 76-90: Update apply_shadow to snapshot every parameter’s original
data before performing any EMA copy, including aliased parameters returned by
_named_model_parameters. Separate backup collection from shadow-value
application, then restore all originals in finally so shared parameters retain
their pre-EMA training values after the context exits.

In `@deepmd/pt/train/training.py`:
- Around line 1227-1229: The checkpoint_dir parameter passed to
resolve_best_checkpoint_dir uses Path(self.save_ckpt).parent as a default, but
this may differ from the active checkpoint store directory
(self.ckpt_store.directory) when training.save_dir is set and
validating.save_best_dir is unset. Update the resolve_best_checkpoint_dir call
to pass self.save_dir alongside validating_params and self.save_ckpt, so it can
construct the default validation checkpoint directory consistently from
self.ckpt_store.directory rather than inferring the parent directory from the
checkpoint file path alone.
- Line 463: Move the pretrained_model construction into the
scoped_env_defaults(eval_env_defaults) context manager block. Currently,
pretrained_model is built outside this context, which causes
get_model_for_wrapper to sample environment flags like DP_COMPILE_INFER,
DP_TF32_INFER, and DP_AMP_INFER using ambient settings rather than the intended
eval defaults. Ensure the pretrained_model is fully constructed and available
within the scoped_env_defaults context so that all downstream model building
operations use consistent environment configuration.

In `@source/tests/pt_expt/test_training.py`:
- Line 2253: Update the assertion for model_ema.ckpt.pt in the affected training
test to first verify the file exists, then check os.path.islink only when
platform.system() is not Windows. Add the platform import if needed, matching
the equivalent logic in the PT training test.

---

Outside diff comments:
In `@deepmd/utils/argcheck.py`:
- Around line 6028-6033: Update the compiled_infer Argument declaration so its
documentation advertises the option only for the pt backend, unless a real
pt_expt torch.compile implementation is added; do not expose it as an eval
torch.compile option for pt_expt.
🪄 Autofix

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 Plus

Run ID: 50fdceaf-7e0f-4d86-a25b-a86afa80e3f5

📥 Commits

Reviewing files that changed from the base of the PR and between c0c1f0c and 775b68f.

📒 Files selected for processing (26)
  • deepmd/dpmodel/train/__init__.py
  • deepmd/dpmodel/train/checkpoint.py
  • deepmd/dpmodel/train/sharding.py
  • deepmd/dpmodel/train/timing.py
  • deepmd/dpmodel/train/trainer.py
  • deepmd/loggers/training.py
  • deepmd/pt/train/training.py
  • deepmd/pt_expt/train/ema.py
  • deepmd/pt_expt/train/gradient.py
  • deepmd/pt_expt/train/training.py
  • deepmd/pt_expt/train/utils.py
  • deepmd/pt_expt/train/validation.py
  • deepmd/utils/argcheck.py
  • doc/train/training-advanced.md
  • source/tests/common/dpmodel/test_train_abstract_trainer.py
  • source/tests/common/dpmodel/test_train_checkpoint.py
  • source/tests/common/dpmodel/test_train_sharding.py
  • source/tests/common/dpmodel/test_train_timing.py
  • source/tests/common/test_argcheck_training.py
  • source/tests/common/test_loggers_training.py
  • source/tests/pt/test_training.py
  • source/tests/pt/test_validation.py
  • source/tests/pt_expt/test_entrypoint.py
  • source/tests/pt_expt/test_train_gradient.py
  • source/tests/pt_expt/test_training.py
  • source/tests/pt_expt/test_training_ddp.py
🛑 Comments failed to post (1)
deepmd/pt_expt/train/ema.py (1)

76-90: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Take all parameter backups before applying EMA values.

_named_model_parameters can return multiple names for aliased parameters in a model dictionary. apply_shadow backs up and overwrites each name in one pass. The second alias is then backed up after the first alias already contains EMA values. The finally block can leave the shared parameter EMA-weighted after the context exits. The next training step then uses incorrect weights.

Collect all backups before the first copy_, then apply shadow values in a second pass.

Proposed fix
         backups: dict[str, torch.Tensor] = {}
+        named_parameters = self._named_model_parameters(model)
         try:
             with torch.no_grad():
-                for name, param in self._named_model_parameters(model):
+                for name, param in named_parameters:
                     backups[name] = param.detach().clone()
+                for name, param in named_parameters:
                     param.copy_(
                         self.shadow_params[name].to(
                             device=param.device,
                             dtype=param.dtype,
@@
         finally:
             with torch.no_grad():
-                for name, param in self._named_model_parameters(model):
+                for name, param in named_parameters:
                     if name in backups:
                         param.copy_(backups[name])

Also applies to: 178-200

🤖 Prompt for 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.

In `@deepmd/pt_expt/train/ema.py` around lines 76 - 90, Update apply_shadow to
snapshot every parameter’s original data before performing any EMA copy,
including aliased parameters returned by _named_model_parameters. Separate
backup collection from shadow-value application, then restore all originals in
finally so shared parameters retain their pre-EMA training values after the
context exits.

@wanghan-iapcm wanghan-iapcm left a comment

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 shape of this is right and I want to say that before the problems. Extracting checkpoint layout, retention, timing and sharding into deepmd/dpmodel/train/ follows the precedent #5603 set, the three new modules import no torch so the backend-independence is real rather than nominal, and the alternative -- reimplementing the same policies inside pt_expt -- would have produced exactly the drift the PR body describes. Several things I went looking for turned out clean: the ZeRO collective ordering is correct (_collect_checkpoint_states runs consolidate_state_dict before the rank != 0 return, so no rank can skip a collective), ZeRO-1 restart across a different world size still works, pt's non-finite gradient handling is a pure rename with no behavioural diff, and resolve_keep_ckpt_count actually fixes a latent ZeroDivisionError on save_freq <= 0.

Two blocking problems, both inline, and one question.

The second one is the serious one: CheckpointStore.prune deletes checkpoints it is supposed to keep, on pt as well as pt_expt. I verified it by running the new class against the implementation it replaces rather than by reading, and the divergence is not subtle -- with max_ckpt_keep=10 and nine checkpoints on disk, master keeps all nine and this branch keeps two.

I also want to flag the ordering problem this creates for review itself. Because CI never got past collection, none of the ~19958 tests ran, including all the new ones. So the checkpoint bug was not caught by the suite, and more importantly nothing else in this 2600-line diff has been exercised either -- the parts I checked by hand look right, but "looks right on inspection" is a much weaker statement than this PR deserves given it touches production pt training. Worth fixing the import first and letting a green run tell us what else is there before anyone reads the rest too closely.

A few smaller notes I am recording rather than asking you to act on. format_training_message's step_time parameter and the avg = ... s/step field are gone, along with the TestFormatTrainingMessageStepTime test; those came from #5500 whose stated goal was to fold that average into the normal log line, and TrainingTimer.format_average() only prints once at end of run, so pt_expt, jax and tf2 all lose the per-interval figure. build_checkpoint_stores gates the retention log and store.prepare() on rank == 0 but every test uses the default rank, so the non-chief branch is unexercised -- the same gap I raised on #5603 for is_chief. ema_ckpt_keep moving from a hard 3 to inheriting max_ckpt_keep silently shrinks EMA retention for anyone who set max_ckpt_keep to 1 or 2; it is documented, so this is a release-note item rather than a defect. And pt_expt now inherits pt's "delete every checkpoint numbered above the current step" rule, which is a new data-loss path for pt_expt users restarting from an older checkpoint in a directory holding a longer run -- pre-existing for pt, new for pt_expt, and not mentioned in doc/train/training-advanced.md.

Comment thread source/tests/pt_expt/test_entrypoint.py Outdated
Comment thread deepmd/dpmodel/train/checkpoint.py Outdated
Bound checkpoint retention before slicing, measure elapsed time with a monotonic clock, and preserve aliased parameters across EMA swaps. Apply eval defaults to finetune source models and align cross-platform tests and backend documentation.
Copilot AI review requested due to automatic review settings August 5, 2026 03:24

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
deepmd/pt/train/training.py (2)

1987-1996: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

Make ZeRO-1 full-validation checkpoint saves collective.

save_model collects optimizer state by default, and the ZeRO-1 branch calls self.optimizer.consolidate_state_dict(to=0). This requires every rank to enter _collect_checkpoint_states.

Full-validation only executes save_checkpoint when self.rank == 0, so ZeRO-1 non-LoRA full-validation can hang when validating.full_validation=true and validating.save_best=true. Use a collective best-checkpoint save path, or reject this configuration.

🤖 Prompt for 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.

In `@deepmd/pt/train/training.py` around lines 1987 - 1996, Update the ZeRO-1
checkpoint flow around _collect_checkpoint_states and save_checkpoint so
full-validation best-checkpoint saves enter the optimizer consolidation
collectively on every rank, including non-LoRA runs. Ensure rank 0 still
performs the actual checkpoint output after collective state gathering, or
explicitly reject the incompatible validating.full_validation and
validating.save_best configuration before entering the save path.

1744-1758: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Guard or reject save_freq=0 before the modulo.

self.save_freq defaults to 1000, but training_params.get("save_freq") allows arbitrary integer config values. With save_freq=0, display_step_id % self.save_freq raises ZeroDivisionError before any final-checkpoint save, so either validate save_freq > 0 or skip the periodic modulo when it is disabled.

Proposed fix
-            should_save_checkpoint = (
-                (display_step_id) % self.save_freq == 0 and _step_id != self.start_step
-            ) or (display_step_id) == self.num_steps
+            should_save_checkpoint = (
+                (
+                    self.save_freq > 0
+                    and display_step_id % self.save_freq == 0
+                    and _step_id != self.start_step
+                )
+                or display_step_id == self.num_steps
+            )
🤖 Prompt for 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.

In `@deepmd/pt/train/training.py` around lines 1744 - 1758, Validate
self.save_freq before the should_save_checkpoint calculation in the training
flow, rejecting or explicitly handling zero so display_step_id % self.save_freq
is never evaluated with a zero divisor. Preserve the final-checkpoint condition
based on display_step_id == self.num_steps, and apply the validation at the
configuration or initialization point that consumes
training_params.get("save_freq").
🤖 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.

Outside diff comments:
In `@deepmd/pt/train/training.py`:
- Around line 1987-1996: Update the ZeRO-1 checkpoint flow around
_collect_checkpoint_states and save_checkpoint so full-validation
best-checkpoint saves enter the optimizer consolidation collectively on every
rank, including non-LoRA runs. Ensure rank 0 still performs the actual
checkpoint output after collective state gathering, or explicitly reject the
incompatible validating.full_validation and validating.save_best configuration
before entering the save path.
- Around line 1744-1758: Validate self.save_freq before the
should_save_checkpoint calculation in the training flow, rejecting or explicitly
handling zero so display_step_id % self.save_freq is never evaluated with a zero
divisor. Preserve the final-checkpoint condition based on display_step_id ==
self.num_steps, and apply the validation at the configuration or initialization
point that consumes training_params.get("save_freq").

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 92ca0272-1a21-4c24-9860-24d2aeb56d12

📥 Commits

Reviewing files that changed from the base of the PR and between 775b68f and c814ddd.

📒 Files selected for processing (11)
  • deepmd/dpmodel/train/checkpoint.py
  • deepmd/dpmodel/train/timing.py
  • deepmd/pt/train/training.py
  • deepmd/pt_expt/train/ema.py
  • deepmd/utils/argcheck.py
  • doc/train/training-advanced.md
  • source/tests/common/dpmodel/test_train_checkpoint.py
  • source/tests/common/dpmodel/test_train_timing.py
  • source/tests/pt_expt/test_ema.py
  • source/tests/pt_expt/test_entrypoint.py
  • source/tests/pt_expt/test_training.py
💤 Files with no reviewable changes (1)
  • source/tests/pt_expt/test_entrypoint.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • doc/train/training-advanced.md
  • source/tests/common/dpmodel/test_train_timing.py
  • deepmd/dpmodel/train/timing.py
  • source/tests/common/dpmodel/test_train_checkpoint.py
  • deepmd/utils/argcheck.py
  • source/tests/pt_expt/test_training.py

@OutisLi
OutisLi requested review from njzjz and wanghan-iapcm August 5, 2026 03:48

@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.

Reviewed against the request: are the docs updated, and do other backends break?

Docs: partially updated

  • Done: doc/train/training-advanced.md gets a max_ckpt_keep bullet, save_dir/ckpt_keep_ratio now tagged PyTorch + PyTorch Exportable, and a restart-retention note. The argcheck doc strings (auto-rendered into the options table via .. dargs:: in doc/train/train-input.rst) are also updated: ema_ckpt_keep inheritance, pt_expt tags on the save/EMA/ZeRO/full-validation/amp options, the zero_stage enable_compile exclusion for stages 2/3, and the tf32_infer note.
  • Gap: doc/train/parallel-training.md is not updated, although this PR makes zero_stage available to the PyTorch Exportable backend. That page still says ZeRO "Works only in PyTorch backend" and lists constraints without pt_expt or the enable_compile/enable_ema restrictions on stages 2/3. Users reaching for the new feature from the parallel-training guide will get stale guidance.

Other backends: not broken (verified by installing the PR head)

  • All six trainer modules import cleanly: pt, pt_expt, jax, tf2, pd, tf.
  • The moves of deepmd.pt.train.{ema,utils,validation} to pt_expt leave no dangling imports anywhere in the tree.
  • The shared AbstractTrainer refactor is consumed by jax/tf2/pt_expt; ran source/tests/jax/test_training.py + source/tests/tf2/test_training.py (33 passed) and source/tests/pt/test_training.py (52 passed) — no regressions.
  • format_training_message dropped step_time; tf/pd callers don't use it.
  • The ema_ckpt_keep argcheck change (int default 3 → None, inherits max_ckpt_keep) affects only pt/pt_expt; common argcheck/schema tests pass.

Regression found (pt_expt)

  • source/tests/pt_expt/test_training.py::test_unsupported_optimizer_has_clear_error fails: unsupported optimizer types now raise KeyError: 'adam_beta1' instead of ValueError("Unsupported optimizer type: ..."). See the inline comment.

Behavior change worth a release note

  • pt's default EMA checkpoint retention changes from 3 to inheriting max_ckpt_keep (default 5). It is documented in the options table and training-advanced.md, but the on-disk retention for existing users changes silently.

Attribution

Coding agent: opencode
opencode version: 1.18.13
Model: ustc/deepseek-v4-flash
Reasoning effort: max

Comment thread deepmd/pt_expt/train/training.py

@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.

Follow-up review of deepmd/utils/argcheck.py in this PR.

Correct / consistent:

  • ema_ckpt_keep type change int(default 3) → [int, None](default None) is sound: the extra_check admits None, the generated JSON schema stays valid (test_doc_train_input passes), and the only reader is build_checkpoint_stores (deepmd/dpmodel/train/checkpoint.py:277) which handles None by inheriting max_ckpt_keep. No other code reads it. Note the deliberate behavior change: the pt backend's default EMA retention moves from 3 to max_ckpt_keep (default 5).
  • The tf32_infer note is accurate: pt_expt never reads DP_TF32_INFER (grep of deepmd/pt_expt confirms it only sets it via infer_env_defaults), so "no effect there" is correct.

Two doc inconsistencies (not functional breaks):

  1. doc_max_ckpt_keep was not updated, although the PR makes max_ckpt_keep the default retention window for the EMA family too. Its text still reads as applying to regular checkpoints only, while the new ema_ckpt_keep doc ("When unset, it inherits max_ckpt_keep") and doc/train/training-advanced.md both describe the inheritance. Users reading the generated options table learn that ema_ckpt_keep inherits from max_ckpt_keep, but not that max_ckpt_keep governs the EMA family by default. Suggest amending doc_max_ckpt_keep to mention the EMA-family inheritance, e.g. "The maximum number of recent periodic checkpoints retained for each checkpoint family; the EMA family inherits this window by default."
  2. Inconsistent backend tags under validating: amp_infer is retagged supported_backends("pt", "pt_expt"), but compiled_infer stays pt-only. Both go through the same new infer_env_defaults in pt_expt (deepmd/pt_expt/train/utils.py:67 maps compiled_inferDP_COMPILE_INFER, line 69 maps amp_inferDP_AMP_INFER), and pt_expt code actually consumes both: DP_COMPILE_INFER in deepmd/pt_expt/descriptor/dpa4_nn/block.py:107, DP_AMP_INFER in deepmd/pt_expt/descriptor/dpa4.py:208. If amp_infer merits the pt_expt tag, compiled_infer should be tagged identically.

Attribution

Coding agent: opencode
opencode version: 1.18.13
Model: ustc/deepseek-v4-flash
Reasoning effort: max

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 64 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.35%. Comparing base (a3195b0) to head (a425d1e).

Files with missing lines Patch % Lines
deepmd/pt_expt/train/training.py 75.00% 43 Missing ⚠️
deepmd/pt/train/training.py 87.03% 7 Missing ⚠️
deepmd/dpmodel/train/sharding.py 81.81% 6 Missing ⚠️
deepmd/pt_expt/train/utils.py 85.71% 4 Missing ⚠️
deepmd/pt_expt/train/validation.py 87.50% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5958      +/-   ##
==========================================
- Coverage   79.59%   79.35%   -0.24%     
==========================================
  Files        1081     1085       +4     
  Lines      126244   126405     +161     
  Branches     4592     4598       +6     
==========================================
- Hits       100490   100315     -175     
- Misses      24101    24437     +336     
  Partials     1653     1653              

☔ 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.

@wanghan-iapcm wanghan-iapcm left a comment

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.

Both of my earlier findings are fixed, and I checked each against c814ddd97 rather than going by the replies.

The import removal is right, including the part of your reply I had not verified when I raised it: open_stat_file really does own nested HDF5 parent creation (stat_file.py calls target.parent.mkdir(parents=True, exist_ok=True)), and test_stat_file.py already exercises it through tmp_path / "nested" / "stat.hdf5". So the deleted test was redundant rather than coverage quietly dropped, and removing the helper was the better call.

For prune I reconstructed the pre-fix line and ran your new test_prune_keeps_every_checkpoint_below_the_window against both versions instead of reading it:

PRE-FIX   FAIL -> deleted steps [1, 2, 3, 4, 5, 6, 7]
POST-FIX  PASS

It fails pre-fix for exactly the right reason, so it is a genuine regression test. The training-advanced.md paragraph covers the retention note I raised in the body too.

The other changes in this round check out: _named_model_parameters returns a list, so collecting the backups up front in apply_shadow is safe to re-iterate and is what makes tied weights restore correctly; eval_env_defaults is in scope at the new pt/train/training.py use site; and the argcheck edit is an accuracy fix rather than a narrowing, since DP_COMPILE_INFER and DP_TF32_INFER are only read under deepmd/pt/ while DP_AMP_INFER is read in deepmd/kernels/utils.py and pt_expt/descriptor/dpa4.py.

One blocker left, inline. Getting collection working turned out to expose a regression this PR introduces against its own base, and it is the only red shard remaining.

Still open from my first review, as notes rather than blockers: format_training_message losing step_time and the avg = ... s/step field, which drops that figure for pt_expt, jax and tf2; and the rank == 0 branch of build_checkpoint_stores being unexercised because every test uses the default rank.

Comment thread deepmd/pt_expt/train/training.py
wanghan-iapcm pushed a commit to wanghan-iapcm/deepmd-kit that referenced this pull request Aug 6, 2026
…backend"

This reverts the pt_expt TF32 policy (99d33ea plus its comment edits in
ae72043), restoring the warn-and-ignore behavior on master.

The knob is unrelated to this PR's measured speedup (the benchmark card has
no TF32 silicon; the whole 1.69x/3.01x gain comes from the contraction fix),
its benefit was never measured, and PR deepmodeling#5958 owns the pt_expt training
runtime alignment -- including the documented position that pt_expt runs at
'highest' matmul precision.  Keeping a second, contradicting implementation
here would split ownership of the same policy across two PRs.
Copilot AI review requested due to automatic review settings August 7, 2026 01:36

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@OutisLi
OutisLi requested a review from wanghan-iapcm August 7, 2026 01:38
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