Skip to content

[Feat] deepseek_v41: Score SGLang's page8 index-K pool in K2i - #80

Open
ChangLi811 wants to merge 1 commit into
sgl-project:mainfrom
ChangLi811:dsv41-index-k-page8
Open

ChangLi811 wants to merge 1 commit into
sgl-project:mainfrom
ChangLi811:dsv41-index-k-page8

Conversation

@ChangLi811

@ChangLi811 ChangLi811 commented Oct 28, 2026 •

Copy link
Copy Markdown

Motivation

SGLANG_HIP_DSV41_INDEX_K_PAGE8=1 (sgl-project/sglang#44006) keeps the ratio-1 index-K pool in aiter's row-group page-8 byte layout, which SGLang's own indexer uses for prefill and wide decode. The monokernels require it off, so a server cannot have both; it is a server-wide switch, and on PD both peers must share it.

Modifications

  • The pool's 8-row pages are K2i's virtual pages (8 × the 64-row page + the sub page), so the page tables, the candidate table and the selection's map back to slots are unchanged; only the bytes of a page differ. score_fp4._keys reads a page8 plane as aiter's page-8 walk does; the page-64 branch is unchanged.
  • build_k2i(page8) builds that variant; each layer's plane carries its pool's low_ratio_index_k_page8, which picks the build (aiter without the row-group kernel keeps page-64).
  • SPEC no longer requires the variable off; the README's PD note names it among the layout both peers must share, as SGLang's PD handshake does not compare it.
  • test_deepseek_v41_k2i.py runs each case on a page8 plane too (the same keys packed by aiter's pack_kv_cache).
  • Also in the README: the V4.1 usage note no longer says decode graph capture fails without AITER_BF16_FP8_MOE_BOUND=0; on the current image (aiter 285579b62) SGLang's decode starts and captures without it. The command keeps the variable, as the serving tests do.

Accuracy Tests

K2i kernel tests (24): FULL logits on a page8 plane bit for bit with the page-64 plane's and within k2i.logits.rel of SGLang's and fp64; a REINDEX consumer's top-512 as a set equal to SGLang's topk_within_candidate_blocks_page8; the two-level pipeline and a graph replay.

End to end: DeepSeek-V4.1-Flash, one MI355X node, rocm/sgl-dev:v0.5.22-rocm10-mi35x-20261025, SGLang aa6e51982 (a local merge of #43465, #44730, #44031 and #46036), PAGE8=1 with aiter's row-group kernel, TP4 + prefill CP4 + DSpark block 5; GSM8K: sgl-eval, 1319 questions, max tokens 4096; on and off on fresh servers:

KV monokernels GSM8K accept length
FP8 unified on 0.967 3.54
FP8 unified off 0.961 3.51
paged on 0.965 3.55
paged off 0.961 3.51

TP2 + CP2 + FP8 unified: 0.965 on, 0.963 off. With PAGE8=0 the same server scores 0.964 (the page-64 path unchanged).

GSM8K above runs 64 threads, so most verify steps are wider than 8 requests and take SGLang's path. With AgentX's c1 recipe (max-running-requests 2, its 19 environment variables, SGLANG_HIP_DSV41_INDEX_K_PAGE8=1), every verify step runs on the monokernels; on #70 (now in main) plus this PR, #78 and #79 (DeepSeek-V4.1-Flash-1017 weights, fresh servers):

config on off
TP4, paged 0.967 / 3.41 0.967 / —
TP4 + prefill CP4, FP8 unified 0.967 / 3.41 0.963 / —
TP2, paged 0.967 / 3.39 0.968 / 3.40
TP2 + prefill CP2, FP8 unified 0.968 / 3.41 0.967 / 3.39

(GSM8K / DSpark accept length. TP4 "off" accept lengths are left out: with the recipe's --disable-shared-experts-fusion, SGLang's own path accepts less at batch 1–3, which --enforce-shared-experts-fusion, as the README uses, restores.)

PD

MoRI PD (enabled by #70) over two MI355X nodes: P = TP4 + prefill CP4 + DSpark without the monokernels, D = TP4 + DSpark; SGLANG_HIP_DSV41_INDEX_K_PAGE8=1 on both peers; P and D started afresh for every row; GSM8K through the PD router.

KV D's monokernels GSM8K, 64 threads GSM8K, 8 threads accept length, 8 threads
FP8 unified on 0.964 0.966 3.54
FP8 unified off 0.962 0.962 3.52
paged on 0.967 0.964 3.55
paged off 0.963 0.964 3.53

At 8 threads every verify step of the D runs on the monokernels when they are on. Greedy prompts (short, 15k–100k) and 50 long needles pass on every row; no GPU faults on either peer.

Speed Tests and Profiling

c1 TPOT on the FP8 unified server above: 1.17 ms on, 1.78 ms off.

Checklist

  • pre-commit run --all-files passes (ruff, isort, codespell).
  • Tests under test/ cover the change.
    • CPU, against SGLang aa6e51982: pytest test/unit passes 262, as on main, including the deepseek_v41 contract, which also passes against SGLang main (c6c753395). The 7 failures and 3 collection errors (GLM-5.3, MiniMax and DeepSeek-V4-Pro tests) are the same on main and unrelated to this PR. The K2i change is covered by test_deepseek_v41_k2i.py on the GPU (above).
  • The SGLang commit or PR this was tested against is named above.
  • Accuracy and speed results are attached when kernel or forward code changes.
  • A new model adds one line to .github/CODEOWNERS that names its owner. (No new model.)

SGLANG_HIP_DSV41_INDEX_K_PAGE8=1 keeps the ratio-1 index-K pool in aiter's
row-group page-8 byte layout (pack_kv_cache) with unchanged page size and shapes,
when aiter ships the row-group kernel. Its 8-row pages are K2i's virtual pages
(8 x the 64-row page + the sub page), so the page tables, the candidate table and
the selection's map back to slots are unchanged; only the bytes of a page differ.
The spec refused the setting since K2i read such a pool as page-64.

- score_fp4._keys reads a page8 plane as aiter's page-8 walk does: a lane's four
  key tiles 128 B apart and their scales one dword. The page-64 branch is
  unchanged.
- build_k2i(page8) builds that variant; each layer's plane carries its pool's
  low_ratio_index_k_page8, which picks the build. Startup precompiles the page8
  variant for the ratio-1 index sources when the env is set.
- SPEC no longer requires SGLANG_HIP_DSV41_INDEX_K_PAGE8=False; the README's PD
  note names it among the layout both peers must share, as SGLang's PD handshake
  does not compare it.

test_deepseek_v41_k2i.py runs each case on a page8 plane too (the same keys
packed by aiter's pack_kv_cache): FULL logits bit for bit with the page-64
plane's and within k2i.logits.rel of SGLang's and fp64, a REINDEX consumer's
top-512 as a set equal to SGLang's topk_within_candidate_blocks_page8, the
two-level pipeline against SGLang's, and a graph replay against eager.

The README's V4.1 usage note no longer says decode graph capture fails
without AITER_BF16_FP8_MOE_BOUND=0: on the current image (aiter 285579b62)
SGLang's decode starts and captures its graphs without it. The command keeps
the variable, as the serving tests do.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant