Skip to content

[ROCm] Fix packed-bag pooling truncation in nbit inference forward - #6259

Open
aryaman-gupta wants to merge 5 commits into
pytorch:mainfrom
ROCm:aryaman/fix-packed-bag-pooling
Open

[ROCm] Fix packed-bag pooling truncation in nbit inference forward#6259
aryaman-gupta wants to merge 5 commits into
pytorch:mainfrom
ROCm:aryaman/fix-packed-bag-pooling

Conversation

@aryaman-gupta

@aryaman-gupta aryaman-gupta commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Problem

On ROCm, TBE_ROCM_INFERENCE_PACKED_BAGS packs several bags into a single wave. When those bags have different pooling lengths, the longer one is silently truncated to its partner's length and the embedding output is wrong.

It only surfaces when a bag's length exceeds InputRowsInFlight, and it disappears entirely when packed bags happen to share a length — which is why it went unnoticed.

Cause

The accumulate/store stages map lanes to bags differently from the load stage (uint vs uint4 granularity). The kernel translated Ls[] between the two by shuffling it in place inside the L_start loop. That corrupts the load stage, which keeps reading Ls[] on later passes, and also corrupts the shuffle source lanes themselves — so from the second pass onwards every lane broadcasts an already-permuted value.

Fix

  • Translate once into a separate Ls_acc[] before the loop, leaving Ls[] intact for the load stage. Being loop-invariant, this also removes shuffles from every pass.
  • Make max_Ls wave-uniform under PackedMode. The loop is wave-collective (syncwarp, shfl_sync), so a per-lane bound let short-bag lanes exit while the remaining lanes still shuffled against them.

Both changes are required — either one alone still fails.

Testing (MI350X / gfx950, ROCm 7.1)

  • nbit_forward_test.py: 2 failed → 12 passed, 8 subtests passed
  • Also verified across a range of bag-length patterns and embedding dimensions
  • Performance neutral: packed and non-packed configs all within run-to-run noise

CUDA and the nobag path are unaffected — PackedMode is ROCm-and-pooled-only.

Re-enabled test

Removes the ROCm skip on the int4_small_weighted and int4_small_unweighted subcases of test_nbit_forward_nan_zero_fill. Those were disabled in da743f0 to unblock the MI350 CI migration, and are the two failures this fix addresses — the corrupted Ls[] breaks the input_row_idx >= Ls[i] predicate that drives the zero-fill, so NaNs survive into the output.

aryaman-gupta and others added 4 commits September 2, 2026 11:42
In PackedMode the accumulate and store stages map lanes to bags at uint
granularity while the load stage uses uint4 granularity, so the two need
different entries of Ls[].  The kernel translated between them by shuffling
Ls[] in place, inside the L_start loop.  That corrupts the load stage, which
keeps reading Ls[] on later passes, and corrupts the shuffle source lanes
themselves, so from the second pass onwards the longer bag of a packed pair is
truncated to its partner's pooling length.

Compute the accumulate-stage lengths once into a separate Ls_acc[], leaving
Ls[] intact for the load stage.  The mapping is loop-invariant, so this also
removes OutputRowsPerThread shuffles from every pass of the loop.

Also make max_Ls wave-uniform under PackedMode.  The L_start loop is
wave-collective, so a per-lane bound lets the short-bag lanes exit while the
remaining lanes still shuffle against them.  Per-row validity checks against
Ls[]/Ls_acc[] still bound each lane's own work, and a divergent wave already
executes the union of all lanes' iterations, so this adds none.

Fixes the INT4 D=160 subtests of test_nbit_forward_nan_zero_fill.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
max_Ls is wave-uniform under PackedMode, so
min(InputRowsInFlight, max_Ls - L_start) already evaluates identically on every
lane and the shuffle that followed it is the identity.

nbit_forward_test.py is unchanged at 12 passed / 8 subtests passed, and the
divergent-Ls probes stay clean. Mean of 3 runs on MI350X, INT4 pooled forward:

  D=160  packed, ragged    65.40 -> 64.97 us
  D=240  packed, ragged    64.97 -> 64.50 us
  D=160  packed, uniform   45.73 -> 45.83 us
  D=1024 not packed       139.80 -> 139.40 us

The non-packed config cannot be affected by this change yet moves by a similar
amount, so treat ~0.3% as the noise floor and the change as perf-neutral.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…gths

test_nbit_forward_nan_zero_fill only covers bag lengths that happen to agree
between packed neighbours, so it does not catch a kernel that applies one bag's
L to its partner. Add a case with alternating short/long lengths, which is the
shape that exposes it.

Every row is 1.0 and nothing is pruned, so each bag must sum to its own L.
Verified red/green: passes on the fixed kernel, and fails without the
wave-uniform max_Ls (max abs diff 60.0, 272/1280 elements).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Replace the hand-rolled butterfly with warp_reduce_max() from
utils/find_qparams.cuh, which is the same shfl_xor reduction over kWarpSize and
is already in scope via embedding_forward_template_helpers.cuh.

Guard test_nbit_forward_packed_bags_uneven_pooling with skipIfNotRocm: bag
packing is a ROCm-only path, so elsewhere the test would pass without
exercising what its name describes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
These were skipped on ROCm in da743f0 while the CI migration was in flight.
This change fixes the underlying failure, so the skip comes out with it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@pytorch-bot

pytorch-bot Bot commented Sep 2, 2026

Copy link
Copy Markdown

Workflows were awaiting approval. CI has now been triggered for the ciflow labels on this PR.

@meta-codesync

meta-codesync Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

@q10 has imported this pull request. If you are a Meta employee, you can view this in D118491445.

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.

1 participant