Skip to content

[WS1] Close embedding and LM-head invariance coverage - #243

Merged
Flink-ddd merged 6 commits into
mainfrom
codex/issue-151-complete
Jul 29, 2026
Merged

[WS1] Close embedding and LM-head invariance coverage#243
Flink-ddd merged 6 commits into
mainfrom
codex/issue-151-complete

Conversation

@inaniloquentee

@inaniloquentee inaniloquentee commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

Relationship to existing PRs

Closes #151

Validation

  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest -q tests/test_issue151_embedding_lm_head_invariance.py tests/test_embedding.py tests/test_lm_head.py tests/test_linear_logp.py tests/test_op_checks.py tests/test_operator_inputs.py tests/test_deepspeed_training_worker.py rl_engine/tests/test_dispatch.py
  • python -m black --target-version py310 --line-length=100 --check
  • python -m isort --profile black --line-length=100 --check-only
  • python -m ruff check
  • python -m mypy --python-version 3.10 --ignore-missing-imports rl_engine/
  • python -m pre_commit run --files

Summary by CodeRabbit

  • New Features
    • Added SM90 (Hopper) optimized backends for embedding and LM-head, including explicit forward_fp32 variants.
    • Extended kernel/operator coverage and dispatch to handle embedding and lm_head with device-aware backend selection.
  • Bug Fixes
    • Improved LM-head projection to enforce stable fixed-K row-wise behavior, strict fp32 semantics, and correct zero-row output handling.
    • Ensured embedding and LM-head outputs/gradients remain invariant across batch layouts, row permutations, and padded-token tail mutations.
  • Documentation
    • Updated embedding and LM-head operator contracts, dtype/precision behavior, and backend dispatch guidance.
  • Tests
    • Added/expanded invariance, dispatch/device-selection, SM90 wrapper, and fixed-K correctness regression coverage.

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

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 383b1b7f-097b-4101-baf0-44be92294333

📥 Commits

Reviewing files that changed from the base of the PR and between 7e3aae6 and 42ae3cb.

📒 Files selected for processing (3)
  • csrc/cuda/embedding_lm_head_sm90.cu
  • rl_engine/kernels/ops/cuda/linear/embedding.py
  • tests/test_sm90_linear_wrappers.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/test_sm90_linear_wrappers.py
  • rl_engine/kernels/ops/cuda/linear/embedding.py
  • csrc/cuda/embedding_lm_head_sm90.cu

📝 Walkthrough

Walkthrough

The PR adds fixed-K row-wise LM-head projection, Hopper SM90 embedding and LM-head kernels, explicit-device dispatch, operator harness registrations, and tests for batch-layout, padding, permutation, gradient, and LM-head-to-log-probability invariance.

Changes

Batch-invariant forward path

Layer / File(s) Summary
Fixed-K LM-head projection and contract
docs/operators/lm_head.md, rl_engine/kernels/ops/pytorch/linear/lm_head.py, tests/test_lm_head.py
LM-head projection uses row-wise fixed-K torch.mv computation with explicit zero-row handling and updated dtype, shape, purity, dispatch, accuracy, and validation semantics.
SM90 embedding and LM-head backend
csrc/cuda/embedding_lm_head_sm90.cu, csrc/ops.cpp, rl_engine/kernels/ops/cuda/linear/*, rl_engine/_C.pyi, setup.py, tests/test_sm90_linear_wrappers.py, docs/operators/embedding.md
Hopper-specific embedding and LM-head forward kernels, Python wrappers, extension bindings, build wiring, fallback behavior, documentation, deterministic gradient handling, and integration tests are added.
Device-aware kernel dispatch
rl_engine/kernels/registry.py, rl_engine/executors/deepspeed_trainer.py, rl_engine/tests/test_dispatch.py, tests/test_deepspeed_training_worker.py, tests/test_kernel_registry.py, tests/test_embedding.py
Kernel lookup accepts an explicit device, resolves CUDA or ROCm accordingly, prioritizes compiled SM90 backends on Hopper, and DeepSpeed passes the worker device for linear-logp lookup.
Embedding and LM-head operator harness contracts
rl_engine/kernels/gtest/operator_specs.py, rl_engine/kernels/gtest/operator_inputs.py, tests/test_operator_inputs.py, tests/test_op_checks.py
Embedding and LM-head specifications, normalized hidden dimensions, deterministic inputs, and gradient-enabled operator-suite tests are added.
Embedding and log-probability invariance validation
tests/test_issue151_embedding_lm_head_invariance.py
Tests cover layout, permutation, padding, dtype, device, gradient, gather, and LM-head-to-log-probability invariance behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant DeepSpeedTrainingWorker
  participant KernelRegistry
  participant SM90LMHeadOp
  participant SM90Extension
  participant NativeLMHeadOp
  DeepSpeedTrainingWorker->>KernelRegistry: resolve linear_logp for worker device
  KernelRegistry->>SM90LMHeadOp: select Hopper backend when available
  SM90LMHeadOp->>SM90Extension: compute LM-head forward
  SM90LMHeadOp-->>NativeLMHeadOp: fallback for ineligible inputs
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: bitborne, flink-ddd, kjldefeated, maxiaosong1124, frank-2077

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.36% 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 is concise and accurately summarizes the main change: closing embedding and LM-head invariance coverage.
Linked Issues check ✅ Passed The PR adds the requested embedding and LM-head batch-invariance tests, fixed-K LM-head behavior, shared #108 harness coverage, and gradient-invariance checks.
Out of Scope Changes check ✅ Passed The code changes stay aligned with the embedding/LM-head invariance work and the explicit device-routing update, with no clear unrelated additions.
✨ 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 codex/issue-151-complete

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: 2

🤖 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/kernels/ops/pytorch/linear/lm_head.py`:
- Around line 118-119: Update the empty-input branch in the LM-head
implementation around flat_hidden to return an empty matmul result connected to
hidden and weight instead of hidden.new_empty, preserving autograd for zero-row
inputs. Also add the equivalent zero-row branch to the reference snippets in
tests/test_lm_head.py lines 94-100 and docs/operators/lm_head.md lines 57-61 so
they avoid torch.stack([]); the implementation site is
rl_engine/kernels/ops/pytorch/linear/lm_head.py lines 118-119.

In `@tests/test_issue151_embedding_lm_head_invariance.py`:
- Around line 240-271: Update
test_lm_head_linear_logp_handoff_is_layout_invariant_for_rl_batches to reuse the
existing CUDA/bfloat16 parameterization, covering both CPU/float32 and
CUDA/bfloat16 execution. Before calling NativeLinearLogpOp, alter the hidden
rows selected by ~mask while leaving valid rows unchanged, then retain the
reference and layout-invariance assertions so padded hidden values cannot affect
results.
🪄 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

Run ID: daceb832-b7f6-4a13-8c93-19c721132703

📥 Commits

Reviewing files that changed from the base of the PR and between 063a261 and 171c4d6.

📒 Files selected for processing (12)
  • docs/operators/lm_head.md
  • rl_engine/executors/deepspeed_trainer.py
  • rl_engine/kernels/gtest/operator_inputs.py
  • rl_engine/kernels/gtest/operator_specs.py
  • rl_engine/kernels/ops/pytorch/linear/lm_head.py
  • rl_engine/kernels/registry.py
  • rl_engine/tests/test_dispatch.py
  • tests/test_deepspeed_training_worker.py
  • tests/test_issue151_embedding_lm_head_invariance.py
  • tests/test_lm_head.py
  • tests/test_op_checks.py
  • tests/test_operator_inputs.py

Comment thread rl_engine/kernels/ops/pytorch/linear/lm_head.py Outdated
Comment thread tests/test_issue151_embedding_lm_head_invariance.py Outdated
Signed-off-by: inaniloquentee <3051000145@qq.com>
@maxiaosong1124

Copy link
Copy Markdown
Collaborator

please resolve conflicts first, thanks!

@maxiaosong1124

Copy link
Copy Markdown
Collaborator

@inaniloquentee I only see the PyTorch implementation; I don’t see the corresponding CUDA and Triton implementations. If they haven't been implemented yet, could you please add them? Thank you.

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

# Conflicts:
#	rl_engine/kernels/gtest/operator_specs.py
#	rl_engine/kernels/registry.py
Add single-card Hopper/H200 CUDA forward paths for embedding gather and LM-head projection. The LM-head kernel assigns one CTA per output logit and reduces the full hidden dimension without Split-K, preserving batch-invariant reduction order.

Signed-off-by: inaniloquentee <3051000145@qq.com>
@inaniloquentee
inaniloquentee requested a review from bitborne as a code owner July 29, 2026 09:30

@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

🧹 Nitpick comments (1)
csrc/cuda/embedding_lm_head_sm90.cu (1)

82-112: 🧹 Nitpick | 🔵 Trivial

Confirm the no-reuse, one-CTA-per-logit LM-head design is an accepted throughput tradeoff.

Each block independently re-reads the full hidden_row/weight_row from global memory with zero data reuse across the vocab_size blocks that share the same token, so total memory traffic scales as num_tokens * vocab_size * hidden_size. This matches the PR's stated intent ("no Split-K... to preserve batch-invariant reduction order"), so flagging for awareness only — for large vocabularies this reference kernel will be significantly memory-bandwidth bound relative to a tiled GEMM.

🤖 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 `@csrc/cuda/embedding_lm_head_sm90.cu` around lines 82 - 112, Confirm that the
one-CTA-per-logit implementation in lm_head_sm90_forward_kernel is an
intentional throughput tradeoff: preserve its independent full
hidden_row/weight_row reads and deterministic reduction order, and document or
acknowledge that this reference design may be memory-bandwidth bound for large
vocabularies rather than refactoring it for cross-logit data reuse.
🤖 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 `@csrc/cuda/embedding_lm_head_sm90.cu`:
- Around line 166-207: Resolve the conflicting out-of-range ID handling between
the host path around embedding_sm90_forward and the kernel’s bounds check:
either remove the synchronizing min/max validation and preserve/document the
kernel’s zero-for-invalid-ID behavior, or retain host-side validation and remove
the kernel masking branch. Ensure only one policy remains and eliminate the
unreachable or unnecessary path.

In `@rl_engine/kernels/ops/cuda/linear/embedding.py`:
- Around line 34-52: Update backward in the custom embedding autograd
implementation to replace grad_weight.index_add_ with a deterministic
accumulation path for duplicate token_ids on CUDA, such as sorting ids followed
by fixed-order segmented reduction or a dedicated deterministic kernel. Preserve
filtering of invalid ids, output shape/device/dtype, and the existing
no-gradient path while ensuring torch.use_deterministic_algorithms(True)
succeeds and matches PyTorch reference gradients.

In `@rl_engine/kernels/ops/cuda/linear/lm_head.py`:
- Around line 44-61: Update SM90LMHeadOp.backward to compute grad_hidden and
grad_weight through the deterministic det_gemm_da and det_gemm_db wrappers
instead of torch.matmul, preserving the existing reshaping, float conversion,
dtype conversion, and ctx.needs_input_grad guards. Use the wrappers with the
equivalent dC @ B^T and A^T @ dC operands, while leaving grad_bias and the
returned tuple unchanged.

---

Nitpick comments:
In `@csrc/cuda/embedding_lm_head_sm90.cu`:
- Around line 82-112: Confirm that the one-CTA-per-logit implementation in
lm_head_sm90_forward_kernel is an intentional throughput tradeoff: preserve its
independent full hidden_row/weight_row reads and deterministic reduction order,
and document or acknowledge that this reference design may be memory-bandwidth
bound for large vocabularies rather than refactoring it for cross-logit data
reuse.
🪄 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: e244f52c-c9ab-4bce-8b8f-a39c6622e67f

📥 Commits

Reviewing files that changed from the base of the PR and between f5673a6 and 0e5cd3e.

📒 Files selected for processing (17)
  • csrc/cuda/embedding_lm_head_sm90.cu
  • csrc/ops.cpp
  • docs/operators/embedding.md
  • docs/operators/lm_head.md
  • rl_engine/_C.pyi
  • rl_engine/kernels/gtest/operator_inputs.py
  • rl_engine/kernels/gtest/operator_specs.py
  • rl_engine/kernels/ops/cuda/linear/__init__.py
  • rl_engine/kernels/ops/cuda/linear/embedding.py
  • rl_engine/kernels/ops/cuda/linear/lm_head.py
  • rl_engine/kernels/registry.py
  • setup.py
  • tests/test_embedding.py
  • tests/test_kernel_registry.py
  • tests/test_lm_head.py
  • tests/test_operator_inputs.py
  • tests/test_sm90_linear_wrappers.py
🚧 Files skipped from review as they are similar to previous changes (6)
  • tests/test_operator_inputs.py
  • rl_engine/kernels/gtest/operator_specs.py
  • rl_engine/kernels/gtest/operator_inputs.py
  • tests/test_lm_head.py
  • docs/operators/lm_head.md
  • rl_engine/kernels/registry.py

Comment thread csrc/cuda/embedding_lm_head_sm90.cu
Comment thread rl_engine/kernels/ops/cuda/linear/embedding.py
Comment thread rl_engine/kernels/ops/cuda/linear/lm_head.py
@Flink-ddd

Copy link
Copy Markdown
Collaborator

cc @maxiaosong1124 @KJLdefeated @frank-2077 PTAL, we need merge this pr tonight. Thank you.

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

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

Overall look good and clean. Left some comment. After addressed, I am happy to approve.

"embedding_sm90 supports fp32, fp16, and bf16 weights");

c10::cuda::CUDAGuard device_guard(weight.device());
check_sm90_device();

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.

check_sm90_device() calls cudaGetDeviceProperties on every forward. The Python _can_use_sm90 already gates on _is_hopper, then the C++ re-checks prop.major == 9 and hard-TORCH_CHECKs. Maybe this is not necessary?


namespace {

constexpr int kThreads = 256;

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.

Non-blocking. One CTA per logit and kThreads = 256 would be slow for Qwen3 prefill (say 4096 tokens × 151936 vocab) that's ~622M blocks, each reducing 4096 elements with 256 threads (16 elements/thread). But this is fine for reference kernel.

@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: 2

🧹 Nitpick comments (1)
csrc/cuda/embedding_lm_head_sm90.cu (1)

134-141: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Cache the SM90 capability probe per device.

check_sm90_device calls cudaGetDeviceProperties on every embedding/LM-head forward. The full cudaDeviceProp fetch is heavier than needed on a per-step hot path; cudaDeviceGetAttribute(cudaDevAttrComputeCapabilityMajor, ...) or a static per-device cache keeps the check but removes the repeated cost.

♻️ Proposed refactor
 void check_sm90_device() {
     int device = 0;
     C10_CUDA_CHECK(cudaGetDevice(&device));
-    cudaDeviceProp prop{};
-    C10_CUDA_CHECK(cudaGetDeviceProperties(&prop, device));
-    TORCH_CHECK(prop.major == 9, "SM90 embedding/lm_head kernels require Hopper, got sm_",
-                prop.major, prop.minor);
+    static std::array<int, 2> cached_cc[8] = {};  // or a small map keyed by device
+    int major = 0;
+    int minor = 0;
+    C10_CUDA_CHECK(cudaDeviceGetAttribute(&major, cudaDevAttrComputeCapabilityMajor, device));
+    C10_CUDA_CHECK(cudaDeviceGetAttribute(&minor, cudaDevAttrComputeCapabilityMinor, device));
+    TORCH_CHECK(major == 9, "SM90 embedding/lm_head kernels require Hopper, got sm_",
+                major, minor);
 }
🤖 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 `@csrc/cuda/embedding_lm_head_sm90.cu` around lines 134 - 141, Update
check_sm90_device to avoid calling cudaGetDeviceProperties on every invocation:
use cudaDeviceGetAttribute for the compute capability or cache the per-device
SM90 result, while preserving the existing device lookup, CUDA error checks, and
Hopper-only TORCH_CHECK validation.
🤖 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/kernels/ops/cuda/linear/embedding.py`:
- Around line 50-64: Replace the per-row accumulation loop in the embedding
backward helper with a padded [num_unique, max_count, H] segment buffer and fold
each segment using a vectorized reduction, preserving left-to-right accumulation
order while reducing launches to max(counts). Reuse the existing sorting/segment
structure and handle invalid IDs through the discarded sorted segment rather
than a separate validity synchronization; avoid introducing additional device
synchronizations beyond the existing counts.max().item() path.

In `@tests/test_sm90_linear_wrappers.py`:
- Around line 198-206: Update expected_weight in the relevant gradient test to
mirror det_gemm_db’s hidden^T @ dy orientation, then transpose the result to the
expected weight shape before comparison. Keep the existing dtype conversions and
torch.equal assertion unchanged.

---

Nitpick comments:
In `@csrc/cuda/embedding_lm_head_sm90.cu`:
- Around line 134-141: Update check_sm90_device to avoid calling
cudaGetDeviceProperties on every invocation: use cudaDeviceGetAttribute for the
compute capability or cache the per-device SM90 result, while preserving the
existing device lookup, CUDA error checks, and Hopper-only TORCH_CHECK
validation.
🪄 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: 99987810-767d-4463-8203-4fe39da02fc1

📥 Commits

Reviewing files that changed from the base of the PR and between 0e5cd3e and 7e3aae6.

📒 Files selected for processing (6)
  • csrc/cuda/embedding_lm_head_sm90.cu
  • docs/operators/embedding.md
  • docs/operators/lm_head.md
  • rl_engine/kernels/ops/cuda/linear/embedding.py
  • rl_engine/kernels/ops/cuda/linear/lm_head.py
  • tests/test_sm90_linear_wrappers.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • rl_engine/kernels/ops/cuda/linear/lm_head.py

Comment thread rl_engine/kernels/ops/cuda/linear/embedding.py Outdated
Comment thread tests/test_sm90_linear_wrappers.py
Signed-off-by: inaniloquentee <3051000145@qq.com>

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

LGTM now! Thanks!

@inaniloquentee

Copy link
Copy Markdown
Collaborator Author

Overall look good and clean. Left some comment. After addressed, I am happy to approve.

Thanks! Addressed the review, cached the SM90 device capability check, reduced embedding backward launches with a fixed-order segmented fold, and fixed the LM-head backward test expectation. CI is green now, including H100 SM90.

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

LGTM, Thank you for work.

@Flink-ddd
Flink-ddd merged commit 0b12d34 into main Jul 29, 2026
6 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.

[WS1][kernels] Batch-invariant embedding + LM head projection

4 participants