Skip to content

feat(lmdb): support mixed-size batches and lazy label availability - #5962

Open
OutisLi wants to merge 7 commits into
deepmodeling:masterfrom
OutisLi:pr/lmdb
Open

feat(lmdb): support mixed-size batches and lazy label availability#5962
OutisLi wants to merge 7 commits into
deepmodeling:masterfrom
OutisLi:pr/lmdb

Conversation

@OutisLi

@OutisLi OutisLi commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • support mix:N LMDB batches containing frames with different atom counts, using padded rectangular batches for dense models and a flat ragged node axis for eligible graph models
  • compact phantom atoms before graph-model evaluation and make loss reductions, validation weighting, and epoch sizing use real atom counts
  • resolve label availability lazily after data requirements are registered, so required, optional, defaulted, and partially available fields are handled without an eager full-dataset scan
  • keep non-mixing and native-spin models on their existing rectangular public paths; the ragged regression coverage uses upstream DPA1 and avoids model-specific dependencies

Behavioral changes

  • Masked per-atom loss terms now pool all included labels across the batch instead of averaging per-frame means. This intentionally retires the bit-identical reduction guarantee from fix(loss): exclude mixed_type padding atoms from the training loss #5738/refactor(loss): extract masked per-frame reduction idioms to cut nested branching #5783 for existing mixed_type NPY datasets whose frames have different real atom counts: those frames are weighted by their real label counts rather than equally. Uniform-atom-count batches are unchanged. Hessian pair terms remain normalized per frame so their quadratic component count does not make large structures dominate a batch.
  • Legacy LMDB files have no exact per-frame label-availability metadata. To avoid an eager O(N) startup scan, the reader uses a bounded probe and conservatively reduces per-frame find_* flags at collation. A missed rare signature may discard valid supervision for the affected batch, but default-filled values are never treated as real labels. Recording exact availability metadata when generating LMDB datasets is tracked in Record exact label availability when building LMDB datasets #5954.

Testing

  • all pre-commit hooks passed for the changed files
  • 375 passed, 2 skipped, 1 deselected, and 13 subtests passed in the main targeted LMDB/PT/PT-expt/model suite
  • 15 passed in the isolated loss-reduction and decoder-pool regression suite
  • review fixes: 100 passed, 2 skipped, and 2 subtests passed in the common loss suite; 60 passed in the PT padding-loss suite; all 25 LMDB training tests passed; padded/unpadded DPA2 graph and Hessian parity tests passed

Summary by CodeRabbit

  • New Features

    • Added LMDB batching for frames with different atom counts, including mixed-size and ragged layouts.
    • Added ragged-batch inference and training for supported energy and spin models.
    • Added configurable data-source policies for optional labels and parameters.
    • Added safer handling of padded atoms across neighbor graphs and model outputs.
  • Bug Fixes

    • Improved per-atom loss normalization for uneven and padded batches.
    • Prevented padded atoms from affecting neighbor searches, metrics, or losses.
    • Improved handling of missing labels and default-valued data.
  • Documentation

    • Documented mixed-size batching, ragged data, and per-atom normalization.

Closes #5965

@OutisLi
OutisLi marked this pull request as ready for review August 8, 2026 15:42
Copilot AI lite review requested due to automatic review settings August 8, 2026 15:42
LMDB batches previously required every frame to have the same atom count,
which can leave sparse size groups under-filled and give their frames a
disproportionate optimizer weight. Add `batch_size: "mix:N"` so frames of
different sizes can share one atom-budgeted batch.

Use two layouts according to the model contract. Eligible graph models consume
one concatenated node axis with per-frame `n_node` counts; other models retain
rectangular batches whose shorter frames are padded with `atype = -1`. Keep
native-spin models on the rectangular public path because their output
translation is spin-specific.

Exclude phantom rows from neighbor graphs and model evaluation, scatter
per-atom outputs back at the public boundary, and make losses and validation
weight only real atoms. Consolidate LMDB sampling and decoding around an
explicit batch layout so serial and worker-process decoding preserve the same
field shapes and frame order.

Cover the ragged training path with the existing DPA1 graph lower, alongside
padding, compaction, loss-reduction, sampler, and decoder regressions.
@dosubot dosubot Bot added the new feature label Aug 8, 2026

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.

Large LMDBs must not pay O(frame count) random I/O or Python-object allocation before the active training contract is known. Read metadata through sequential readahead, keep frame tables in compact NumPy arrays, and choose readahead according to the access pattern of each reader.

Defer availability resolution until requirements are registered. Probe only optional tracked fields; uniform datasets start without a full scan, while detected mixed datasets build one compact cached signature index through a sequential reader with bounded progress logging. Mandatory fields fail at decode, default-backed inputs remain available per frame, and derived fields are computed from normalized structure data.

Apply the contract consistently to statistics, samplers, full validation, and both PT training paths. Declare only active loss labels, preserve explicit values beside defaults, and gate force-derived losses by the availability of their force target.

Keep filtered frame and system indices, mixed-nloc packing, and validation views in one index domain, and retain the block-allocation and batch-layout fixes found while consolidating the data path.
@OutisLi
OutisLi marked this pull request as draft August 8, 2026 15:44
@OutisLi
OutisLi marked this pull request as ready for review August 8, 2026 15:44
@dosubot dosubot Bot added the enhancement label Aug 8, 2026
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds mixed-atom-count LMDB batching, ragged graph execution, phantom-node filtering, source-policy metadata, and pooled per-atom loss reductions across DPModel and PyTorch paths.

Changes

Mixed-NLOC and ragged execution

Layer / File(s) Summary
Loss requirements and pooled reductions
deepmd/dpmodel/loss/*, deepmd/pt/loss/*
Losses use conditional requirements, per-frame node counts, pooled masked reductions, and real-atom normalization.
Node counts and graph compaction
deepmd/dpmodel/utils/neighbor_graph/*, deepmd/pt_expt/utils/*
Neighbor graphs accept per-frame node counts, exclude phantom atoms, and compact or expand node axes.
LMDB mixed-NLOC batching
deepmd/pt/utils/lmdb_dataset.py, deepmd/pt_expt/utils/lmdb_dataset.py, deepmd/pt/train/training.py
LMDB loading uses shared samplers and supports mixed-NLOC padded or ragged batches.
Ragged model paths
deepmd/pt_expt/model/*, deepmd/pt_expt/train/*, deepmd/dpmodel/model/make_model.py
Graph models expose ragged forwarding, compact padded nodes, and restore rectangular outputs.
Source policies and validation
deepmd/utils/data.py, deepmd/utils/argcheck.py, source/tests/*, doc/*
Data requirements identify tracked, default, or derived sources. Tests and documentation cover mixed-NLOC behavior, padding, and loss normalization.

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

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 67.20% 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
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the two main changes: mixed-size LMDB batches and lazy label availability.
✨ 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.

Comment thread source/tests/common/dpmodel/test_lmdb_data.py

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

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 (1)

292-328: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Non-distributed LMDB sampler drops the configured seed.

The distributed branch passes seed=_training_params.get("seed") to DistributedLmdbBatchSampler at Line 312. The non-distributed branch does not pass a seed to LmdbBatchSampler at Line 316. LmdbBatchSampler accepts an optional seed and falls back to OS entropy when it is None, so single-process training with a configured training.seed produces a different batch shuffle on every run, while multi-rank training stays reproducible. Pass the same seed to both samplers.

🐛 Proposed fix to pass the configured seed to the non-distributed sampler
                 else:
                     _inner_sampler = LmdbBatchSampler(
                         _data._reader,
                         shuffle=True,
+                        seed=_training_params.get("seed"),
                         block_targets=_block_targets,
                     )
🤖 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 292 - 328, Pass the configured
training seed from _training_params.get("seed") to the non-distributed
LmdbBatchSampler construction, matching the existing DistributedLmdbBatchSampler
branch while preserving the current sampler options.
deepmd/pt_expt/train/wrapper.py (1)

223-238: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Remove the unsafe ragged-spin dispatch risk.

_forward_without_loss passes input_dict directly to model.forward_ragged() when n_node is set. forward_ragged does not accept spin or **kwargs, but input_dict can include spin for spin-capable models. Add a local guard or dispatch path so a ragged input for a spin model fails explicitly instead of as TypeError: forward_ragged() got an unexpected keyword argument 'spin'.

🤖 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/wrapper.py` around lines 223 - 238, Update
_forward_without_loss so ragged inputs with spin are detected before calling
model.forward_ragged; explicitly reject this combination with a clear supported
error, while preserving the existing forward_ragged dispatch for ragged inputs
without spin and the regular model call for non-ragged inputs.
🧹 Nitpick comments (4)
deepmd/pt_expt/train/training.py (1)

1704-1708: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pass the normalized per-task data maps instead of the raw constructor arguments.

_configure_batch_layout re-implements the dict-or-bare dispatch that _as_task_map already performed at lines 1593-1602. self.training_data_by_task and self.validation_data_by_task are keyed by self.model_keys and are available at this point. Using them removes the second normalization path and lets _configure_batch_layout drop its isinstance(data_map, dict) branch.

♻️ Proposed change
-        self._configure_batch_layout(training_data, validation_data)
+        self._configure_batch_layout(
+            self.training_data_by_task, self.validation_data_by_task
+        )
🤖 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/training.py` around lines 1704 - 1708, Update the call
to _configure_batch_layout in the training initialization flow to pass
self.training_data_by_task and self.validation_data_by_task instead of the raw
training_data and validation_data constructor arguments. Then simplify
_configure_batch_layout to consume these normalized per-task maps directly and
remove its redundant isinstance(data_map, dict) dispatch.
deepmd/utils/data.py (1)

1226-1232: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the allowed values from the alias.

The literal set duplicates DataRequirementSourcePolicy. A future value added to the alias would pass type checking but fail at runtime. Use typing.get_args so one declaration governs both.

♻️ Proposed refactor
-        if source_policy not in {"tracked", "default", "derived"}:
+        if source_policy not in get_args(DataRequirementSourcePolicy):
             raise ValueError(
                 "source_policy must be 'tracked', 'default', or 'derived', "
                 f"got {source_policy!r}"
             )

Add the import next to Literal:

from typing import (
    Literal,
    get_args,
)
🤖 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/data.py` around lines 1226 - 1232, Update the validation in the
function containing source_policy to derive allowed values with
typing.get_args(DataRequirementSourcePolicy) instead of duplicating the literal
set, and import get_args alongside Literal. Preserve the existing ValueError and
message behavior for invalid values.
source/tests/pt/test_loss_default_pf.py (1)

232-236: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Derive the leading dimension of drdq from the label batch.

The test hard-codes a batch dimension of 1. self.label_with_pref["force"] carries the batch size of the loaded water system. The mismatch is harmless today, because find_force = 0.0 suppresses the generalized-force branch before drdq is used. If that gating changes, the test would fail for a shape reason instead of the reason it checks.

♻️ Proposed refactor
         label["drdq"] = torch.ones(
-            (1, self.nloc * 3 * numb_generalized_coord),
+            (label["force"].shape[0], self.nloc * 3 * numb_generalized_coord),
             dtype=label["force"].dtype,
             device=label["force"].device,
         )
🤖 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/pt/test_loss_default_pf.py` around lines 232 - 236, Update the
drdq initialization in the relevant test setup to derive its leading dimension
from self.label_with_pref["force"] rather than hard-coding 1, while preserving
the existing coordinate dimension, dtype, and device.
source/tests/pt_expt/utils/test_nv_matrix_decode.py (1)

171-182: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Rounded floats make set equality brittle.

_graph_edges rounds each edge_vec component to 8 decimals and puts the result in a set. Two builders that produce values differing by less than 1e-8 still land in different sets when a value sits on a rounding boundary, for example 1.234567895. Set membership gives no tolerance.

Key the comparison on the integer endpoints only, then compare the matched vectors with np.testing.assert_allclose.

♻️ Proposed refactor
-def _graph_edges(graph) -> set:
-    """Edges as (src, dst, rounded edge_vec), so two builders can be compared."""
-    keep = graph.edge_mask
-    return {
-        (int(s), int(d), *(round(float(x), 8) for x in v))
-        for s, d, v in zip(
-            graph.edge_index[0][keep],
-            graph.edge_index[1][keep],
-            graph.edge_vec[keep],
-            strict=True,
-        )
-    }
+def _graph_edges(graph) -> dict:
+    """Edge vectors keyed by (src, dst), so two builders can be compared."""
+    keep = graph.edge_mask
+    return {
+        (int(s), int(d)): np.asarray(v, dtype=np.float64)
+        for s, d, v in zip(
+            graph.edge_index[0][keep],
+            graph.edge_index[1][keep],
+            graph.edge_vec[keep],
+            strict=True,
+        )
+    }
+
+
+def _assert_same_edges(actual, expected) -> None:
+    assert set(actual) == set(expected)
+    for key, vector in expected.items():
+        np.testing.assert_allclose(actual[key], vector, atol=1e-10)

Then call _assert_same_edges(_graph_edges(nv), _graph_edges(dense)) at line 222.

🤖 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/pt_expt/utils/test_nv_matrix_decode.py` around lines 171 - 182,
Update _graph_edges to key edges by integer (src, dst) endpoints while retaining
each edge vector without rounding, and add or update _assert_same_edges to match
endpoint keys and compare corresponding vectors with np.testing.assert_allclose.
Replace the comparison near the indicated call site with
_assert_same_edges(_graph_edges(nv), _graph_edges(dense)), preserving detection
of missing or extra endpoint pairs.
🤖 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_expt/model/make_model.py`:
- Around line 892-910: Update the graph Hessian path around
_cal_hessian_ext_graph and _WrapperForwardEnergyGraph to use the same compacted
node set as the forward branch. Pass the compacted coordinates, atom types,
batch metadata, and parameters—or apply the equivalent valid-node mask when
rebuilding each frame—so phantom padded nodes are excluded from Hessian graph
construction.

In `@deepmd/pt_expt/utils/graph_builder.py`:
- Around line 11-13: Remove the module-scope PHANTOM_ATOM_TYPE import from
graph_builder.py and relocate the constant to a lightweight dependency-free
module, then update graph_builder references to use that module. Ensure the
graph-building path no longer imports deepmd.dpmodel.utils.lmdb_data or its
lmdb/msgpack dependencies.

In `@deepmd/pt/model/model/sezm_model.py`:
- Around line 3277-3279: Update _make_inter_potential_edge_mask to accept and
use the already-computed real_atom mask from core_compute instead of
re-sanitizing descriptor_atype. Pass real_atom at the core_compute call site,
preserve the existing atom-exclusion logic, and ensure phantom atoms remain
excluded even when edge builders include them.

In `@source/tests/pt_expt/test_lmdb_training.py`:
- Line 851: Rename the keyword-only compile parameter in _run to enable_compile,
then update its call site and the assignment to
config["training"]["enable_compile"] to use the new name while preserving the
existing behavior.

---

Outside diff comments:
In `@deepmd/pt_expt/train/wrapper.py`:
- Around line 223-238: Update _forward_without_loss so ragged inputs with spin
are detected before calling model.forward_ragged; explicitly reject this
combination with a clear supported error, while preserving the existing
forward_ragged dispatch for ragged inputs without spin and the regular model
call for non-ragged inputs.

In `@deepmd/pt/train/training.py`:
- Around line 292-328: Pass the configured training seed from
_training_params.get("seed") to the non-distributed LmdbBatchSampler
construction, matching the existing DistributedLmdbBatchSampler branch while
preserving the current sampler options.

---

Nitpick comments:
In `@deepmd/pt_expt/train/training.py`:
- Around line 1704-1708: Update the call to _configure_batch_layout in the
training initialization flow to pass self.training_data_by_task and
self.validation_data_by_task instead of the raw training_data and
validation_data constructor arguments. Then simplify _configure_batch_layout to
consume these normalized per-task maps directly and remove its redundant
isinstance(data_map, dict) dispatch.

In `@deepmd/utils/data.py`:
- Around line 1226-1232: Update the validation in the function containing
source_policy to derive allowed values with
typing.get_args(DataRequirementSourcePolicy) instead of duplicating the literal
set, and import get_args alongside Literal. Preserve the existing ValueError and
message behavior for invalid values.

In `@source/tests/pt_expt/utils/test_nv_matrix_decode.py`:
- Around line 171-182: Update _graph_edges to key edges by integer (src, dst)
endpoints while retaining each edge vector without rounding, and add or update
_assert_same_edges to match endpoint keys and compare corresponding vectors with
np.testing.assert_allclose. Replace the comparison near the indicated call site
with _assert_same_edges(_graph_edges(nv), _graph_edges(dense)), preserving
detection of missing or extra endpoint pairs.

In `@source/tests/pt/test_loss_default_pf.py`:
- Around line 232-236: Update the drdq initialization in the relevant test setup
to derive its leading dimension from self.label_with_pref["force"] rather than
hard-coding 1, while preserving the existing coordinate dimension, dtype, and
device.
🪄 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: 70419c7e-1272-4870-b2b2-4070566e7cf3

📥 Commits

Reviewing files that changed from the base of the PR and between bc902da and a809f15.

📒 Files selected for processing (53)
  • deepmd/dpmodel/loss/dos.py
  • deepmd/dpmodel/loss/ener.py
  • deepmd/dpmodel/loss/ener_spin.py
  • deepmd/dpmodel/loss/reduction.py
  • deepmd/dpmodel/loss/tensor.py
  • deepmd/dpmodel/model/make_model.py
  • deepmd/dpmodel/utils/__init__.py
  • deepmd/dpmodel/utils/batch.py
  • deepmd/dpmodel/utils/lmdb_data.py
  • deepmd/dpmodel/utils/neighbor_graph/__init__.py
  • deepmd/dpmodel/utils/neighbor_graph/ase_builder.py
  • deepmd/dpmodel/utils/neighbor_graph/from_ijs.py
  • deepmd/dpmodel/utils/neighbor_graph/graph.py
  • deepmd/pt/loss/dens.py
  • deepmd/pt/loss/dos.py
  • deepmd/pt/loss/ener.py
  • deepmd/pt/loss/tensor.py
  • deepmd/pt/model/model/sezm_model.py
  • deepmd/pt/model/model/sezm_native_spin_model.py
  • deepmd/pt/train/training.py
  • deepmd/pt/utils/lmdb_dataset.py
  • deepmd/pt/utils/nv_nlist.py
  • deepmd/pt_expt/model/ener_model.py
  • deepmd/pt_expt/model/make_model.py
  • deepmd/pt_expt/train/training.py
  • deepmd/pt_expt/train/validation.py
  • deepmd/pt_expt/train/wrapper.py
  • deepmd/pt_expt/utils/edge_schema.py
  • deepmd/pt_expt/utils/graph_builder.py
  • deepmd/pt_expt/utils/lmdb_dataset.py
  • deepmd/pt_expt/utils/nv_graph_builder.py
  • deepmd/pt_expt/utils/vesin_graph_builder.py
  • deepmd/pt_expt/utils/vesin_neighbor_list.py
  • deepmd/utils/argcheck.py
  • deepmd/utils/data.py
  • deepmd/utils/data_system.py
  • doc/data/system.md
  • doc/train/training-advanced.md
  • source/tests/common/dpmodel/test_from_ijs.py
  • source/tests/common/dpmodel/test_graph_ragged.py
  • source/tests/common/dpmodel/test_lmdb_data.py
  • source/tests/common/dpmodel/test_loss_ener.py
  • source/tests/common/dpmodel/test_loss_padding.py
  • source/tests/common/dpmodel/test_loss_reduction.py
  • source/tests/consistent/test_lmdb_data.py
  • source/tests/pt/model/test_sezm_model.py
  • source/tests/pt/test_lmdb_dataloader.py
  • source/tests/pt/test_loss_default_pf.py
  • source/tests/pt/test_loss_padding.py
  • source/tests/pt_expt/model/test_dpa4_native_spin.py
  • source/tests/pt_expt/test_lmdb_training.py
  • source/tests/pt_expt/test_training.py
  • source/tests/pt_expt/utils/test_nv_matrix_decode.py

Comment thread deepmd/pt_expt/model/make_model.py
Comment thread deepmd/pt_expt/utils/graph_builder.py
Comment thread deepmd/pt/model/model/sezm_model.py Outdated
Comment thread source/tests/pt_expt/test_lmdb_training.py
@codecov

codecov Bot commented Aug 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.28857% with 48 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.49%. Comparing base (7141514) to head (c8c3a67).

Files with missing lines Patch % Lines
deepmd/pt_expt/utils/nv_graph_builder.py 15.78% 16 Missing ⚠️
deepmd/pt/utils/lmdb_dataset.py 81.48% 5 Missing ⚠️
deepmd/pt/loss/dens.py 75.00% 4 Missing ⚠️
deepmd/pt/utils/nv_nlist.py 0.00% 4 Missing ⚠️
deepmd/dpmodel/loss/ener.py 97.27% 3 Missing ⚠️
deepmd/dpmodel/loss/ener_spin.py 95.00% 3 Missing ⚠️
deepmd/dpmodel/utils/neighbor_graph/graph.py 88.46% 3 Missing ⚠️
deepmd/pt_expt/train/training.py 96.00% 3 Missing ⚠️
deepmd/utils/data.py 66.66% 2 Missing ⚠️
deepmd/dpmodel/loss/loss.py 66.66% 1 Missing ⚠️
... and 4 more
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5962      +/-   ##
==========================================
- Coverage   79.63%   79.49%   -0.14%     
==========================================
  Files        1085     1085              
  Lines      126577   127139     +562     
  Branches     4592     4592              
==========================================
+ Hits       100798   101068     +270     
- Misses      24127    24419     +292     
  Partials     1652     1652              

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

@iProzd iProzd added the P1 Required for DPA4/DPA4C release readiness. label Aug 10, 2026
@njzjz njzjz added this to the v3.2.0 milestone Aug 10, 2026

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

Mixed-size batching is worth having and much of the machinery here is careful. Things I checked rather than assumed, and found correct: the frame-offset arithmetic in the from_ijs ragged rewrite, and that all four call sites were updated to pass full((nf,), nloc); compact_nodes / expand_node_values being an exact round trip, since keep_index holds distinct positions so the segment_sum scatter inverts the gather; the claim that every builder already refuses an edge touching a phantom, which holds in ase_builder, nv_graph_builder and vesin_graph_builder alike; and the sezm_model rebinding, which actually fixes a pre-existing case where atom_excl was applied to raw possibly-negative atype. One reported problem I want to explicitly withdraw before it wastes your time: I looked hard at whether the padded generalized-force path was mis-normalised by phantom rows, and it is not -- _finalize_atomic_ret sets mask unconditionally, so that path takes the masked branch.

Four things I do want changed, and two of them are the same bug.

The shared root cause: ragged is switched on for models that never asked for it. _configure_batch_layout enables the ragged layout for every LMDB data system whenever the model is non-spin, graph-lower and has forward_ragged -- with no reference to the batch-size rule and no reference to whether frames actually differ in size. So every pt_expt LMDB run on a dpa2/dpa3-class model changes layout, including runs with perfectly uniform frames that never opted into mixed-size batching. That is what turns two narrow new-feature concerns into regressions on existing configurations. Gating it on the reader actually needing it fixes both inline comments below at once.

The other regression is the LMDB availability rewrite, and it is the one I would fix first. This is not anchorable inline because the file is too large for GitHub to serve a patch, so here it is with permalinks.

PR #5839 (e37cc0095), which fixed #5636, deliberately did two things: an exact per-frame availability signature used to partition batches, and a hard ValueError in collate_lmdb_frames if the find_* flags disagreed inside a batch. This PR removes both. The scan becomes _probe_uniform_availability over _AVAILABILITY_PROBE_FRAMES = 256 evenly spaced frames, and when the probe reports uniform, availability_groups returns a single group -- so no partitioning happens for the rest of the dataset at all. The guard becomes _batch_find_flags, which on disagreement silently sets the flag to 0 for the whole batch. No exception, and no log line on either path.

Composed, those two changes reproduce #5636 exactly: on a dataset larger than 256 frames where the frames missing a label fall between probe points, the label is silently discarded for every frame in the batch. I am not neutral about this one -- I approved #5839 specifically because it added that guard, and the regression test that reassured me, test_unrequested_labels_form_homogeneous_batches, uses 8 frames, so _evenly_spaced returns every index and the probe is exact for it. It will keep passing while the property it was written to protect is gone. That is worse than the original bug, because the test now certifies it.

The PR's own comment concedes the probe is "a finding and not a proof" and even names caching the signature in __metadata__ as the right fix. I would take that: keep exactness and make it cheap. If you want to keep the probe, then collate_lmdb_frames must keep a defensive check -- silently zeroing a label is precisely the failure mode #5839 existed to prevent.

Two smaller notes I am not blocking on. masked_pair_mean and the coverage gap are inline. Also, deepmd/pt/loss/dens.py still ignores intensive_ener_virial in its energy term while the adjacent rmse_e uses inv_natoms**2, so the displayed metric and the optimised quantity disagree -- that predates this PR (it is not yours to fix here), but you were rewriting those exact lines, so it is worth knowing.

CI is green, which given the above is itself informative: none of these have a test that can see them.

Comment thread deepmd/pt_expt/train/training.py
Comment thread deepmd/dpmodel/loss/ener.py Outdated
Comment thread deepmd/dpmodel/loss/ener.py
Comment thread deepmd/dpmodel/loss/reduction.py Outdated
Comment thread deepmd/dpmodel/loss/reduction.py
Comment thread source/tests/common/dpmodel/test_loss_padding.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.

The mixed-size implementation is substantial and well covered, but two compatibility blockers remain: ragged layout is enabled for existing non-mixing LMDB runs, and the rectangular graph Hessian path does not use the compacted real-node set used by the forward path.

Codex quota is about to reset, so I am using the remaining token budget to complete a concentrated review pass over the outstanding PRs.

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

Comment thread deepmd/dpmodel/utils/lmdb_data.py Outdated
Comment thread deepmd/pt_expt/model/make_model.py
@OutisLi

OutisLi commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator Author

@wanghan-iapcm Thank you for the careful review. On LMDB label availability, I agree with the factual concern: a bounded probe cannot prove uniformity, and when it misses a rare signature the affected batch can lose otherwise valid supervision. I am intentionally not restoring either the eager O(N) startup scan or the batch-time ValueError in this PR. The former defeats lazy startup for large legacy LMDB datasets, while the latter makes a missed probe turn into a shuffle-dependent training abort. The current batch-level AND reduction is conservative: it may disable a disputed label for that batch, but it never treats a default-filled value as real supervision. Exactness needs to be established when the LMDB is generated, by recording a schema-versioned per-frame availability signature in metadata and using that as the reader source of truth. That work is tracked in #5954. I have made both the legacy tradeoff and the long-term exact solution explicit in the PR body. The remaining requested changes are addressed in 38cc7b9 and replied to individually in their threads.

@OutisLi
OutisLi requested a review from wanghan-iapcm August 10, 2026 14:21

@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

Caution

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

⚠️ Outside diff range comments (2)
deepmd/pt_expt/model/make_model.py (1)

355-417: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass spin and charge_spin through the graph Hessian path.

Lines 923-942 pass spin_flat and charge_spin to normal graph evaluation. Lines 385-403 and 976-989 omit both inputs when constructing the Hessian wrapper.

A native-spin or charge-conditioned model can then compute a Hessian for a different energy function. It can also fail when the model requires the omitted conditioning.

Add spin and charge_spin parameters to _cal_hessian_ext_graph and _WrapperForwardEnergyGraph. Compact spin with node_index. Pass the per-frame charge_spin value unchanged. Add a conditioned Hessian parity test.

Also applies to: 976-989

🤖 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/model/make_model.py` around lines 355 - 417, Update
_cal_hessian_ext_graph and _WrapperForwardEnergyGraph to accept and forward spin
and charge_spin, matching the normal graph evaluation path. In
_cal_hessian_ext_graph, compact each frame’s spin using node_index and pass the
corresponding per-frame charge_spin unchanged when constructing the wrapper;
update all callers, including the additional Hessian path around the referenced
wrapper construction. Add a parity test covering conditioned/native-spin Hessian
evaluation.
source/tests/pt_expt/test_lmdb_training.py (1)

777-950: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a 60-second pytest timeout to TestRaggedTrainingBatches.

source/tests/pt_expt/test_lmdb_training.py imports pytest elsewhere, pyproject.toml installs pytest-timeout, and existing PT training tests use @pytest.mark.timeout(60). Add that class-level marker to this training-test class.

🤖 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/pt_expt/test_lmdb_training.py` around lines 777 - 950, Add the
class-level pytest timeout marker to TestRaggedTrainingBatches, using the
existing project convention for a 60-second timeout. Keep the marker applied to
the entire class so all its training tests are covered.

Source: Coding guidelines

🤖 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/ener.py`:
- Around line 303-313: In the ragged-batch energy path, update the
coefficient-weighted atomic energy reduction to use the existing n_node-derived
frame IDs and segment_sum, producing one value per frame with shape (_nf, 1)
before comparison with energy_hat. Apply this in the enable_atom_ener_coeff flow
near the is_ragged handling, while preserving the existing non-weighted
reduction behavior.
- Around line 303-313: Update the ragged-frame normalization in the is_ragged
branch of the loss calculation to avoid dividing by zero when included_n_node is
zero. Compute a safe denominator and make inv zero for fully excluded frames,
while preserving the existing reciprocal normalization for frames with included
nodes.

---

Outside diff comments:
In `@deepmd/pt_expt/model/make_model.py`:
- Around line 355-417: Update _cal_hessian_ext_graph and
_WrapperForwardEnergyGraph to accept and forward spin and charge_spin, matching
the normal graph evaluation path. In _cal_hessian_ext_graph, compact each
frame’s spin using node_index and pass the corresponding per-frame charge_spin
unchanged when constructing the wrapper; update all callers, including the
additional Hessian path around the referenced wrapper construction. Add a parity
test covering conditioned/native-spin Hessian evaluation.

In `@source/tests/pt_expt/test_lmdb_training.py`:
- Around line 777-950: Add the class-level pytest timeout marker to
TestRaggedTrainingBatches, using the existing project convention for a 60-second
timeout. Keep the marker applied to the entire class so all its training tests
are covered.
🪄 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: 24db1f31-08c7-4f17-bd52-0868462927a6

📥 Commits

Reviewing files that changed from the base of the PR and between ae387ea and 38cc7b9.

📒 Files selected for processing (8)
  • deepmd/dpmodel/loss/ener.py
  • deepmd/dpmodel/loss/reduction.py
  • deepmd/dpmodel/utils/lmdb_data.py
  • deepmd/pt_expt/model/make_model.py
  • deepmd/pt_expt/train/training.py
  • source/tests/common/dpmodel/test_loss_padding.py
  • source/tests/pt_expt/model/test_dpa2_graph_lower.py
  • source/tests/pt_expt/test_lmdb_training.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • deepmd/dpmodel/loss/reduction.py
  • deepmd/pt_expt/train/training.py

Comment thread deepmd/dpmodel/loss/ener.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.

The previous two blockers are fixed on this head: non-mixed batch rules remain rectangular, and the padded graph Hessian is compacted to real nodes before being scattered back. The current implementation still has correctness blockers in optional-label partitioning, atomic-label alias handling, loss-aware layout selection, ragged coefficient-weighted energy reduction, and Hessian dispatch/conditioning. The inline comments contain concrete reproductions and the required fixes.

Codex quota is about to reset, so I am using the remaining token budget to complete a concentrated review pass over the outstanding PRs.

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

Comment thread deepmd/dpmodel/utils/lmdb_data.py
Comment thread deepmd/dpmodel/utils/lmdb_data.py Outdated
Comment thread deepmd/pt_expt/train/training.py
Comment thread deepmd/dpmodel/loss/ener.py
Comment thread deepmd/pt_expt/model/ener_model.py
Comment thread deepmd/pt_expt/model/make_model.py Outdated
@OutisLi
OutisLi enabled auto-merge August 11, 2026 07:15

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

The backend/layout work now looks coherent, and the current head addresses the earlier ragged-model, loss-contract, mask, and Hessian issues. One blocking correctness issue remains: the bounded legacy-LMDB availability probe discussed in #5962 (comment) can still miss a rare signature and silently demote a valid label for the entire mixed batch. I reproduced this on dc4c99a with 512 frames: frame 1 omitted energy, the 256-frame probe returned one 512-frame group, and decoding frames 0 and 1 changed find_energy from 1 for frame 0 alone to 0 for the batch. This makes the training objective depend on batch composition without an error. Please make availability exact for tracked optional labels (metadata when available, otherwise an exact scan), or fail loudly when mixed availability reaches collation. I do not think a probabilistic correctness boundary is safe for training.

@OutisLi
OutisLi requested a review from iProzd August 12, 2026 03:16

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

All six findings from my earlier review are addressed at dc4c99a.

  • The ragged layout is now genuinely opt-in: use_ragged_batches ends with ragged and self.mixed_nloc, and mixed_nloc is self._mix_rule is not None, so only a mix:N rule can select the flat node axis. Existing dpa2/dpa3 LMDB runs keep the rectangular layout, which also removes the root cause of the generalized-force abort.
  • The ragged loss branch consumes the model mask whenever it is present. frame_id_from_n_node plus segment_sum(mask, frame_id) give the included counts, n_node now only sets the frame boundaries, and every per-atom term goes through masked_atom_mean on both layouts, so atom_exclude_types affects numerator and denominator alike.
  • The generalized-force guard fires only under is_ragged, which is the representation it actually cannot serve.
  • masked_pair_mean is now a documented third reduction category with the quadratic-growth rationale, rather than an unexplained holdout.
  • The layout-agreement test is replaced by one that can fail: n_node=[2,3], a real padding slot, a model-excluded physical atom, force, atomic-energy, virial and atom-prefactor terms enabled, and a 1e-14 comparison of the total loss and every metric across the two layouts.
  • The reduction tests were re-derived algebraically through a frame_weights parameter rather than re-baselined, so no golden value was updated and no tolerance was widened.

Two things I am accepting as documented decisions rather than defects. First, pooling retires the bit-identical guarantee from #5738/#5783 for existing mixed_type NPY data whose frames hold different real atom counts; the Behavioral changes section states this and uniform-count batches are unchanged. Second, the bounded label-availability probe may discard valid supervision for a batch on a missed rare signature, with exact metadata tracked in #5954.

Worth keeping in mind on merge: the CUDA jobs report skipping, so a PR that reworks graph-model batching and the pt_expt training loop has no GPU coverage in CI.

@OutisLi
OutisLi added this pull request to the merge queue Aug 12, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Aug 12, 2026
@OutisLi
OutisLi enabled auto-merge August 12, 2026 12:54
Comment thread deepmd/dpmodel/loss/ener_spin.py Dismissed

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

🧹 Nitpick comments (2)
deepmd/pt_expt/model/native_spin_model.py (1)

106-133: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the parameters of forward_ragged.

Every other public method in this class carries a full numpydoc Parameters block, including the sibling forward and forward_lower_graph_exportable. forward_ragged is a public entry whose positional order (spin at index 4, after n_node) differs from forward (spin at index 3). A reader cannot see the flat shapes or the argument order from the single summary line.

Add a Parameters and Returns block stating that coord is (N, 3) frame-major over n_node, atype is (N,), spin is (N, 3), and that per-atom outputs keep the flat axis while n_node is returned alongside them.

🤖 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/model/native_spin_model.py` around lines 106 - 133, Expand the
docstring of NativeSpinModel.forward_ragged with numpydoc Parameters and Returns
sections. Document the positional arguments, including spin after n_node, their
flat frame-major shapes—coord (N, 3), atype (N,), and spin (N, 3)—plus the
optional inputs, and state that per-atom outputs retain the flat axis while
n_node is returned alongside them.
source/tests/pt_expt/loss/test_ener_spin.py (1)

345-354: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a ragged case without mask.

model_ragged always carries mask, so the test only covers the ragged branch that derives included_n_node from segment_sum. EnergySpinLoss.call also has a branch for a ragged batch with no mask, which reads included_n_node straight from n_node and skips the mask_mag intersection. A ragged producer reaches that branch whenever it emits no mask; _translate_eager_call copies mask only when the key is present in the model result.

Add a second ragged dict without the mask key and assert the same parity. It is a two-line addition that locks the branch the graph path can actually take.

♻️ Proposed addition
         rectangular_loss, rectangular_more = loss_fn(1.0, 3, model_rect, label_rect)
         ragged_loss, ragged_more = loss_fn(1.0, 5, model_ragged, label_ragged)
 
         torch.testing.assert_close(ragged_loss, rectangular_loss)
         assert ragged_more.keys() == rectangular_more.keys()
         for key in ragged_more:
             torch.testing.assert_close(ragged_more[key], rectangular_more[key])
+
+        # A ragged producer that emits no ``mask`` must reduce identically.
+        model_no_mask = {k: v for k, v in model_ragged.items() if k != "mask"}
+        no_mask_loss, no_mask_more = loss_fn(1.0, 5, model_no_mask, label_ragged)
+        torch.testing.assert_close(no_mask_loss, rectangular_loss)
+        for key in no_mask_more:
+            torch.testing.assert_close(no_mask_more[key], rectangular_more[key])
🤖 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/pt_expt/loss/test_ener_spin.py` around lines 345 - 354, Add a
second ragged test case alongside model_ragged without the "mask" key, while
retaining the other relevant fields, and assert that its loss matches the
existing expected parity result. This should exercise EnergySpinLoss.call’s
no-mask ragged branch using n_node directly.
🤖 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.

Nitpick comments:
In `@deepmd/pt_expt/model/native_spin_model.py`:
- Around line 106-133: Expand the docstring of NativeSpinModel.forward_ragged
with numpydoc Parameters and Returns sections. Document the positional
arguments, including spin after n_node, their flat frame-major shapes—coord (N,
3), atype (N,), and spin (N, 3)—plus the optional inputs, and state that
per-atom outputs retain the flat axis while n_node is returned alongside them.

In `@source/tests/pt_expt/loss/test_ener_spin.py`:
- Around line 345-354: Add a second ragged test case alongside model_ragged
without the "mask" key, while retaining the other relevant fields, and assert
that its loss matches the existing expected parity result. This should exercise
EnergySpinLoss.call’s no-mask ragged branch using n_node directly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: e96d2f0a-a9b7-4c4c-8f79-c02bc6cb312f

📥 Commits

Reviewing files that changed from the base of the PR and between dc4c99a and eebf9b0.

📒 Files selected for processing (22)
  • deepmd/dpmodel/loss/ener_spin.py
  • deepmd/dpmodel/model/native_spin_model.py
  • deepmd/dpmodel/model/spin_model.py
  • deepmd/pt/model/model/spin_model.py
  • deepmd/pt_expt/model/make_model.py
  • deepmd/pt_expt/model/native_spin_model.py
  • deepmd/pt_expt/model/spin_ener_model.py
  • deepmd/pt_expt/train/training.py
  • deepmd/pt_expt/train/validation.py
  • deepmd/pt_expt/train/wrapper.py
  • deepmd/pt_expt/utils/graph_builder.py
  • deepmd/pt_expt/utils/vesin_graph_builder.py
  • deepmd/utils/argcheck.py
  • source/tests/common/dpmodel/test_loss_padding.py
  • source/tests/pt/model/test_sezm_model.py
  • source/tests/pt_expt/loss/test_ener_spin.py
  • source/tests/pt_expt/model/test_dpa2_graph_lower.py
  • source/tests/pt_expt/model/test_dpa4_native_spin.py
  • source/tests/pt_expt/model/test_get_model_dpa4.py
  • source/tests/pt_expt/model/test_spin_ener_model.py
  • source/tests/pt_expt/test_lmdb_training.py
  • source/tests/pt_expt/test_training.py
🚧 Files skipped from review as they are similar to previous changes (10)
  • deepmd/pt_expt/utils/vesin_graph_builder.py
  • source/tests/pt_expt/test_training.py
  • deepmd/pt_expt/train/validation.py
  • deepmd/utils/argcheck.py
  • deepmd/pt_expt/train/wrapper.py
  • source/tests/pt_expt/model/test_dpa2_graph_lower.py
  • deepmd/pt_expt/utils/graph_builder.py
  • source/tests/pt/model/test_sezm_model.py
  • source/tests/common/dpmodel/test_loss_padding.py
  • source/tests/pt_expt/model/test_get_model_dpa4.py

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

Labels

Docs enhancement new feature P1 Required for DPA4/DPA4C release readiness. Python

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

pt_expt: support correct mixed-size spin batches and ragged training

7 participants