Skip to content

fix(disp): div 0 error when using disp_avg under multitask - #5809

Open
OutisLi wants to merge 3 commits into
deepmodeling:masterfrom
OutisLi:pr/disp
Open

fix(disp): div 0 error when using disp_avg under multitask#5809
OutisLi wants to merge 3 commits into
deepmodeling:masterfrom
OutisLi:pr/disp

Conversation

@OutisLi

@OutisLi OutisLi commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Added per-task averaged training metrics for display intervals, with support for single-task and multi-task training.
    • Validation metrics are now reported per task alongside averaged training metrics.
    • Tasks without sampled training steps display NaN values rather than incomplete results.
  • Bug Fixes
    • Improved consistency between metric columns, displayed steps, and reported values in multi-task training logs.
  • Documentation
    • Clarified the behavior and backend support of the disp_avg training option.
  • Tests
    • Added coverage for averaged metrics, restarts, validation, unsampled tasks, and metric reporting.

Copilot AI review requested due to automatic review settings July 16, 2026 01:40
@dosubot dosubot Bot added the bug label Jul 16, 2026
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: df3fa3bf-d3bd-4760-a26c-08150161f021

📥 Commits

Reviewing files that changed from the base of the PR and between a73ebb0 and 67837d7.

📒 Files selected for processing (25)
  • deepmd/dpmodel/loss/dos.py
  • deepmd/dpmodel/loss/ener.py
  • deepmd/dpmodel/loss/ener_spin.py
  • deepmd/dpmodel/loss/loss.py
  • deepmd/dpmodel/loss/property.py
  • deepmd/dpmodel/loss/tensor.py
  • deepmd/dpmodel/train/__init__.py
  • deepmd/dpmodel/train/metrics.py
  • deepmd/dpmodel/train/trainer.py
  • deepmd/pt/loss/denoise.py
  • deepmd/pt/loss/dos.py
  • deepmd/pt/loss/ener.py
  • deepmd/pt/loss/ener_spin.py
  • deepmd/pt/loss/loss.py
  • deepmd/pt/loss/population.py
  • deepmd/pt/loss/property.py
  • deepmd/pt/loss/tensor.py
  • deepmd/pt/train/training.py
  • deepmd/pt_expt/train/training.py
  • deepmd/utils/argcheck.py
  • source/tests/common/dpmodel/test_train_abstract_trainer.py
  • source/tests/common/dpmodel/test_train_metrics.py
  • source/tests/common/test_disp_avg.py
  • source/tests/common/test_loss_metric_names.py
  • source/tests/pt/test_multitask.py

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Training display averaging now uses declared per-task metric names and a shared accumulator. PyTorch and PyTorch Exportable trainers average detached training metrics per display interval, render NaN for unsampled tasks, and reset interval state. Tests cover metric declarations, averaging, validation, restart behavior, and output columns.

Changes

Training metric display averaging

Layer / File(s) Summary
Training metric contracts
deepmd/dpmodel/loss/*, deepmd/pt/loss/*
Loss classes expose configuration-dependent training_metric_names without l2_ metrics.
Per-task accumulation
deepmd/dpmodel/train/metrics.py, deepmd/dpmodel/train/trainer.py
TrainingMetricAccumulator stores detached metrics, computes arithmetic means, preserves metric ordering, and returns NaN for unsampled tasks.
Trainer integration
deepmd/pt/train/training.py, deepmd/pt_expt/train/training.py, deepmd/dpmodel/train/trainer.py
Training loops accumulate metrics, use averaged values for display, evaluate validation per task, suppress false NaN checks for unsampled tasks, and reset windows after display.
Validation and documentation
source/tests/common/*, source/tests/pt/test_multitask.py, deepmd/utils/argcheck.py
Tests cover backend behavior, restart windows, metric declarations, unsampled intervals, and the updated disp_avg configuration.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant Trainer
  participant TrainingMetricAccumulator
  participant LearningCurveWriter
  Trainer->>TrainingMetricAccumulator: Add detached metrics for each task step
  Trainer->>TrainingMetricAccumulator: Request per-task interval averages
  TrainingMetricAccumulator-->>Trainer: Return averages or NaN for unsampled tasks
  Trainer->>LearningCurveWriter: Log training and validation results
Loading

Suggested reviewers: njzjz

Merge Risk: ⚪ Minimal · up to 67837

The display-averaging changes cover unsampled tasks, metric schemas, interval resets, and learning-curve alignment without an identified merge-blocking risk.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.05% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 88 functions across 25 files. 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 identifies the primary change: fixing a division-by-zero error when using disp_avg with multitask training.
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.
  • Fix all pre-merge checks with AI
✨ 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: 2

🤖 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/train/training.py`:
- Around line 1619-1632: Update the multitask display-step handling in run()
around the disp_avg branch: perform a dummy forward pass for each unsampled
model key to populate self.train_loss_accu with its loss keys before generating
train_results, excluding l2_ fields as in the proposed flow. Move the validation
and rank-zero console logging loop outside the disp_avg conditional so
log_loss_valid and progress messages execute for both averaged and non-averaged
modes.

In `@source/tests/pt/test_multitask.py`:
- Around line 273-277: Update the lcurve.out assertions in the multitask test to
read and tokenize the header row, then assert every data row has exactly the
header’s column count. Preserve the existing displayed_steps and “nan” checks
while ensuring the unsampled task does not produce a shorter row.
🪄 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: 5ac6ebae-2ba6-49ed-b897-c162751bd612

📥 Commits

Reviewing files that changed from the base of the PR and between 1c5ff68 and 620847a.

📒 Files selected for processing (2)
  • deepmd/pt/train/training.py
  • source/tests/pt/test_multitask.py

Comment thread deepmd/pt/train/training.py Outdated
Comment thread source/tests/pt/test_multitask.py Outdated
@OutisLi
OutisLi requested a review from njzjz July 16, 2026 02:08

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 encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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

Requesting changes because the filtered-batch path can still leave an unsampled task without the metric schema required by learning-curve output.

Coding agent: Codex
Codex version: codex-cli 0.144.4
Model: gpt-5.6-sol
Reasoning effort: xhigh

Comment thread deepmd/pt/train/training.py Outdated
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.72650% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.06%. Comparing base (28b7d06) to head (67837d7).

Files with missing lines Patch % Lines
deepmd/dpmodel/loss/loss.py 66.66% 1 Missing ⚠️
deepmd/dpmodel/train/trainer.py 92.30% 1 Missing ⚠️
deepmd/pt/loss/denoise.py 66.66% 1 Missing ⚠️
deepmd/pt/loss/loss.py 66.66% 1 Missing ⚠️
deepmd/pt/train/training.py 95.65% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5809      +/-   ##
==========================================
- Coverage   77.25%   77.06%   -0.19%     
==========================================
  Files        1153     1154       +1     
  Lines      138930   138975      +45     
  Branches     5056     5056              
==========================================
- Hits       107328   107100     -228     
- Misses      29717    29990     +273     
  Partials     1885     1885              

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

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

🤖 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/train/training.py`:
- Around line 1715-1716: Update the unsampled-task initialization around the `if
not task_input` early return so filtered `{}` batches cannot leave
`train_results[_key]` without its metric keys. Populate the expected metric
schema independently of a consumable training batch, or continue fetching until
`get_data()` returns a valid batch, while preserving the existing `disp_avg` and
validation display behavior.
🪄 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: 28d64a76-12e4-4629-83a0-3c8a68c14e06

📥 Commits

Reviewing files that changed from the base of the PR and between 620847a and a73ebb0.

📒 Files selected for processing (2)
  • deepmd/pt/train/training.py
  • source/tests/pt/test_multitask.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/tests/pt/test_multitask.py

Comment thread deepmd/pt/train/training.py Outdated

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

Request changes: the unsampled-task schema is still not guaranteed when a training batch is filtered out by min_pair_dist.

initialize_task_loss_accumulator() returns immediately on an empty task_input. That leaves train_results[_task_key] empty. At the first display, print_on_training() iterates the validation metric keys and indexes the corresponding missing training keys, causing a KeyError; without validation, the generated lcurve.out header is incomplete and later rows can become misaligned.

Please populate the placeholder metric schema without depending on a single consumable batch (or keep fetching until one is usable), and add a regression test covering an unsampled task whose initialization batch is fully filtered.

All CI checks are otherwise passing.

— OpenClaw 2026.6.11

Comment thread deepmd/pt/train/training.py Outdated

@njzjz njzjz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks — the second half of this is a clear bug fix, but the accumulator seeding worries me.

The logging hoist is right

On master the for _key in self.model_keys: loop that calls log_loss_valid and emits the _trn/_val lines sits inside the else: (non-disp_avg) branch, so a multi-task run with disp_avg: true produced no per-task log lines at all — train_results was filled and then dropped on the floor. Hoisting it to its own loop over model_keys fixes that, and threading check_total_rmse_nan=False for a task with step_count_per_task == 0 is the right way to keep the deliberate NaN from tripping the NaN guard. dict.fromkeys(task_losses, float("nan")) for the zero-step case is a clean replacement for the silent empty dict.

initialize_task_loss_accumulator runs a real training step to learn column names

self.optimizer.zero_grad(set_to_none=True)
task_input, task_label, _ = self.get_data(is_train=True, task_key=_task_key)
if not task_input:
    return
_, _, task_more_loss = self.wrapper(**task_input, cur_lr=pref_lr, label=task_label, task_key=_task_key)

I understand why: lcurve.out writes its header once, so every task's column set has to be known at the first display step, and more_loss keys only exist after a forward. But this pays for that with four side effects on a path that is supposed to be pure reporting:

  1. It consumes a training batch. get_data(is_train=True, task_key=...) advances that task's training iterator. So the task the display is about to report as "not sampled this interval" is sampled — the batch is drawn, fed forward, and thrown away. That shifts the data stream and epoch bookkeeping for that task by one batch per display step until it is first sampled naturally. Your own test has to mock dp_random.choice with an exact [0, 1] sequence, which is a hint at how sensitive this is.
  2. No torch.no_grad(). The enclosing block runs after self.wrapper.eval(), so this forward builds a full autograd graph in eval mode and discards it — wasted memory and time on every display step until the task is seeded, and for a large task that is not cheap.
  3. self.optimizer.zero_grad(set_to_none=True) mutates optimizer state from inside the display path. The non-disp_avg branch does the same thing on master, so there is precedent, but that branch is at least computing numbers it then reports; here the loss is discarded.
  4. DDP. The if not task_input: return early exit is evaluated per rank. If it can ever be true on some ranks and not others, the ranks disagree on whether to run a forward and the collectives desynchronize. Worth confirming get_data cannot return a falsy task_input on a subset of ranks.

Two directions that avoid all four:

  • Keep the keys, not the counters. The reset at the end of the display block already zeroes values and preserves keys, so a task only needs seeding if it has never been sampled since training started. If the header could be deferred until every task has been seen once, or written with a per-task placeholder set derived from the loss configuration (which terms are enabled: energy / force / virial / …) rather than from an executed more_loss, no forward is needed at all.
  • If a forward really is unavoidable, at minimum wrap it in torch.no_grad(), and draw from the validation loader rather than the training one so the training stream is untouched.

Smaller points

  • initialize_task_loss_accumulator is redefined on every display step; it does not close over anything that changes except pref_lr, so it could be a method or moved above the loop.
  • The test asserts self.assertIn("nan", data_lines[1]) — that checks the literal token appears somewhere in the row. Asserting that the NaN falls in that task's columns (via header_columns.index(...)) would pin the actual contract; as written it would still pass if the NaN landed in the wrong task's column.
  • Worth a line in the test docstring or a comment saying which task is unsampled and why, so the [0, 1] mock sequence is not load-bearing but unexplained.

Copilot AI review requested due to automatic review settings July 28, 2026 07:26

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.

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