Skip to content

[Bugfix] Promote MRV2 staged-write row offsets to int64 to avoid int32 overflow - #59167

Closed
vllmellm wants to merge 1 commit into
vllm-project:mainfrom
EmbeddedLLM:issue-57030-int64-row-offsets
Closed

vllmellm wants to merge 1 commit into
vllm-project:mainfrom
EmbeddedLLM:issue-57030-int64-row-offsets

Conversation

@vllmellm

Copy link
Copy Markdown
Contributor

What this PR does

Closes #57030.

Casts the row index to tl.int64 at load in the three Model Runner V2 kernels whose stride products can leave int32:

File Site Overflow product
vllm/v1/worker/gpu/buffer_utils.py _apply_write_kernel (row_idx * row_stride + start_idx) row_idx * max_model_len
vllm/v1/worker/gpu/sample/penalties.py _bincount_kernel (req_state_idx * all_token_ids_stride) req_state_idx * max_model_len
vllm/v1/worker/gpu/sample/penalties.py _penalties_kernel (same load, same mapping, cast for symmetry) req_state_idx * strides

row_idx comes from an int32 staging buffer and row_stride is a host scalar (gpu.stride(0)) that Triton specializes to int32, so the multiply runs in int32. Same for the penalties path, where PenaltiesState.apply_staged_writes builds idx_mapping with an explicit dtype=torch.int32 (everywhere else the mapping is np.intp/int64 and Triton's usual i32 * i64 -> i64 promotion hides the problem).

Once row_idx * stride >= 2**31 the offset wraps → out-of-bounds store/load (GPU memory access fault) or, when the re-wrapped offset lands back inside the allocation, silent corruption on the wrong row. With RequestState.all_token_ids = (max_num_reqs, max_model_len) int32 and MRV2's free_indices.pop() assigning the first request the highest row, any engine at 1M context with max_num_reqs >= 2149 dies on request #1 — and the penalties variant additionally trips during MRV2's kernel warmup (sampler warmup stages penalty requests bottom-up from max_num_reqs - 1). This is not ROCm-specific: any platform running MRV2 with large max_model_len * max_num_reqs is exposed; the issue is only labelled ROCm because that's where it was reported.

The pattern matches the existing house fix in _zero_kv_blocks_kernel (vllm/v1/worker/utils.py), which already does .to(tl.int64). Cost: one widened multiply per program.

Verification (8x MI355X, gfx950, ROCm 7.2, torch 2.12.0+git6bbd260, main @ 6cbbea8721)

Repo is pristine on this branch except for this PR (git status clean before/after).

Repro (issue's own cases) main (unpatched) with this PR
Issue's exact repro: StagedWriteTensor((65536, 40000), int32), write row 65535 Memory access fault by GPU node-2 ... Reason: Unknown (SIGABRT + GPU coredump) — byte-identical to the reporter's crash passes, [1,2,3,4] at row 65535
Realistic all_token_ids-shaped (4096, 1_000_000), row 3000 fault passes
Control: same tensor, row 2000 (2.0e9 < 2**31) passes passes
Silent variant: (5000, 1M), row 4295 no fault — tokens appear at row 0 col 32704, target row stays zeros lands at row 4295 as requested
bincount at row 2559 of (2560, 1M) (real penalties.bincount) fault passes

Threshold, for reviewers wondering why the defaults don't trip it: overflow iff row_idx * max_model_len >= 2**31 → row ≥ 2148 at 1M ctx → needs max_num_reqs ≥ 2149 (or ~716 for mrope prefill_positions, num_dims=3). At ≤512k context with default max_num_seqs the product stays below 2**31, which is why the reporter saw "≤512k fine, 1M dies every time".

Tests

New tests/v1/worker/test_gpu_int64_row_offsets.py:

  • test_staged_write_high_row — write through _apply_write_kernel at row 2559 of a (2560, 1M) int32 tensor (10 GiB), assert values land at the target row and not clobber row 0 (the silent-corruption signature).
  • test_bincount_high_row — same-shaped all_token_ids through the real penalties.bincount, assert prompt bits / output counts land on the right row.

Both are gated on current_platform.is_cuda_alike() + ≥11 GiB free device memory (torch.accelerator.get_memory_info), since the tensors must exceed 2³¹ elements by construction — no smaller shape demonstrates the bug.

Switchback evidence on this branch: with the casts reverted, each test independently aborts the process with Fatal Python error: Aborted (GPU memory access fault, exit 134); with the fix, 2 passed. Full-repo sanity: tests/kernels/core/test_uva.py 24 passed, 4 skipped.

Note on #57524

#57524 targets the same bug. This PR is an independent fix preferred by maintainers: it is based on current main (#57524 no longer applies — its test_uva.py hunk anchors at an EOF that has since moved), gates the bincount test on is_cuda_alike() instead of is_cuda() (which is False under RocmPlatform, so that test could never run on the platform named in the issue), and covers _penalties_kernel as well.

…2 overflow

_apply_write_kernel computes row_idx * row_stride + start_idx where row_idx
is loaded from an int32 staging buffer and row_stride is a host scalar
(gpu.stride(0)) that Triton specializes to int32. The multiply therefore runs
in int32 and wraps once row_idx * stride >= 2**31, yielding a negative (or
re-wrapped) offset: an out-of-bounds store, i.e. a GPU memory access fault or
a silent write to the wrong row.

The same class of bug affects the sampler penalties path: PenaltiesState
builds its idx_mapping with an explicit dtype=torch.int32 (everywhere else
the mapping is np.intp/int64 and Triton promotes), so _bincount_kernel and
_penalties_kernel compute req_state_idx * stride in int32.

Practical trigger: RequestState.all_token_ids is (max_num_reqs,
max_model_len) int32, so at max_model_len=1M any row index >= 2148 overflows;
MRV2's free_indices.pop() assigns the first request the highest row, so any
engine with max_num_reqs >= 2149 faults on request #1 (or during warmup, when
sampler warmup exercises the bincount path eagerly). This affects every
platform, not just ROCm.

Promote the row index to int64 at load in _apply_write_kernel,
_penalties_kernel and _bincount_kernel, matching the existing house pattern
in _zero_kv_blocks_kernel. Width-promoted index math costs one widened
multiply per program.

Regression tests allocate a >2^31-element int32 tensor and are skipped unless
the device has enough free memory. Both tests abort the process with a GPU
memory access fault when the casts are reverted.

Closes vllm-project#57030

Signed-off-by: vllmellm <vllm.ellm@embeddedllm.com>

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added mrv2 Model Runner V2 specific bug Something isn't working labels Sep 29, 2026
@vllmellm vllmellm closed this Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working mrv2 Model Runner V2 specific

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MRV2 StagedWriteTensor.apply_write computes row offsets in int32 and walks off the end of the buffer once row_idx * stride(0) > 2**31

1 participant