refactor(loss): extract masked per-frame reduction idioms to cut nested branching#5783
Conversation
Extract the three per-frame reduction idioms introduced by the mixed_type padding fix (deepmodeling#5738) into deepmd/dpmodel/loss/reduction.py, written with array_api_compat so both the dpmodel and pt loss backends can share them. Callers still keep the original non-masked expression in the else branch, so the bit-identical-for-non-mixed guarantee is preserved. No call sites adopt the helpers yet. Ref deepmodeling#5768
Collapse the idiom-1 (force/atom-energy/prefactor-force) and idiom-2 (energy) masked per-frame arithmetic in deepmd/dpmodel/loss/ener.py to calls into the shared reduction helpers. Huber, f_use_norm, generalized force and the non-masked else branches are untouched; numerics are bit-identical. Ref deepmodeling#5768
Complete the dpmodel EnergyLoss adoption by routing the virial masked per-frame branches through per_frame_component_mean, matching the energy term and EnergySpinLoss. Numerics bit-identical; else/huber/display branches untouched. Ref deepmodeling#5768
EnergySpinLoss now reuses the same reduction helpers as EnergyLoss for its energy/real-force/atom-energy/virial masked branches, cutting the largest duplication between the two files. The magnetic-force (mask_mag) block is spin-specific and left bespoke. Numerics bit-identical. Ref deepmodeling#5768
…loss Local dos/cdf/tensor masked branches use the idiom-1 helper; the global dos/cdf/tensor terms keep their plain-mean loss and only route the display-only atom-count divisor through masked_atom_num. Numerics bit-identical. Ref deepmodeling#5768
…Loss deepmd/pt/loss/ener.py now imports the array_api_compat reduction helpers from deepmd.dpmodel.loss.reduction and calls them with torch tensors; the torch namespace dispatch preserves autograd and is bit-identical to the prior inlined torch code. Huber / f_use_norm / generalized-force paths and the .to(GLOBAL_PT_FLOAT_PRECISION) casts are unchanged. Ref deepmodeling#5768
…ng, test) Address final-review nits on the shared reduction module: per_frame_component_mean now derives nf from err.shape[0] internally (matching masked_atom_mean and removing a caller footgun), add the missing Returns type line to masked_atom_num's docstring, and add an autograd test for per_frame_component_mean. Numerics bit-identical. Ref deepmodeling#5768
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe change adds shared masked-reduction helpers, tests their NumPy and PyTorch behavior, and applies them across dpmodel and PyTorch DOS, energy, spin-energy, and tensor losses. ChangesMasked loss reduction refactor
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
source/tests/common/dpmodel/test_loss_reduction.py (1)
19-33: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
_refduplicates the implementation formula rather than independently verifying it.
_refmirrorsmasked_atom_mean's exact computation, sotest_numpy_matches_referencemainly confirms the numpy dispatch path matches itself rather than validating correctness against an independently derived expected value. Consider adding at least one case with a manually computed expected scalar to catch a shared logic bug in both.🤖 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 `@source/tests/common/dpmodel/test_loss_reduction.py` around lines 19 - 33, Replace or supplement the formula-based _ref oracle in test_numpy_matches_reference with at least one test case whose expected reduction scalar is manually computed and asserted directly. Keep coverage for both ncomp values as appropriate, but ensure the test can detect a shared error in masked_atom_mean and _ref rather than comparing two identical implementations.
🤖 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/loss/reduction.py`:
- Around line 30-55: Update masked_atom_mean to guard zero values in
per_frame_dof before dividing per_frame_sum by it, assigning a finite neutral
per-frame result for all-padding frames while preserving the existing
calculation for positive degrees of freedom and the final frame mean.
---
Nitpick comments:
In `@source/tests/common/dpmodel/test_loss_reduction.py`:
- Around line 19-33: Replace or supplement the formula-based _ref oracle in
test_numpy_matches_reference with at least one test case whose expected
reduction scalar is manually computed and asserted directly. Keep coverage for
both ncomp values as appropriate, but ensure the test can detect a shared error
in masked_atom_mean and _ref rather than comparing two identical
implementations.
🪄 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: 611b6d93-b490-4ef1-b0e6-52b9b72acb1c
📒 Files selected for processing (10)
deepmd/dpmodel/loss/dos.pydeepmd/dpmodel/loss/ener.pydeepmd/dpmodel/loss/ener_spin.pydeepmd/dpmodel/loss/reduction.pydeepmd/dpmodel/loss/tensor.pydeepmd/pt/loss/dos.pydeepmd/pt/loss/ener.pydeepmd/pt/loss/ener_spin.pydeepmd/pt/loss/tensor.pysource/tests/common/dpmodel/test_loss_reduction.py
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #5783 +/- ##
==========================================
- Coverage 79.77% 78.32% -1.46%
==========================================
Files 1020 1050 +30
Lines 116872 120545 +3673
Branches 4308 4348 +40
==========================================
+ Hits 93239 94413 +1174
- Misses 22095 24574 +2479
- Partials 1538 1558 +20 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
njzjz-bot
left a comment
There was a problem hiding this comment.
Reviewed commit 13b74c9. I found no blocking correctness issues in the shared reduction helpers or their dpmodel and PyTorch call sites. The refactor preserves the existing shape, dtype, masking, normalization, and reduction order.
Local validation:
- common helper and padding tests: 65 passed
- PyTorch padding tests: 58 passed
- cross-backend loss consistency tests: 232 passed, 188 skipped
- Ruff check and format check: passed
Coding agent: Codex
Codex version: codex-cli 0.144.1
Model: gpt-5.6-sol
Reasoning effort: xhigh
An all-padding frame has zero real atoms, so per_frame_dof is 0 and the per-frame ratio became 0/0 = NaN, poisoning the frame mean (and its gradient under autograd) for every loss term using this helper. Divide by a safe denominator and map zero-dof frames to a neutral 0. Frames with real atoms are untouched, preserving the bit-identical guarantee.
Add finiteness regression tests for the all-padding (zero real atom) case: one asserting the numpy value is finite and equals the neutral-0 expectation, one asserting the torch value and autograd gradient stay finite. Existing tests never exercised an all-zero mask and shared the reference formula, so they could not catch the 0/0 = NaN defect.
Resolves #5768. Follow-up to #5738.
What this does
The mixed_type padding fix (#5738) added a masked (
maskf is not None) branch to every term of the shared losses. Combined with the existinghas_*/mse|mae/use_huberconditions, that produced deeply nested, duplicated per-frame arithmetic repeated across energy, force, virial, atom_ener, atom_pref, dos, tensor, and again acrossener_spin, in both the dpmodel and pt backends.This PR extracts the three recurring masked reduction "idioms" into a single shared
array_api_compatmodule,deepmd/dpmodel/loss/reduction.py:masked_atom_mean(elem, maskf, ncomp)— idiom 1: per-atom masked mean overncompcomponents (force, atom_ener, atom_pref, local dos/tensor, spin real-force).per_frame_component_mean(err)— idiom 2 primitive: per-frame mean over components; the caller applies the extensive1/real_natoms**norm_expweighting (energy, virial).masked_atom_num(mask, natoms, dtype)— idiom 3 companion: the display-only atom-count divisor for already-reduced global quantities (global dos/tensor).Both backends import the same module; the pt backend calls it with torch tensors, and
array_api_compat's torch-namespace dispatch preserves autograd and produces bit-identical results.Numerics are preserved
This is a pure readability/maintainability refactor with no intended behavior change. Each helper implements only the masked branch; every call site keeps its original non-masked
elseexpression verbatim, so the "bit-identical for non-mixed batches" guarantee from #5738 holds (defaulting the mask to all-ones would change the reduction order at the ULP level and is deliberately avoided). Operand order and the mask-application point are preserved in every conversion.Testing
The safety net is the grad-accumulation-invariant and no-op-for-non-mixed tests added in #5738 plus the cross-backend consistency loss tests, all of which stay green:
source/tests/common/dpmodel/test_loss_padding.py,source/tests/pt/test_loss_padding.pysource/tests/consistent/loss(dpmodel <-> pt <-> tf): 244 passed, 176 skipped — unchanged, the load-bearing bit-identity guardsource/tests/common/dpmodel/test_loss_reduction.pyKnown limitations
deepmd/dpmodel/lossand is imported bydeepmd/pt/loss— the first such cross-backend import in the loss layer. This is intentional (single source of truth for the reduction math, consistent withdeepmd/dpmodelbeing the designated shared-math layer), but it does couple the pt loss backend toarray_api_compat.f_use_normforce variant, the generalized-force (gf) input masking, and the spin magnetic-force (mask_mag) block are intentionally NOT routed through the helpers — they are not one of the three idioms.deepmd/tf) loss backend is out of scope; it does not use the mixed_type padding mask.Summary by CodeRabbit
Improvements
Tests
Nonemasks), plus PyTorch autograd checks and no-NaN behavior on all-padding frames.