Repository navigation
Conversation
|
@yichiche Here are the test results. All three runs of each suite pass at The runs use the setup of the PR body: one node with 8× MI355X, TP8, the image python3 -m pytest -v -s -p no:cacheprovider test/unit/test_contract.py test/unit/test_qwen38_a95b_mxfp4_spec.py -k 'qwen38 or common'
python3 -m pytest -v -s -p no:cacheprovider test/kernels/test_qwen38_a95b_mxfp4_kernels.py
python3 -m pytest -v -s -p no:cacheprovider test/e2e/test_qwen38_a95b_mxfp4_server.pyThe kernel run mounts the two kernel folders of sgl-project/sglang#43516 read-only for the port test. The e2e servers set
The three runs have the same tests with the same results. This is the pytest output of run 1: unit: 12 passedkernels: 6 passede2e: 5 passedEach full e2e log is about 2 to 4 MB, because it holds the server output. I can attach the logs if you need them. |
…ne K2 launch A decode step of 1 to 8 rows runs SGLang's input norm and its attention or GDN up to the projection input. Then one K2 launch runs the rest of the layer: - out_proj or o_proj and its TP all-reduce - the post-attention norm and the router - the shared expert and the MXFP4 routed experts, and the MoE all-reduce The spec refuses the fused shared expert. It also refuses the MXFP4 GDN projections, which SGLang turns on by default since sgl-project/sglang#43839. Start the server with SGLANG_ROCM_GDN_ONLINE_MXFP4=0. The kernels are the K2 kernels of sgl-project/sglang#43516 without their fse and oq variants, with guarded waits. Co-authored-by: Cursor <cursoragent@cursor.com>
…s routed alike K2 computes its own router logits. On an exact bf16 tie at the 10th logit, K2 and SGLang's topk can pick different experts. Then the layer output of that row differs by a full expert, and the out cosine of the check can fail. This occurred in 1 of 3 check runs of an earlier build with the fused shared expert. The same tie can occur without it. - Capture the top-k ids that SGLang's MoE block hands its experts. - Read K2's top-k ids from ROUTE. - Check the layer output only on the rows that both sides route alike. Check the residual on all rows. - Log each other row as a route line, with the bf16 logits of the experts that only one side picked. - In the e2e check test, fail if more than 1% of the rows route elsewhere. - Add a unit test for routed_alike. Co-authored-by: Cursor <cursoragent@cursor.com>
A target-verify step of 1 to 8 rows runs the same launches as a decode step: SGLang's attention or GDN, then K2. K2 works on each row alone, so the draft tokens need no new kernel code. The spec accepts speculative decoding. It refuses a draft tree: the tested verify step takes one draft chain (topk 1, draft tokens = steps + 1). Co-authored-by: Cursor <cursoragent@cursor.com>
…as one K1 launch On the 69 GDN layers, a decode step runs one K1 launch in front of K2: - the residual add and the input norm - in_proj_qkvz and in_proj_ba - the conv and the gated delta rule, with their state updates in place - the gated norm K1 reads a bf16 in_proj_qkvz. The spec refuses an MXFP4 in_proj_qkvz, as it refuses an MXFP4 out_proj. K1 writes SGLang's conv and SSM state rows in place, in f32 or bf16. A verify step keeps SGLang's GDN, because K1 keeps no state for each draft token. The spec refuses ReplaySSM without speculative decoding, an SSM state that is not f32 or bf16, and a conv state that is not bf16. Check mode compares the core, the residual and both state rows with SGLang's GDN. Co-authored-by: Cursor <cursoragent@cursor.com>
Part 3 of 5 of the split of #16. This PR is stacked on #88. Until #83 and #88 merge, the diff also shows their three commits. Review only the last commit,
3a8fada.Motivation
#83 runs the tail of each decoder layer as one K2 launch. In front of K2, each of the 69 Gated DeltaNet (GDN) layers still runs SGLang's input norm and GDN as separate launches: the residual add and the input norm, in_proj_qkvz and in_proj_ba, the conv update, the gated delta rule and the gated norm.
This PR runs that front as one K1 launch on each rank. K1 is the
gdn_prekernel of sgl-project/sglang#43516 (at51b0c592b2), which adapts it from vllm-project/vllm#60833. As in K2, each wait in K1 has a deadline.A decode step of 1 to 8 rows now runs each GDN layer as K1, then K2. The 23 full-attention layers run SGLang's attention, then K2, as in #83.
The verify steps of #88 do not use K1. K1 updates the SSM state in place and keeps no state for each draft token, so a rejected draft token cannot roll back. With NEXTN, the server builds no K1 kernel. The verify steps run SGLang's GDN, then K2.
Modifications
qwen38_a95b_mxfp4/gfx950/gdn.pyqwen38_a95b_mxfp4/spec.pyqwen38_a95b_mxfp4/check.pyNUMERICSaddsk1.core.cosine,k1.conv.cosineandk1.state.cosine≥ 0.9999.test/kernels/qwen38_a95b_mxfp4/check_k1.pytest/kernels/qwen38_a95b_mxfp4/check_port.py,inputs.pygdn_prekernel of sgl-project/sglang#43516, bit for bit.test/kernels/test_qwen38_a95b_mxfp4_kernels.pyTestQwen38MoeK1.test/e2e/test_qwen38_a95b_mxfp4_server.pytest/unit/test_qwen38_a95b_mxfp4_spec.pyREADME.mdRefusals. This PR adds these startup refusals:
--enable-linear-replayssmwithout speculative decoding. K1 updates the SSM state in place, and ReplaySSM defers the update to a ring. [Feat] [MI355X] qwen38_a95b_mxfp4: Run each decoder layer's tail as one K2 launch #83 accepted this flag.The spec still refuses the MXFP4 GDN projections. That check now covers in_proj_qkvz too.
With the test CUDA graphs, each rank builds 8 K1 and 4 K2 kernels and logs "Qwen3.8 monokernel: 8 K1 and 4 K2 launches (widths [1, 2, 4, 8], decode)". With NEXTN 3/1/4, it logs "0 K1 and 2 K2 launches (widths [4, 8], verify)".
This PR does not change the K2 kernels,
common/or the other models.Accuracy Tests
All the results below use this setup:
rocm/sgl-dev:v0.5.22-rocm10-mi35x-20261028@sha256:b51edf074b567263afb19be9108756645ee00d0604374b6e4c5006caccaa17fe5bb7ecc2c247db05e3de41d21f36aed7aa1a2dfb, which has sgl-project/sglang#43839, with no unmerged SGLang PR285579b6211b0da30f35b91a890c29c823597cd1, 0.4.013dc9676cdd9e2374c3efb50389d3392b975acaeARGSoftest/e2e/qwen38_a95b_mxfp4_server.py(--mamba-ssm-dtype bfloat16, so K1 stores a bf16 SSM state). The monokernel-on servers add--monokernel-backend amd.5bba9fc(tagq38v3-tested/3-k1). The head3a8fadais rebased ontomainatb10f3b7. Its model code and tests differ from that commit only in the text of a docstring and a skip message. The test runs used the old base0aa9749, before #76 changedPeerBuffer.close().The port test (
check_port.py) compares K1 and K2 with the original kernels of sgl-project/sglang#43516 at51b0c592b2. That PR is not merged, so the test run mounts its two kernel folders (k3_mono_flydslandqwen3_5_mono_flydsl) read-only into SGLang's tree in the container. No SGLang code imports them. Only the port test does.GSM8K, monokernels on and off
The test uses lm_eval 0.4.13 with
local-chat-completionsand the chat template. It runs all 1,319 questions, 5-shot, at temperature 0, withmax_gen_toks32768. It sends 8 concurrent requests, so the decode steps take the K1 and K2 path. Each arm starts a fresh server. Each question has the sameprompt_hashin both arms.The arms differ by 5 questions on flexible-extract and by 10 on strict-match. McNemar's test finds no accuracy difference on either filter.
Strict-match needs a
#### Nline. Of the answers that flexible-extract finds right, 140 have no#### Nline in the off arm, and 155 in the on arm. Strict-match fails exactly these answers. Most of them end in**Answer: N**instead (79 and 90), for example**Answer: 25**. So the strict-match gap is in the answer format, not in the answers.Kernel checks (
test/kernels)gdn_prekernelEach run also repeats the K2 checks of #83, with the same results. The kernel tests do not compare K1 with SGLang's ops. Check mode in the e2e test does that, with the real weights.
Three runs of all tests
k1.core.cosinek2.out.cosineEach run has 1104 K1 checks and 1472 K2 checks. In all three runs,
k1.state.cosineis at least 0.999989 andk1.conv.cosinerounds to 1.000000. K1's residual is bit for bit equal to SGLang's (k1.res_out.max_absis 0), andk2.res_out.cosinerounds to 1.000000.The e2e tests are the five tests of #88:
TestMonokernelVerify): 200 questions at 4 concurrent requests, with NEXTN 3/1/4. The verify steps run K2 only.NUMERICS, at 1, 3 and 8 rows. The test fails if check mode skips more than 1% of the K2 rows.The tests do not cover TP4 or MI350X, because the spec serves only TP8 on MI355X.
Speed Tests and Profiling
The test uses
sglang.bench_servewith random prompts of 8192 input and 1024 output tokens (range ratio 0.8). Each arm starts a fresh server, in the order off, on, on, off. The servers use the flags and environment oftest/e2e/qwen38_a95b_mxfp4_server.py, plus--tool-call-parser qwen3_coderand--watchdog-timeout 12000.Registers. The table comes from a compile-only build of the 32 K1 build keys at
c3b16fb(the head before the rebase, with the same kernel code) for gfx950. Each key has astop=Truebuild (the spec) and astop=Falsebuild (the tests). The numbers come from the AMDGPU metadata in FlyDSL's ISA dump. Where the builds of one width differ (first layer, bf16 state,stop), a cell gives the range.Kernels before and after. The table compares one decode step at batch 1 in two TP-0 profiles with the monokernels on: #83 at
3daea98(K2 only) and this PR at3a8fada(K1 and K2). Both servers ran on one node with 8× MI355X. The prompt has 8192 tokens, and the output has 100 tokens. The counts come from thestep[DECODE bs=1]slices on the GPU stream. All 89 decode steps after the first 10 have the same counts. Each time is the mean of these 89 steps._gemma_fused_add_rmsnorm_kernel;_gemma_rmsnorm_kernelon layer 0qwen38_mono_k1_s1_f0_b1(68 calls per step, 27.8 µs each) andqwen38_mono_k1_s1_f1_b1(layer 0, 27.1 µs)Cijk_Alik_Bljk_…_MT64x16x256_…fused_qkvzba_split_reshape_cat_contiguous_kernel_causal_conv1d_update_kernelfused_recurrent_gated_delta_rule_packed_decode_kernel_layer_norm_fwd_1pass_kernelqwen38_mono_k2_s1_a0f1is the key of layer 0, which has no residual input.b1is the key of a bf16 SSM state.s1is 1 row.step[DECODE bs=1]slice and shows only its kernels (filterCategory = kernel).Before (#83), one decode step:

After (this PR), one decode step:

Checklist
pre-commit run --all-filespasses.test/cover the change..github/CODEOWNERSthat names its owner. (N/A:mainalready has**/*qwen38_a95b_mxfp4* @omirosh @yichiche @chuyeh-amd.)Review and Merge Process
lintandCheck PR titlechecks must pass..github/CODEOWNERSto review.main.