Skip to content

test(pt_expt): document compiled neighbor type equivalence - #5860

Merged
njzjz merged 4 commits into
deepmodeling:masterfrom
njzjz-bot:fix/pt-expt-neighbor-types-5670
Aug 2, 2026
Merged

test(pt_expt): document compiled neighbor type equivalence#5860
njzjz merged 4 commits into
deepmodeling:masterfrom
njzjz-bot:fix/pt-expt-neighbor-types-5670

Conversation

@njzjz-bot

@njzjz-bot njzjz-bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Closes #5670 as a false positive.

Summary

  • make the compiled-training merged-candidate contract explicit at the call site
  • add an adversarial A/A/B neighbor-list regression that compares the lower-layer type split with an early type split
  • preserve runtime behavior and the established global sum(sel)-candidate semantics

Why the reported divergence does not occur

  • compiled dense training builds the global nearest sum(sel) candidates and forward_common_lower then calls model.format_nlist
  • for non-mixed descriptors, format_nlist applies nlist_distinguish_types using the per-type sel layout
  • build_neighbor_list with distinguish_types=True also globally truncates to sum(sel) before applying that same split, so changing the flag only changes the intermediate layout
  • the new type-skewed regression proves False plus the lower split equals True, including the disputed farther type-1 case

This matches the maintainer conclusion on the same report in PR #5491:
#5491 (comment)

If the desired behavior is instead to retain a per-type-safe candidate superset before truncation, that is a separate cross-backend neighbor-list contract change affecting eager, inference, export, and compiled paths.

Why existing tests did not make this obvious

  • compiled-versus-eager prediction and gradient consistency tests already cover final outputs, but their fixtures are not type-skewed
  • there was no focused low-level test documenting that early and lower-layer type layouts are equivalent after the shared global truncation
  • the scan therefore interpreted an intentional intermediate-layout flag as a final-neighbor semantic difference

Validation

  • pytest source/tests/common/dpmodel/test_nlist.py -q: 11 passed
  • ruff format .
  • ruff check .
  • git diff --check

The local pt_expt training module could not be collected because the installed libdeepmd_op_pt.so has a stale PyTorch ABI; this PR changes no executable behavior, and the backend-independent contract regression passes.

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

Summary by CodeRabbit

  • Documentation

    • Added guidance clarifying neighbor-list handling and layout compatibility during compiled model execution.
    • Documented how candidate neighbors align with the expected formatted layout.
  • Tests

    • Added coverage comparing type-agnostic and type-aware neighbor-list construction.
    • Verified consistent behavior across truncation, batching, ghost and virtual atoms, distance ties, neighbor ordering, per-type formatting, and padding.

Clarify that compiled training intentionally passes merged global candidates because the lower model performs the same non-mixed type layout.

Add an adversarial A/A/B regression proving early and lower-layer type splits produce the same final neighbor list under the established contract.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.4
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@njzjz-bot, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: bb91365b-33e3-4771-a720-dad78bac05cc

📥 Commits

Reviewing files that changed from the base of the PR and between 8dbf0d4 and 49b9bd9.

📒 Files selected for processing (1)
  • source/tests/common/dpmodel/test_nlist.py
📝 Walkthrough

Walkthrough

The change documents the dense compiled-training neighbor-list contract and adds regression coverage for deferred type splitting versus direct type-aware neighbor-list construction.

Changes

Neighbor-list layout

Layer / File(s) Summary
Layout contract and regression coverage
deepmd/pt_expt/train/training.py, source/tests/common/dpmodel/test_nlist.py
Comments explain why dense compiled training uses distinguish_types=False and how the candidate layout matches the lower-model contract. Tests compare deferred and direct type splitting across truncation, batching, ghosts, ties, virtual atoms, multiple local atoms, and padding.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested labels: enhancement, Docs

Suggested reviewers: wanghan-iapcm

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the added test and documentation for compiled neighbor type equivalence.
Linked Issues check ✅ Passed The regression test and comments address issue #5670 by demonstrating equivalence under the compiled neighbor-list contract.
Out of Scope Changes check ✅ Passed The changes are limited to relevant documentation and regression coverage for issue #5670.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ 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.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.22%. Comparing base (6c3b985) to head (49b9bd9).
⚠️ Report is 66 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5860      +/-   ##
==========================================
+ Coverage   78.58%   79.22%   +0.63%     
==========================================
  Files        1050     1072      +22     
  Lines      120637   125041    +4404     
  Branches     4356     4541     +185     
==========================================
+ Hits        94801    99060    +4259     
- Misses      24278    24357      +79     
- Partials     1558     1624      +66     

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

@njzjz

njzjz commented Jul 18, 2026

Copy link
Copy Markdown
Member

Possible reviewers based on changed lines, exact file history, and exact-file review history:

  • @wanghan-iapcm — 13 commits on changed files; 37 reviews on exact changed files (deepmd/pt_expt/train/training.py, source/tests/common/dpmodel/test_nlist.py).
  • @iProzd — 11 reviews on exact changed files (deepmd/pt_expt/train/training.py).

No review request was made automatically.

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

@njzjz
njzjz requested review from iProzd and wanghan-iapcm and removed request for iProzd and wanghan-iapcm July 18, 2026 07:26

@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 false-positive verdict is correct, and I checked it rather than taking it on trust. build_neighbor_list sorts by distance, drops self, truncates globally to sum(sel), masks beyond rcut, and only then applies nlist_distinguish_types as the final step - so the flag toggles nothing about which atoms survive. I reproduced the test's scenario directly (merged = [1, 2], both orderings give [1, -1]), and a randomised sweep over roughly 400 configurations - 1-3 types, varying sel, nloc 1-5, 0-7 ghosts, tied coordinates, virtual atoms, exclude_types, extra_nlist_sort on and off, multiple frames - produced no mismatches. I also confirmed the compiled path cannot bypass format_nlist: the graph route is gated on mixed_types(), and spin models are rejected from compiled training outright.

Documenting this at the call site is worth doing, and the conclusion matches what I concluded on the same report in #5491.

One comment inline, on the fixture. Non-blocking.

Smaller notes:

  1. The test calls nlist_distinguish_types directly and never goes through format_nlist, which is the seam the new comment is actually about - specifically the equal-width branch preserving order, the split being applied unconditionally, and the extra_nlist_sort=True path used by compressed models. Asserting format_nlist(build(distinguish_types=False)) against format_nlist(build(distinguish_types=True)) would pin the claim where it is load-bearing rather than one level below it.

  2. The comment says "forward_common_lower always calls model.format_nlist", but what _trace_and_compile traces is model.forward_lower, and each concrete model's forward_lower calls call_common_lower directly. forward_common_lower only appears in the spin plumbing, so it is not in this path at all. The substantive claim survives - call_common_lower does call format_nlist unconditionally - but someone grepping for the name the comment gives them will not find it here.

  3. Worth one more sentence on pair exclusion, since the ordering differs between the two paths: in the eager build, exclusion is applied after the type split; here it is applied before, with the split deferred to format_nlist. That would normally let a farther same-type neighbour slide into a freed slot. It does not, but only because PairExcludeMask is keyed on the type pair, so every neighbour of an excluded type is erased uniformly and the bucket ends up empty either way. That is not obvious from the current text.

Separately, and not a request on this PR: the fixture makes the underlying design choice very visible - a type-1 atom at distance 2.0 is inside rcut=3.0 and still absent from the final list. That is the intended contract and the PR is right to say a per-type-safe candidate superset would be a separate cross-backend change. But it is worth having that written down somewhere more discoverable than a scan-triage thread, since this is the second time the same report has been raised.

Comment thread source/tests/common/dpmodel/test_nlist.py Outdated
Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh

@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 `@source/tests/common/dpmodel/test_nlist.py`:
- Around line 231-240: Replace the expected-value construction around
build_neighbor_list with an independent type-aware reference that groups
candidates by atom type, sorts each group by distance, and truncates using the
corresponding sel limits. Compare both lower_formatted and early_formatted
against this independently computed reference, rather than comparing the two
implementation paths to each other.
🪄 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 Plus

Run ID: e7f034d7-7208-4ec0-a25c-2fb69abf4edc

📥 Commits

Reviewing files that changed from the base of the PR and between 7f5b037 and c17f783.

📒 Files selected for processing (1)
  • source/tests/common/dpmodel/test_nlist.py

Comment thread source/tests/common/dpmodel/test_nlist.py Outdated
@njzjz
njzjz requested a review from wanghan-iapcm August 1, 2026 14:57
Validate both neighbor-list formatting paths against a standalone per-type distance-sort and truncation reference.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh

@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 `@source/tests/common/dpmodel/test_nlist.py`:
- Around line 45-53: Update _reference_type_neighbor_list to build and
distance-sort one global candidate list, truncate it to sum(sel), then populate
each type bucket from those globally selected neighbors while preserving
per-type limits. Add a test case verifying that a farther type-specific neighbor
outside the global cap is excluded.
🪄 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 Plus

Run ID: 47d61283-8f9d-47e6-98aa-a347292f0d6b

📥 Commits

Reviewing files that changed from the base of the PR and between c17f783 and 8dbf0d4.

📒 Files selected for processing (1)
  • source/tests/common/dpmodel/test_nlist.py

Comment thread source/tests/common/dpmodel/test_nlist.py Outdated
@njzjz
njzjz requested review from wanghan-iapcm and removed request for wanghan-iapcm August 1, 2026 16:27
Coding-Agent: Codex\nCodex-Version: codex-cli 0.144.6\nModel: gpt-5.6-sol\nReasoning-Effort: xhigh
@njzjz
njzjz requested review from wanghan-iapcm and removed request for wanghan-iapcm August 1, 2026 17:02

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

Coverage gap addressed, and thoroughly. Every case I named is now present and, importantly, in the right place: it is a method on TestDPModelFormatNlist beside the other short/equal/long neighbor-list cases rather than a separate class rebuilding its own fixture.

The one that mattered is same_type_nearest. With sel = [2, 1] and three type-0 neighbours at 1.0/1.5/2.5 against a type-1 at 2.0, nlist_distinguish_types finally has to choose the nearest among several same-type candidates, and the 2.5 atom is correctly displaced by the global cap. That is the mechanism the whole equivalence claim rests on and the one thing the old single-slot fixture could not exercise. batched_ghost_ties_virtual_padding covers the rest in one case - two frames, nloc=2, nall > nloc, three atoms at an exact distance tie, a virtual atype < 0, and a padded bucket with an explicit assertion that padding is present. global_cap_excludes_far_type pins the disputed case from the report itself.

You also went past what I asked in the way that counts most. _reference_type_neighbor_list is an independent brute-force oracle, and both the lower split and the early split are asserted against it rather than merely against each other. The original test's weakness was that comparing the two paths to one another cannot detect a shared misconception; comparing both to an independent construction can.

One note purely for the record, requiring no change: I went looking at whether the exact-tie case depends on unspecified ordering, since build_neighbor_list calls xp.argsort(rr, axis=-1) without stable=True while the same function passes it explicitly a few lines later. It does not - the array API specifies stable=True as the default for argsort, so the compat namespace guarantees it, and I confirmed numpy, torch and jax all produce identical orderings on a tie-heavy array. The tie ordering the fixture relies on is guaranteed rather than incidental.

The training.py comment accurately describes what the test now demonstrates, and the change remains behavior-preserving.

@njzjz
njzjz added this pull request to the merge queue Aug 2, 2026
Merged via the queue into deepmodeling:master with commit 109ae09 Aug 2, 2026
57 of 58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Code scan] Distinguish neighbor types in pt_expt compiled training

3 participants