diff --git a/docs/cookbook/autoregressive/InternLM/Intern-S2-Mobius.mdx b/docs/cookbook/autoregressive/InternLM/Intern-S2-Mobius.mdx index 39ea0ac8c..851665c74 100644 --- a/docs/cookbook/autoregressive/InternLM/Intern-S2-Mobius.mdx +++ b/docs/cookbook/autoregressive/InternLM/Intern-S2-Mobius.mdx @@ -1,6 +1,6 @@ --- title: Intern-S2-Mobius -description: "Deploy Intern-S2-Mobius with SGLang — InternLM's Mobius-v0 multimodal model with a globally shared Knowledge Memory, hybrid GDN + full attention, MTP (NEXTN) speculative decoding, and 256K context, on NVIDIA H200 and B200." +description: "Deploy the BF16 and FP8 Intern-S2-Mobius checkpoints with SGLang on NVIDIA H200 and B200." tag: NEW --- @@ -52,7 +52,7 @@ import { benchmarks } from "/src/snippets/configs/internlm/intern-s2-mobius-benc - Speed numbers are measured with `--random-range-ratio 1.0`, `--flush-cache`, on 2×H200 TP=2 against `main @ e0828ee3` + PR [#33691](https://github.com/sgl-project/sglang/pull/33691) head (since merged 2026-08-08 — `lmsysorg/sglang:dev` is the live equivalent). GSM8K is the full 1319-example test split; GPQA is Diamond 198 problems × 8 repeats (pass@1 avg-of-8). Both ran with no server-side sampling override, so the checkpoint's `generation_config.json` defaults applied (temperature 1.0, top_p 0.95, top_k 20). The B200 recipes are inferred from the H200 ones and unverified — same flags, just a TP=2 or TP=1 Blackwell equivalent. + Speed and accuracy numbers apply to the BF16 checkpoint. They were measured with `--random-range-ratio 1.0`, `--flush-cache`, on 2×H200 TP=2 against `main @ e0828ee3` + PR [#33691](https://github.com/sgl-project/sglang/pull/33691) head (since merged 2026-08-08 — `lmsysorg/sglang:dev` is the live equivalent). GSM8K is the full 1319-example test split; GPQA is Diamond 198 problems × 8 repeats (pass@1 avg-of-8). Both ran with no server-side sampling override, so the checkpoint's `generation_config.json` defaults applied (temperature 1.0, top_p 0.95, top_k 20). The FP8 H200 recipes have launch, text, vision, and stop-reason verification but no benchmark numbers yet. All B200 recipes are inferred and unverified. ## Playground @@ -94,20 +94,27 @@ On the serving side the model is a hybrid: 30 of 40 transformer layers use **GDN 262,144 Apache-2.0 + + Intern-S2-Mobius-FP8 + Mobius-v0 · GDN ×30 + full ×10 · MoE-2560 / top-8 · MTP · FP8 E4M3 + 262,144 + Apache-2.0 + -**Recommended generation:** `temperature=0.8`, `top_p=1.0`, `top_k=50`, `min_p=0.0` — the values the [model card](https://huggingface.co/internlm/Intern-S2-Mobius) recommends. Note these are *not* what the checkpoint ships in `generation_config.json` (`temperature=1.0`, `top_p=0.95`, `top_k=20`), and SGLang applies that file by default (`--sampling-defaults model`) — so send the recommended values explicitly per request if you want them. +**Recommended generation:** `temperature=0.8`, `top_p=1.0`, `top_k=50`, `min_p=0.0` — the values the [model card](https://huggingface.co/internlm/Intern-S2-Mobius) recommends. Both checkpoints ship `generation_config.json` with different values (`temperature=1.0`, `top_p=0.95`, `top_k=20`), and SGLang applies that file by default (`--sampling-defaults model`) — so send the recommended values explicitly per request if you want them. -**Resources:** [HuggingFace](https://huggingface.co/internlm/Intern-S2-Mobius) · [GitHub (InternLM/Intern-S2-Mobius)](https://github.com/InternLM/Intern-S2-Mobius). +**Resources:** [HuggingFace (BF16)](https://huggingface.co/internlm/Intern-S2-Mobius) · [HuggingFace (FP8)](https://huggingface.co/internlm/Intern-S2-Mobius-FP8) · [GitHub (InternLM/Intern-S2-Mobius)](https://github.com/InternLM/Intern-S2-Mobius). ## 2. Configuration Tips - **Trust remote code is required.** Intern-S2-Mobius ships a custom `configuration_interns2_mobius.py` / `modeling_interns2_mobius.py` on its HF repo; every recipe adds `--trust-remote-code`. +- **FP8 uses one H200.** The FP8 checkpoint stores E4M3 weights with 128×128 block scales and dynamic activation quantization. The verified H200 recipes use TP=1, `deep_gemm`, `--mem-fraction-static 0.6`, disabled prefill CUDA graphs, and decode graphs up to batch size 16. Higher memory fractions or the default graph range exhausted H200 memory during DeepGEMM warmup; the running MTP server used about 99 GB. - **Speculative decoding schedule.** The checkpoint ships one MTP layer. Enable MTP for the lowest latency (`--speculative-algorithm NEXTN --speculative-num-steps 3 --speculative-eagle-topk 1 --speculative-num-draft-tokens 4`). We measured accept-length ~3.9/4 draft tokens at 8K-in / 1K-out, roughly tripling single-stream decode speed (median TPOT 9.79 ms → 3.13 ms at conc=1, 14.26 ms → 6.84 ms at conc=16) and roughly doubling mid-concurrency total throughput (9358 → 18029 tokens/s at conc=16, 21395 → 26033 tokens/s at conc=64). The high-throughput recipe stays spec-off because once you can batch wide, its saturation point is higher (34786 tokens/s at conc=256 vs the spec recipe's peak at conc=64). - **Mamba pool sizing.** GDN layers live in a separate Mamba state pool; the `--mamba-full-memory-ratio` (defaults to 0.9) controls the split between the 10 full-attention layers' KV pool and the 30 GDN layers' conv+SSM state pool. Default split comfortably handles conc=64 on a 2×H200 node; if you need higher concurrency than `--max-running-requests` allows for your workload, raise `--mamba-full-memory-ratio` slightly (each +1% mamba ratio costs full-attn KV). - **Vision input.** Images are accepted via the standard `image_url` chat message type. Vision tokens are counted into the prompt (`prompt_tokens_details.image_tokens` shows the count), and the model honors `<|vision_start|>` / `<|vision_end|>` boundaries exactly. -- **B200 sizing.** B200 (192 GB HBM) fits the BF16 weights + KV + Mamba pool on a **single GPU** with `--tp 1`. The B200 cells in the panel inherit the H200 recipe with only `--tp` changed — unverified; treat them as a starting point until the Intern-S2-Mobius team publishes a Blackwell measurement. +- **B200 sizing.** B200 (192 GB HBM) fits the BF16 weights + KV + Mamba pool on a **single GPU** with `--tp 1`. The FP8 B200 cells inherit the verified H200 TP=1 recipe but remain unverified; treat them as a starting point until a Blackwell measurement is available. ## 3. Advanced Usage diff --git a/docs/src/snippets/configs/internlm/intern-s2-mobius.jsx b/docs/src/snippets/configs/internlm/intern-s2-mobius.jsx index 68e44646e..c053f6b74 100644 --- a/docs/src/snippets/configs/internlm/intern-s2-mobius.jsx +++ b/docs/src/snippets/configs/internlm/intern-s2-mobius.jsx @@ -6,12 +6,12 @@ export const config = { supportedHardware: ["h200", "b200"], - // Single released checkpoint — no variant axis. variants: [ - { id: "default", label: "Intern-S2-Mobius", subtitle: "Mobius-v0 · BF16" }, + { id: "default", label: "Intern-S2-Mobius", subtitle: "Mobius-v0" }, ], quantizations: [ { id: "bf16", label: "BF16" }, + { id: "fp8", label: "FP8" }, ], strategies: [ { id: "low-latency", label: "Low-Latency" }, @@ -23,6 +23,7 @@ export const config = { modelNames: { "default|bf16": "internlm/Intern-S2-Mobius", + "default|fp8": "internlm/Intern-S2-Mobius-FP8", }, placeholders: { @@ -105,9 +106,7 @@ sgl-eval run gpqa \\ // — models/interns2_mobius.py), so EP has nothing to shard. The runtime enforces // that: server_args._handle_model_specific_adjustments raises for this arch on // `--ep-size != 1` (and `--pp-size != 1`), so an EP chip would emit a command - // that cannot start. `--moe-a2a-backend deepep` is out for the same reason, and - // arg_groups/overrides.py pins moe_runner_backend to triton_kernel as the only - // runner validated for the 2,560-expert bank. + // that cannot start. `--moe-a2a-backend deepep` is out for the same reason. // ----- Card: "Parsers" ----- parsers: { @@ -175,6 +174,46 @@ sgl-eval run gpqa \\ "--port {{PORT}}", ], }, + { + match: { hw: "h200", variant: "default", quant: "fp8", strategy: "low-latency", nodes: "single" }, + verified: true, + env: [], + flags: [ + "--trust-remote-code", + "--model-path {{MODEL_NAME}}", + "--tp 1", + "--mem-fraction-static 0.6", + "--context-length 262144", + "--reasoning-parser qwen3", + "--moe-runner-backend deep_gemm", + "--disable-prefill-cuda-graph", + "--cuda-graph-max-bs-decode 16", + "--speculative-algorithm NEXTN", + "--speculative-num-steps 3", + "--speculative-eagle-topk 1", + "--speculative-num-draft-tokens 4", + "--host {{HOST_IP}}", + "--port {{PORT}}", + ], + }, + { + match: { hw: "h200", variant: "default", quant: "fp8", strategy: "high-throughput", nodes: "single" }, + verified: true, + env: [], + flags: [ + "--trust-remote-code", + "--model-path {{MODEL_NAME}}", + "--tp 1", + "--mem-fraction-static 0.6", + "--context-length 262144", + "--reasoning-parser qwen3", + "--moe-runner-backend deep_gemm", + "--disable-prefill-cuda-graph", + "--cuda-graph-max-bs-decode 16", + "--host {{HOST_IP}}", + "--port {{PORT}}", + ], + }, // ==== B200, 2 GPUs, BF16, low-latency (MTP NEXTN on) — INFERRED from H200 ==== { match: { hw: "b200", variant: "default", quant: "bf16", strategy: "low-latency", nodes: "single" }, @@ -211,5 +250,45 @@ sgl-eval run gpqa \\ "--port {{PORT}}", ], }, + { + match: { hw: "b200", variant: "default", quant: "fp8", strategy: "low-latency", nodes: "single" }, + verified: false, + env: [], + flags: [ + "--trust-remote-code", + "--model-path {{MODEL_NAME}}", + "--tp 1", + "--mem-fraction-static 0.6", + "--context-length 262144", + "--reasoning-parser qwen3", + "--moe-runner-backend deep_gemm", + "--disable-prefill-cuda-graph", + "--cuda-graph-max-bs-decode 16", + "--speculative-algorithm NEXTN", + "--speculative-num-steps 3", + "--speculative-eagle-topk 1", + "--speculative-num-draft-tokens 4", + "--host {{HOST_IP}}", + "--port {{PORT}}", + ], + }, + { + match: { hw: "b200", variant: "default", quant: "fp8", strategy: "high-throughput", nodes: "single" }, + verified: false, + env: [], + flags: [ + "--trust-remote-code", + "--model-path {{MODEL_NAME}}", + "--tp 1", + "--mem-fraction-static 0.6", + "--context-length 262144", + "--reasoning-parser qwen3", + "--moe-runner-backend deep_gemm", + "--disable-prefill-cuda-graph", + "--cuda-graph-max-bs-decode 16", + "--host {{HOST_IP}}", + "--port {{PORT}}", + ], + }, ], }; diff --git a/python/sglang/srt/models/interns2_mobius.py b/python/sglang/srt/models/interns2_mobius.py index fe19dc245..2e5aa8468 100644 --- a/python/sglang/srt/models/interns2_mobius.py +++ b/python/sglang/srt/models/interns2_mobius.py @@ -91,6 +91,10 @@ def _normalize_mobius_weight_name(name: str) -> str: return name +def _is_optional_mobius_parameter(name: str) -> bool: + return name.endswith((".attn.k_scale", ".attn.v_scale")) + + def _load_fused_mobius_expert_weight( *, name: str, @@ -99,8 +103,17 @@ def _load_fused_mobius_expert_weight( num_experts: int, record_slot, ) -> None: - if name.endswith("experts.gate_up_proj"): - parameter_name = name.replace("experts.gate_up_proj", "experts.w13_weight") + gate_up_suffixes = { + "experts.gate_up_proj": "experts.w13_weight", + "experts.gate_up_proj_scale_inv": "experts.w13_weight_scale_inv", + } + gate_up_suffix = next( + (suffix for suffix in gate_up_suffixes if name.endswith(suffix)), None + ) + if gate_up_suffix is not None: + parameter_name = ( + name.removesuffix(gate_up_suffix) + gate_up_suffixes[gate_up_suffix] + ) if parameter_name not in params_dict: raise KeyError( f"Mobius fused gate/up destination is missing: {parameter_name}" @@ -127,8 +140,15 @@ def _load_fused_mobius_expert_weight( ) return - if name.endswith("experts.down_proj"): - parameter_name = name.replace("experts.down_proj", "experts.w2_weight") + down_suffixes = { + "experts.down_proj": "experts.w2_weight", + "experts.down_proj_scale_inv": "experts.w2_weight_scale_inv", + } + down_suffix = next( + (suffix for suffix in down_suffixes if name.endswith(suffix)), None + ) + if down_suffix is not None: + parameter_name = name.removesuffix(down_suffix) + down_suffixes[down_suffix] if parameter_name not in params_dict: raise KeyError( f"Mobius fused down destination is missing: {parameter_name}" @@ -166,11 +186,15 @@ def _expected_mobius_load_slots( if parameter_id in seen_parameters: continue seen_parameters.add(parameter_id) - if ".meta_mlp." in name and name.endswith("experts.w13_weight"): + if ".meta_mlp." in name and name.endswith( + ("experts.w13_weight", "experts.w13_weight_scale_inv") + ): for expert_id in range(num_experts): expected.add((name, "w1", expert_id)) expected.add((name, "w3", expert_id)) - elif ".meta_mlp." in name and name.endswith("experts.w2_weight"): + elif ".meta_mlp." in name and name.endswith( + ("experts.w2_weight", "experts.w2_weight_scale_inv") + ): for expert_id in range(num_experts): expected.add((name, "w2", expert_id)) elif ".qkv_proj." in name and name.startswith("model.layers."): @@ -185,6 +209,8 @@ def _expected_mobius_load_slots( elif ".in_proj_ba." in name: expected.add((name, 0, None)) expected.add((name, 1, None)) + elif _is_optional_mobius_parameter(name): + continue else: expected.add((name, None, None)) return expected @@ -215,7 +241,12 @@ def _load_mobius_weights_strict( name = _normalize_mobius_weight_name(source_name) if ".meta_mlp." in name and name.endswith( - ("experts.gate_up_proj", "experts.down_proj") + ( + "experts.gate_up_proj", + "experts.down_proj", + "experts.gate_up_proj_scale_inv", + "experts.down_proj_scale_inv", + ) ): _load_fused_mobius_expert_weight( name=name, @@ -250,6 +281,8 @@ def _load_mobius_weights_strict( ) parameter = params_dict[name] loader = getattr(parameter, "weight_loader", default_weight_loader) + if _is_optional_mobius_parameter(name): + expected_slots.add((name, None, None)) record_slot(name) loader(parameter, loaded_weight) @@ -802,6 +835,16 @@ class InternS2MobiusForConditionalGeneration(Qwen3_5ForConditionalGeneration): prefix: str = "", language_model_cls=InternS2MobiusForCausalLM, ) -> None: + ignored_layers = getattr(quant_config, "ignored_layers", None) + if ( + getattr(quant_config, "is_checkpoint_fp8_serialized", False) + and ignored_layers + ): + # HF treats these parent entries as exact names; SGLang prefix matching + # would also skip their quantized qkv/z and output projections. + quant_config.ignored_layers = [ + name for name in ignored_layers if not name.endswith(".linear_attn") + ] super().__init__(config, quant_config, prefix, language_model_cls) def should_apply_lora(self, module_name: str) -> bool: