Skip to content

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

Open
aryaman-gupta wants to merge 4 commits into
aryaman/upstreamfrom
aryaman/fix-packed-bag-pooling
Open

[ROCm] Fix packed-bag pooling truncation in nbit inference forward#166
aryaman-gupta wants to merge 4 commits into
aryaman/upstreamfrom
aryaman/fix-packed-bag-pooling

Conversation

@aryaman-gupta

@aryaman-gupta aryaman-gupta commented Aug 19, 2026

Copy link
Copy Markdown

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.

aryaman-gupta and others added 2 commits August 19, 2026 09:04
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>
@aryaman-gupta aryaman-gupta changed the title Aryaman/fix packed bag pooling [ROCm] Fix packed-bag pooling truncation in nbit inference forward Aug 19, 2026
…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>

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

Strange thing, but I recall addressing this issue a while ago. Anyway, nice catch and great changes

self._execute_nan_zero_fill(weights_ty, D, output_dtype, weighted)

@unittest.skipIf(*gpu_unavailable)
def test_nbit_forward_packed_bags_uneven_pooling(self) -> None:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be guarded with @skipIfNotRocm?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I previously thought the test could be useful on CUDA also, but you're right that it doesn't make sense to have a test with packed_bags in the name running on CUDA. Done in 7e12fd6

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