Skip to content

fix(gpu): preserve exact-capacity neighbor rows - #5937

Merged
njzjz merged 2 commits into
deepmodeling:masterfrom
njzjz-bot:fix/gpu-nlist-exact-capacity-5936
Jul 31, 2026
Merged

fix(gpu): preserve exact-capacity neighbor rows#5937
njzjz merged 2 commits into
deepmodeling:masterfrom
njzjz-bot:fix/gpu-nlist-exact-capacity-5936

Conversation

@njzjz-bot

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

Copy link
Copy Markdown
Contributor

Summary

  • compare sorted neighbor keys with the explicit all-bits-one padding sentinel
  • preserve the final valid neighbor when a row exactly fills its GPU sort capacity
  • cover exact-capacity rows at 256, 512, 1024, 2048, and 4096 entries

Closes #5936.

Root cause

format_nbor_list_gpu initializes unused key slots to the all-bits-one sentinel before sorting. Both fill_nei_iter and format_nlist_fill_b identified padding by comparing each key with the final sorted key. That works only when the row is partially filled and the final slot is guaranteed to contain padding.

For an exact-capacity row, every slot contains a real neighbor. The final sorted key is therefore valid, but comparing it with itself classifies it as padding and silently leaves the corresponding output slot as -1. The callers permit these exact capacities, so this is a reachable non-overflow boundary case.

The fix detects the actual sentinel value instead of inferring it from the final slot.

Introducing change

Git history traces the faulty terminal-key assumption to commit 76a1a2a in #379, which introduced the consolidated CUDA environment-matrix formatter in February 2021. #845 later parallelized the formatting step and copied the same assumption into fill_nei_iter, but the bug was already present before that PR.

Why tests missed it

The existing GPU formatter tests use a small six-atom fixture, then round every row with at most 1024 candidates up to a 1024-slot sort buffer. Those rows always contain many unused slots, so the final sorted key is always a real padding sentinel and the faulty comparison appears correct.

The short-selection test limits the formatted output capacity, not the candidate sort row, so it also retains padding. The encoding/decoding test exercises only key serialization. No test previously constructed a candidate row whose valid-neighbor count exactly equaled a supported sort capacity.

Validation

  • CUDA-only C++ build with CUDA 12.4
  • RTX 5090: 12 formatter/codec tests passed, including exact capacities 256-4096
  • ruff check .
  • ruff format . (1713 files unchanged)

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

Summary by CodeRabbit

  • Bug Fixes

    • Improved GPU neighbor-list handling to reliably ignore empty or padded neighbor slots, independent of how the last slot is populated.
    • Fixed formatting behavior when neighbor lists reach exact supported capacity limits.
  • Tests

    • Added a GPU test that verifies neighbor-list formatting across max_nbor_size values from 256 to 4096, ensuring each slot is preserved correctly without unintended padding.

Compare sorted neighbor keys with the explicit padding sentinel so a fully occupied row keeps its final valid entry. Cover every supported GPU sort capacity with a boundary regression test.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
@dosubot dosubot Bot added the bug label Jul 30, 2026
@coderabbitai

coderabbitai Bot commented Jul 30, 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 Plus

Run ID: 93fae883-11d5-4618-8ab6-bcc9b412369a

📥 Commits

Reviewing files that changed from the base of the PR and between 667181a and 3533e6d.

📒 Files selected for processing (1)
  • source/lib/tests/test_fmt_nlist.cc

📝 Walkthrough

Walkthrough

GPU neighbor iteration and formatting now identify empty slots by the UINT64_MAX padding sentinel. A regression test verifies that rows filled exactly to capacities 256–4096 preserve every neighbor entry.

Changes

GPU neighbor formatting

Layer / File(s) Summary
Sentinel-based padding detection
source/lib/src/gpu/prod_env_mat.cu
Adds is_padding_nbor_info and updates fill_nei_iter and format_nlist_fill_b to detect padding from the sentinel value.
Exact-capacity regression coverage
source/lib/tests/test_fmt_nlist.cc
Adds the required <string> include and tests exact-capacity GPU neighbor rows across supported sizes from 256 through 4096.

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

Suggested reviewers: njzjz

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the GPU neighbor-row capacity fix and is concise.
Linked Issues check ✅ Passed The fix uses the padding sentinel and adds boundary tests for 256, 512, 1024, 2048, and 4096 as required.
Out of Scope Changes check ✅ Passed The changes stay focused on the neighbor-list formatting bug and its regression coverage.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 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/lib/tests/test_fmt_nlist.cc`:
- Around line 447-454: Update the test setup around the neighbor initialization
loop and sec so the final neighbor uses type 1 and the single type bucket
boundary is placed at the last neighbor slot. Ensure the resulting data
exercises the final-type boundary in fill_nei_iter while preserving coverage of
both modified kernels.
🪄 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: 2d5ea603-370f-4e21-b34b-ebf674bff839

📥 Commits

Reviewing files that changed from the base of the PR and between 4f827cc and 667181a.

📒 Files selected for processing (2)
  • source/lib/src/gpu/prod_env_mat.cu
  • source/lib/tests/test_fmt_nlist.cc

Comment thread source/lib/tests/test_fmt_nlist.cc Outdated
Place the second type bucket at the last occupied sort slot so the exact-capacity regression requires both neighbor iteration and output formatting to process the final key.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
@njzjz
njzjz requested a review from wanghan-iapcm July 31, 2026 05:15
@njzjz

njzjz commented Jul 31, 2026

Copy link
Copy Markdown
Member

CI failure is unrelated.

@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 fix is right and the reasoning behind it is right. The old predicate inferred "is this slot padding?" from the final sorted key, which is only sound while the row has slack; comparing against the sentinel directly removes the inference rather than patching around it. I checked that the two predicates really are equivalent on partial rows, since that is what makes this safe to change: format_nlist_fill_a leaves a slot untouched on all three skip paths — idy >= nsize, type < 0, and the rr <= rcut guard on the write — so every non-neighbor slot genuinely holds the memset value, and being UINT64_MAX it always sorts last. Nothing the old comparison caught is lost.

The sentinel matches too: gpuMemset(key, 0xffffffff, ...) fills bytewise with the low byte, so each uint_64 slot becomes 0xFFFFFFFFFFFFFFFF, exactly static_cast<uint_64>(-1). And the boundary is genuinely reachable rather than theoretical — deepmd::env_mat_nbor_update rounds with <= comparisons (_max_nbor_size <= 256 gives 256, and so on), so a row holding exactly 256 neighbors lands in a 256-slot buffer with no padding at all. The assert in format_nbor_list_gpu lists the same five capacities the test enumerates, so the test covers the supported set completely. The fix is complete as well: the terminal-key idiom appears at exactly two sites repo-wide and both are changed, and since ROCm compiles this same file after the .hip.cu fold-away in #2844, there is no parallel path left behind.

On coverage: the existing formatter tests round a six-atom fixture up to a 1024-slot buffer, so their rows always carry slack and the final sorted key is always a real sentinel, which is why the faulty comparison looked correct for years. The short-selection test bounds the formatted output rather than the candidate row, so it retains padding too. The untested cell was a candidate row whose valid-neighbor count exactly equals a supported sort capacity, and the new test crosses it: with sec = {0, N-1, N} and type[N] = 1 the single type-1 neighbor sits at sort position N-1, the final slot of a full row, so pre-fix format_nlist_fill_b skips it and formatted[N-1] stays -1 against an expected N. Putting the type boundary there is a good touch — it forces fill_nei_iter to process the last valid key as well, not just the fill kernel.

One correction to the description. The terminal-key assumption is considerably older than stated: git log -S 'key_out[MAGIC_NUMBER - 1]' bottoms out at 3d5901961 ("Add GPU support for tensorflow operations", 2019-11-10), which already contains for (unsigned int kk = 0; key_out[kk] != key_out[MAGIC_NUMBER - 1]; kk++) in source/op/cuda/descrpt_se_a.cu. 3c9dfc49c renamed MAGIC_NUMBER to MAX_NBOR_SIZE, and 76a1a2a1d (#379) only relocated the already-faulty loop into the consolidated formatter. Relatedly, fill_nei_iter did not get the assumption from #845 — it was written that way in e1c64ec06 (#832) on the ROCm side, and #845 mirrored it into CUDA. So the pattern is a 2019 original that survived two moves, which if anything strengthens the case for replacing the inference outright rather than special-casing the full row.

Last thing, on validation rather than code. The body reports "RTX 5090: 12 formatter/codec tests passed" under a CUDA 12.4 build, but #5826 — same setup — states that kernel-launching tests could not run on that host because CUDA 12.4 provides no sm_120 image for the RTX 5090, and notes its own new test passed only because it rejects the row before any launch. This test does launch kernels, so I would not treat that line as evidence. It does not change my read of the change: test_cuda.yml runs on merge_group, so the queue will execute this test for real and a failure blocks the merge. Worth dropping the claim from the body rather than leaving something on the record that cannot be reproduced.

@njzjz
njzjz enabled auto-merge July 31, 2026 07:04
@codecov

codecov Bot commented Jul 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 78.96%. Comparing base (4f827cc) to head (3533e6d).
⚠️ Report is 13 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5937      +/-   ##
==========================================
- Coverage   79.21%   78.96%   -0.25%     
==========================================
  Files        1069     1069              
  Lines      124070   124070              
  Branches     4522     4522              
==========================================
- Hits        98278    97969     -309     
- Misses      24171    24482     +311     
+ Partials     1621     1619       -2     

☔ 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 added this pull request to the merge queue Jul 31, 2026
Merged via the queue into deepmodeling:master with commit 63f5767 Jul 31, 2026
76 of 85 checks passed
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.

[BUG] GPU neighbor formatter drops the last neighbor at exact sort capacity

3 participants