Skip to content

[Bugfix][ROCm] Fix startup OOM in AITER MLA FP8 prefill workspace sizing - #57923

Merged
dllehr-amd merged 2 commits into
vllm-project:mainfrom
simondanielsson:fix/aiter-asm-prefill-sizing
Sep 23, 2026
Merged

dllehr-amd merged 2 commits into
vllm-project:mainfrom
simondanielsson:fix/aiter-asm-prefill-sizing

Conversation

@simondanielsson

@simondanielsson simondanielsson commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Fixes a startup OOM on Kimi K2.5 (or similar) when using the AITER ASM MLA prefill kernel, caused by overly pessimistic bounds on the size of the required persistent metadata buffers. These sizes were subsequently passed to the workspace manager which triggered the OOM.

Triggered by: The AITER bump to v0.1.22post1 picked up ROCm/aiter#3606 which slightly changed the logic for workspace sizing.

Long term fix in AITER: ROCm/aiter#5759

Why

For causal FP8 MLA the AITER backend uses a persistent kernel. It needs buffers to hold the work items to be completed by each work group. The metadata scheduler is responsible for sizing these buffers so that they are sufficiently large to handle any requests that might come in.

The MLA metadata scheduler now emits a very loose bound. The bound is based on the assumption that every request can use max_qlen query tokens. (The actual upper bound is that the sum of query tokens equals max_num_batched_tokens). If one uses these to size the logits/attn_lse partial buffers we get huge KV reservations:

max_num_batched_tokens before aiter#3606 after aiter#3606 (OOM) this PR
8192 1.88 GiB 64.03 GiB 2.56 GiB
16384 3.75 GiB 128.03 GiB 2.62 GiB

Short term fix: Size with assumption that the total query tokens is bounded by the token budget.

Long term fix: AITER should give us this budget-aware max buffer size. Ticket: ROCm/aiter#5729

References:

Test Result

Run DSv3 with FP8 KV on MI350 with max num batched tokens 16k and model len 160k.

vllm serve deepseek-ai/DeepSeek-V3-0324 \
    --tensor-parallel-size 8 \
    --enable-expert-parallel \
    --kv-cache-dtype fp8 \
    --gpu-memory-utilization 0.9 \
    --max-model-len 160k 

Before:

(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/worker/gpu_worker.py", line 757, in initialize_from_config
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]     self.model_runner.initialize_kv_cache(
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/worker/gpu/model_runner.py", line 742, in initialize_kv_cache
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]     kv_caches_dict = init_kv_cache(
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]                      ^^^^^^^^^^^^^^
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/worker/gpu/attn_utils.py", line 354, in init_kv_cache
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]     kv_caches = allocate_kv_cache(
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]                 ^^^^^^^^^^^^^^^^^^
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]   File "/usr/local/lib/python3.12/dist-packages/vllm/v1/worker/utils.py", line 421, in allocate_kv_cache
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]     buf = torch.zeros(buf_size, dtype=torch.int8, device=device)
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053]           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
(Worker_TP0_EP0 pid=937) ERROR 09-21 08:55:12 [multiproc_executor.py:1053] torch.OutOfMemoryError: CUDA out of memory. Tried to allocate 158.02 GiB. GPU 0 has a total capacity of 287.98 GiB of which 67.28 GiB is free. Of the allocated memory 209.87 GiB is allocated by PyTorch, with 19.96 MiB allocated in private pools (e.g., CUDA Graphs), and 640.25 MiB is reserved by PyTorch but unallocated. If reserved but unallocated memory is large try setting PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True to avoid fragmentation.  See documentation for Memory Management  (https://docs.pytorch.org/docs/stable/notes/cuda.html#optimizing-memory-usage-with-pytorch-cuda-alloc-conf)

After:

(APIServer pid=7) INFO 09-21 09:06:10 [launcher.py:69] Route: /is_scaling_elastic_ep, Methods: POST
(APIServer pid=7) INFO:     Started server process [7]
(APIServer pid=7) INFO:     Waiting for application startup.
(APIServer pid=7) INFO:     Application startup complete.
...
(APIServer pid=7) INFO:     127.0.0.1:37336 - "POST /v1/chat/completions HTTP/1.1" 200 OK
$ rocm-smi


============================================= ROCm System Management Interface =============================================
======================================================= Concise Info =======================================================
Device  Node  IDs              Temp        Power     Partitions          SCLK     MCLK     Fan  Perf  PwrCap   VRAM%  GPU%
              (DID,     GUID)  (Junction)  (Socket)  (Mem, Compute, ID)
============================================================================================================================
0       3     0x75a0,   16604  50.0°C      287.0W    NPS1, SPX, 0        2200Mhz  1900Mhz  0%   auto  1000.0W  90%    0%
1       5     0x75a0,   36049  46.0°C      284.0W    NPS1, SPX, 0        2198Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
2       4     0x75a0,   26868  46.0°C      274.0W    NPS1, SPX, 0        2208Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
3       2     0x75a0,   42233  50.0°C      287.0W    NPS1, SPX, 0        2200Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
4       7     0x75a0,   57533  51.0°C      285.0W    NPS1, SPX, 0        2207Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
5       9     0x75a0,   11440  48.0°C      290.0W    NPS1, SPX, 0        2208Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
6       8     0x75a0,   51349  47.0°C      282.0W    NPS1, SPX, 0        2204Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
7       6     0x75a0,   37977  50.0°C      282.0W    NPS1, SPX, 0        2203Mhz  1900Mhz  0%   auto  1000.0W  91%    0%
============================================================================================================================
=================================================== End of ROCm SMI Log ====================================================

Also long context bench serve runs without crashing

 vllm bench serve \
  --backend vllm --model deepseek-ai/DeepSeek-V3-0324  \
  --dataset_name random --random_input_len 60000 --random_output_len 128 \
  --max_concurrency 12 --num_prompts 30 --num_warmups 16 \
  --host localhost --port 8000 --ignore_eos --seed 5655

GSM8k DSv3 fp8 kv

This branch

Tasks Version Filter n-shot Metric Value Stderr
gsm8k 3 flexible-extract 5 exact_match ↑ 0.9454 ± 0.0063
strict-match 5 exact_match ↑ 0.9447 ± 0.0063

Main branch before bumping to aiter v0.1.22post1

Tasks Version Filter n-shot Metric Value Stderr
gsm8k 3 flexible-extract 5 exact_match ↑ 0.9484 ± 0.0061
strict-match 5 exact_match ↑ 0.9492 ± 0.0060

Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

Signed-off-by: simondanielsson <simon.danielsson99@hotmail.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.

@github-actions

Copy link
Copy Markdown

✅ @simondanielsson, CI is now available for this PR.

  • /ci run starts upstream CI; /amd-ci run starts AMD CI only.
  • Your branch must contain every commit currently on its upstream target branch. Merge or rebase onto the latest target branch, then rerun the command. Append --allow-stale to a run command to test an outdated branch at your own risk.
  • /ci retry retries failed jobs in the CI build for the current PR head. If the current head has no CI build, it starts a new CI build for the current head containing only jobs that failed in the latest earlier CI build for this PR.
  • /amd-ci retry retries failed jobs in AMD CI for the current PR head. Use /amd-ci run when the current head has no AMD CI build.
  • /ci cancel cancels scheduled or running CI builds for this PR branch; /amd-ci cancel does the same for AMD CI only.

@okorzh-amd

Copy link
Copy Markdown
Contributor

/ci run

@github-actions

Copy link
Copy Markdown

❌ @okorzh-amd, Only reviewers with write access can use CI commands before CI is delegated to the PR author.

@simondanielsson

Copy link
Copy Markdown
Contributor Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #90611 for commit db2a03f02ce8.

@dllehr-amd
dllehr-amd self-requested a review September 23, 2026 14:51

@dllehr-amd dllehr-amd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good. Thanks @simondanielsson

@dllehr-amd

Copy link
Copy Markdown
Contributor

@simondanielsson on the Aiter side, can we add a benchmark to check the workspace sizes for future cases where we may explode the allocations. Thanks @jamesETsmith for the recommendation.

@simondanielsson

Copy link
Copy Markdown
Contributor Author

@simondanielsson on the Aiter side, can we add a benchmark to check the workspace sizes for future cases where we may explode the allocations. Thanks @jamesETsmith for the recommendation.

Added regression test in the aiter PR: ROCm/aiter@d22abd7:

@dllehr-amd
dllehr-amd merged commit b5ea0c7 into vllm-project:main Sep 23, 2026
170 of 171 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in AMD Sep 23, 2026
@simondanielsson
simondanielsson deleted the fix/aiter-asm-prefill-sizing branch September 23, 2026 16:45
fululi12 added a commit to fululi12/vllm that referenced this pull request Sep 28, 2026
…unks

`_expand_page_indices_kernel` is launched with a grid of one program per
request, and each program walks that request's tokens in a serial loop.
The parallelism is therefore entirely over the batch, which degenerates
at low concurrency: with num_reqs == 1 a single workgroup expands the
whole sequence while the rest of the GPU idles.

On a long-context decode workload (MI355X, TP8, ~97k-token context,
concurrency 1) this measured 61.6 us per call and 7 calls per model step,
0.431 ms or 1.23% of a 34.9 ms step. For scale, 97k int32 stores is about
388 KB -- well under a microsecond of HBM bandwidth. The kernel is not
bandwidth-bound, it is serialised.

Add a second grid dimension over token chunks and drop the loop, so each
program handles exactly one BLOCK_SIZE chunk. The arithmetic, the masks
and the stores are unchanged; only the iteration space is mapped onto the
GPU rather than onto one workgroup. The chunk count is derived host-side
from the block table width, an upper bound on tokens per request, so no
device synchronisation is introduced; over-launched programs mask out
entirely.

This affects any MLA model on ROCm at low concurrency, not one model in
particular.

Test plan
---------
Bit-exactness against the existing serial kernel, both built from source
and compared on device over 8 shapes:

  conc=1 long context, kbs=1    97000 indices identical
  conc=1 long context, kbs=16   97000 indices identical
  exact multiple of 1024         4096 indices identical
  1024+1                         1025 indices identical
  1024-1                         1023 indices identical
  ragged 4 reqs                 38011 indices identical
  kbs=16 ragged tail             4212 indices identical
  single token                      1 indices identical

End-to-end, same workload, 1200 s per arm against a shared baseline:
interactivity p90 110.28 -> 112.06 (+1.61%), tpot p90 0.00907 -> 0.00892.
Accuracy is unaffected by construction -- the page indices are bit-
identical, so the model reads exactly the same KV -- and this was
confirmed by GSM8k over the full 1319 questions remaining at 0.9659.

AI assistance
-------------
AI assistance was used (Claude). The measurements above come from runs on
real hardware, not estimates. The submitter has reviewed every changed
line.

Not a duplicate: no open PR touches `expand_page_indices`; the nearby
ROCm MLA PRs (vllm-project#57085, vllm-project#53492, vllm-project#57923, vllm-project#46172, vllm-project#54899) address DCP, the
sparse indexer, prefill workspace sizing and head padding respectively.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: lifulu <fululi12@amd.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working ready ONLY add when PR is ready to merge/full CI is needed rocm Related to AMD ROCm

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants