Skip to content

[Feat][Kernel] Add TP Fused linear logp Triton#208

Open
KJLdefeated wants to merge 1 commit into
mainfrom
feat/linear-logp-cuda-backward
Open

[Feat][Kernel] Add TP Fused linear logp Triton#208
KJLdefeated wants to merge 1 commit into
mainfrom
feat/linear-logp-cuda-backward

Conversation

@KJLdefeated

@KJLdefeated KJLdefeated commented Jul 5, 2026

Copy link
Copy Markdown
Collaborator

[FEAT][kernels] Add tensor-parallel path to the Triton linear_logp kernel

Summary

Gives the Triton linear_logp op a real vocab-parallel (tensor-parallel)path, so a vocab-sharded LM head runs the fused Triton forward on each rank instead of silently falling back to the pure-PyTorch TP implementation.

logp[t] = (hidden @ W_local^T + b_local)[t, target[t]] - logsumexp_over_all_ranks(...)
Path Status
Triton TP forward (_TensorParallelTritonLinearLogpFunction) Local-shard fused forward + cross-rank logsumexp merge. New.
Triton op dispatch TritonLinearLogpOp now routes TP calls.
Shared TP backward (tensor_parallel_chunked_linear_logp_backward) Extracted from the byte-identical native + SM90 backwards; now used by all three.
Tests tests/linear_logp_tp.py --op-source triton

Builds on the merged #122 (fused linear_logp) and #189 (native + SM90 TP).

Implementation

Triton TP forward — rl_engine/kernels/ops/triton/loss/linear_logp.py

  • Factored the forward-kernel launch into _run_forward_kernel(...) -> (logp, lse),
    reused by both the plain and TP forwards (no kernel change).
  • _TensorParallelTritonLinearLogpFunction mirrors the SM90 TP path
    (_FusedTensorParallelLinearLogpSM90Function):
    • each rank clamps the global target to a valid local column and runs the
      fused forward over its shard, giving the local lse and (for the owned
      token) the target logit, recovered as target_logit = logp + lse;
    • ranks merge shards with the standard online-softmax reduction:
      all_reduce(MAX) the running max, rescale + all_reduce(SUM) the running
      sum, all_reduce(SUM) the target logit; logp = target_logit - global_lse;
    • _validate_tp_vocab_partition + _validate_global_targets (from [FEAT][kernels] Add tensor-parallel linear_logp path #189)
      guarantee a contiguous [0, V) partition and in-range targets, so each
      target has exactly one owner — matching the SM90 path (no extra owner-count
      all-reduce).
  • TritonLinearLogpOp.apply now dispatches TP calls to
    _triton_tensor_parallel_linear_logp.

Shared TP backward — rl_engine/kernels/ops/pytorch/loss/linear_logp.py

  • The native and SM90 TP backwards were byte-identical. Reusetensor_parallel_chunked_linear_logp_backward.

Validation

Validated base on torchrun --standalone --nproc_per_node=4 tests/linear_logp_tp.py --op-source triton reference from docs/operators/linear-logp-tp-test.md

Results (backend=nccl, world_size=4)

run result
triton fp32 (--atol 1e-3 --rtol 1e-3) PASS — output 7.63e-6, all grads pass
triton bf16 correctness output PASS (2.69e-1); grads = native/sm90 (see below)
triton bf16 uneven shards output PASS
triton bf16 stress (tokens=4096, hidden=2048, V=32768) finite PASS, 31 ms, 0.47 GB/rank

Backend equivalence (bf16, same harness/config, 4 NCCL ranks):

backend output hidden_grad max_abs hidden_grad max_rel
triton (this PR) PASS 2.69e-1 5.781250e-01 2.281e+02
native (#189) PASS 2.69e-1 5.781250e-01 2.281e+02
sm90 (#189) PASS 2.69e-1 5.781250e-01 2.271e+02

Unit teststests/test_linear_logp.py, 27 passed (incl. the SM90 TP tests that exercise the shared backward).

Files

  • rl_engine/kernels/ops/triton/loss/linear_logp.py — Triton TP forward + dispatch.
  • rl_engine/kernels/ops/pytorch/loss/linear_logp.py — shared TP backward helper.
  • rl_engine/kernels/ops/cuda/loss/linear_logp.py — SM90 TP backward now uses the shared helper.

Potential Issues: per-forward validation collectives

The exactly-one-owner guarantee comes from _validate_tp_vocab_partition and _validate_global_targets, which run on every forward and issue collectives on top of the 3 all-reduces the TP reduction itself needs (target logit, running
max, running sum):

  • _validate_tp_vocab_partition → 2 all_gathers (shard ranges + declared global sizes). The vocab partition is fixed at model construction, so this re-proves a static invariant every step.
  • _validate_global_targets → up to 3 all_reduces (invalid-flag MAX, target MIN, target MAX) plus .item() host-device syncs. Data-dependent, but the syncs serialize the stream.

So validation roughly doubles the collective count per forward. The messages are tiny (scalar / 2-element), so this is latency-bound, not bandwidth-bound — but on high-rank or high-latency interconnects the extra round-trips (and the host
syncs) can become a connection/throughput bottleneck in a tight training loop. This behavior is inherited unchanged from #189 (native + SM90 run the same checks); the Triton path mirrors it for consistency rather than diverging.

Summary by CodeRabbit

  • New Features

    • Added a new Triton-based tensor-parallel path for target log-prob computation.
    • Tensor-parallel runs now support distributed reductions for more consistent results across ranks.
  • Bug Fixes

    • Improved gradient computation for tensor-parallel log-prob operations.
    • Fixed handling for cases where bias is not provided.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The tensor-parallel backward computation for linear_logp was consolidated into a new shared helper function, tensor_parallel_chunked_linear_logp_backward, in the pytorch loss module. The CUDA SM90 and Triton implementations were refactored to delegate to this helper instead of using inline backward logic. A new Triton tensor-parallel forward autograd function was also added.

Changes

Tensor-parallel linear_logp backward consolidation

Layer / File(s) Summary
Shared TP chunked backward helper
rl_engine/kernels/ops/pytorch/loss/linear_logp.py
Adds tensor_parallel_chunked_linear_logp_backward, recomputing per-chunk logits, deriving gradients for hidden/weight/bias, and all-reducing grad_hidden; the pytorch autograd backward now delegates to it.
CUDA SM90 backward delegation
rl_engine/kernels/ops/cuda/loss/linear_logp.py
Imports the shared helper and replaces the inline SM90 backward logic with a call to it.
Triton forward refactor and new TP forward path
rl_engine/kernels/ops/triton/loss/linear_logp.py
Extracts _run_forward_kernel for kernel launch, adds _TensorParallelTritonLinearLogpFunction with distributed all_reduce-based forward and delegated backward, and updates TritonLinearLogpOp.apply dispatch.

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

Possibly related PRs

  • RL-Align/RL-Kernel#189: Introduces the TP vocab-sharded linear_logp path that this PR's shared backward helper builds upon.

Suggested reviewers: EthanZero2Hero, bitborne, Flink-ddd

🚥 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 matches the main change: adding a Triton tensor-parallel fused linear logp path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/linear-logp-cuda-backward

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.

🧹 Nitpick comments (2)
rl_engine/kernels/ops/triton/loss/linear_logp.py (1)

201-208: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Per-forward TP validation adds collectives + host syncs on the hot path.

_validate_tp_vocab_partition (2× all_gather + .item() syncs) and _validate_global_targets (all_reduce MIN/MAX/MAX + .item() syncs) run on every forward call. As flagged in the PR description, these extra collectives and host syncs can become a latency bottleneck at higher TP degree or higher inter-rank latency. Consider gating them behind a one-time/debug validation (e.g. validate once per (tp_group, shard-layout) and cache the result, or behind an env/debug flag) since the vocab partition is static across steps.

🤖 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/kernels/ops/triton/loss/linear_logp.py` around lines 201 - 208, The
per-forward validation in linear_logp is adding expensive collectives and host
syncs on the hot path. Update the validation flow around
_validate_tp_vocab_partition and _validate_global_targets to avoid running on
every forward call, e.g. by guarding them with a debug/env flag or caching a
one-time result per tp_group and shard layout. Keep the existing checks in place
for validation mode, but make the default forward path skip the repeated
all_gather/all_reduce and .item() syncs.
rl_engine/kernels/ops/pytorch/loss/linear_logp.py (1)

458-474: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Drop the per-chunk host sync in the backward loop.

bool(owns_target.any().item()) forces a device→host synchronization on every chunk iteration, in a hot backward path. The guard is only an optimization: when no rows own a target, rows is empty and the scatter-add is a safe no-op — which is exactly how the non-TP chunked_linear_logp_backward handles it (it scatters unconditionally). Removing the sync keeps correctness and improves throughput on higher-rank/higher-latency setups.

♻️ Proposed change to remove the per-chunk sync
         dz = -torch.exp(logits.float() - lse[i0:i1].unsqueeze(1))
         local_idx = target_1d[i0:i1] - vocab_start_index
         owns_target = (local_idx >= 0) & (local_idx < local_vocab)
-        if bool(owns_target.any().item()):
-            rows = torch.arange(i1 - i0, device=dz.device)[owns_target]
-            dz[rows, local_idx[owns_target].long()] += 1.0
+        rows = torch.arange(i1 - i0, device=dz.device)[owns_target]
+        dz[rows, local_idx[owns_target].long()] += 1.0
         dz *= g[i0:i1].unsqueeze(1)
🤖 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/kernels/ops/pytorch/loss/linear_logp.py` around lines 458 - 474,
The backward loop in linear_logp’s chunked path is doing an unnecessary
device-to-host sync via bool(owns_target.any().item()) on every iteration.
Update the chunk handling in the linear_logp backward logic to avoid the
host-side guard and perform the target scatter-add unconditionally, matching
chunked_linear_logp_backward behavior. Keep the existing owns_target/local_idx
logic in place, but remove the .item()-based branching so the hot path stays
fully on device.
🤖 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 `@rl_engine/kernels/ops/pytorch/loss/linear_logp.py`:
- Around line 458-474: The backward loop in linear_logp’s chunked path is doing
an unnecessary device-to-host sync via bool(owns_target.any().item()) on every
iteration. Update the chunk handling in the linear_logp backward logic to avoid
the host-side guard and perform the target scatter-add unconditionally, matching
chunked_linear_logp_backward behavior. Keep the existing owns_target/local_idx
logic in place, but remove the .item()-based branching so the hot path stays
fully on device.

In `@rl_engine/kernels/ops/triton/loss/linear_logp.py`:
- Around line 201-208: The per-forward validation in linear_logp is adding
expensive collectives and host syncs on the hot path. Update the validation flow
around _validate_tp_vocab_partition and _validate_global_targets to avoid
running on every forward call, e.g. by guarding them with a debug/env flag or
caching a one-time result per tp_group and shard layout. Keep the existing
checks in place for validation mode, but make the default forward path skip the
repeated all_gather/all_reduce and .item() syncs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 19b2aa5f-d05f-4d12-9075-ab9d4fea8163

📥 Commits

Reviewing files that changed from the base of the PR and between e4df107 and b0e588f.

📒 Files selected for processing (3)
  • rl_engine/kernels/ops/cuda/loss/linear_logp.py
  • rl_engine/kernels/ops/pytorch/loss/linear_logp.py
  • rl_engine/kernels/ops/triton/loss/linear_logp.py

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

Great!Here's a small suggestion for your consideration.

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.

2 participants