Skip to content

feat(attention): add single-gpu comparison harness - #253

Open
inaniloquentee wants to merge 3 commits into
mainfrom
feat/ws2-attention-single-gpu-harness-pr2
Open

feat(attention): add single-gpu comparison harness#253
inaniloquentee wants to merge 3 commits into
mainfrom
feat/ws2-attention-single-gpu-harness-pr2

Conversation

@inaniloquentee

@inaniloquentee inaniloquentee commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Part of #235.

Summary

  • Add a single-GPU attention comparison harness for full prefill, chunked-query prefill, and paged-KV replay.
  • Report out, attention-domain lse, optional active-token dlogp, RoPE Q/K drift, and runtime provenance.
  • Add a RoPE+Attention attribution path comparing canonical RoPE -> Attention with fused-like RoPE+Attention materialization.
  • Reuse Transformer Engine (TE) context-parallel correction helpers only as an optional merge oracle, not as a runtime dependency or source of truth.
  • Harden the TE optional path with capability probing, all-masked / empty-KV guards, and explicit provenance.
  • Add docs/design/ws2-attention-transformer-engine-reuse-plan.md as the local design mirror for the issue-level TE reuse plan.

Scope

Implemented in this PR:

  • full_prefill: training-style full-sequence softmax attention.
  • chunked_prefill: rollout-style query chunk replay over full KV.
  • rl_kernel_paged_kv: rollout-style KV page replay with RL-Kernel FP32 (out, lse) merge.
  • transformer_engine_paged_kv: optional TE merge-oracle path when TE is installed and compatible.
  • unfused_rope_attention: canonical RoPE -> Attention path.
  • fused_like_rope_attention: semantic fused-boundary path that applies the same canonical RoPE rules, then records fusion_boundary provenance.

Boundary:

  • This is still single-GPU attribution infrastructure.
  • It does not introduce CP collectives.
  • It does not register TE fused attention as a production backend.
  • It does not claim TE backward reuse.

Transformer Engine Reuse

TE is lazy-loaded from:

transformer_engine.pytorch.attention.dot_product_attention.context_parallel

The optional oracle uses only these helpers:

flash_attn_fwd_softmax_lse_correction
flash_attn_fwd_out_correction_init
flash_attn_fwd_out_correction

The merge contract remains RL-Kernel-owned:

lse_new = logaddexp(lse_prev, lse_i)
out_new = exp(lse_prev - lse_new) * out_prev
        + exp(lse_i    - lse_new) * out_i

TE hardening added in the latest update:

  • helper presence checks;
  • helper signature-prefix checks;
  • tiny numeric merge self-test before enabling the TE path;
  • elementwise all-masked / empty-KV guard so lse = -inf and out = 0 do not become NaN;
  • drift handling for equal infinities, so -inf vs -inf is zero drift;
  • provenance fields for te_available, te_version, te_module, te_symbols, te_capability_probe, te_signature_checked, te_numeric_selftest, actual_backend, actual_backend_source, accum_dtype, and downcast_at.

DCO

  • All commits include Signed-off-by.
  • Latest commit: cbb5e2c fix(attention): harden transformer engine merge oracle.

Local Validation

Environment note: local pytest/pre-commit tools ran under Windows with Python 3.13.7. The GitHub CI workflow will rerun on Ubuntu/Python 3.10.

  • pre-commit run --files rl_engine/testing/attention_comparison.py tests/test_attention_comparison.py docs/design/ws2-attention-transformer-engine-reuse-plan.md - passed.
  • ruff check rl_engine/testing/attention_comparison.py tests/test_attention_comparison.py - passed.
  • mypy --ignore-missing-imports rl_engine/ - passed, Success: no issues found in 90 source files.
  • pytest tests/test_attention_comparison.py -q - passed, 10 passed.
  • pytest rl_engine/tests/test_dispatch.py -v - passed, 6 passed.
  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 pytest tests/test_attention_correctness.py -q -rs - passed, 45 passed, 82 skipped.
  • pytest tests/test_attention.py -v -k "not large and not gpu" - passed, 24 passed, 2 deselected.
  • pytest tests/test_kv_cache_attention.py -v -k "not large and not gpu" - passed, 15 passed.
  • mkdocs build --strict -f mkdocs.yaml - passed. It emitted existing MkDocs/Material and non-nav page informational warnings only.
  • git diff --check - passed with Windows CRLF conversion warnings only.

GitHub Actions

Latest push: cbb5e2c.

  • CI-Pipeline / linting - passed, 52s.
  • CI-Pipeline / docs - passed, 54s.
  • CI-Pipeline / unit-tests - passed, 1m22s.
  • GPU CI / gpu-tests - skipped as expected because the PR does not have the needs-gpu-ci label.
  • CodeRabbit - review completed.

Signed-off-by: inaniloquentee <3051000145@qq.com>
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a single-GPU attention comparison harness with full, chunked, paged-KV, and RoPE paths. It reports output, LSE, selected-logprob, and post-RoPE drift. It adds optional Transformer Engine handling, public exports, design documents, and pytest coverage.

Changes

Attention comparison harness

Layer / File(s) Summary
Public contracts and comparison entry points
rl_engine/testing/attention_comparison.py, rl_engine/testing/__init__.py, docs/design/ws2-attention-single-gpu-harness.md
Defines comparison inputs, results, drift serialization, validation, comparison entry points, and public exports. Documents supported paths and report fields.
Attention and RoPE execution paths
rl_engine/testing/attention_comparison.py
Runs full, chunked-query, paged-KV, unfused RoPE, and fused-like RoPE paths with masks, partitioning, casting, and provenance.
Drift reporting and optional Transformer Engine handling
rl_engine/testing/attention_comparison.py, docs/design/ws2-attention-single-gpu-harness.md, docs/design/ws2-attention-transformer-engine-reuse-plan.md
Computes output, LSE, selected-logprob, and post-RoPE drift. Detects Transformer Engine capabilities, validates helpers, merges partial states, and records fallback behavior.
Harness validation
tests/test_attention_comparison.py, docs/design/ws2-attention-single-gpu-harness.md
Tests attention and RoPE behavior, masks, serialization, optional backends, all-masked rows, helper failures, and operator registration.

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

Sequence Diagram(s)

sequenceDiagram
  participant Test
  participant AttentionComparator
  participant AttentionPaths
  participant TransformerEngine
  participant AttentionComparisonReport
  Test->>AttentionComparator: submit Q/K/V and comparison options
  AttentionComparator->>AttentionPaths: run full, chunked, paged-KV, and RoPE paths
  AttentionPaths->>TransformerEngine: optionally merge partial states
  TransformerEngine-->>AttentionPaths: return merged output and LSE
  AttentionPaths-->>AttentionComparator: return path results
  AttentionComparator-->>AttentionComparisonReport: calculate drift statistics
  AttentionComparisonReport-->>Test: return serialized report
Loading

Possibly related issues

Possibly related PRs

  • RL-Align/RL-Kernel#188: Provides the NativeAttentionOp and "attention" registry path used by the harness.
  • RL-Align/RL-Kernel#236: Relates to the attention and RoPE contracts, metadata, and dispatch validation used by the harness.
  • RL-Align/RL-Kernel#240: Provides the deterministic attention operator exercised by the comparison harness.

Suggested labels: needs-gpu-ci

Suggested reviewers: flink-ddd, kjldefeated, zhangj1an

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.55% 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 describes the main change: a single-GPU attention comparison harness.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ws2-attention-single-gpu-harness-pr2

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

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 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 `@rl_engine/testing/attention_comparison.py`:
- Around line 411-441: The _load_te_context_parallel helper must validate that
the imported module provides callable flash_attn_fwd_softmax_lse_correction,
flash_attn_fwd_out_correction_init, and flash_attn_fwd_out_correction
attributes. If any helper is missing or unusable, raise
TransformerEngineUnavailable from the load/validation boundary, preserving the
existing normalization of import failures.

In `@tests/test_attention_comparison.py`:
- Around line 73-76: Update the p99 assertion in the report serialization test
to validate the actual expected tolerance or compare
payload["drifts"][0]["out"]["p99_abs"] with the corresponding drift.out.p99_abs
value, rather than asserting it is merely nonnegative.
- Around line 79-105: Strengthen
test_single_gpu_attention_harness_preserves_key_padding_mask with an independent
assertion that masked key/value positions do not affect the result, rather than
relying only on agreement among masked candidates. Compare the masked
computation against an equivalent reference where padded KV entries are removed
or neutralized, and assert the outputs and log-sum-exp values remain within the
existing tolerance.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 535a60a1-79a3-4d30-92b3-5d8d7977e9cb

📥 Commits

Reviewing files that changed from the base of the PR and between 0b12d34 and aa15aa8.

📒 Files selected for processing (4)
  • docs/design/ws2-attention-single-gpu-harness.md
  • rl_engine/testing/__init__.py
  • rl_engine/testing/attention_comparison.py
  • tests/test_attention_comparison.py

Comment thread rl_engine/testing/attention_comparison.py
Comment on lines +73 to +76
payload = report.to_dict()
assert payload["reference_name"] == "full_prefill"
assert payload["drifts"][0]["out"]["p99_abs"] >= 0.0
json.dumps(payload)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the p99 assertion meaningful.

p99_abs >= 0.0 is tautological for an absolute-error metric, so it will not catch an incorrect serialized value. Assert the expected tolerance or compare the payload value with drift.out.p99_abs.

🧰 Tools
🪛 ast-grep (0.45.0)

[info] 75-75: use jsonify instead of json.dumps for JSON output
Context: json.dumps(payload)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

🤖 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 `@tests/test_attention_comparison.py` around lines 73 - 76, Update the p99
assertion in the report serialization test to validate the actual expected
tolerance or compare payload["drifts"][0]["out"]["p99_abs"] with the
corresponding drift.out.p99_abs value, rather than asserting it is merely
nonnegative.

Comment thread tests/test_attention_comparison.py
Signed-off-by: inaniloquentee <3051000145@qq.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
docs/design/ws2-attention-single-gpu-harness.md (1)

27-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix the compound-modifier hyphenation.

"production fused kernels" needs a hyphen when used as a compound modifier before "kernels".

✏️ Proposed fix
 The RoPE path is still single-GPU attribution. It proves that both sides agree
 on post-RoPE Q/K, `out`, attention-domain `lse`, and optional active-token
-`dlogp` before CP communication or production fused kernels are introduced.
+`dlogp` before CP communication or production-fused kernels are introduced.
🤖 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 `@docs/design/ws2-attention-single-gpu-harness.md` around lines 27 - 29, Update
the RoPE path description to hyphenate the compound modifier before “kernels,”
changing “production fused kernels” to the grammatically correct form while
preserving the surrounding technical content.

Source: Linters/SAST tools

rl_engine/testing/attention_comparison.py (1)

223-284: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider consolidating the identical reference and candidate RoPE paths.

run_unfused_rope_attention (Lines 223-251) and run_fused_like_rope_attention (Lines 254-282) run the identical body: _apply_rope_to_qk followed by _attention_with_lse with the same arguments. Only the name, materialization, and fusion_boundary strings differ. If this is an intentional placeholder for a future fused kernel implementation, add a short comment stating that intent. Otherwise, extract a shared private helper that takes name, materialization, and fusion_boundary as parameters, so a future change to the shared RoPE/attention computation does not need to be applied twice.

♻️ Proposed refactor
+def _run_rope_attention_path(
+    inputs: AttentionComparisonInputs,
+    *,
+    name: str,
+    materialization: str,
+    fusion_boundary: str,
+) -> AttentionPathResult:
+    post_rope_q, post_rope_k = _apply_rope_to_qk(inputs)
+    out, lse = _attention_with_lse(
+        post_rope_q,
+        post_rope_k,
+        inputs.v,
+        causal=inputs.causal,
+        scale=inputs.scale,
+        key_padding_mask=inputs.key_padding_mask,
+        q_start=0,
+        k_start=0,
+        total_query_len=post_rope_q.size(2),
+        total_kv_len=post_rope_k.size(2),
+        output_dtype=inputs.output_dtype,
+    )
+    return AttentionPathResult(
+        name=name,
+        out=out,
+        lse=lse,
+        provenance=_rope_attention_provenance(
+            inputs,
+            materialization=materialization,
+            fusion_boundary=fusion_boundary,
+        ),
+        post_rope_q=post_rope_q,
+        post_rope_k=post_rope_k,
+    )
+
+
 def run_unfused_rope_attention(inputs: AttentionComparisonInputs) -> AttentionPathResult:
     """Canonical ``RoPE -> Attention`` reference materialization."""
-    post_rope_q, post_rope_k = _apply_rope_to_qk(inputs)
-    out, lse = _attention_with_lse(
-        post_rope_q,
-        post_rope_k,
-        inputs.v,
-        causal=inputs.causal,
-        scale=inputs.scale,
-        key_padding_mask=inputs.key_padding_mask,
-        q_start=0,
-        k_start=0,
-        total_query_len=post_rope_q.size(2),
-        total_kv_len=post_rope_k.size(2),
-        output_dtype=inputs.output_dtype,
-    )
-    return AttentionPathResult(
-        name="unfused_rope_attention",
-        out=out,
-        lse=lse,
-        provenance=_rope_attention_provenance(
-            inputs,
-            materialization="rope_then_attention",
-            fusion_boundary="unfused_rope_attention",
-        ),
-        post_rope_q=post_rope_q,
-        post_rope_k=post_rope_k,
-    )
+    return _run_rope_attention_path(
+        inputs,
+        name="unfused_rope_attention",
+        materialization="rope_then_attention",
+        fusion_boundary="unfused_rope_attention",
+    )


 def run_fused_like_rope_attention(inputs: AttentionComparisonInputs) -> AttentionPathResult:
     """Semantic fused ``RoPE+Attention`` path using the same canonical RoPE rules."""
-    post_rope_q, post_rope_k = _apply_rope_to_qk(inputs)
-    out, lse = _attention_with_lse(
-        post_rope_q,
-        post_rope_k,
-        inputs.v,
-        causal=inputs.causal,
-        scale=inputs.scale,
-        key_padding_mask=inputs.key_padding_mask,
-        q_start=0,
-        k_start=0,
-        total_query_len=post_rope_q.size(2),
-        total_kv_len=post_rope_k.size(2),
-        output_dtype=inputs.output_dtype,
-    )
-    return AttentionPathResult(
-        name="fused_like_rope_attention",
-        out=out,
-        lse=lse,
-        provenance=_rope_attention_provenance(
-            inputs,
-            materialization="fused_like_rope_attention",
-            fusion_boundary="fused_rope_attention",
-        ),
-        post_rope_q=post_rope_q,
-        post_rope_k=post_rope_k,
-    )
+    return _run_rope_attention_path(
+        inputs,
+        name="fused_like_rope_attention",
+        materialization="fused_like_rope_attention",
+        fusion_boundary="fused_rope_attention",
+    )
🤖 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 `@rl_engine/testing/attention_comparison.py` around lines 223 - 284,
Consolidate the duplicated computation in run_unfused_rope_attention and
run_fused_like_rope_attention by extracting a shared private helper that accepts
name, materialization, and fusion_boundary, while preserving the existing
_apply_rope_to_qk and _attention_with_lse arguments and returned provenance.
Have both public paths delegate to the helper; if duplication is intentionally
retained for a future fused kernel, add a brief comment documenting that intent
instead.
tests/test_attention_comparison.py (1)

150-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extend RoPE validation coverage.

This test only exercises the "missing rope_positions" branch of _validate_rope_inputs. That function also rejects mismatched Sq/Skv, an invalid rope_rotary_dim, rope_cast_at != "after_rope", a device mismatch, an invalid position dtype, and a malformed position shape. Add parametrized negative tests for these branches to guard the validation logic added in this PR.
Do you want me to generate the additional test cases?

🤖 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 `@tests/test_attention_comparison.py` around lines 150 - 155, Add parametrized
negative tests alongside
test_single_gpu_rope_attention_requires_position_metadata covering each
remaining _validate_rope_inputs rejection: mismatched Sq/Skv, invalid
rope_rotary_dim, rope_cast_at other than "after_rope", position/device mismatch,
invalid rope_positions dtype, and malformed rope_positions shape. Build each
case from _comparison_inputs, assert ValueError, and match the corresponding
validation error while preserving the existing missing-metadata test.
🤖 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 `@docs/design/ws2-attention-single-gpu-harness.md`:
- Around line 27-29: Update the RoPE path description to hyphenate the compound
modifier before “kernels,” changing “production fused kernels” to the
grammatically correct form while preserving the surrounding technical content.

In `@rl_engine/testing/attention_comparison.py`:
- Around line 223-284: Consolidate the duplicated computation in
run_unfused_rope_attention and run_fused_like_rope_attention by extracting a
shared private helper that accepts name, materialization, and fusion_boundary,
while preserving the existing _apply_rope_to_qk and _attention_with_lse
arguments and returned provenance. Have both public paths delegate to the
helper; if duplication is intentionally retained for a future fused kernel, add
a brief comment documenting that intent instead.

In `@tests/test_attention_comparison.py`:
- Around line 150-155: Add parametrized negative tests alongside
test_single_gpu_rope_attention_requires_position_metadata covering each
remaining _validate_rope_inputs rejection: mismatched Sq/Skv, invalid
rope_rotary_dim, rope_cast_at other than "after_rope", position/device mismatch,
invalid rope_positions dtype, and malformed rope_positions shape. Build each
case from _comparison_inputs, assert ValueError, and match the corresponding
validation error while preserving the existing missing-metadata test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d8fe70ab-d037-4e5d-966a-f46b00eb15d6

📥 Commits

Reviewing files that changed from the base of the PR and between aa15aa8 and 6d57478.

📒 Files selected for processing (4)
  • docs/design/ws2-attention-single-gpu-harness.md
  • rl_engine/testing/__init__.py
  • rl_engine/testing/attention_comparison.py
  • tests/test_attention_comparison.py

Signed-off-by: inaniloquentee <3051000145@qq.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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)
tests/test_attention_comparison.py (1)

307-337: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the numeric self-test failure path.

_probe_te_context_parallel has three hardening layers: a callable check, a signature check, and a numeric self-test. This file tests the first two (test_transformer_engine_path_reports_missing_helpers, test_transformer_engine_path_reports_incompatible_helper_signature), but no test exercises a helper that has a correct name and signature yet produces wrong numeric output. Add a sibling test that monkeypatches helpers with matching signatures but incorrect arithmetic, and assert report.unavailable reports the numeric self-test failure.

def test_transformer_engine_path_reports_numeric_selftest_failure(monkeypatch):
    def lse_correction(softmax_lse, softmax_lse_per_step):
        softmax_lse.copy_(softmax_lse + softmax_lse_per_step)  # wrong: not logaddexp

    def out_correction_init(out_init_step, softmax_lse, softmax_lse_init_step, seq_dim):
        return out_init_step

    def out_correction(out, out_per_step, softmax_lse, softmax_lse_per_step, seq_dim):
        out.add_(out_per_step)

    monkeypatch.setitem(
        sys.modules,
        _TE_CONTEXT_PARALLEL_MODULE,
        types.SimpleNamespace(
            flash_attn_fwd_softmax_lse_correction=lse_correction,
            flash_attn_fwd_out_correction_init=out_correction_init,
            flash_attn_fwd_out_correction=out_correction,
        ),
    )

    report = compare_single_gpu_attention(
        _comparison_inputs(),
        query_chunk_size=3,
        kv_page_size=2,
        include_transformer_engine=True,
    )

    assert len(report.unavailable) == 1
    assert "numeric self-test failed" in report.unavailable[0]
🤖 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 `@tests/test_attention_comparison.py` around lines 307 - 337, The existing test
only covers incompatible helper signatures; add a sibling test for the numeric
self-test failure in the Transformer Engine path. In the new test, monkeypatch
the helpers exposed by _TE_CONTEXT_PARALLEL_MODULE with matching signatures but
intentionally incorrect arithmetic, invoke compare_single_gpu_attention with
include_transformer_engine=True, and assert exactly one unavailable result
containing “numeric self-test failed”.
🤖 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 `@rl_engine/testing/attention_comparison.py`:
- Around line 636-698: Update the numeric checks in _probe_te_context_parallel
so compatible Transformer Engine FP32 implementations are not rejected for minor
accumulation differences. Keep the existing torch.allclose validations and
absolute tolerance, but add a small relative tolerance appropriate for TE FP32
accuracy to both the LSE and output comparisons.

---

Nitpick comments:
In `@tests/test_attention_comparison.py`:
- Around line 307-337: The existing test only covers incompatible helper
signatures; add a sibling test for the numeric self-test failure in the
Transformer Engine path. In the new test, monkeypatch the helpers exposed by
_TE_CONTEXT_PARALLEL_MODULE with matching signatures but intentionally incorrect
arithmetic, invoke compare_single_gpu_attention with
include_transformer_engine=True, and assert exactly one unavailable result
containing “numeric self-test failed”.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9eb554da-43cd-408b-bc34-022091ca4095

📥 Commits

Reviewing files that changed from the base of the PR and between 6d57478 and cbb5e2c.

📒 Files selected for processing (3)
  • docs/design/ws2-attention-transformer-engine-reuse-plan.md
  • rl_engine/testing/attention_comparison.py
  • tests/test_attention_comparison.py

Comment on lines +636 to +698
def _probe_te_context_parallel(module: Any) -> None:
missing = [
name for name in _TE_CONTEXT_PARALLEL_HELPERS if not callable(getattr(module, name, None))
]
if missing:
raise TransformerEngineUnavailable(
f"{_TE_CONTEXT_PARALLEL_MODULE} missing required helpers: {', '.join(missing)}"
)

for name, expected in _TE_CONTEXT_PARALLEL_HELPERS.items():
helper = getattr(module, name)
try:
parameters = tuple(inspect.signature(helper).parameters)
except (TypeError, ValueError) as exc:
raise TransformerEngineUnavailable(
f"{_TE_CONTEXT_PARALLEL_MODULE}.{name} signature is not inspectable"
) from exc
if parameters[: len(expected)] != expected:
raise TransformerEngineUnavailable(
f"{_TE_CONTEXT_PARALLEL_MODULE}.{name} has incompatible signature "
f"{parameters}; expected prefix {expected}"
)

try:
lse_a = torch.tensor([[[0.0, -1.0]]], dtype=torch.float32)
lse_b = torch.tensor([[[1.0, -3.0]]], dtype=torch.float32)
out_a = torch.tensor([[[[1.0, -2.0], [0.5, 2.0]]]], dtype=torch.float32)
out_b = torch.tensor([[[[-1.0, 4.0], [3.0, -0.5]]]], dtype=torch.float32)
expected_lse = torch.logaddexp(lse_a, lse_b)
expected_out = (
torch.exp(lse_a - expected_lse).unsqueeze(-1) * out_a
+ torch.exp(lse_b - expected_lse).unsqueeze(-1) * out_b
)

probed_lse = lse_a.clone()
module.flash_attn_fwd_softmax_lse_correction(probed_lse, lse_b)
probed_out = module.flash_attn_fwd_out_correction_init(
out_a.clone(),
probed_lse,
lse_a,
seq_dim=2,
)
module.flash_attn_fwd_out_correction(
probed_out,
out_b,
probed_lse,
lse_b,
seq_dim=2,
)
except Exception as exc:
raise TransformerEngineUnavailable(
f"{_TE_CONTEXT_PARALLEL_MODULE} helper numeric self-test failed: {exc}"
) from exc

if not torch.allclose(probed_lse, expected_lse, atol=1.0e-6, rtol=0.0):
raise TransformerEngineUnavailable(
f"{_TE_CONTEXT_PARALLEL_MODULE} LSE helper numeric self-test failed"
)
if not torch.allclose(probed_out, expected_out, atol=1.0e-6, rtol=0.0):
raise TransformerEngineUnavailable(
f"{_TE_CONTEXT_PARALLEL_MODULE} out helper numeric self-test failed"
)

Copy link
Copy Markdown

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

🧩 Analysis chain

🌐 Web query:

NVIDIA Transformer Engine flash_attn_fwd_out_correction numerical precision fp32 accumulation

💡 Result:

In the NVIDIA Transformer Engine, the flash_attn_fwd_out_correction function is a JIT-fused helper used during context parallelism (CP) to merge partial attention outputs across distributed ranks [1][2]. Regarding numerical precision and FP32 accumulation: 1. Purpose of Accumulation: When employing context parallelism, the attention output is computed in pieces across different ranks. The flash_attn_fwd_out_correction function performs the online softmax correction required to merge these partial outputs accurately [1][2]. 2. FP32 Accumulation Improvements: To enhance numerical stability and accuracy—particularly for BF16 and FP16 precision—the Transformer Engine was updated (notably in PR #821) to implement FP32 accumulation for these attention operations [3][4]. 3. Impact: Adopting FP32 buffers for the accumulation of attention outputs (in the forward pass) and the gradient computations (in the backward pass) significantly reduced numerical mismatches [3][4]. While earlier implementations or lower-precision accumulations led to higher error rates in output comparisons, the transition to FP32 buffers provided stricter numerical adherence with a negligible increase in memory usage (typically less than 2%) [3][4]. The function itself performs the following operation: O_new = O_old * exp(LSE_old - LSE_new) + O_step * exp(LSE_step - LSE_new), which facilitates the correct combination of distributed partial attention results [1].

Citations:


🌐 Web query:

TransformerEngine fp32 accumulation flash_attn_fwd_softmax_lse_correction precision tolerance

💡 Result:

In NVIDIA Transformer Engine, specifically within the context parallelism (CP) implementation, the flash_attn_fwd_softmax_lse_correction function is used to merge partial softmax log-sum-exp (LSE) stats across different sequence chunks [1][2]. Regarding precision and accumulation: 1. FP32 Accumulation for Softmax LSE Correction: The flash_attn_fwd_softmax_lse_correction function has been updated to perform LSE correction in FP32 precision [3]. Previously, it utilized higher precision (FP64), but was lowered to FP32 to align with cuDNN's native softmax LSE representation, as this maintains sufficient accuracy for the combined CP ranks while staying within float limits [3]. The implementation uses torch.log1p for improved numerical stability when handling values close to zero [3][4]. 2. FP32 Accumulation in Attention Operations: Beyond the LSE correction, Transformer Engine incorporates FP32 accumulation buffers for attention operations (both forward and backward passes) when context parallelism is enabled [5][6]. This practice, similar to techniques used in Megatron-LM for bf16 precision, was extended to fp16 to improve numerical accuracy [5][6]. 3. Precision Tolerance: When validating these implementations, Transformer Engine developers have used strict tolerances, such as rtol = 1.3e-6 and atol = 1e-5, with torch.testing.assert_close to measure the effectiveness of using FP32 accumulation [5][6]. These tolerances are consistent with standard requirements for FP32 computations and have been used to demonstrate reductions in numerical mismatches during both forward and backward passes [5][6].

Citations:


🌐 Web query:

"flash_attn_fwd_out_correction" "torch.allclose" "TransformerEngine"

💡 Result:

The function flash_attn_fwd_out_correction is a JIT-fused utility within NVIDIA's Transformer Engine used specifically to support context parallelism [1][2][3]. It merges partial attention outputs and log-sum-exp (LSE) values collected from different sequence chunks during the attention forward pass [2][3]. The mathematical operation performed by this function is: O_new = O_old * exp(LSE_old - LSE_new) + O_step * exp(LSE_step - LSE_new) [2] Key technical details: - Implementation: It is defined in transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py and uses the @jit_fuser decorator to optimize execution [1][4]. - Context: It was previously associated with issues related to torch.compile and movedim (e.g., Issue #1235), which were resolved in later versions of Transformer Engine (e.g., v1.12+) [5][4]. - Precision: To improve numerical stability in context-parallel attention, Transformer Engine implements FP32 accumulation for these types of operations [6]. Regarding torch.allclose, it is a standard PyTorch utility used to verify that two tensors are numerically close within a specified absolute (atol) and relative (rtol) tolerance [7]. In the context of Transformer Engine development, torch.testing.assert_close (often with FP32-specific tolerances) is frequently used in test suites to validate that these fused operations or FP32-accumulated attention passes match expected outputs, especially after performance-oriented modifications [6]. There is no direct functional coupling between the flash_attn_fwd_out_correction code and the torch.allclose function, other than the latter being used to verify the numerical correctness of the former during testing [6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant probe code and nearby TE availability usage without executing repository code.
if [ -f rl_engine/testing/attention_comparison.py ]; then
  sed -n '600,710p' rl_engine/testing/attention_comparison.py
  printf '\n--- TE unavailable usages ---\n'
  rg -n "TransformerEngineUnavailable|load_and_probe_te_context_parallel|_probe_te_context_parallel|attention_comparison" -S .
else
  echo "rl_engine/testing/attention_comparison.py not found"
  git ls-files | rg 'attention_comparison|transformer|attention' || true
fi

Repository: RL-Align/RL-Kernel

Length of output: 6382


🌐 Web query:

TransformerEngine context_parallel.py flash_attn_fwd_softmax_lse_correction implementation source

💡 Result:

The function flash_attn_fwd_softmax_lse_correction is implemented in the NVIDIA TransformerEngine library within the file transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py [1][2][3]. It is a JIT-fused helper function designed to merge the Log-Sum-Exp (LSE) statistics from different steps of attention computation when using context parallelism [4][5]. The implementation performs an in-place update on the provided softmax_lse tensor using the following logic [1][2]: @jit_fuser def flash_attn_fwd_softmax_lse_correction( softmax_lse: torch.Tensor, softmax_lse_per_step: torch.Tensor,): """Merge softmax stats of each step in Attention with context parallelism""" max_scale = torch.max(softmax_lse, softmax_lse_per_step) min_scale = torch.min(softmax_lse, softmax_lse_per_step) new_scale = max_scale + torch.log1p(torch.exp(min_scale - max_scale)) softmax_lse.copy_(new_scale) This approach computes the updated LSE value numerically stably by identifying the maximum of the two LSE values and using the log1p(exp(...)) transformation to calculate the contribution of the smaller value [1][2]. The in-place copy_(new_scale) operation was specifically implemented to resolve issues where non-in-place updates resulted in incorrect final LSE values or numerical instability when handling values outside the range of double-precision floating-point numbers [6].

Citations:


🌐 Web query:

TransformerEngine context_parallel.py flash_attn_fwd_out_correction implementation source

💡 Result:

The function flash_attn_fwd_out_correction is defined in the TransformerEngine library within the file transformer_engine/pytorch/attention/dot_product_attention/context_parallel.py [1][2]. Its primary purpose is to merge partial attention outputs generated during different steps of context parallelism (CP) [1][3]. When using context parallelism, the attention operation is split across multiple ranks/steps, and this function computes the corrected output by accounting for the log-sum-exp (LSE) differences between the partial step and the global state [1][3]. The implementation follows this logic [1][2]: @jit_fuser def flash_attn_fwd_out_correction( out: torch.Tensor, out_per_step: torch.Tensor, softmax_lse: torch.Tensor, softmax_lse_per_step: torch.Tensor, seq_dim: int,): """Merge partial outputs of each step in Attention with context parallelism""" softmax_lse_corrected_exp = torch.exp(softmax_lse_per_step - softmax_lse).movedim(2, seq_dim) softmax_lse_corrected_exp = softmax_lse_corrected_exp.unsqueeze(-1) out_corrected = out_per_step * softmax_lse_corrected_exp out.add_(out_corrected) Key implementation details include: - Decorator: It is typically decorated with @jit_fuser to optimize the execution via TorchScript or torch.compile [4][2]. - Calculation: It uses the formula O_new = O_old + O_step * exp(LSE_step - LSE_global), where LSE_step and LSE_global are the partial and accumulated log-sum-exp values respectively [3]. - Data Handling: The function adjusts the dimensions of the LSE correction factor using movedim and unsqueeze to ensure it aligns with the output tensor's shape for element-wise multiplication and accumulation [1][4][5]. Historical context: The function has undergone refinements, including fixes to resolve compatibility issues with torch.compile, handling of integer arguments, and ensuring correct output-indexing logic when switching between different versions of FlashAttention (e.g., FA2 vs FA3) [6][4][5].

Citations:


Avoid using the probe as a strict numeric compatibility gate.

torch.allclose(..., atol=1.0e-6, rtol=0.0) can reject a functionally compatible Transformer Engine build when TE’s @jit_fuser implementation accumulates these small fp32 operands differently than the CPU reference. Use a tolerance that matches TE FP32 accuracy expectations, such as adding a small relative component; otherwise TransformerEngineUnavailable masks a working context-parallel adapter.

🤖 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 `@rl_engine/testing/attention_comparison.py` around lines 636 - 698, Update the
numeric checks in _probe_te_context_parallel so compatible Transformer Engine
FP32 implementations are not rejected for minor accumulation differences. Keep
the existing torch.allclose validations and absolute tolerance, but add a small
relative tolerance appropriate for TE FP32 accuracy to both the LSE and output
comparisons.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant