Skip to content

feat(dpmodel): NeighborGraph 3-body angle machinery (PR-E)#5717

Open
wanghan-iapcm wants to merge 18 commits into
deepmodeling:masterfrom
wanghan-iapcm:feat-graph-angles-prE
Open

feat(dpmodel): NeighborGraph 3-body angle machinery (PR-E)#5717
wanghan-iapcm wants to merge 18 commits into
deepmodeling:masterfrom
wanghan-iapcm:feat-graph-angles-prE

Conversation

@wanghan-iapcm

@wanghan-iapcm wanghan-iapcm commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

NeighborGraph PR-E: the optional 3-body angle extension of the edge-graph neighbor-list contract (design discussion wanghan-iapcm#4). An angle is a pair of edges sharing a center (dst(edge_a) == dst(edge_b)), stored as angle_index (2, A) into [0, E) + angle_mask (A,)edge_vec stays the ONLY geometry leaf, so force/virial assembly is untouched (proven by an invariance test).

Stacked on #5715 (PR-D) — reuses its center_edge_pairs. Please merge #5715 first; this branch then rebases clean onto master. Only the last 10 commits belong to this PR.

What's added (all in deepmd/dpmodel/utils/neighbor_graph/)

  • pad_and_guard_angles (graph.py) — angle-axis padder mirroring pad_and_guard_edges (dynamic guard append / static angle_capacity with overflow ValueError).
  • angles.py (new):
    • build_angle_index(edge_index, edge_vec, edge_mask, n_total, a_rcut, *, ordered=False, include_self=False, layout=None) — unordered, no-self pairs of edges sharing a center where BOTH edges are within a_rcut; built on PR-D's center_edge_pairs; pair_mask folded into angle_mask (never discarded).
    • attach_angles(graph, a_rcut, ...) — post-hoc: edge graph in, graph with angle fields out (dataclasses.replace); default builders keep angles None.
    • angle_to_edge_sum / angle_to_node_sum — segment-sum aggregation to the query edge / shared center.
    • graph_angle_cos(angle_index, edge_vec, eps=1e-6) — per-angle cos θ mirroring dpa3 repflows cosine_ij eps placement exactly (+eps in norm denominators, *(1-eps) on the product).
    • angle_padding_fraction(graph) — mask-derived padding-waste report for static capacities.

Semantics / decisions

  • Unordered, no-self by default: dpa3's dense angle tensor is the redundant ordered a_sel x a_sel square including the j==k diagonal; the graph set keeps one entry per unordered {j,k} pair and moves the degenerate diagonal to the (a_rcut-filtered) edge channel. Dense parity is therefore asserted against the OFF-DIAGONAL cosine_ij[j,k] (j != k) at rtol/atol 1e-12 (same-math fp64), at non-binding a_sel. The ordered+self full square stays available via flags.
  • a_sel = normalization-only (carry-all within a_rcut), consistent with the edge-sel decision.
  • Second oracle: se_t dot-product convention cross-checked from coordinates in the sw == 1 regime (rtol 1e-12).

Tests

source/tests/common/dpmodel/test_angle_builder.py (21) + test_graph_angle_cos_parity.py (6): brute-force triplet oracle (all flag combinations, multi-center, static layout, node_capacity branch), dpa3 dense-parity + no-self-angle assertion, se_t coordinate oracle, force/virial bit-exact invariance with/without angles, padding-fraction (incl. total==0), torch-namespace smoke tests for every new function. Full neighbor-graph suite: 54 passed.

Known limitations

  • Machinery + angle-channel math only — no dpa3 graph descriptor here (dpa3 is message-passing; wiring = PR-G). se_t/se_t_tebd are not migrated (mixed_types=False), used as oracle only.
  • Angle enumeration is the compact eager form (nonzero in center_edge_pairs) even when a static layout is passed — angle_capacity fixes the output shape only; shape-static enumeration for export is deferred to PR-G.
  • Not bit-parity with dense dpa3 by construction (unordered/no-self reformulation; recoverable in PR-G via symmetric angle→edge 2x + edge-channel diagonal).
  • Aggregation helpers follow the mask-then-reduce convention: callers mask per-angle data by angle_mask before summing (padding angles point at edge 0).
  • numpy/torch validated; jax rides the array-API surface (no jax-specific test here); A ~ sum(deg^2) capacity overhead mitigated by a_rcut < rcut and reported by angle_padding_fraction.

Summary by CodeRabbit

  • New Features

    • Added graph-native support for angle-based descriptor paths and attention-based execution (including additional attention-layer modes) suitable for tracing/export workflows.
    • Introduced new neighbor-graph utilities for building angle graphs, enumerating center-sharing edge pairs, and performing segment reductions (including max and softmax).
  • Bug Fixes

    • Improved masking/padding correctness and safer handling of empty or fully-masked segments in graph computations.
    • Enhanced alignment of graph-native vs dense attention/descriptor behavior across more settings.
  • Tests

    • Added/expanded parity, masking, and tracing coverage for angle cosine, pair enumeration, segment ops, and graph-attention outputs/gradients.

Han Wang added 16 commits July 3, 2026 00:10
…ftmax

Built on the existing xp_maximum_at (no new array_api helper needed).
Part of NeighborGraph PR-D (graph-native attention).
Segment-based (global (E,E) boolean deliberately avoided): compact eager
form for carry-all graphs + shape-static nonzero-free form for the
center-major static layout (jit/export/make_fx traceable).
Part of NeighborGraph PR-D; PR-E angles reuse (unordered, no-self).
…r > 0)

DescrptBlockSeAtten.call_graph grows _graph_attention: the dense per-center
(nnei, nnei) attention square becomes the edge-pair axis (center_edge_pairs,
ordered + self-included), softmax over keys becomes segment_softmax grouped
by the query edge. Op-for-op mirror of GatedAttentionLayer.call (head_dim
QKV slicing, normalize q/k/v, temperature/scaling, smooth shift trick,
post-softmax sw and dotr weighting, residual + LayerNorm per layer).

- shape-static adapter path (static_nnei threaded from the dense call
  adapter): bit-exact vs the dense body, rtol 1e-12, full flag matrix
  (attn_layer 1/2 x dotr x smooth x normalize x temperature, binding and
  non-binding sel).
- carry-all (compact) graphs: exact for non-smooth; for smooth the dense
  branch keeps sel-padding slots in the softmax denominator (dense output is
  sel-DEPENDENT, up to ~1e-4) — the carry-all form drops those phantom terms
  by design (user decision 2026-07-03), pinned by a clean-divergence test.
- edge_env_mat(return_sw=True) exposes the per-edge switch (zeroed on
  padding) for the smooth branch.
- uses_graph_lower: attention configs are now graph-eligible (concat tebd,
  no exclude_types still required).
…ial parity

- test_make_fx_graph_attn: graph forward + autograd.grad at attn_layer=2
  traces under make_fx for BOTH smooth branches (the shape-static
  center_edge_pairs form is nonzero-free) — required since pt_expt compiled
  training routes eligible models through the graph lower.
- model-level graph-vs-legacy lower parity now parametrized over
  attn_layer {0, 2} (energy/force/virial/atom_virial, 1e-12 CPU).
- eligibility pins: attention+concat is graph-eligible; se_atten_v2
  (tebd_input_mode='strip') correctly stays dense (strip = later PR;
  the plan's 'se_atten_v2 inherits for free' did not hold).
- linear-model weight tests: pin smooth_type_embedding=False — the standard
  (graph-routed, carry-all) and linear (graph-ineligible, dense) submodels
  otherwise differ by the accepted smooth-attention denominator divergence
  (~1e-6), which is a route artifact, not a weight-combination bug.
- new binding-sel sanity: carry-all graph attention diverges from the
  sel-truncated dense path when sel binds (spec decision deepmodeling#17).
…rity)

neighbor_list=None now takes the carry-all graph default for eligible
attention models; explicit World-1 builders take the legacy dense route.
With smooth attention the two routes differ by design (PR-D), so the
route-equivalence tests pin smooth_type_embedding=False.
@dosubot dosubot Bot added the new feature label Jul 3, 2026
@github-actions github-actions Bot added the Python label Jul 3, 2026
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 06ae3ca3-46d1-496a-8591-60652090ade5

📥 Commits

Reviewing files that changed from the base of the PR and between 19f0cf1 and ad73de0.

📒 Files selected for processing (1)
  • source/tests/pt_expt/test_plugin.py

📝 Walkthrough

Walkthrough

This PR expands NeighborGraph utilities for angle and pair construction, exposes smooth-switch data from edge environment matrices, and extends DPA1 graph-native execution to support transformer attention with attn_layer > 0. It also updates parity and export tests across common and pt_expt paths.

Changes

Graph-native DPA1 attention support

Layer / File(s) Summary
Segment reduction primitives
deepmd/dpmodel/utils/neighbor_graph/segment.py, source/tests/common/dpmodel/test_segment_softmax.py
Adds segment_max and segment_softmax with mask-aware, numerically stable reductions, plus tests for correctness, masking, and torch/numpy parity.
Center-edge pair enumeration
deepmd/dpmodel/utils/neighbor_graph/pairs.py, source/tests/common/dpmodel/test_center_edge_pairs.py
Adds center_edge_pairs with compact and shape-static enumeration modes for same-center edge pairs, validated against an oracle and torch/numpy parity.
Angle graph construction and smooth-switch env output
deepmd/dpmodel/utils/neighbor_graph/angles.py, graph.py, env.py, __init__.py, source/tests/common/dpmodel/test_angle_builder.py, test_graph_angle_cos_parity.py
Adds angle padding, indexing, attachment, cosine, aggregation, and padding-fraction utilities; extends edge_env_mat with return_sw; re-exports new symbols; adds corresponding unit and parity tests.
DPA1 graph-native attention forward
deepmd/dpmodel/descriptor/dpa1.py
Broadens graph-lowering eligibility, threads static_nnei through graph calls, captures sw_e via edge_env_mat(return_sw=True), and adds graph-native attention helpers for per-center edge pairs.
DPA1 graph attention parity and eligibility tests
source/tests/common/dpmodel/test_dpa1_call_graph_block.py, test_dpa1_graph_attention_parity.py
Removes the outdated attn_layer > 0 fail-fast test; adds dense-vs-graph parity, compact-graph checks, torch smoke tests, and eligibility/divergence assertions.
pt_expt test config alignment
source/tests/pt_expt/descriptor/test_dpa1.py, model/test_dpa1_graph_lower.py, model/test_linear_model.py, utils/test_neighbor_list.py, test_plugin.py
Adds fx-tracing coverage for graph attention, parametrizes parity tests over attn_layer, pins smooth_type_embedding=False in configs, and hardens deepmd.pt_expt re-import cleanup.

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

Sequence Diagram(s)

sequenceDiagram
  participant DescrptDPA1
  participant DescrptBlockSeAtten
  participant edge_env_mat
  participant center_edge_pairs
  participant _graph_attention_one_layer

  DescrptDPA1->>DescrptBlockSeAtten: call_graph(static_nnei)
  DescrptBlockSeAtten->>edge_env_mat: return_sw=True
  edge_env_mat-->>DescrptBlockSeAtten: rr, sw_e
  DescrptBlockSeAtten->>center_edge_pairs: static_nnei
  center_edge_pairs-->>DescrptBlockSeAtten: query_edge, key_edge, pair_mask
  DescrptBlockSeAtten->>_graph_attention_one_layer: attention inputs
  _graph_attention_one_layer-->>DescrptBlockSeAtten: attended edge embeddings
  DescrptBlockSeAtten-->>DescrptDPA1: output, rotation matrix
Loading

Possibly related PRs

Suggested labels: new feature, Python

Suggested reviewers: iProzd

🚥 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 accurately summarizes the main change: adding NeighborGraph 3-body angle machinery.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

ng = build_neighbor_graph(coord, atype, None, 2.0)
ng_default = attach_angles(ng, a_rcut=1.5)
ng_full = attach_angles(ng, a_rcut=1.5, ordered=True, include_self=True)
ai_def = np.asarray(ng_default.angle_index)
ng_full = attach_angles(ng, a_rcut=1.5, ordered=True, include_self=True)
ai_def = np.asarray(ng_default.angle_index)
am_def = np.asarray(ng_default.angle_mask)
ai_full = np.asarray(ng_full.angle_index)
am = np.asarray(ng.angle_mask)
ai = np.asarray(ng.angle_index)
ei = np.asarray(ng.edge_index) # (2, E): [src, dst]
ev = np.asarray(ng.edge_vec)

@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

🧹 Nitpick comments (1)
source/tests/common/dpmodel/test_angle_builder.py (1)

326-364: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a padded-angle_index case to the aggregation tests.

Both aggregation tests here only use angle data where every slot is real (no padding). Given the padding-mask precondition gap noted in angles.py (angle_to_edge_sum/angle_to_node_sum), a test with a padded angle_index and non-zero padding data values would catch regressions if that precondition is ever violated.

🤖 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_angle_builder.py` around lines 326 - 364,
The aggregation tests in test_angle_builder only cover fully real angle slots,
so add a new padded-angle case for angle_to_edge_sum and angle_to_node_sum using
a padded angle_index with non-zero data in the padded positions. Reuse the
existing test_angle_aggregation and test_angle_aggregation_torch_namespace
patterns, but assert that padding is ignored in both the numpy and
torch-namespace paths so regressions around the padding-mask precondition are
caught.
🤖 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/utils/neighbor_graph/segment.py`:
- Around line 59-89: The masked softmax in segment_softmax can overflow because
shifted is computed from raw data instead of data_for_max, allowing masked
entries to produce inf and then nan when multiplied by the zero mask. Update
segment_softmax to base the subtraction on data_for_max so masked elements
remain -inf before exp, keeping segment_sum and the denom_e guard from being
poisoned; use the existing segment_max, segment_sum, and mask handling paths to
locate the fix.

---

Nitpick comments:
In `@source/tests/common/dpmodel/test_angle_builder.py`:
- Around line 326-364: The aggregation tests in test_angle_builder only cover
fully real angle slots, so add a new padded-angle case for angle_to_edge_sum and
angle_to_node_sum using a padded angle_index with non-zero data in the padded
positions. Reuse the existing test_angle_aggregation and
test_angle_aggregation_torch_namespace patterns, but assert that padding is
ignored in both the numpy and torch-namespace paths so regressions around the
padding-mask precondition are caught.
🪄 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: b038075f-6809-4579-8ce1-5cf5d1d41c90

📥 Commits

Reviewing files that changed from the base of the PR and between dd38b35 and 19f0cf1.

📒 Files selected for processing (17)
  • deepmd/dpmodel/descriptor/dpa1.py
  • deepmd/dpmodel/utils/neighbor_graph/__init__.py
  • deepmd/dpmodel/utils/neighbor_graph/angles.py
  • deepmd/dpmodel/utils/neighbor_graph/env.py
  • deepmd/dpmodel/utils/neighbor_graph/graph.py
  • deepmd/dpmodel/utils/neighbor_graph/pairs.py
  • deepmd/dpmodel/utils/neighbor_graph/segment.py
  • source/tests/common/dpmodel/test_angle_builder.py
  • source/tests/common/dpmodel/test_center_edge_pairs.py
  • source/tests/common/dpmodel/test_dpa1_call_graph_block.py
  • source/tests/common/dpmodel/test_dpa1_graph_attention_parity.py
  • source/tests/common/dpmodel/test_graph_angle_cos_parity.py
  • source/tests/common/dpmodel/test_segment_softmax.py
  • source/tests/pt_expt/descriptor/test_dpa1.py
  • source/tests/pt_expt/model/test_dpa1_graph_lower.py
  • source/tests/pt_expt/model/test_linear_model.py
  • source/tests/pt_expt/utils/test_neighbor_list.py

Comment on lines +59 to +89
def segment_softmax(
data: Array,
segment_ids: Array,
num_segments: int,
mask: Array | None = None,
) -> Array:
"""Softmax over entries sharing a segment id, numerically stable.

Mirrors the dense ``np_softmax`` max-subtraction trick with a PER-SEGMENT
max. ``mask`` (bool, per entry) removes masked entries from the softmax
entirely (zero weight AND excluded from the denominator). Empty or
fully-masked segments produce all-zero weights (no NaN).
"""
xp = array_api_compat.array_namespace(data)
if mask is not None:
# keep masked entries out of the per-segment max: send them to -inf
neg = xp.full_like(data, -xp.inf)
data_for_max = xp.where(mask, data, neg)
else:
data_for_max = data
seg_max = segment_max(data_for_max, segment_ids, num_segments)
# guard -inf (empty / fully-masked segments) so gather doesn't yield inf-inf
seg_max = xp.where(xp.isinf(seg_max), xp.zeros_like(seg_max), seg_max)
shifted = data - xp.take(seg_max, segment_ids, axis=0)
ex = xp.exp(shifted)
if mask is not None:
ex = ex * xp.astype(mask, ex.dtype)
denom = segment_sum(ex, segment_ids, num_segments)
denom_e = xp.take(denom, segment_ids, axis=0)
safe = xp.where(denom_e > 0, denom_e, xp.ones_like(denom_e))
return ex / safe

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.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Masked-entry overflow can NaN-poison the whole segment.

shifted is computed from the raw data (line 82), not data_for_max. If a masked entry's data value is much larger than the segment's (unmasked) max, exp(shifted) can overflow to inf; multiplying by the zero mask (line 85) then yields inf * 0 = nan. That NaN propagates through segment_sum (line 86) and poisons the softmax denominator for the entire segment, not just the masked slot — the denom_e > 0 guard doesn't catch NaN (nan > 0 is False), so safe falls back to 1, silently returning an unnormalized result for every entry sharing that segment. This contradicts the docstring's "no NaN" guarantee for masked/partially-masked segments.

Using data_for_max (already -inf on masked entries) for the subtraction avoids ever computing exp(inf).

🐛 Proposed fix
     seg_max = segment_max(data_for_max, segment_ids, num_segments)
     # guard -inf (empty / fully-masked segments) so gather doesn't yield inf-inf
     seg_max = xp.where(xp.isinf(seg_max), xp.zeros_like(seg_max), seg_max)
-    shifted = data - xp.take(seg_max, segment_ids, axis=0)
+    shifted = data_for_max - xp.take(seg_max, segment_ids, axis=0)
     ex = xp.exp(shifted)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def segment_softmax(
data: Array,
segment_ids: Array,
num_segments: int,
mask: Array | None = None,
) -> Array:
"""Softmax over entries sharing a segment id, numerically stable.
Mirrors the dense ``np_softmax`` max-subtraction trick with a PER-SEGMENT
max. ``mask`` (bool, per entry) removes masked entries from the softmax
entirely (zero weight AND excluded from the denominator). Empty or
fully-masked segments produce all-zero weights (no NaN).
"""
xp = array_api_compat.array_namespace(data)
if mask is not None:
# keep masked entries out of the per-segment max: send them to -inf
neg = xp.full_like(data, -xp.inf)
data_for_max = xp.where(mask, data, neg)
else:
data_for_max = data
seg_max = segment_max(data_for_max, segment_ids, num_segments)
# guard -inf (empty / fully-masked segments) so gather doesn't yield inf-inf
seg_max = xp.where(xp.isinf(seg_max), xp.zeros_like(seg_max), seg_max)
shifted = data - xp.take(seg_max, segment_ids, axis=0)
ex = xp.exp(shifted)
if mask is not None:
ex = ex * xp.astype(mask, ex.dtype)
denom = segment_sum(ex, segment_ids, num_segments)
denom_e = xp.take(denom, segment_ids, axis=0)
safe = xp.where(denom_e > 0, denom_e, xp.ones_like(denom_e))
return ex / safe
def segment_softmax(
data: Array,
segment_ids: Array,
num_segments: int,
mask: Array | None = None,
) -> Array:
"""Softmax over entries sharing a segment id, numerically stable.
Mirrors the dense ``np_softmax`` max-subtraction trick with a PER-SEGMENT
max. ``mask`` (bool, per entry) removes masked entries from the softmax
entirely (zero weight AND excluded from the denominator). Empty or
fully-masked segments produce all-zero weights (no NaN).
"""
xp = array_api_compat.array_namespace(data)
if mask is not None:
# keep masked entries out of the per-segment max: send them to -inf
neg = xp.full_like(data, -xp.inf)
data_for_max = xp.where(mask, data, neg)
else:
data_for_max = data
seg_max = segment_max(data_for_max, segment_ids, num_segments)
# guard -inf (empty / fully-masked segments) so gather doesn't yield inf-inf
seg_max = xp.where(xp.isinf(seg_max), xp.zeros_like(seg_max), seg_max)
shifted = data_for_max - xp.take(seg_max, segment_ids, axis=0)
ex = xp.exp(shifted)
if mask is not None:
ex = ex * xp.astype(mask, ex.dtype)
denom = segment_sum(ex, segment_ids, num_segments)
denom_e = xp.take(denom, segment_ids, axis=0)
safe = xp.where(denom_e > 0, denom_e, xp.ones_like(denom_e))
return ex / safe
🤖 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/dpmodel/utils/neighbor_graph/segment.py` around lines 59 - 89, The
masked softmax in segment_softmax can overflow because shifted is computed from
raw data instead of data_for_max, allowing masked entries to produce inf and
then nan when multiplied by the zero mask. Update segment_softmax to base the
subtraction on data_for_max so masked elements remain -inf before exp, keeping
segment_sum and the denom_e guard from being poisoned; use the existing
segment_max, segment_sum, and mask handling paths to locate the fix.

@codecov

codecov Bot commented Jul 3, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.97959% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.18%. Comparing base (55d7e79) to head (ad73de0).
⚠️ Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
deepmd/dpmodel/descriptor/dpa1.py 97.95% 1 Missing ⚠️
deepmd/dpmodel/utils/neighbor_graph/env.py 80.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5717      +/-   ##
==========================================
- Coverage   81.26%   81.18%   -0.09%     
==========================================
  Files         988      990       +2     
  Lines      110876   111079     +203     
  Branches     4234     4233       -1     
==========================================
+ Hits        90103    90175      +72     
- Misses      19247    19375     +128     
- Partials     1526     1529       +3     

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

test_plugin popped deepmd.pt_expt from sys.modules without restoring it,
leaving the package's cached submodules bound to a dead parent. Any later
import of a cached submodule (e.g. deepmd.pt_expt.infer.deep_eval)
re-created a BARE parent whose utils/infer attributes were never rebound,
and mock.patch('deepmd.pt_expt.utils...') in
test_deep_eval_serialize_api failed with AttributeError under py3.10's
mock target resolution. Shard-order dependent: this PR's new test files
reshuffled CI shard 4 and exposed the latent master-side hygiene bug.
Snapshot the whole deepmd.pt_expt module tree before the re-import and
restore it (including the parent-package attribute) afterwards.
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.

2 participants