Repository navigation
Conversation
added 3 commits
October 25, 2026 15:32
The scratch holds every in-launch hand-off (mailboxes, ready flags, the published rows). It is now a one-rank PeerBuffer, the uncached allocation the symmetric peer buffer already uses, so a consumer's load is served at the line's home memory instead of through the producer XCD's L2. close() frees it.
Every vector memory instruction takes its turn in the CU's in-order queue, so loads issued ahead of a poll delay it whatever their size: - a thread's contiguous bf16 norm weights in one load (the gates, the router's own row, the q_a norm, the tail); - a gate's mix partials two pairs a load into LDS, summed there in the same order.
The index selection drained its tokens and set the row's tok_ready flag, and a split task polled the flag, then read its 64 tokens: two round trips in series. The selected and tail tokens are now tagged pairs, with no drain and no flag, and a split's wave 0 polls its 64 tokens together with the cache's kv_ready before its acquire. check_layer.py --check-keys reads tok_sel as pairs and checks that every selected and tail token of a scored row carries the launch's tag.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
After #23, a decode step of GLM-5.3-Flash runs 46 launches. Inside a launch, much of the time goes to hand-offs between CTAs: a consumer polls a mailbox, and the poll waits behind whatever its CU has already queued. This PR makes the hand-offs cheaper in three ways. It doesn't change any value: every output and every write is bitwise equal to
mainat TP2, TP4 and TP8, at every width.Result on MI355X, 2048 in / 256 out, median TPOT,
main→ this PR (two rounds each, same allocation):time_layers.py --kind model, µs a launch): 50.1 → 48.0 / 61.7 → 59.1 / 88.7 → 87.3 at TP4, 2 / 4 / 8 rows; 69.8 → 67.1 / 93.1 → 90.7 at TP2; 43.8 → 41.0 / 55.0 → 51.8 / 72.6 → 69.2 at TP8.Merge order: independent of sgl-project#61 (the first launch's wait deadline), which changes only
spec.py. A second perf PR (scalar-polled boxes, plain rows, o_proj first at 2 rows) is stacked on this one.Modifications
Three changes, one commit each, each with its own measured gain (Speed Tests, "Each change on its own").
1. The launch scratch is uncached (
runner.py)The scratch holds every in-launch hand-off: mailboxes, ready flags, published rows, scores.
FlashRuntimenow allocates it as a one-rankcommon.peer.PeerBuffer, the uncached allocation (hipExtMallocWithFlags(..., hipDeviceMallocUncached), 2 MiB granules) that the symmetric peer buffer already uses.common/doesn't change, andclose()frees the buffer.check.pyand the kernel tests readrt.scratchthrough torch, as before.2. Fewer vector loads ahead of the polls (
kernel.py)Every vector memory instruction holds the CU's in-order queue for about the same time, so loads issued ahead of a poll delay it, whatever their size. Two places now issue fewer:
ld_bf16s), in the gates, the router's own row, the q_a norm and the tail.xs + PM_WORDS).3. The selected tokens are tagged pairs (
kernel.py,layout.py)The index selection used to drain its tokens and then set the row's
tok_readyflag; a split polled the flag and then read its 64 tokens: two round trips in series. Now:SEL_STRIDE + TOPK_TOKENS), as tagged pairs (put), with no drain and no flag;kv_ready, then acquires, as before;tok_seldoubles in size andtok_readygoes.Test change, needed by the new layout:
check_layer.py --check-keysreadstok_selas pairs and also checks that every selected and tail token of a scored row carries the launch's tag (the onekv_readycarries). Negative control: with the tail tokens stored untagged, the check fails on every scored row and rank; it passes on this head.Every build keeps
main's number of guarded wait sites (the split polls its tokens where it polled the flag), soSGLANG_AMD_MONO_FAULT_INJECT's site numbers don't move.Accuracy Tests
main's, byte for byte, at TP2, TP4 and TP8 and every width each TP builds. Kinds: FFN-only, KDA, sparse attention (scored rows past 2,048 tokens with tails, and short rows), dense, embed and tail; FP8 preshuffled and row-major; 3 launches a step (inputs chained), 3 steps, the last row a pad row from 4 rows on. That is 36 / 108 / 216 files and 996 / 2,988 / 5,976 tensors (3.4 / 11.8 / 20.8 GB) at TP2 / TP4 / TP8, compared by SHA-256. So [Fix] [MI355X] glm53: Load a NextN launch's pointer arguments from the kernarg segment at each stage (preview) #23's accuracy results hold.SGLANG_AMD_MONO_CHECK=1(the e2e check case's requests): 874 checks, 0 violations at TP2, TP4 and TP8, onmainand on this head. The values aren't equal across runs, because SGLang's reference forward isn't run-to-run reproducible: three runs ofmainat TP4 differ from each other in 31 to 34 of the 43 reported metrics (min / max over the checks), and this head differs frommainin 34. The launches themselves are bitwise equal (above).test_glm53_flash_server.pypass at TP4 on this head: GSM8K past the largest width, decode against prefill (KL), the check case, the returned hidden states and the injected fault (withHSA_DISABLE_COREDUMP_ON_EXCEPTION=1from [Test] glm53/glm53_flash: Turn off crash dumps in the fault tests sgl-project/sgl-amd-mono#55).Speed Tests and Profiling
Setup as #22 and #23: one node with 8 × MI355X,
rocm/sgl-dev:v0.5.21-rocm10-mi35x-20261014(sha256:e4498f5d595f, FlyDSL 0.3.4.1), SGLangab580b2e06,amd/GLM-5.3-Flash-Quark-MXFP4atb5688f25, the e2e test's server flags,sglang.benchmark.servingrandom with fixed lengths, max(16, 4c) prompts after c warm-up requests, two rounds with the second in reverse order. The tables give the mean of the rounds' median TPOT in ms (ITL in parentheses); the rounds agree within 0.5%.All numbers are from this head (9bad2e8) against
main(9484c60).TP4, 2048 / 256:
mainTP4, 8192 / 256:
mainTP2, 2048 / 256 (4-row cap: 8 and 16 requests run SGLang's path):
mainTP8, 2048 / 256:
mainKernel-only (
time_layers.py)torchrun --nproc-per-node <TP> test/kernels/glm53_flash/time_layers.py --kind <kind> --rows 2,4,8(--pos 640and--pos 8191for sparse attention), µs a launch,main→ this PR; the mean of two passes in alternating order in one allocation (they agree within 1%).modelis #22's 41 launches with sparse-attention rows at position 2,047,full#23's whole step of 46.TP4:
TP2:
TP8:
Each change on its own
Each commit against the one before it, in the same allocations as above. Kernel-only
--kind model, µs a launch; the token change also at--kind dsa --pos 8191, because the model order's sparse-attention rows sit at 2,047 and aren't scored. Serving: TP4, median TPOT at 1 / 2 / 4 / 8 requests, two rounds.Registers
A compile-only build of every key the spec and the tests build (TP2, TP4 and TP8; FFN-only, KDA, sparse attention, dense, embed, tail; every width; FP8 preshuffled, and row-major for sparse attention, dense and embed) gives 72 distinct symbols, as on
main.mainit uses all 256 VGPRs and spills none.mainalike when built withstop=True, which neither the spec nor the tests use.Tests
mainon SGLangab580b2e06(257 passed; the deepseek_v41 and minimax_m31 contract cases and the two minimax spec modules fail there, as onmain).test_glm53_flash_kernels.pywith 8 GPUs (TP2, TP4 and TP8): 8 passed (17 min 54 s).check_layer.py --check-keyswith the tag check passes at TP4; its negative control fails (above).Checklist
pre-commit run --all-filespasses.test/cover the change..github/CODEOWNERSthat names its owner. (No new model.)