Repository navigation
[AMD][V4.1] Mono kernel framework (--layer-backend mono) and DeepSeek-V4.1 decode layers - #43984
kkHuang-amd wants to merge 6 commits into
Conversation
Model-agnostic runtime for persistent fused decode-layer ("mono") kernels:
layer specs, routing, guards, fail-stop reports, step fences, mailboxes and
the shared FlyDSL device helpers (vendored from ROCm/ATOM). The new
--enable-mono-kernel flag attaches a model's mono spec at load and refuses
unsupported deployments at startup with every reason listed.
Also adds a MiniMax-M3 spec skeleton (refuses until its kernels land) and
CPU tests for the specs and the runtime.
All 40 decoder layers run as two persistent launches each (K1: attention seam + front, K2: attention back + FFN seam + MoE, TP all-reduce in-kernel) on the production KV layouts. The 8 indexer layers keep SGLang's indexer and compressor between K1 and K2. Gated by SGLANG_MONO_DSV41_WHOLE, SGLANG_MONO_DSV41_INDEXER and SGLANG_MONO_DSV41_EDGE (layer 0 / Engram).
Every draft stage as a whole-layer launch on a runner of its own (window attention, 128 experts / top-3 routing). SGLANG_MONO_DSV41_DRAFT=1.
Skip publish_ready.synchronize() when no CPU seq_lens are needed (stream order fences the gather). With SGLANG_SIMULATE_ACC_LEN, also keep sampled batches on the captured verify epilogue: the bonus is drawn there at the simulated correct length (SGLANG_DSPARK_SIMULATE_FOLD, default on) instead of falling back to the eager accept.
SGLANG_MONO_DSV41_K2A=1 (needs SGLANG_MONO_DSV41_INDEXER=1): decode and verify logits of the 8 indexer layers come from a persistent FlyDSL launch vendored from ATOM's K2a FP4 scorer, with a device walk plan instead of the aiter decode schedule. Selection stays on SGLang's top-k / candidate kernels; candidate consumers score only their 16k candidate columns. SGLANG_MONO_DSV41_K2A_CHECK=1 compares against the SGLang path (eager).
Take the better parts of zijiecode/sglang-mono-amd (m3-mono) into the mono
framework and make the kernels share one device library, as ATOM does.
- --layer-backend {module,mono} replaces --enable-mono-kernel. With mono the
validated DeepSeek-V4.1 set is the default (whole layers incl. indexer and
edge layers, DSpark draft stages, K2i scorer); SGLANG_MONO_DSV41_K2A=0
stays for A/B only. Refusals keep the default-deny feature list.
- Runtime: MonoStep.query_len and a rows-per-request decline; check mode
(SGLANG_MONO_CHECK=1, eager) compares each routed layer with the module
path; MonoSpec.state_bindings for per-layer cache readiness (HiCache).
- Kernels move to python/sglang/kernels/ops/mono/gfx950/{common,deepseek_v41}.
DeepSeek-V4.1 drops its private copy of the common code and most of the
attention primitives in favour of gfx950/common (generated ISA unchanged
for all K1/K2/FFN/draft/K2i builds); the JIT key now covers common too.
- Tests: switch, capture width, check mode, state bindings, and a guard
against a kernel family redefining a shared helper; K2i GPU test in CI.
|
Hi @kkHuang-amd, thanks for sharing this. This PR and #43497 both port the DeepSeek-V4.1 mono decode kernels from vllm-project/vllm#60397 to SGLang on gfx950, so I want to link them to avoid duplicate work.
Could we agree on one path? One option: the framework part of this PR follows RFC #43949, #43497 moves onto it as the DeepSeek-V4.1 PR, and your K2, indexer and draft stages come on top. Happy to discuss here or offline. |
|
Thanks @amd-pedghazi, agreed that we should not carry two ports of the same kernels. Happy to converge on RFC #43949. What we plan to change on our side:
For DeepSeek-V4.1 itself, two options, either is fine with us:
It may be easiest to decide with numbers from both on the same image and recipe; we can run that. A few things from our side that might be worth folding into the RFC:
Glad to sync offline as well to settle the split and the order. |
|
Thanks @kkHuang-amd, that plan lines up with RFC #43949. For reference, the plugin shape is implemented on
Everything else (the framework, the model glue, your FP4 indexer scorer) belongs in the plugin. Three runtime points beyond your list, all from the RFC's rules:
One heads-up: on the branch, the RFC's Everything else looks good. |
|
I am adapting #43481 into either this mono framework or rfc #43949 . I didn't observe any blockers. But I think that we still need to eliminate most of the kernel related change from srt/layers/mono/runtime/ e.g. runtime/abi.py specifically. It would be good to drop it and keep KernelAbi on the kernel side. Also mono kernels are under active development so we need to consider it as experimental feature as well. |
… sync On HIP the scheduler waits for the forward's seq_lens publish with a host synchronize (Event.wait() regresses TPOT on MI355), so the next batch's CPU preparation runs while the GPU idles. When the batch needs no CPU seq_lens mirror (DSv4 / DSpark), the gather now runs on the forward stream right after it waits on the schedule stream: the publish was recorded on that stream, so stream order fences the gather and the scheduler never blocks. Ported from sgl-project#43984. Co-authored-by: wunhuang <wunhuang@amd.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… sync On HIP the scheduler waits for the forward's seq_lens publish with a host synchronize (Event.wait() regresses TPOT on MI355), so the next batch's CPU preparation runs while the GPU idles. When the batch needs no CPU seq_lens mirror (DSv4 / DSpark), the gather now runs on the forward stream right after it waits on the schedule stream: the publish was recorded on that stream, so stream order fences the gather and the scheduler never blocks. Ported from sgl-project#43984. Co-authored-by: wunhuang <wunhuang@amd.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Motivation
At decode batch sizes a DeepSeek-V4.1-Flash layer is dozens of small launches plus two all-reduces, and on MI355X the launch gaps and reduce latency dominate the step. This PR adds a model-agnostic framework for persistent fused "mono" kernels in SGLang, and uses it to run every DeepSeek-V4.1-Flash decoder layer (and the DSpark draft stages) as two launches on gfx950.
Modifications
--layer-backend {module,mono}(defaultmodule). Withmono, decode and target-verify steps the model's spec supports run on its kernels; every other step (prefill, unsupported widths) runs the normal module path. Unsupported deployments are refused at startup with every reason listed, never silently degraded.python/sglang/srt/layers/mono/(no model code): spec contract, registry, startup guards, per-step routing, peer memory, check mode and fail-stop plumbing.python/sglang/kernels/ops/mono/gfx950/: a shared device library (common/, adapted from ROCm/ATOM) and the DeepSeek-V4.1 kernels (deepseek_v41/). The model glue lives next to the model insrt/models/deepseek_common/amd/mono/.How the framework fits into SGLang
flowchart TD A["--layer-backend mono"] --> B["config hook<br/>registry: HF architecture -> MonoSpec<br/>spec.refuse_config + feature guards"] B -->|any reason| R["refuse at startup<br/>(all reasons listed)"] B --> C["model load (normal path)"] C --> D["first forward: attach()<br/>spec.refuse_model -> spec.pack (weights as views)<br/>-> spec.build per width (before graph capture)"] D --> E["each step: runtime.begin_step(forward_batch)<br/>routing: decode / verify, width, spec.eligible"] E -->|not routed| M["every layer: module path"] E -->|routed| F["each layer i: runtime.routed(i)"] F -->|Route| K["model glue calls the spec's kernel<br/>same inputs / outputs as the module code"] F -->|None| M2["layer i: module path"]A step is decided once for all layers, from the CUDA-graph key (step type, rows), so a captured graph and its replays always take the same path. Kernel layers and module layers can alternate inside one step because a kernel takes and returns exactly what the model's own layer code does at that point.
sequenceDiagram participant Model as model.forward participant RT as MonoRuntime participant Spec as model's MonoSpec / kernels Model->>RT: begin_step(forward_batch) RT-->>Model: Decision(step or reason) loop layer i Model->>RT: routed(i) alt Route(mode, kernel, step) Model->>Spec: kernel.launch...(layer inputs) Spec-->>Model: layer outputs else None Model->>Model: module path end endLayout
DeepSeek-V4.1 layer on mono
flowchart LR P["previous layer's<br/>pending FFN post"] --> K1["K1<br/>attention mHC seam, norm,<br/>wqkv_a, q/kv norm, KV insert, wq_b"] K1 --> X{"indexer layer?"} X -->|yes| I["SGLang compressor<br/>+ K2i FP4 scorer, top-k"] X -->|no| K2 I --> K2["K2<br/>sparse attention, wo_a / wo_b,<br/>TP reduce, FFN mHC seam,<br/>router, shared + routed experts,<br/>MoE TP reduce"] K2 --> N["pending FFN post<br/>-> next layer's K1"]Adding another model
kernels/ops/mono/<arch>/<model>/and build oncommon/(a CPU test fails if a model directory redefines a shared helper).MonoSpecnext to the model (seesrt/models/minimax_m3_mono/spec.pyfor a minimal one):architectures,layers(hf_config) -> {layer id: LayerMode.WHOLE | FFN},widths(rows per launch, built before capture),supported_tpsupports: the features the kernels handle (e.g.Feature.SPEC_VERIFY); anything else in use is refusedrefuse_config(server_args, hf_config)/refuse_model(model): return reasons, empty when finepack(model, ctx): weights as views of the loaded tensors (or a one-time conversion), plus the per-layer modesbuild(ctx, packed, width) -> LayerKernelwithlaunch(...)andhealth()eligible(ctx, step)for per-step limits,state_bindingsfor per-layer cache readiness (HiCache)registry.pyas an import path.attachonce the weights are final (DeepSeek-V4.1 does it on the first forward, before any capture),begin_step(forward_batch)at the top offorward, and per layerroute = mono.routed(i): launch the kernel when it returns a route, otherwise run the module code unchanged.test/registered/mono/,SGLANG_MONO_CHECK=1with--disable-cuda-graph(logs each routed layer's difference from the module path), then GSM8K and a long-context check.Accuracy Tests
GSM8K (1319, TP4,
--layer-backend mono): 0.901 (module path 0.911); TP2 0.908; NIAH 32k/64k/120k 60/60.Speed Tests and Profiling
AgentX (DSpark, TP4), TTT/GPU / P90 interactivity, vs the SGLang AgentX CI baseline (module path, same recipe): c1 6,019 / 406.5 -> 7,401 / 610.3, c4 8,807 / 331.6 -> 9,561 / 516.0, c8 15,362 / 282.2 -> 16,357 / 409.9, c16 28,106 / 214.6 -> 29,078 / 272.5.
Checklist
Review and Merge Process
/tag-and-rerun-ci,/tag-run-ci-label,/rerun-failed-ciCI States
Latest PR Test (Base): ❌ Run #38399664249
Latest PR Test (Extra): ❌ Run #38399663724
Latest PR Test (AMD ROCm 10): ❌ Run #38399664574