Skip to content

[Perf] [AMD] mla: Use the gfx950 aiter RoPE, RMSNorm and MLA KV-row paths on gfx1250 - #48154

Open
jiaryang wants to merge 5 commits into
sgl-project:mainfrom
jiaryang:amd/gfx1250-prefill-aiter-rope-rmsnorm-kv
Open

jiaryang wants to merge 5 commits into
sgl-project:mainfrom
jiaryang:amd/gfx1250-prefill-aiter-rope-rmsnorm-kv

Conversation

@jiaryang

@jiaryang jiaryang commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

On gfx1250, MLA prefill runs three small ops on different paths from gfx950: DeepseekScalingRotaryEmbedding runs forward_native (about nine torch kernels), RMSNorm uses a gfx1250-only Triton branch, and the MLA KV rows go through index_put. aiter's cached-positions RoPE kernel and rmsnorm2d_fwd build and run on gfx1250 with the current gfx1250 aiter pin; the RMSNorm row-tail fix for N % 8 != 0 is ROCm/aiter#6348, which the pin includes. This PR extends the gfx950 paths for these three ops to gfx1250.

Modifications

  • DeepseekScalingRotaryEmbedding: on gfx1250 with SGLANG_USE_AITER=1, call aiter.rope_cached_positions_2c_fwd_inplace in place. The module stays sglang's because ROCm MLA decode paths read cos_sin_cache, which aiter's rope modules do not have. The kernel reads cos/sin without strides, so it gets contiguous query-dtype copies of the module's own cos_sin_cache, built on first use and rebuilt when the cache or the query dtype changes. forward_native is kept for calls with offsets and when rl_on_policy_target forces native rope. A key passed as its own query (the IndexerKPool K-only path) is rotated once, since the kernel loads both inputs before storing.
  • RMSNorm: drop the gfx1250 Triton branch and use aiter rmsnorm2d_fwd / rmsnorm2d_fwd_with_add, as on other ROCm targets. This applies to every model on gfx1250 with SGLANG_USE_AITER=1, in prefill and decode.
  • MLA KV rows: use store_mla / read_mla on gfx1250, as on gfx950.
  • Update the rope factory and DeepSeek utils comments that said aiter rope does not build on gfx1250.
  • Add a gfx1250-only test in test/registered/rotary/test_rope_rocm.py that compares the aiter RoPE path with forward_native (neox and GPT-J styles, strided q_pe/k_pe views shaped like the MLA callers, in-place output, cache growth, dtype change, a key passed as its own query, and the forward_native fallbacks).

gfx950 is unchanged. On gfx1250, RoPE now runs in place with query-dtype cos/sin, the same as the aiter rope module on gfx950. MLA decode RoPE on gfx1250 also moves to this kernel, except where #44459 fuses it into the decode kernel; the measurements below were taken with #44458 and #44459 applied, so this PR should land after #44459.

Validated on gfx1250 with Kimi-K2-Thinking-MXFP4 at TP8.

Accuracy Tests

GSM8K, 8-shot, 1319 questions, 3 runs:

Runs Mean
Before 0.944 / 0.939 / 0.938 0.940
After 0.945 / 0.935 / 0.939 0.940

Speed Tests and Profiling

bench_serving, 8k input / 1k output, FP8 KV cache, aiter decode backend, page size 64.

Concurrency Throughput per GPU (tok/s/GPU) Interactivity (tok/s/user) Median TTFT (ms) Median TPOT (ms)
4 Before 442.3 104.7 262.3 9.55
4 After 443.2 104.7 238.8 9.55
4 Change +0.2% 0.0% -9.0% 0.0%
16 Before 1017.8 59.6 270.6 16.78
16 After 1038.1 60.7 248.0 16.49
16 Change +2.0% +1.8% -8.4% -1.7%
64 Before 1520.0 21.8 430.3 45.97
64 After 1568.2 22.4 411.0 44.59
64 Change +3.2% +3.1% -4.5% -3.0%

In the prefill profile (8192-token chunks, GPU kernel time excluding all-reduce, mean over 8 ranks), a chunk goes from 175.0 ms to 153.6 ms at concurrency 4. Elementwise kernels go from 25.2 to 10.0 ms and RMSNorm from 19.4 to 15.6 ms; the new RoPE kernel takes 1.0 ms. Decode kernel time is unchanged.

Checklist

Review and Merge Process

  1. Ping Merge Oncalls to start the process. See the PR Merge Process.
  2. Get approvals from CODEOWNERS and other reviewers.
  3. Trigger CI tests with comments or contact authorized users to do so.
    • Common commands include /tag-and-rerun-ci, /tag-run-ci-label, /rerun-failed-ci
  4. After green CI and required approvals, ask Merge Oncalls or people with Write permission to merge the PR.

CI States

Latest PR Test (Base): 🚫 Run #41756161586
Latest PR Test (Extra): ❌ Run #41756161550
Latest PR Test (AMD ROCm 10): ✅ Run #41756161600

Tip

Base CI only covers the core cases, and its workload may not exercise your change. Use /rerun-test to run the tests your change touches, or copy this prompt for an agent:

Find the registered tests this PR affects and run them with /rerun-test, following https://github.com/sgl-project/sglang/blob/main/.agents/skills/rerun-test/SKILL.md

jiaryang and others added 4 commits November 2, 2026 17:57
On gfx1250 the MLA prefill took a different path from gfx950 for three
small ops:

- DeepseekScalingRotaryEmbedding ran forward_native (about nine torch
  kernels). It now calls aiter's cached-positions rope kernel in place on
  contiguous cos/sin copies of the module's own cos_sin_cache, rebuilt
  when the cache grows. The module stays sglang's because ROCm MLA decode
  paths read cos_sin_cache, which aiter's rope modules do not have.
- RMSNorm used the triton kernel. It now uses aiter rmsnorm2d_fwd and
  rmsnorm2d_fwd_with_add, as on other ROCm targets.
- MLA KV rows went through index_put. They now use store_mla/read_mla,
  as on gfx950.

Also drops the stale "aiter rope does not build on gfx1250" comments and
adds a gfx1250-only test comparing the aiter rope path with forward_native.

Kimi-K2 TP8 on gfx1250, 8k/1k (measured on an earlier base): prefill
kernel time per 8192-token step drops from 361.9 to 312.2 ms. At
concurrency 64 median TTFT goes from 1417 to 1316 ms (ABBA, two runs
each) with TPOT unchanged; at concurrency 4 TTFT does not move because
prefill there is bound by CPU kernel launches. GSM8K stays within noise
(0.926-0.938).

Co-authored-by: Cursor <cursoragent@cursor.com>
The in-place aiter rope kernel would rotate a key passed as its own query
twice (IndexerKPool's K-only path), so such calls keep forward_native, as
calls with offsets already do. RL on-policy targets force native rope in
the base class; the gfx1250 override no longer bypasses that.

Rename _aiter_rope_unsupported_arch to _keeps_sglang_rope_modules, since
aiter's rope kernel is now used on gfx1250, and add tests for the dtype
rebuild and the forward_native fallbacks.
The cached-positions kernel loads both inputs before storing, so a key
passed as its own query (IndexerKPool's K-only path) is rotated once.
Drop the forward_native fallback for that case and test the aliased
call through the kernel instead.
@github-actions github-actions Bot added the amd label Nov 3, 2026
@jiaryang jiaryang changed the title [Perf] [AMD] Use the gfx950 aiter RoPE, RMSNorm and MLA KV-row paths on gfx1250 [Perf] [AMD] mla: Use the gfx950 aiter RoPE, RMSNorm and MLA KV-row paths on gfx1250 Nov 3, 2026
@jiaryang
jiaryang marked this pull request as ready for review November 3, 2026 12:05
@jiaryang
jiaryang requested a review from Fridge003 as a code owner November 3, 2026 12:06

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

@yctseng0211 yctseng0211 added the run-ci CI: run the baseline test suite on this PR label Nov 4, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

amd run-ci CI: run the baseline test suite on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants