B300 NextN fp4: keep modelopt_fp4 for fp4-capable runners + generalize MTP exclude-remap

GLM-5.2-NVFP4's NextN (MTP) layer-78 routed experts are fp4, but deepseek_nextn unconditionally
dropped modelopt_fp4 for NextN (a DeepSeek-R1-FP4 assumption: its MTP is unquantized). Two fixes
(opus-designed, full back-compat matrix):

A) Drop fp4 for NextN only when the MoE runner can't consume it. Keep fp4 for flashinfer
   trtllm/trtllm_routed/cutedsl/cutlass (extends wxiwnd's helper to include our trtllm runner).
   R1-FP4/V3-FP4 (non-flashinfer-fp4 or unquantized MTP) still drop → bf16, no regression.

B) The NextN hf_to_sglang_mapper was hardcoded `model.layers.61 -> model.decoder` (R1's MTP idx).
   For GLM (MTP=layer 78) this never remapped the layer-78 self_attn/indexer exclude patterns to
   the internal model.decoder prefix → NextN attention wrongly quantized fp4 → crash. Worse, keeping
   the hardcoded 61 would mis-remap GLM's *real* layer 61. Fix: empty the class mapper and inject
   `model.layers.{num_hidden_layers} -> model.decoder` per-config in loader.py (gated on
   num_nextn_predict_layers; dataclasses.replace, no class mutation), generalizing to any MTP index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-21 16:00:23 +00:00
parent c616bb7095
commit 2aca99d677
2 changed files with 53 additions and 7 deletions

View File

@@ -247,6 +247,24 @@ def _get_quantization_config(
f"{supported_dtypes}"
)
hf_to_sglang_mapper = getattr(model_class, "hf_to_sglang_mapper", None)
# NextN/MTP models map their HF decoder prefix "model.layers.{N}" to the
# internal "model.decoder". N == num_hidden_layers is checkpoint specific
# (and for an N-layer model "model.layers.61" can be a real layer), so
# build a per-config copy of the mapper instead of hardcoding the index.
if hf_to_sglang_mapper is not None and getattr(
model_config.hf_config, "num_nextn_predict_layers", None
):
nextn_layer = getattr(model_config.hf_config, "num_hidden_layers", None)
if nextn_layer is not None:
from dataclasses import replace
hf_to_sglang_mapper = replace(
hf_to_sglang_mapper,
orig_to_new_substr={
**hf_to_sglang_mapper.orig_to_new_substr,
f"model.layers.{nextn_layer}": "model.decoder",
},
)
# pass mappings by reference to quant_config
if hf_to_sglang_mapper is not None and quant_config is not None:
quant_config.apply_weight_name_mapper(hf_to_sglang_mapper)

View File

@@ -45,6 +45,7 @@ from sglang.srt.layers.dp_attention import (
)
from sglang.srt.layers.layernorm import RMSNorm
from sglang.srt.layers.logits_processor import LogitsProcessor
from sglang.srt.layers.moe import get_moe_runner_backend
from sglang.srt.layers.quantization import Fp8Config
from sglang.srt.layers.quantization.base_config import QuantizationConfig
from sglang.srt.layers.vocab_parallel_embedding import (
@@ -78,6 +79,30 @@ def _log_eagle_accept_cp_draft_hidden_debug(key: str, message: str, *args):
)
def _should_drop_nextn_modelopt_fp4_quant_config(
quant_config: Optional[QuantizationConfig],
) -> bool:
"""Whether to drop the modelopt_fp4 quant config for the NextN/MTP module.
DeepSeek-R1-FP4 / V3-0324-FP4 ship an *unquantized* MTP module (no fp4 scales
for the MTP MoE, and their hf_quant_config does NOT list the MTP layer in
`ignore`); for those we must drop fp4 so the whole MTP loads bf16 (PR #7376).
GLM-5.2-NVFP4 instead ships fp4 routed experts on the MTP layer and runs on a
flashinfer fp4 MoE backend that consumes them — there we keep fp4 and let the
exclude-list make attn / indexer / shared_experts / gate bf16.
"""
if quant_config is None or quant_config.get_name() != "modelopt_fp4":
return False
backend = get_moe_runner_backend()
keep_fp4 = (
backend.is_flashinfer_trtllm()
or backend.is_flashinfer_trtllm_routed()
or backend.is_flashinfer_cutedsl()
or backend.is_flashinfer_cutlass()
)
return not keep_fp4
class DeepseekModelNextN(nn.Module):
def __init__(
@@ -96,7 +121,7 @@ class DeepseekModelNextN(nn.Module):
else:
moe_quant_config_override = None
if quant_config is not None and quant_config.get_name() == "modelopt_fp4":
if _should_drop_nextn_modelopt_fp4_quant_config(quant_config):
logger.warning(
"Overriding DeepseekV3ForCausalLMNextN quant config for modelopt_fp4 Deepseek model."
)
@@ -498,13 +523,16 @@ class DeepseekModelNextN(nn.Module):
class DeepseekV3ForCausalLMNextN(DeepseekV3ForCausalLM):
# Support amd/DeepSeek-R1-0528-MXFP4 renaming: model.layers.61*.
# Ref: HF config.json for amd/DeepSeek-R1-0528-MXFP4
# https://huggingface.co/amd/DeepSeek-R1-0528-MXFP4/blob/main/config.json
# The NextN/MTP decoder lives at HF prefix ``model.layers.{num_hidden_layers}``
# but internally at ``model.decoder``. The concrete layer index is checkpoint
# specific (DeepSeek-R1 / R1-0528-MXFP4 => 61, GLM-5.2 => 78), and for an
# N-layer model ``model.layers.61`` may be a *real* hidden layer — so the
# ``model.layers.{N}`` substr key is injected from model_config at load time
# (see model_loader/loader.py), never hardcoded. Kept as a (placeholder) class
# attribute so the loader knows this model family remaps its HF MTP-layer
# prefix onto ``model.decoder``.
hf_to_sglang_mapper = WeightsMapper(
orig_to_new_substr={
"model.layers.61": "model.decoder",
},
orig_to_new_substr={},
)
def __init__(