Skip to content

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

Open
KJLdefeated wants to merge 4 commits into
mainfrom
feat/linear-logp-cuda-backward
Open

[Feat][Kernel] Add TP Fused linear logp Triton#208
KJLdefeated wants to merge 4 commits 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

  • Enhancements
    • Improved log-probability calculations for tensor-parallel and distributed workloads.
    • Added validation for vocabulary and target metadata to identify configuration issues earlier.
    • Improved forward and backward computation across parallel partitions while preserving existing bias handling.
    • Optimized reductions for target logits and normalization values.

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@KJLdefeated, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ac2c8baa-847b-492c-b0a7-075743df8abb

📥 Commits

Reviewing files that changed from the base of the PR and between b4fb33d and 3551dcb.

📒 Files selected for processing (1)
  • rl_engine/kernels/ops/pytorch/loss/linear_logp.py
📝 Walkthrough

Walkthrough

The Triton linear_logp implementation now shares forward-kernel launch logic and supports validated tensor-parallel forward and backward execution with distributed reductions.

Changes

Triton tensor-parallel linear_logp

Layer / File(s) Summary
Triton forward kernel execution
rl_engine/kernels/ops/triton/loss/linear_logp.py
_run_forward_kernel handles output allocation, grid setup, strides, and kernel launch. _LinearLogpFunction.forward uses this helper and preserves dummy-bias handling.
Triton tensor-parallel path
rl_engine/kernels/ops/triton/loss/linear_logp.py
The tensor-parallel autograd path validates distributed metadata and targets, computes local results, performs SUM and MAX/SUM reductions, delegates backward computation, and receives dispatch from TritonLinearLogpOp.apply.

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

Sequence Diagram(s)

sequenceDiagram
  participant Op as TritonLinearLogpOp.apply
  participant Wrapper as _triton_tensor_parallel_linear_logp
  participant Function as _TensorParallelTritonLinearLogpFunction
  participant Kernel as _run_forward_kernel
  participant TPGroup as Tensor-parallel group

  Op->>Wrapper: dispatch tensor-parallel inputs
  Wrapper->>Function: normalize metadata and invoke autograd path
  Function->>Kernel: compute local logp and LSE
  Function->>TPGroup: reduce target logits with SUM
  Function->>TPGroup: reduce LSE with MAX then SUM
  Function-->>Wrapper: return global target log-probabilities
  Function->>Function: delegate backward computation
Loading

Suggested reviewers: inaniloquentee, ethanzero2hero

🚥 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 identifies the main change: adding a tensor-parallel fused Triton implementation for linear log probabilities.
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.

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

local_vocab_size=weight.size(0),
global_vocab_size=global_vocab_size,
)
_validate_global_targets(target_1d, global_vocab_size, tp_group)

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.

Calling _validate_tp_vocab_partition and _validate_global_targets here introduces extra collectives (all_gather, all_reduce) and .item() host syncs on the hot path for every forward step.

Since the TP vocabulary partition is static after initialization, evaluating this on every pass will become a severe latency bottleneck in a distributed training loop. I recommend caching the validation state (e.g., executing it only once per tp_group) or gating these checks behind a validation/debug flag to keep the production forward pass as lightweight as possible.

@Flink-ddd

Copy link
Copy Markdown
Collaborator

please resolve the code conflicts first, then we will merge this PR.

KJLdefeated and others added 2 commits August 2, 2026 22:09
Resolve conflicts by keeping main's TP backward helpers
(tensor_parallel_linear_logp_backward / _sm90_linear_logp_backward),
which add needs_input_grad gating and avoid per-chunk host syncs, and
dropping this branch's duplicate tensor_parallel_chunked_linear_logp_backward.
The new Triton TP path now routes its backward through the shared helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PyjQEqDJwy9Cos4Sb9QBK
Use the memoized _validate_tp_vocab_partition_cached (the partition is
static per tp_group after init) and gate _validate_global_targets behind
the existing RL_KERNEL_LINEAR_LOGP_VALIDATE_TP_TARGETS debug flag,
matching the native and SM90 TP paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012PyjQEqDJwy9Cos4Sb9QBK

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

Caution

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

⚠️ Outside diff range comments (1)
rl_engine/kernels/ops/triton/loss/linear_logp.py (1)

204-217: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject TP vocab shards with local_vocab_size <= 0 in _validate_tp_vocab_partition.

should_use_tensor_parallel_linear_logp already rejects zero local vocab in non-TP or single-rank cases, but _validate_tp_vocab_partition still accepts local_vocab_size <= 0 when world_size >= 2. A zero shard creates a [x, x) rank range; the partition loop then skips it, an out-of-range global target can be clamped into another rank’s local column, and the backward path can divide by local_vocab == 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 `@rl_engine/kernels/ops/triton/loss/linear_logp.py` around lines 204 - 217,
Update _validate_tp_vocab_partition to reject any local_vocab_size <= 0 for
tensor-parallel groups, including world_size >= 2, before validating or
iterating partition ranges. Preserve the existing validation behavior for
positive shard sizes and ensure invalid zero-sized shards cannot reach the
forward or backward paths.
🤖 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 `@rl_engine/kernels/ops/triton/loss/linear_logp.py`:
- Around line 204-217: Update _validate_tp_vocab_partition to reject any
local_vocab_size <= 0 for tensor-parallel groups, including world_size >= 2,
before validating or iterating partition ranges. Preserve the existing
validation behavior for positive shard sizes and ensure invalid zero-sized
shards cannot reach the forward or backward paths.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ddd5b3f0-5d03-4f5f-b041-dccdd64c4301

📥 Commits

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

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

@KJLdefeated
KJLdefeated requested a review from Flink-ddd August 3, 2026 02:25

@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 now, Thank you for update.

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.

2 participants