Checklist
Motivation
Add support for Step 3.5 Flash, StepFun's open-source sparse-MoE LLM (released 2026-01, Apache 2.0). It is a strong "intelligence-density" agentic model (~196B total / ~11B active) and is architecturally close to MiniMax-M2 (already supported in sglang-jax), so most existing MoE building blocks can be reused with no new kernel and no new state structure (it is plain GQA softmax attention, not MLA / not linear attention).
Model configuration (verified against config.json + modeling_step3p5.py):
- Architecture:
Step3p5ForCausalLM (model_type: step3p5), decoder-only sparse MoE
- ~196B total / ~11B active per token (incl. 0.81B MTP head, per technical report)
- 45 layers, hidden_size 4096, vocab 128896, max_position 262144 (256K), BF16
- Attention: GQA (
att_impl_type: GQA), head_dim 128, per-head QK-norm (zero-centered / Gemma-style RMSNorm), head-wise attention gate
- Heterogeneous layers (3:1 pattern, full at layers 0,4,8,…,44 → 12 full / 33 sliding):
- full-attention layers: 64 query / 8 KV heads,
partial_rotary_factor 0.5, rope_theta 5e6 + llama3 rope scaling (factor 2.0)
- sliding-attention layers: 96 query / 8 KV heads,
partial_rotary_factor 1.0, rope_theta 1e4, window 512, no rope scaling (yarn_only_types: ["full_attention"])
- MLP: first 3 layers dense (intermediate 11264); layers 3–44 are MoE
- MoE: 288 routed experts + 1 shared expert (intermediate 1280 each), top-8, sigmoid routing computed in fp32 + router bias + normalized top-k weights + routed-scaling 3.0
- Clamped SwiGLU on the last layers only (
swiglu_limits, value 0.0 means no clamp); MTP head (3 nextn-predict layers)
- Official weights: BF16 (also FP8 / Int8 / Int4 / GGUF)
Reuse map to existing sglang-jax components (no new kernel):
- GQA softmax attention →
RadixAttention; sliding window via sliding_window_size (cf. gemma2)
- per-head QK-norm + zero-centered RMSNorm →
GemmaRMSNorm + per-head pattern (cf. qwen3, gemma2)
- per-layer RoPE (
rope_theta / partial_rotary_factor / llama3 scaling differ per layer) → existing RotaryEmbedding / Llama3RotaryEmbedding (cf. embeddings.get_rope)
- head-wise attention gate → one
Linear(hidden → num_heads) + sigmoid, broadcast-multiplied over heads before o_proj (a few lines)
- first-k dense + MoE layers →
first_k_dense_replace pattern (cf. deepseek_v3)
- sigmoid routing + router bias + renorm + routed-scaling + shared expert →
GateLogit / TopK / EPMoE / FusedEPMoE (cf. minimax_m2, qwen2_moe)
- clamped SwiGLU → SiLU-and-mul with an elementwise clamp branch (a few lines)
Key difficulties / things to get right (correctness-sensitive, not blockers):
- MoE routing order must match exactly: scores are
sigmoid in fp32; the router bias is only used to select top-k, while the gating weights are gathered from the bias-free sigmoid scores; then normalize top-k weights and multiply by routed_scaling_factor = 3.0. Need to confirm TopK(correction_bias=...) in sglang-jax reproduces this exact semantics (bias affects selection only, not weighting).
- Per-layer RoPE wiring: full vs sliding layers differ in
rope_theta, partial_rotary_factor, and whether llama3 scaling applies — each layer must instantiate RoPE with the correct parameters.
- Head-wise gate layout: gate comes from the attention input hidden states (not the attention output); after
sigmoid it multiplies the [tokens, num_heads, head_dim] output per head, before o_proj. Broadcast axis must line up with the sglang-jax attention output layout.
- Heterogeneous head counts under TP: full=64 / sliding=96 query heads must be divisible by TP size; KV=8 needs replication when TP>8 (the existing
kv_head_padding / KV-head replica logic in minimax_m2 can be reused).
- Per-head QK-norm: norm is applied per head (over
head_dim) after reshaping, using the zero-centered (weight+1) variant.
MTP (nextn-predict) layers can be skipped for the initial base-inference support.
Related resources
Implementation strategy
Phased verification — model code is written once (full parameterized file); phases control what gets verified, not what gets built.
| Phase |
MoE backend |
KV pool |
What it verifies |
| Phase 1 (alignment) |
EPMoE (verifiable, GMM == per-expert-loop) |
MHATokenToKVPool (--disable-hybrid-swa-memory) |
Implementation correctness via fp32 per-layer alignment |
| Phase 2 (production) |
EPMoE → FusedEPMoE (#1391) |
SWAKVPool (default) |
Engine self-consistency (SWA == full output equality) |
Sub-issues
Checklist
Motivation
Add support for Step 3.5 Flash, StepFun's open-source sparse-MoE LLM (released 2026-01, Apache 2.0). It is a strong "intelligence-density" agentic model (~196B total / ~11B active) and is architecturally close to MiniMax-M2 (already supported in sglang-jax), so most existing MoE building blocks can be reused with no new kernel and no new state structure (it is plain GQA softmax attention, not MLA / not linear attention).
Model configuration (verified against
config.json+modeling_step3p5.py):Step3p5ForCausalLM(model_type: step3p5), decoder-only sparse MoEatt_impl_type: GQA), head_dim 128, per-head QK-norm (zero-centered / Gemma-style RMSNorm), head-wise attention gatepartial_rotary_factor0.5,rope_theta5e6 + llama3 rope scaling (factor 2.0)partial_rotary_factor1.0,rope_theta1e4, window 512, no rope scaling (yarn_only_types: ["full_attention"])swiglu_limits, value0.0means no clamp); MTP head (3 nextn-predict layers)Reuse map to existing sglang-jax components (no new kernel):
RadixAttention; sliding window viasliding_window_size(cf.gemma2)GemmaRMSNorm+ per-head pattern (cf.qwen3,gemma2)rope_theta/partial_rotary_factor/ llama3 scaling differ per layer) → existingRotaryEmbedding/Llama3RotaryEmbedding(cf.embeddings.get_rope)Linear(hidden → num_heads)+sigmoid, broadcast-multiplied over heads beforeo_proj(a few lines)first_k_dense_replacepattern (cf.deepseek_v3)GateLogit/TopK/EPMoE/FusedEPMoE(cf.minimax_m2,qwen2_moe)Key difficulties / things to get right (correctness-sensitive, not blockers):
sigmoidin fp32; the router bias is only used to select top-k, while the gating weights are gathered from the bias-free sigmoid scores; then normalize top-k weights and multiply byrouted_scaling_factor = 3.0. Need to confirmTopK(correction_bias=...)in sglang-jax reproduces this exact semantics (bias affects selection only, not weighting).rope_theta,partial_rotary_factor, and whether llama3 scaling applies — each layer must instantiate RoPE with the correct parameters.sigmoidit multiplies the[tokens, num_heads, head_dim]output per head, beforeo_proj. Broadcast axis must line up with the sglang-jax attention output layout.kv_head_padding/ KV-head replica logic inminimax_m2can be reused).head_dim) after reshaping, using the zero-centered (weight+1) variant.MTP (nextn-predict) layers can be skipped for the initial base-inference support.
Related resources
python/sglang/srt/models/step3p5.pyImplementation strategy
Phased verification — model code is written once (full parameterized file); phases control what gets verified, not what gets built.
GMM == per-expert-loop)--disable-hybrid-swa-memory)SWA == fulloutput equality)GMM == per-expert-loopinvariant check; FusedEPMoE switch deferred to Step 3.5 Flash: FusedEPMoE performance backend switch #1391output(full) == output(SWA))Sub-issues