Skip to content

feat(logprob): add deterministic ROCm vocab-parallel path - #328

Open
hihaluemen wants to merge 58 commits into
RL-Align:testfrom
hihaluemen:work/ws2-logprob-rocm
Open

feat(logprob): add deterministic ROCm vocab-parallel path#328
hihaluemen wants to merge 58 commits into
RL-Align:testfrom
hihaluemen:work/ws2-logprob-rocm

Conversation

@hihaluemen

@hihaluemen hihaluemen commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Add deterministic ROCm backend for WS2 vocab-parallel logprob path (#241)

This adds a ROCm HIP path for the WS2 vocab-parallel logprob implementation. The native part is deliberately small: it only handles the rank-local vocab-tile statistics. The existing TP merge order and logprob/entropy contract are left alone, which keeps the training and inference paths on the same definition.

Changes

  • Add deterministic_logp_tile_stats for FP32 max/sumexp partials. It uses a fixed block reduction and no atomicAdd, and the same source is used for CUDA and HIP builds.
  • Keep the existing WS2 all-gather and global tile-order merge. CP still sits outside the numerical merge axis.
  • Leave selected-token logprob, entropy, masking, and backward on the shared Python path. There is no need to duplicate that logic in the ROCm kernel.
  • Add the rocm-vocab-parallel-logp-ws2 backend. It is registered only when the ROCm extension and symbol load successfully. An explicit request for the native backend fails immediately if the extension is missing; it will not silently turn into the PyTorch reference path.
  • Gate CUDA-only IPC/PTX registrations so they are not pulled into a ROCm build.

Benchmark results

Operator-only benchmark — seeded logits, no checkpoint, tokenizer, or serving engine.
Qwen3 vocabulary V=151936 split into 64 tiles of 2374 columns. Tables show 2048 tokens, BF16.

Platform Hardware Runtime
mi300x AMD Instinct MI300X ×8 torch 2.12.0+rocm7.14.0a20260608, HIP 7.14.60850
h100 NVIDIA H100 80GB HBM3 ×8 torch 2.13.0+cu130, CUDA 13.0
cpu x86_64, 192 cores torch 2.13.0+cu130

Single device (BF16, 2048 tokens)

Platform Backend Fwd median (ms) Fwd p95 (ms) Fwd peak MiB Fwd+bwd median (ms) Fwd+bwd peak MiB Speedup vs native (fwd / fwd+bwd)
mi300x native 6.1387 6.3072 1245.0 12.3477 7715.7 1.00× / 1.00×
mi300x triton 0.9514 0.9801 2.0 2.1619 1187.1 6.45× / 5.71×
mi300x hip 0.8923 0.9162 2.0 2.0359 1187.1 6.88× / 6.07×
h100 native 7.0518 7.0676 1246.0 16.6661 7722.7 1.00× / 1.00×
h100 triton 0.7990 0.8314 2.0 2.0525 1189.1 8.83× / 8.12×
h100 cuda 0.7388 0.7532 2.0 1.9692 1188.1 9.55× / 8.46×
cpu native 421.5963 430.5902 1277.0 1442.7101 7714.4 1.00× / 1.00×

Distributed (BF16, 2048 tokens, forward / forward+backward median ms)

Topology mi300x native mi300x triton mi300x hip h100 native h100 triton h100 cuda
tp2 3.5877 / 7.1482 0.8894 / 1.7667 0.8487 / 1.5938 3.9802 / 9.2129 0.9375 / 1.8345 0.8378 / 1.6801
tp4 2.2691 / 4.4688 0.9221 / 1.7425 0.8714 / 1.5001 2.5194 / 6.2461 0.9734 / 2.2329 0.8695 / 1.8626
tp8 1.8066 / 3.3579 1.1149 / 1.8465 1.0631 / 1.6417 1.8771 / 3.9526 1.0773 / 1.8571 0.9952 / 1.6378
tp2_cp2 3.3882 / 5.6092 0.8308 / 1.4580 0.7809 / 1.3925 3.5072 / 6.5482 0.8173 / 1.5814 0.7557 / 1.4018
tp4_cp2 2.2382 / 3.7864 0.8831 / 1.6803 0.8386 / 1.4029 2.3596 / 4.4815 0.8932 / 1.6965 0.8499 / 1.4813
tp2_cp4 3.5261 / 4.9700 0.8240 / 1.5611 0.7547 / 1.3959 3.5499 / 5.6437 0.8201 / 1.6327 0.7561 / 1.4039

Single-GPU latency

Single-GPU peak memory

Distributed latency

Full report ·
Raw JSON

ryankert01 and others added 30 commits August 2, 2026 22:51
Implements PR 1 of issue RL-Align#241: a typed contract for vocab-parallel
selected-token logprob, mirroring the WS2 attention contract pattern.

- rl_engine/kernels/logprob_contract.py: LogprobContract, ShardingSpec
  (per-rank vocab shard bounds, padded-vs-real vocab, TP/CP rank
  metadata, owner_rank resolution), MaskSpec (active-token mask,
  ignore_index), ReductionSpec (fp32 (max, sumexp) merge in fixed
  global vocab-shard index order, all-gather transport, CP declared a
  non-merge axis), and LogprobBackendCapability.
- KernelRegistry.get_logprob_op(contract): contract-aware dispatch that
  only selects backends with a declared capability; incompatible or
  undeclared candidates are rejected with explicit reasons and never
  used as a silent fallback. Existing WS1 batch-invariant logp backends
  are declared truthfully as single-shard references, so strict WS2
  requests fail loudly until the deterministic vocab-parallel TP
  reference (PR 3) lands. Legacy get_op() behavior is unchanged.
- Design doc, runtime-dispatch and operator doc updates, and CPU-safe
  contract/dispatch tests covering the Qwen3-8B TP=2 BF16 target and
  the TP=1/2/4 sweep shapes. Tolerance values remain owned by RL-Align#108.
- docs: correct the TP-invariance claim — fixed merge order gives
  determinism per TP degree; cross-degree bitwise equality additionally
  requires a TP-degree-independent local tile decomposition (PR 3
  obligation), otherwise RL-Align#108 tolerances apply
- contract: store backend_id stripped so id-based dispatch matches;
  summarize the active mask in to_dict() provenance instead of copying
  every per-token boolean; sort __all__ per RUF022
- registry: add public register_logprob_backend() seam for PR 3 and
  tests; delegate _platform() to _platform_for_device(None); reuse
  _get_or_create_backend() in get_op so WS2 and legacy dispatch share
  one cache/blacklist code path
- tests: use the registration seam instead of poking private state,
  pin _even_bounds' last bound for non-divisible vocabularies, assert
  candidate-list decoupling in both directions, cover registration
  replace semantics and backend_id normalization
- docs: state that cross-TP bitwise equality needs a global tile-level
  merge structure independent of TP partitioning (per-shard tiles alone
  leave different grouping at shard boundaries), and that padded columns
  are masked to -inf before the local (max, sumexp) partials
- registry: scope logprob capabilities per platform so the same backend
  enum can declare different support on cuda/rocm/cpu; validate the
  platform argument of register_logprob_backend against known platforms
- contract: derive IMPLEMENTATION_KINDS from RESERVED_DISPATCH_POLICIES
  and use it for the kind check; wrap non-iterable roles/dtypes in
  LogprobContractError for consistent error handling
- tests: cover per-platform capability scoping, unknown-platform
  rejection, and non-iterable roles/dtypes
…typed contract

Address external review: the cross-TP bitwise guarantee lived only in
prose, so a fixed-topology-deterministic backend could pass dispatch as
fully conformant.

- DeterminismScope (fixed_topology | cross_tp_bitwise): requested via
  ReductionSpec (default cross_tp_bitwise, the RL-Align#241 PR 3 target),
  declared per backend via determinism_scopes, enforced by dispatch;
  replaces the deterministic_tp_merge bool
- MaskMode (explicit_active_mask | ignore_index) replaces
  supports_inactive_tokens: the contract permits inactive targets that
  do not hold ignore_index, so ignore-index-only backends are rejected
  for contracts with inactive tokens
- LogprobOutputSpec pins the output surface: fp32 selected logprob and
  fp32 vocab LSE, replicated across the TP group
- implementation_kind is now a tier (reference | production);
  determinism is no longer conflated with it, and requesting
  "deterministic" as a policy raises a loud error pointing at
  determinism_scope
- fallback provenance: policy evaluation now precedes capability
  checks, so a candidate excluded by the caller's own policy never
  counts as a fallback even when it also lacks capabilities
- docs: define the (-inf, 0) identity partial for padding-only or
  all--inf shards; document that requested_backend="auto" is not
  distributed-safe and specify the preflight fingerprint agreement
- LogprobContract.cross_rank_fingerprint(): rank-independent identity
  for that preflight; provenance now records active_mask_sha256 so
  masks with equal active counts remain distinguishable
Fold the normative reduction semantics (padded-column masking, fp32
(max, sumexp) merge formulas, the (-inf, 0) identity partial, and the
cross-TP tile-structure requirement) into the ReductionSpec and
DeterminismScope docstrings, and repoint the runtime-dispatch and
batch-invariant-logp doc references at the module. The contract summary
moves to the PR description.
Shrink class docstrings toward the attention-contract one-liner style and
cut design-rationale comments; the normative reduction semantics stay in
the ReductionSpec and DeterminismScope docstrings.
The guard added per review rejects requested_backend="auto" whenever
tp_world_size > 1, so TP-sharded dispatch tests now name an explicit
policy and auto-policy tests use TP=1 contracts.  Add coverage for the
guard itself and document the restriction in get_logprob_op.

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

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 87ac9fd9-99af-44d6-ac91-d292539a65ca

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@hihaluemen
hihaluemen changed the base branch from main to test August 21, 2026 21:00
@hihaluemen
hihaluemen force-pushed the work/ws2-logprob-rocm branch 2 times, most recently from 1a2eb62 to df1e637 Compare August 21, 2026 21:08
@hihaluemen
hihaluemen force-pushed the work/ws2-logprob-rocm branch from df1e637 to f79602c Compare August 21, 2026 21:11
zhangj1an added a commit that referenced this pull request Aug 24, 2026
…patch

Adds the dispatch seam and Vime adapter that make the strict ROCm attention
core reachable as an explicit, fail-closed backend, mirroring the WS2
logprob provider in #328.

Registry:
- register_attention_backend / get_attention_op over AttentionContract, with
  a candidate list kept separate from the legacy attn/attention priority maps
  and empty on every platform by default. A WS2 caller can therefore never be
  served by an SDPA-shaped wrapper that does not export attention-domain LSE,
  and the strict core can never be selected by a legacy get_op caller.
- aiter.rocm.ck_dense_mha is registered only when aiter.ops.mha genuinely
  imports and exposes mha_fwd/mha_bwd. An explicit request for it fails
  loudly when the vendor stack is absent instead of degrading to a different
  backend.

Vime adapter (rl_engine/integrations/vime/attention.py):
- Structural request in, (out, lse) plus provenance out; RL-Kernel never
  imports Vime, and native fallback is signalled through an
  attention_provider_unavailable marker rather than an imported type.
- Each logical batch row is materialized on its own. Raw AITER mha_fwd is
  batch-composition sensitive in BF16 at some shapes (S=256 B=4, S=512
  B=2/B=4; up to 1.5625e-02) while invariant at others, so batching would
  otherwise change the bits for a subset of shapes only.
- Position identity is derived and validated, so a training-side full
  sequence and a rollout-side chunk provably describe the same tokens.
- CP>1 and decode fail closed rather than being served by a core that does
  not own the cross-rank merge or KV-cache identity.

Contract:
- AttentionContract.cross_rank_fingerprint() for cross-rank preflight,
  mirroring LogprobContract; auto dispatch stays rejected under CP>1.

Benchmarks (MI300X, gfx942, torch 2.12.0+rocm7.14, BF16, Qwen3-8B heads):
forward is within 4% of SDPA and ~1.9x faster than the Triton FlashAttention
backend at S=4096. The deterministic backward costs ~2.4x time and scales
O(S^2) in memory (16.8 GiB at S=4096); that cost is AITER's mha_bwd, not the
integration - raw AITER peaks at 16657 MiB against the provider's 16722 MiB.

Tests: 38 new cases; the full suite is unchanged against the pre-change
baseline (27 failed / 224 skipped / 607 errors both before and after, all
from the unbuilt native extension), with passed rising 1068 -> 1102.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QfN3b2ep5DvxVDir7J36j3
@Flink-ddd Flink-ddd added the platform: rocm Specific tasks specific to AMD graphics cards (such as CK, bpreshuffle/FA) label Aug 27, 2026
zhangj1an added a commit that referenced this pull request Aug 29, 2026
Applies the PR #325 / #328 measurement matrix and presentation to the strict ROCm
Attention path: their timing and accuracy helpers, their spawned distributed world,
and their figure style, so the three reports read side by side. Operator-only, no
checkpoint; Qwen3-8B shapes (Hq=32, Hkv=8, D=128).

Headline: the Triton core is bit-identical to _C.deterministic_attention_* on all
eight (dtype, sequence) cases -- out, lse, dQ, dK and dV, zero mismatched elements.

TP-degree invariance reproduces PR #319 on independent inputs and shows why the
per-KV-group launch schedule is load-bearing. Raw AITER is non-invariant at 5 of 12
(S, TP) points, up to 7.8e-03 out max-abs, and which points fail is shape-dependent:
S=512 and S=4096/TP=2 both look clean. The per-KV-group schedule is bitwise at 12/12.

Distributed CP runs the real AG/RS schedule -- all-gather Q/K/V and position ids,
strict core on the full sequence, reduce-scatter (out, lse) -- and is bitwise against
CP=1 on all six topologies including the 8-rank TP=2/CP=2 x2-replica case.

Three things the numbers say that were not obvious:
- the strict production core is faster than SDPA at S=4096 forward (0.81x), so the
  bitwise arrangements cost almost nothing on the production path;
- AITER's backward peaks at 16.6 GiB at S=4096, 4x the materializing reference core;
- both deterministic cores are closer to an FP64 oracle than SDPA or AITER.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01824WkgMDxtBKD4Ex2NkfYA
zhangj1an added a commit that referenced this pull request Aug 29, 2026
…e script

Three fixes plus the CPU numbers.

The per-KV-group schedule cost was measured by a throwaway script and merged into
results.json by hand, so the full MI300X re-run silently dropped it. It is now
_tp_schedule_cost() inside the benchmark and part of the normal flow. Re-measured:
3.60-7.95x the single-launch forward (previously reported 4.11-7.31x; same
conclusion, run-to-run variance).

_environment() reported device facts regardless of device, so the host column
claimed gpu_count=8, hip=7.14 and an RCCL collective for a run that never touched a
GPU. It now zeroes those on a host run.

Host results, S<=2048, BF16: sdpa 31.5/98.3/304.6 ms and pytorch-native
15.4/51.6/216.2 ms forward at S=512/1024/2048. Only those two paths exist on the
host -- strict-aiter is ROCm-only and the reference and Triton cores need a GPU --
which is the same shape as PR #328's CPU column. S=4096 was dropped after a first
attempt was killed at 25 minutes; the host column is absolute-latency context, not
a headline.

MI300X was re-run so every platform has pytorch-native, the common path, and so the
reference core carries its platform-neutral name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01824WkgMDxtBKD4Ex2NkfYA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

platform: rocm Specific tasks specific to AMD graphics cards (such as CK, bpreshuffle/FA)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants