[AMD] Optimize gpt-oss-120B performance#27063
Conversation
The AITER MXFP4 fused-MoE kernels (FlyDSL `gate_mode="separated"` and CK
`preshuffle_on`) expect the standard `shuffle_weight((16,16))` tile layout
on top of the *separated* (gate-then-up) weight half ordering, with
`w.is_shuffled=True` so AITER picks the preshuffle_on codegen. The previous
path used `shuffle_weight_a16w4` (gate/up-interleaved layout) and never set
`is_shuffled`, so AITER fell back to `preshuffle_off` kernels that
interpreted shuffled bytes as un-shuffled and produced silently wrong
output (gsm8k flexible-extract dropped from 0.88 to ~0.21).
Changes:
- `Mxfp4MoEMethod.process_weights_after_loading` (`_use_aiter` branch):
replace `shuffle_weight_a16w4` / `shuffle_scale_a16w4` with
`shuffle_weight(..., is_guinterleave=False, gate_up=...)` /
`shuffle_scale(..., is_guinterleave=False, gate_up=...)` to match the
layout used by `gptoss_fp4_tuned_fmoe.csv` flydsl entries. Set
`is_shuffled=True` on both `w13_weight` and `w2_weight`.
- `Mxfp4MoEMethod.apply` (`_use_aiter` branch): re-tag `is_shuffled=True`
on the `.view(torch.float4_e2m1fn_x2)` tensors (view() drops the marker).
Forward `swiglu_limit` to AITER via the quant_info so OAI swiglu is
applied correctly.
- `AiterRunnerCore.run`: gate the `GateMode.INTERLEAVE` selection behind
`SGLANG_USE_AITER_MOE_GU_ITLV` and default to `GateMode.SEPARATED`
(matches the new weight layout and the gptoss_fp4 tuned config).
- `environ.py`: register `SGLANG_USE_AITER_MOE_GU_ITLV` (EnvBool, default
False) in the AMD & ROCm section.
Verified with `lm_eval gsm8k --num_fewshot 3` on gpt-oss-120b @ MI355X:
flexible-extract 0.8810 (ATOM baseline 0.8825), strict-match 0.3541
… MI35x test Previous commit defaulted the AITER MoE gate/up tile layout to SEPARATED because that is what `Mxfp4MoEMethod` and the gptoss_fp4 tuned FlyDSL kernels need. However other AITER MXFP4 paths still rely on the INTERLEAVE layout that the pre-fix code produced, so flipping the global default would silently regress them. Restore INTERLEAVE as the default and have the gpt-oss path explicitly opt out via `SGLANG_USE_AITER_MOE_GU_ITLV=0`: - `environ.py`: change `SGLANG_USE_AITER_MOE_GU_ITLV` default from False to True; update the docstring to describe the new semantics. - `aiter.py`: update the inline comment near the gate_mode selection to describe the new default and the opt-out semantics; logic unchanged. - `test/registered/amd/accuracy/mi35x/test_gpt_oss_eval_mi35x.py`: add `SGLANG_USE_AITER_MOE_GU_ITLV=0` to the env_vars of both MXFP4 ModelConfigs (`openai/gpt-oss-20b`, `openai/gpt-oss-120b`) so the AITER MXFP4 + gpt-oss accuracy path keeps using the SEPARATED layout that matches the new Mxfp4MoEMethod shuffle. Other ROCm gpt-oss tests (`mi30x` BF16, NVIDIA CUDA paths) do not touch AITER MXFP4 and are left unchanged.
The MXFP4 MoE method pads hidden_size up to a multiple of 256 (2880 -> 3072 for gpt-oss-120b) via torch.nn.functional.pad inside Mxfp4MoEMethod.apply, which fires a separate pad kernel per layer per forward pass on top of the post-attention RMSNorm + residual add. Fold the pad into the preceding RMSNorm by: * RMSNorm gains an optional `x_pad_to_multiple` ctor arg that wires the aiter Triton `fused_add_rmsnorm_pad` kernel in `forward_aiter`. The fused kernel produces a (M, padded_N) output and an (M, N) residual_out in one launch. * GptOssDecoderLayer constructs `post_attention_layernorm` with the MoE-input alignment (256 for MXFP4) only when env `SGLANG_FUSE_RMSNORM_PAD=1`, the aiter backend is active, the quant method is MXFP4, and TP=1 (the only attn-scatter mode where the pre-padded layernorm output flows through `_simple` without touching AllReduce/scatter helpers that haven't been taught the padded width). * `Mxfp4MoEMethod.apply` skips the explicit pad when the input already arrives at `self.hidden_size`. * `GptOssSparseMoeBlock.forward_normal` slices the (pre-padded) input back to the unpadded width for the router/topk GEMMs, and trims the experts output to the unpadded width before postprocess_layer (which pairs hidden_states with the unpadded residual). Gated behind `SGLANG_FUSE_RMSNORM_PAD` (default off) so other models / TP>1 / non-aiter setups keep the existing code path bit-for-bit. Numerical: per-call RMSNorm output matches the baseline bit-exact at decode batch sizes and within one bf16 ULP at 16k-token prefill (reduction-order difference between the two Triton kernels). End-to-end GSM8K 3-shot on gpt-oss-120b is within ±1 sigma of baseline: flexible-extract baseline 0.8855 ± 0.0088 fused 0.8863 ± 0.0087 strict-match baseline 0.3654 ± 0.0133 fused 0.3525 ± 0.0132 Co-authored-by: Cursor <[email protected]>
End-to-end integration of Atom's pa_decode_gluon decode kernel into SGLang via a SHUFFLE 5D-vectorized KV cache layout, opt-in behind the new env var SGLANG_KV_CACHE_LAYOUT=vectorized_5d. Pool layout (memory_pool.py, swa_memory_pool.py): K: (num_blocks, H_kv, D/X, page, X) V: (num_blocks, H_kv, page/X, D, X) X = 16 / dtype_bytes (=8 bf16) Both the full-attn and SWA sub-pools follow the env-driven layout; aiter mha_batch_prefill_func and pa_decode_gluon consume this shape natively. New writer (layers/attention/utils.py reshape_and_cache_shuffle_5d + launch_reshape_and_cache_shuffle_5d): scatters per-token (num_tokens, H_kv, D) inputs into the 5D pool slots. aiter_backend wiring: - save_kv_cache routes through token_to_kv_pool.set_kv_buffer when the pool is 5D so the SHUFFLE writer is invoked (SWAKVPool dispatches per layer to the right inner pool with the proper full-to-swa loc translation). - mha_batch_prefill_func gains an extend_no_prefix shortcut: with --disable-radix-cache (no prefix reuse) we feed the kernel the raw incoming K/V in 3D linear mode, bypassing the 5D pool on the prefill read path. - forward_decode adds a 5D branch that calls pa_decode_gluon with Atom ps=True / get_recommended_splits convention. Full layers pass forward_metadata.kv_indices + seq_lens; SWA layers pass swa_page_table + seq_lens + sliding_window=sliding_window_size (ctx_part=128, max_part_num=1, mirroring Atom). models/utils.py: disable enable_fused_set_kv_buffer when the pool is 5D. The fused QKV+RoPE+set_kv writer .view()s the pool as 4D NHD, which silently passes numel checks against a 5D buffer but corrupts the data. GSM8K (lm_eval, 3-shot, chat template, gpt-oss-120b on MI355x TP=1): Plan A v2: flexible-extract 0.8908 / strict-match 0.3381 Baseline NHD: flexible-extract 0.8855 / strict-match 0.3647 Perf (8K/1K, c=128, 256 prompts): Plan A v2: median TPOT 6.39 ms, output throughput 2210 tok/s Baseline NHD: median TPOT 7.06 ms, output throughput 2006 tok/s ~10% TPOT improvement; pa_decode_gluon kernel itself is ~46% faster than unified_attention on this workload. Co-authored-by: Cursor <[email protected]>
Profile of Plan A v2 decode (8K/1K c=128) showed ~38ms of elementwise
overhead per 5 decode steps coming from two per-layer copies:
1. MHATokenToKVPool.set_kv_buffer in vectorized_5d mode called
.contiguous() on cache_k / cache_v. The writer kernel uses
key.stride(0) directly for the source token stride and assumes only
that head/dim are contiguous within each token (stride(1)=head_size,
stride(2)=1). Both hold for K/V from a QKV split + RoPE in the
upstream attention path even when the outer per-token stride is
non-canonical, so the protective copies were no-ops semantically
while still launching a large elementwise kernel per layer.
2. forward_decode in the vectorized_5d branch allocated a private
o_out = empty_like(q_in) for pa_decode_gluon and then did
o.copy_(o_out.view_as(o)) per layer. Replaced by a zero-copy
o.view(-1, num_q_heads, v_head_dim) passed directly as the kernel
output.
Profile diff (8K/1K c=128, 5 decode steps):
vectorized_elementwise: 25341 us -> 542 us (-98%)
manual_unroll: 13068 us -> 868 us (-93%)
Benchmark (8K/1K c=128, 256 prompts):
v2: median TPOT 6.39 ms, 2210 tok/s
v3 (this): median TPOT 6.14 ms, 2300 tok/s
NHD baseline: median TPOT 7.06 ms, 2007 tok/s
-> v3 is 13% faster TPOT and 14.6% higher output throughput than
baseline NHD.
GSM8K (lm_eval, 3-shot, chat template) holds:
v3: flexible 0.8878 / strict 0.3525
v2: flexible 0.8908 / strict 0.3381
NHD baseline: flexible 0.8855 / strict 0.3647
Co-authored-by: Cursor <[email protected]>
… pool
The HIP fused rotary-embedding + KV-cache-write kernel
(fused_qk_rope_reshape_and_cache) already supports the SHUFFLE 5D cache
layout natively:
flash_layout=True: key_cache shape (T_cache, page, KH, D) [NHD 4D]
flash_layout=False: key_cache shape (T_cache, KH, D//x, page, x) [5D]
value_cache supports the matching transposed
5D shape (T_cache, KH, page//x, D, x).
We had disabled enable_fused_set_kv_buffer for 5D pools because the
HIP-path arg builder reshaped k_buffer / v_buffer with the 4D NHD
.view(), which silently passes numel checks against a 5D buffer but
corrupts the data. With this commit:
* enable_fused_set_kv_buffer no longer special-cases 5D — both HIP and
CUDA paths can use the fused kernel for any pool layout.
* create_fused_set_kv_buffer_arg detects k_buffer.ndim == 5 and passes
the raw 5D buffer (no view) for the SHUFFLE layout; otherwise it
keeps the legacy NHD 4D paged view.
* forward_cuda in rotary_embedding/base.py detects key_cache.ndim==5
and flips flash_layout accordingly.
Effect: per layer we now run ONE fused kernel instead of separate
rotary_embedding_kernel + reshape_and_cache_shuffle_5d launches.
Benchmark (8K/1K c=128, 256 prompts):
v3 (no fused): median TPOT 6.139 ms, 2300 tok/s
v4 (fused): median TPOT 6.058 ms, 2307 tok/s
NHD baseline: median TPOT 7.055 ms, 2007 tok/s
-> v4 is 14.1% faster TPOT and 15% higher output throughput than NHD.
GSM8K (lm_eval, 3-shot, chat template):
v4: flexible 0.8840 / strict 0.3541
v3: flexible 0.8878 / strict 0.3525
NHD baseline: flexible 0.8855 / strict 0.3647
Co-authored-by: Cursor <[email protected]>
Two fixes to unblock fp8_e4m3 KV cache on the 5D vectorized layout, plus a small UX default. 1) pa_decode_gluon was missing key_scale / value_scale on fp8 KV. The kernel reads fp8 bytes and, without per-tensor dequant scales, feeds the raw fp8 values straight into the dot product. The write side (fused_qk_rope_reshape_and_cache with apply_scale=True) was already correct, so this is purely a decode-side regression. Fix: when self.kv_cache_dtype == fp8_dtype, pass layer.k_scale / layer.v_scale (falling back to the backend's per-tensor scales) into pa_decode_gluon. Shape [1] per-tensor scales are supported natively. 2) MHATokenToKVPool 5D path asserted store_dtype == dtype, which blocked fp8 storage with bf16 compute. The X vectorization width is a property of the on-pool *storage* dtype (16 / store_dtype.itemsize), so compute the width from store_dtype and drop the assert. For bf16 storage X stays 8, for fp8 storage X becomes 16. 3) server_args _handle_page_size: default page_size to 64 when SGLANG_KV_CACHE_LAYOUT=vectorized_5d (the layout the kernel is tuned for), so users don't have to pass --page-size 64 explicitly. GSM8K (lm_eval, 3-shot, chat template, gpt-oss-120b, 1xMI300): NHD + fp8 : flexible 0.8893 / strict 0.3381 (control) Plan A 5D + fp8 : flexible 0.8802 / strict 0.3306 (after fix) Plan A 5D + fp8 : ~0.01 / ~0.00 (before fix) Co-authored-by: Cursor <[email protected]>
The pre-Plan-A note said SWA layers stayed on the NHD unified_attention path because the SWA sub-pool was forced to NHD. This hasn't been true since SWAKVPool started honoring SGLANG_KV_CACHE_LAYOUT for both sub-pools — SWA layers now go through pa_decode_gluon with swa_page_table + sliding_window=layer.sliding_window_size, same as full-attn but with a non-zero sliding_window arg. Update the comment so it reflects what the code actually does. Co-authored-by: Cursor <[email protected]>
forward_extend on the SHUFFLE 5D KV pool was crashing whenever a batch
arrived with non-zero extend_prefix_lens (chunked-prefill 2nd+ segment,
prefix-cache hit, multi-turn, …). The fallback aiter
mha_batch_prefill_func paged path it dropped into doesn't ship a
compiled kernel for (page_size=64, bf16/fp8, paged 5D) and aborts with
"no matching kernel found", which surfaces as a HSA_STATUS_ERROR_EXCEPTION
hardware exception. The repro is just:
SGLANG_KV_CACHE_LAYOUT=vectorized_5d SGLANG_USE_AITER_MOE_GU_ITLV=False \
python3 -m sglang.launch_server \
--model-path /path/to/gpt-oss-120b/ --tp 1 --trust-remote-code \
--mem-fraction-static 0.85 \
--prefill-attention-backend aiter --decode-attention-backend aiter \
--page-size 64 --disable-radix-cache --port 8000
lm_eval --model local-chat-completions ... --tasks gsm8k --num_fewshot 3
(--disable-radix-cache is irrelevant — the scheduler still produces
non-first chunks during chunked prefill.)
Fix: for the 5D pool route the non-no-prefix case through a new
gather-and-linearize path that mirrors the SHUFFLE writer in reverse:
* Add gather_shuffle_5d_to_linear / launch_gather_shuffle_5d_to_linear
in layers/attention/utils.py. Same source addressing as the writer
so it is bit-exact symmetric. Output is (T, num_heads, head_size)
in the cache's store_dtype (uint8 bytes for fp8 pools, bf16 for
bf16 pools).
* In aiter_backend.forward_extend, when self.kv_cache_is_vectorized_5d
take the per-token slot ids straight from
forward_metadata.kv_indices[:seq_lens_sum] (or .swa_page_table for
SWA layers — both are already per-token slot lists populated by
create_flashinfer_kv_indices_triton, no page-table indirection),
triton-gather K/V from the right sub-pool, then run
mha_batch_prefill_func in 3D LINEAR mode (page_size=1) which does
have a working kernel.
* For fp8 store layers we reinterpret the gathered uint8 tensor as
fp8 (zero-copy view), cast q to fp8, and pass q/k/v_descale to
aiter — its LINEAR mha_batch_prefill_func supports fp8 K/V/Q
natively so no host-side dequant is needed.
GSM8K (lm_eval, 3-shot, chat template, gpt-oss-120b, TP=1, MI300, the
exact repro command above, but with --kv-cache-dtype auto vs fp8_e4m3):
bf16 KV: flexible 0.8863 / strict 0.3442
fp8 KV: flexible 0.8848 / strict 0.3374
Matches the pre-A4 single-chunk baseline (0.8802 / 0.3306) within
seed noise, and the server no longer crashes on the 2nd chunk.
Co-authored-by: Cursor <[email protected]>
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
|
@amd-bot ci-status |
|
I have enough to compose the report. Summary of findings:
CI Status for PR #27063PR: [AMD] Optimize gpt-oss-120B performance Warning The PR's core optimizations are NOT exercised by any CI test — a green AMD run does not verify them. All new fast paths are gated behind env vars that default OFF, and the PR adds zero tests:
Grep of AMD: 0 failures | Others: 6 distinct failures (0 related) + 18 cascade/aggregator jobs AMD CI (run 27051282095) ran the full stage-a/b/c MI325 + MI35x matrix — all completed jobs passed; 2 jobs still Other CI Failures
Aggregator jobs that merely report children's status (not independent failures): DetailsDo you need to fix something? For the visible failures — no. None of the 6 distinct failures are on a code path this PR changes:
But the bigger risk is what CI did not test. This PR's entire value (SHUFFLE 5D KV layout,
|
|
@amd-bot ci-status |
|
I have enough to write the report. Key findings: AMD CI ran, but both AMD failures are in code paths untouched by the PR, and the PR's actual target (gpt-oss-120B + the new SHUFFLE-5D/fused-rmsnorm-pad paths) is gated behind default-off env vars and nightly-only AMD tests — so PR CI does not verify the optimization. CI Status for PR #27063PR: [AMD] Optimize gpt-oss-120B performance Caution The PR's actual optimization is untested on PR CI. All new behavior is gated behind default-OFF env vars ( AMD: 2 failures (0 likely related) | Others: 2 root-cause failures + cascade (0 related) AMD CI Failures
Other CI Failures
The remaining ~24 failing jobs ( Details
Generated by amd-bot using Claude Code CLI |
HaiShaw
left a comment
There was a problem hiding this comment.
Can we apply _use_aiter more consistently cross code changes - producer, consumers, utilities?
|
@amd-bot ci-status |
CI Status for PR #27063Merge verdict: No failing job is caused by this PR — every red X is an infra cascade, hang, or unrelated-backend flake. But do not read green-or-unrelated-red as "verified": the PR's entire value (the 5D KV layout + Caution This PR's new code is not exercised by any PR-CI test. All new behavior is gated behind Changed files: AMD: 2 failures (0 related) · Others (NVIDIA): 19 failures (0 related, 1 root cause + 18 cascade) · NPU: 3 failures (0 related) AMD CI Failures
Other CI Failures (NVIDIA)
NPU CI Failures
Details / what to do before mergeThe coverage gap is the only real action item — none of the failures are.
Generated by amd-bot using Claude Code CLI |
| # is then trimmed back to the unpadded width so postprocess_layer | ||
| # can pair it with the (M, hidden_dim_unpadded) residual. | ||
| num_tokens = hidden_states.shape[0] | ||
| hidden_dim_unpadded = self.experts.hidden_size |
There was a problem hiding this comment.
This contradicts the comment: hidden_dim_unpadded should be the model hidden size (config.hidden_size). This regresses GPT-OSS with FlashInfer MXFP4 on SM10X because self.experts.hidden_size is padded to 3072, while config.hidden_size is 2880.
|
@kkHuang-amd @HaiShaw this broke GPT-OSS serving on NVIDIA SM10X with FlashInfer MXFP4. Instead of reverting, this should be a quick fix; I have it here, please check: #27528 |
PR sgl-project#27063's hidden_dim_unpadded = self.experts.hidden_size assumption breaks on non-AMD MXFP4 paths. FusedMoE.__init__ at fused_moe_triton/ layer.py:241-247 rounds hidden_size up to a multiple of 256 when quant_config.get_name() == "mxfp4" and the runner backend is flashinfer_mxfp4. For gpt-oss-120b (hidden=2880) on a CUDA flashinfer MXFP4 path, self.experts.hidden_size becomes 3072 even though the upstream layernorm does *not* prepad — _resolve_moe_input_pad_multiple returns 0 unless AMD + AITER + MXFP4 + TP=1. That mismatch flipped is_prepadded = True incorrectly: the slice [..., :3072] was a no-op on the 2880-wide input, the experts produced (M, 2880) output, and the final ans.view(num_tokens, 3072) blew up with "shape '[4096, 3072]' is invalid for input of size 11796480". Restoring the pre-PR-sgl-project#27063 form: trust hidden_states.shape, route it through router/experts unchanged, view back at the same width. Loses the small router-input slicing optimization that PR sgl-project#27063 wanted on the AMD MXFP4 prepad path, but FusedMoE.forward already handles prepadded inputs correctly internally on that path. The _resolve_moe_input_pad_multiple helper is kept — it still gates the layernorm-side prepad at line 548. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
I also encountered this error in CI of #27449 |
|
hi any plans to make gpt-oss-20b faster on nvidia rtx 6000 pro? |
Motivation
Integrate Atom's
pa_decode_gluonpaged-attention decode kernel intoSGLang's AITER backend to accelerate gpt-oss-120b decode on ROCm
(MI300 / MI355). The kernel requires a SHUFFLE 5D vectorized KV
cache layout that aiter's
mha_batch_prefill_funcalso consumesnatively, so we add the layout as a new opt-in pool format
(
SGLANG_AITER_KV_CACHE_LAYOUT=vectorized_5d) and route the AITER backendthrough it on both prefill and decode.
End result on gpt-oss-120b @ MI300, TP=1, page=64, c=128:
on 8K/1K decode; pa_decode_gluon itself is ~46 % faster than the
legacy
unified_attentiondecode kernel.baseline for both bf16 and fp8 KV cache.
The layout is fully opt-in — when
SGLANG_AITER_KV_CACHE_LAYOUTis unset(default
"nhd") every code path is bit-for-bit identical to currentmain.Modifications
New env var
SGLANG_AITER_KV_CACHE_LAYOUT(environ.py) selects the physical KV poollayout.
"nhd"(default) keeps today's behaviour;"vectorized_5d"enables the SHUFFLE 5D layout.
Memory pool (
mem_cache/)MHATokenToKVPoolallocates the 5D layout when the flag is set:(num_blocks, H_kv, head_dim/X, page_size, X)(num_blocks, H_kv, page_size/X, head_dim, X),where
X = 16 / store_dtype.itemsize(8 for bf16, 16 for fp8).SWAKVPoolpropagates the layout to both full-attn and SWA sub-poolsso SWA decode can also go through
pa_decode_gluon.set_kv_bufferalready does per-tensor fp8 quant on the host beforethe writer kernel, so the kernel just byte-copies into the cache —
no scale plumbing needed on the writer side.
Writer kernel (
layers/attention/utils.py)reshape_and_cache_shuffle_5d/launch_reshape_and_cache_shuffle_5d:Triton scatter from per-token
(num_tokens, num_heads, head_size)K/V into the 5D SHUFFLE layout. Used by
MHATokenToKVPool.set_kv_bufferwhen the fused QKV+RoPE+set_kv writer is unavailable.
Reader kernel (
layers/attention/utils.py)gather_shuffle_5d_to_linear/launch_gather_shuffle_5d_to_linear:bit-exact triton inverse of the writer. Materialises per-token
(T, num_heads, head_size)K/V from a list of pool slot ids. Usedby the chunked-prefill gather-and-linearize fallback (see below).
AITER backend wiring (
layers/attention/aiter_backend.py+ newlayers/attention/aiter_utils.py)Two 5D-specific entry points live in
aiter_utils.pyand are calledfrom
forward_extend/forward_decodeonly when the active pool is5D, keeping the backend file focused on dispatch and the legacy NHD
path:
forward_extend_vectorized_5dextend_prefix_lens == 0(first prefill chunk, no prefix reuse)skip pool reads entirely and feed the raw
(k, v)tomha_batch_prefill_funcin 3D LINEAR mode (page_size=1) on bf16.K/V from the SHUFFLE 5D pool via
launch_gather_shuffle_5d_to_linearinto a contiguous
(T, H, D)buffer instore_dtype, then runthe same LINEAR prefill. fp8-store layers forward the raw fp8
tensors with per-tensor descales — aiter's LINEAR-mode prefill
supports fp8 K/V/Q natively, no host-side dequant needed.
mha_batch_prefill_funcabortswith
"no matching kernel found"for our(page_size=64, bf16/fp8, SHUFFLE 5D)configuration whenever thescheduler produces a non-first chunked-prefill batch.
forward_decode_vectorized_5dpa_decode_gluon(SWA sub-pool is also allocated 5D). Full layers pass
kv_indices+sliding_window=0; SWA layers passswa_page_table+sliding_window=layer.sliding_window_size.fp8 KV cache forwards
layer.k_scale/layer.v_scaleaskey_scale/value_scaleso the kernel dequantises correctly.Fused RoPE + KV write (
layers/rotary_embedding/base.py,models/utils.py)fused_qk_rope_reshape_and_cachekernel alreadysupports the 5D SHUFFLE layout (
flash_layout=False). Re-enableenable_fused_set_kv_bufferfor the 5D pool and have the argbuilder pass the raw 5D buffer when
k_buffer.ndim == 5. Thiscollapses RoPE + KV write into a single launch per layer per step.
UX
server_args._handle_page_sizedefaultspage_size=64whenSGLANG_AITER_KV_CACHE_LAYOUT=vectorized_5d, since that is the page sizethe 5D kernels are tuned for.
Accuracy Tests
All numbers below are GSM8K (3-shot, chat template) on gpt-oss-120b @
MI300, TP=1, run with the following commands:
Specific path coverage:
the bugfix commit): bf16 → 0.8863 / 0.3442, fp8 → 0.8848 / 0.3374.
writer): 0.8848 / 0.3480 — confirms
set_kv_buffer's host-sideper-tensor quant feeds the writer correctly.
Speed Tests and Profiling
InferenceX-style sweep on 8K/1K decode (256 prompts, MI300, TP=1,
--page-size 64 --kv-cache-dtype auto --disable-radix-cache):For full-context fp8 + 8K c=64 we re-verified single-run noise: the
SHUFFLE 5D +
pa_decode_gluonpath matches ATOM throughput within±6 % across the sweep and beats it by 18-26 % at low concurrency.
pa_decode_gluonitself is ~46 % faster thanunified_attentiononthe same
(bs, num_kv_heads, head_dim, ctx_len)for our gpt-ossshapes.
Known issues / how to use
--max-running-requests 256(or omit; auto-tuned default isChecklist
the accuracy and Benchmark the speed.
CI States
Latest PR Test (Base): ⏳ Run #27113289949
Latest PR Test (Extra): ❌ Run #27113289843