Skip to content

fix(tabulate): correct sorted padding two-embed gradients - #5904

Merged
njzjz merged 3 commits into
deepmodeling:masterfrom
njzjz:codex/code-scan-5891
Jul 30, 2026
Merged

fix(tabulate): correct sorted padding two-embed gradients#5904
njzjz merged 3 commits into
deepmodeling:masterfrom
njzjz:codex/code-scan-5891

Conversation

@njzjz

@njzjz njzjz commented Jul 25, 2026

Copy link
Copy Markdown
Member

Summary

  • preserve the folded sorted-padding forward contract and make only the first sentinel depend on two_embed
  • scale that sentinel gradient by the padding-tail length while leaving later padding gradients zero on CPU and GPU
  • add finite-difference and nonuniform-cotangent grad-grad regression tests, including a GPU tail longer than the four-warp tile

Validation

  • source/build/lib/tests/runUnitTests_lib --gtest_filter='TestTabulateSeA.*:TestTabulateSeASortedPaddingTwoEmbed.*'
  • CUDA 12.4 build of deepmd_op_cuda and runUnitTests_lib
  • RTX 5090 Slurm run of all 8 existing/new SE-A CPU and GPU tests
  • ruff format .
  • ruff check .
  • clang-format --dry-run --Werror on changed C++/CUDA files

Closes #5891

Note: #5844 modifies the same GPU kernel for the shared-breakpoint fix, so the later-merging branch may need a small conflict resolution; the gradient contract fixed here is independent.

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

Summary by CodeRabbit

  • Bug Fixes
    • Corrected sorted-padding two-embedding gradient behavior so only the first sentinel receives the full tail multiplicity; later padding sentinel gradients remain zero.
    • Updated CPU and GPU gradient/grad-grad accumulation to apply the padding-tail repeat factor consistently.
    • Ensured the GPU two-embedding gradient buffer is cleared only when applicable to avoid stale values.
  • Tests
    • Strengthened sorted-padding assertions and expanded GPU coverage to confirm forward output consistency with CPU before numerical gradient checks.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
Copilot AI review requested due to automatic review settings July 25, 2026 02:31
@coderabbitai

coderabbitai Bot commented Jul 25, 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: 0e60d99d-6530-4463-ac3f-6f3d13a0fd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 08f27b6 and 82ab1e5.

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

📝 Walkthrough

Walkthrough

Changes

Sorted-padding gradient corrections

Layer / File(s) Summary
Backend gradient logic
source/lib/src/tabulate.cc, source/lib/src/gpu/tabulate.cu
CPU and GPU dy_dtwo handling now assigns the folded tail’s scaled gradient only to its first padding sentinel, with matching grad-grad comments.
GPU gradient buffer initialization
source/lib/src/gpu/tabulate.cu
The GPU wrapper clears dy_dtwo for sorted-padding execution when two_embed is present.
Gradient and forward validation
source/lib/tests/test_tabulate_se_a.cc
Tests enforce zero trailing sorted-padding gradients, compare CPU and GPU forward outputs, and validate numerical first- and second-order gradients.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested labels: bug

Suggested reviewers: copilot, njzjz-bot

🚥 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 states the main fix for sorted-padding two-embed gradients.
Linked Issues check ✅ Passed CPU/GPU backward and grad-grad now match the folded forward contract, and tests cover independent tail perturbations and nonuniform cotangents.
Out of Scope Changes check ✅ Passed The added test helpers and refactors support the same gradient fix and stay within scope.
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.

@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)
source/lib/tests/test_tabulate_se_a.cc (2)

881-888: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use an unsigned index and assert matching extents in dot.

ii is int while lhs.size() is size_type, so -Wsign-compare fires here and in the test loops at Lines 1008, 1019, 1031, 1049, 1060, and 1070 (a -Werror test target would fail to build). Indexing rhs with lhs's extent is also unguarded.

♻️ Proposed fix
   static double dot(const std::vector<double>& lhs,
                     const std::vector<double>& rhs) {
+    EXPECT_EQ(lhs.size(), rhs.size());
     double result = 0.0;
-    for (int ii = 0; ii < lhs.size(); ++ii) {
+    for (std::size_t ii = 0; ii < lhs.size(); ++ii) {
       result += lhs[ii] * rhs[ii];
     }
     return result;
   }

The test loops can likewise use std::size_t ii.

🤖 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/lib/tests/test_tabulate_se_a.cc` around lines 881 - 888, Update dot to
use std::size_t for its loop index and assert that lhs and rhs have matching
extents before indexing. Apply the same unsigned index type to the test loops
identified at lines 1008, 1019, 1031, 1049, 1060, and 1070, preserving their
existing loop behavior.

1045-1058: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add GPU forward parity to this fixture.

two_embed_gradient_matches_forward_gpu reads GPU gradients while the finite-difference reference comes from forward_projection_cpu, so a CPU/GPU forward mismatch reports as a backward failure. Add an assertion comparing tabulate_fusion_se_a_gpu output with the CPU forward for this last_layer_size == 1 configuration before using the GPU gradient in other wrapper coverage.

🤖 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/lib/tests/test_tabulate_se_a.cc` around lines 1045 - 1058, Add a
CPU/GPU forward-output parity assertion in
TestTabulateSeASortedPaddingTwoEmbed’s two_embed_gradient_matches_forward_gpu
test, comparing tabulate_fusion_se_a_gpu with forward_projection_cpu for the
last_layer_size == 1 configuration before validating gradients. Keep the
existing finite-difference gradient checks unchanged.
🤖 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 `@source/lib/tests/test_tabulate_se_a.cc`:
- Around line 881-888: Update dot to use std::size_t for its loop index and
assert that lhs and rhs have matching extents before indexing. Apply the same
unsigned index type to the test loops identified at lines 1008, 1019, 1031,
1049, 1060, and 1070, preserving their existing loop behavior.
- Around line 1045-1058: Add a CPU/GPU forward-output parity assertion in
TestTabulateSeASortedPaddingTwoEmbed’s two_embed_gradient_matches_forward_gpu
test, comparing tabulate_fusion_se_a_gpu with forward_projection_cpu for the
last_layer_size == 1 configuration before validating gradients. Keep the
existing finite-difference gradient checks unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4544f118-1fa2-4780-8a29-b5c4895c7753

📥 Commits

Reviewing files that changed from the base of the PR and between e5fdff0 and 20f0839.

📒 Files selected for processing (3)
  • source/lib/src/gpu/tabulate.cu
  • source/lib/src/tabulate.cc
  • source/lib/tests/test_tabulate_se_a.cc

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.

Pull request overview

Fixes the SE-A tabulation attention (two_embed) gradient contract for sorted padding tails so that only the first padding sentinel contributes to the folded forward output, with its gradient scaled by the tail length, and ensures the GPU path clears unused tail gradients. Adds targeted CPU/GPU finite-difference and grad-grad regression tests for the corrected behavior.

Changes:

  • Update CPU backward to write a repeat-scaled two_embed gradient only at the first sorted-padding sentinel (tail entries remain zero).
  • Update GPU backward to mirror the same folded-tail gradient ownership and explicitly zero dy_dtwo outputs to avoid leaving uninitialized tail values.
  • Add CPU/GPU finite-difference and double-backward regression tests for nonuniform tail cotangents and a tail longer than the 4-warp tile.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
source/lib/tests/test_tabulate_se_a.cc Adds CPU/GPU finite-difference + grad-grad regression tests for sorted-padding two_embed gradients.
source/lib/src/tabulate.cc Fixes CPU two_embed backward to match folded sorted-padding forward contract (only first sentinel gets scaled grad).
source/lib/src/gpu/tabulate.cu Fixes GPU two_embed backward for sorted padding and clears dy_dtwo to keep unused tail gradients at zero.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread source/lib/tests/test_tabulate_se_a.cc Outdated
Comment thread source/lib/src/gpu/tabulate.cu Outdated
@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.96%. Comparing base (cc908a8) to head (82ab1e5).
⚠️ Report is 10 commits behind head on master.

Files with missing lines Patch % Lines
source/lib/tests/test_tabulate_se_a.cc 96.00% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5904      +/-   ##
==========================================
- Coverage   79.03%   78.96%   -0.07%     
==========================================
  Files        1055     1069      +14     
  Lines      122233   124120    +1887     
  Branches     4401     4527     +126     
==========================================
+ Hits        96607    98017    +1410     
- Misses      24061    24483     +422     
- Partials     1565     1620      +55     

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

Copilot AI review requested due to automatic review settings July 27, 2026 01:12

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

Caution

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

⚠️ Outside diff range comments (1)
source/lib/tests/test_tabulate_se_a.cc (1)

986-989: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the em fixture length consistent with nnei.

nnei * 4 is 24, but this initializer contains 28 values. The final four values are ignored, leaving an accidental seventh neighbor in the test data. Remove them or update nnei to the intended size.

🤖 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/lib/tests/test_tabulate_se_a.cc` around lines 986 - 989, Make the em
fixture in the test consistent with nnei: nnei * 4 is 24, so remove the final
four initializer values, unless the test intentionally requires seven neighbors
and nnei should be updated accordingly.
🤖 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.

Outside diff comments:
In `@source/lib/tests/test_tabulate_se_a.cc`:
- Around line 986-989: Make the em fixture in the test consistent with nnei:
nnei * 4 is 24, so remove the final four initializer values, unless the test
intentionally requires seven neighbors and nnei should be updated accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: edd6ee5c-ad93-4a5e-8d85-7201952a53e7

📥 Commits

Reviewing files that changed from the base of the PR and between 20f0839 and 08f27b6.

📒 Files selected for processing (2)
  • source/lib/src/gpu/tabulate.cu
  • source/lib/tests/test_tabulate_se_a.cc
🚧 Files skipped from review as they are similar to previous changes (1)
  • source/lib/src/gpu/tabulate.cu

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.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

source/lib/src/gpu/tabulate.cu:1101

  • dy_dtwo is memset to 0 whenever two_embed != nullptr, but the comment and the rationale only apply to sorted-padding mode. When is_sorted == false, the kernel writes every dy_dtwo entry, so this extra memset is unnecessary work on a hot path (backward) and can become a noticeable overhead for large nloc * nnei * last_layer_size. Consider guarding the memset with is_sorted so it only runs when there may be an unwritten padding tail.
  if (two_embed != nullptr) {
    // The sorted-padding fast path writes only the first sentinel. Explicitly
    // clear the unused tail because framework output buffers are uninitialized.
    DPErrcheck(
        gpuMemset(dy_dtwo, 0, sizeof(FPTYPE) * nloc * nnei * last_layer_size));
  }

source/lib/tests/test_tabulate_se_a.cc:1158

  • This test is named two_embed_gradient_matches_forward_gpu, but its finite-difference reference uses forward_projection_cpu(...). That makes the intent ambiguous and it won’t catch GPU-only forward/backward inconsistencies. Either rename the test to reflect the CPU-forward reference, or add a GPU forward-projection helper and use it here.
TEST_F(TestTabulateSeASortedPaddingTwoEmbed,
       two_embed_gradient_matches_forward_gpu) {

@njzjz
njzjz requested a review from wanghan-iapcm July 28, 2026 04:00

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

Verified this by building tabulate.cc at both the base and the head and running finite-difference drivers against the real deepmd:: entry points, rather than by reading the diff.

The new contract is exactly the derivative of the folded forward. Since the fold multiplies the sentinel row's contribution by (nnei - jj) and uses only that row's two-embedding value before breaking, the output depends on two_embed[jj] with that multiplicity and does not depend on the later padding rows at all. Analytic and central-difference agree at the sentinel (1.0) and on the tail (0.0), where the old code produced 0.2 in every slot -- wrong in magnitude at the sentinel and wrong in support on four rows the forward never reads. The old tail sum happened to come out right, which is presumably why this survived so long. dy_dem and dy_dem_x are byte-identical to base.

The part I found most convincing is that grad_grad needed no functional change, because it was already the JVP of the new contract. That means grad and grad_grad were implementing mutually inconsistent contracts before this PR, and second derivatives of compressed se_atten were wrong accordingly. With a non-uniform cotangent, the finite difference of the backward gives 6.2 at base against the grad-grad kernel's 1.0; at head both are 1.0. So this is not only a gradient fix, it restores self-consistency between the two stages -- and the two comment-only hunks there correctly describe what the surrounding code already did.

The added gpuMemset is genuinely required rather than defensive: the tail used to be fully overwritten by the copy loop, so a partial write now needs it, and the TF op hands in allocate_output memory that is uninitialized. The CPU side already had the equivalent memset, so the "retains the zero initialized above" comment is accurate. grad_grad has a single output that is fully zeroed on both sides, so there is no missing counterpart there.

On the tests: these fail pre-fix, which I checked by running them rather than inferring it. Eleven assertions fail at the base and none at the head, with discrepancies of 0.2 to 5.2 against a 1e-9 tolerance. The oracles are independent of the implementation -- central difference of the forward, plus a grad-to-grad-grad consistency relation with a deliberately non-uniform cotangent so that any tail leakage shows up -- rather than restated implementation constants. Seeding the GPU output buffer with 7.0 to make the memset load-bearing, and choosing nnei = 6 so the tail exceeds the four-warp tile and covers entries no warp visits, are both good touches.

Worth recording why nothing caught this earlier: dy_dtwo has been produced since 2023 and no test asserted its values at all until three days ago. The one that did asserted CPU/GPU parity, which cannot catch a bug both sides share. This PR is the first time the output has been checked against an oracle independent of the kernels.

@njzjz
njzjz enabled auto-merge July 29, 2026 16:47
Avoid clearing fully overwritten unsorted GPU gradients, correct the test fixture extent, and add explicit CPU/GPU forward parity coverage.

Coding-Agent: Codex
Codex-Version: codex-cli 0.144.6
Model: gpt-5.6-sol
Reasoning-Effort: xhigh
Copilot AI review requested due to automatic review settings July 30, 2026 01:17

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.

@njzjz-bot

Copy link
Copy Markdown
Contributor

Addressed the remaining review findings in 82ab1e5:

  • dot now checks operand extents and the new vector loops use std::size_t.
  • The sorted-padding em fixture now contains exactly nnei * 4 values.
  • Added explicit CPU/GPU forward parity coverage.
  • Limited the GPU dy_dtwo initialization to sorted mode.

Validation: Ruff and formatting checks passed; the CUDA sources compiled; all five targeted CPU/GPU tabulation tests passed.

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

@njzjz
njzjz added this pull request to the merge queue Jul 30, 2026
Merged via the queue into deepmodeling:master with commit 7799359 Jul 30, 2026
58 checks passed
@njzjz
njzjz deleted the codex/code-scan-5891 branch July 30, 2026 10:10
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.

[Code scan] Sorted SE-Attention padding has inconsistent two_embed gradients and double backward

4 participants