[NVIDIA] Support NVFP4 MoE for DeepSeek-V4 (#25820)
This commit is contained in:
@@ -59,6 +59,17 @@ def apply_deepseek_v4_defaults(server_args: ServerArgs, model_arch: str) -> None
|
||||
f"Setting swa_full_tokens_ratio to {server_args.swa_full_tokens_ratio} for {model_arch}."
|
||||
)
|
||||
|
||||
# nvidia/DeepSeek-V4-Pro-NVFP4 uses flashinfer_trtllm_routed MoE runner backend.
|
||||
if (
|
||||
server_args.moe_runner_backend == "auto"
|
||||
and server_args.get_model_config().nvfp4_moe_meta is not None
|
||||
):
|
||||
server_args.moe_runner_backend = "flashinfer_trtllm_routed"
|
||||
logger.info(
|
||||
"Use flashinfer_trtllm_routed as MoE runner backend for "
|
||||
f"{model_arch} hybrid FP8+NVFP4 checkpoint."
|
||||
)
|
||||
|
||||
|
||||
def validate_deepseek_v4_cp(server_args: ServerArgs) -> None:
|
||||
"""Validate DeepSeek V4 context-parallel configuration."""
|
||||
|
||||
@@ -302,6 +302,28 @@ class ModelConfig:
|
||||
if n_group is not None:
|
||||
self.hf_config.topk_group = n_group
|
||||
|
||||
# Handle hybrid NVFP4 moe (nvidia/DeepSeek-V4-Pro-NVFP4)
|
||||
self.nvfp4_moe_meta: Optional[dict] = None
|
||||
hybrid_quant_cfg = getattr(self.hf_config, "quantization_config", None)
|
||||
if hybrid_quant_cfg is not None and not isinstance(hybrid_quant_cfg, dict):
|
||||
hybrid_quant_cfg = hybrid_quant_cfg.to_dict()
|
||||
if (
|
||||
hybrid_quant_cfg is not None
|
||||
and str(hybrid_quant_cfg.get("quant_algo", "")).upper() == "MIXED_PRECISION"
|
||||
and str(hybrid_quant_cfg.get("moe_quant_algo", "")).upper() == "NVFP4"
|
||||
and hybrid_quant_cfg.get("group_size") is not None
|
||||
):
|
||||
self.nvfp4_moe_meta = {
|
||||
"group_size": int(hybrid_quant_cfg["group_size"]),
|
||||
"exclude_modules": list(hybrid_quant_cfg.get("ignore") or []),
|
||||
}
|
||||
logger.info(
|
||||
"Auto-detected hybrid FP8+NVFP4 checkpoint "
|
||||
"(NVFP4 MoE group_size=%d, %d exclude_modules)",
|
||||
self.nvfp4_moe_meta["group_size"],
|
||||
len(self.nvfp4_moe_meta["exclude_modules"]),
|
||||
)
|
||||
|
||||
# Check model type
|
||||
self.attention_chunk_size = getattr(
|
||||
self.hf_text_config, "attention_chunk_size", None
|
||||
|
||||
@@ -65,7 +65,14 @@ class HashTopK(nn.Module):
|
||||
)
|
||||
self._init_default_tid2eid()
|
||||
|
||||
assert not apply_routed_scaling_factor_on_output, "not implemented"
|
||||
self.apply_routed_scaling_factor_on_output = (
|
||||
apply_routed_scaling_factor_on_output
|
||||
)
|
||||
if apply_routed_scaling_factor_on_output and num_fused_shared_experts > 0:
|
||||
raise NotImplementedError(
|
||||
"HashTopK + apply_routed_scaling_factor_on_output is not supported "
|
||||
"with fused shared experts; pass --disable-shared-experts-fusion."
|
||||
)
|
||||
|
||||
def _init_default_tid2eid(self) -> None:
|
||||
topk = self.tid2eid.shape[1]
|
||||
@@ -188,6 +195,9 @@ class HashTopK(nn.Module):
|
||||
if _is_hip or _is_npu:
|
||||
topk_weights = topk_weights.to(torch.float32)
|
||||
|
||||
if self.apply_routed_scaling_factor_on_output:
|
||||
topk_weights = topk_weights * self.routed_scaling_factor
|
||||
|
||||
log2phy_prob = None
|
||||
if (
|
||||
expert_location_dispatch_info is not None
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
import contextvars
|
||||
from contextlib import contextmanager
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Generator, cast
|
||||
from typing import TYPE_CHECKING, Generator, Optional, cast
|
||||
|
||||
import torch
|
||||
from torch.nn import Module
|
||||
@@ -857,6 +857,8 @@ class FlashInferTrtllmFp4MoeQuantInfo(MoeQuantInfo):
|
||||
routing_method_type: int
|
||||
use_per_token_activation: bool = False
|
||||
|
||||
gemm1_clamp_limit: Optional[torch.Tensor] = None
|
||||
|
||||
|
||||
def quantize_hidden_states_fp4(
|
||||
hidden_states: torch.Tensor,
|
||||
@@ -953,18 +955,6 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
runner_config.activation, is_gated=runner_config.is_gated
|
||||
)
|
||||
|
||||
# Build per-expert clamp-limit tensor from the per-layer scalar.
|
||||
_clamp_val = runner_config.gemm1_clamp_limit
|
||||
if _clamp_val is not None:
|
||||
gemm1_clamp_limit = torch.full(
|
||||
(quant_info.local_num_experts,),
|
||||
_clamp_val,
|
||||
dtype=torch.float32,
|
||||
device=hs_fp4.device,
|
||||
)
|
||||
else:
|
||||
gemm1_clamp_limit = None
|
||||
|
||||
# Fall back to routed path when topk was already materialized (e.g. sigmoid routing).
|
||||
if not use_routed_topk and TopKOutputChecker.format_is_standard(topk_output):
|
||||
use_routed_topk = True
|
||||
@@ -1020,7 +1010,7 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
gemm1_bias=None,
|
||||
gemm1_alpha=None,
|
||||
gemm1_beta=None,
|
||||
gemm1_clamp_limit=gemm1_clamp_limit,
|
||||
gemm1_clamp_limit=quant_info.gemm1_clamp_limit,
|
||||
gemm2_weights=quant_info.w2_weight,
|
||||
gemm2_weights_scale=quant_info.w2_weight_scale.view(torch.float8_e4m3fn),
|
||||
gemm2_bias=None,
|
||||
@@ -1060,7 +1050,7 @@ def fused_experts_none_to_flashinfer_trtllm_fp4(
|
||||
gemm1_bias=None,
|
||||
gemm1_alpha=None,
|
||||
gemm1_beta=None,
|
||||
gemm1_clamp_limit=gemm1_clamp_limit,
|
||||
gemm1_clamp_limit=quant_info.gemm1_clamp_limit,
|
||||
gemm2_weights=quant_info.w2_weight,
|
||||
gemm2_weights_scale=quant_info.w2_weight_scale.view(torch.float8_e4m3fn),
|
||||
gemm2_bias=None,
|
||||
|
||||
@@ -41,6 +41,7 @@ from sglang.srt.layers.quantization.fp4_utils import (
|
||||
fp4_quantize,
|
||||
get_fp4_gemm_runner_backend,
|
||||
)
|
||||
from sglang.srt.layers.quantization.fp8 import Fp8Config
|
||||
from sglang.srt.layers.quantization.fp8_kernel import scaled_fp8_quant
|
||||
from sglang.srt.layers.quantization.fp8_utils import (
|
||||
apply_fp8_linear,
|
||||
@@ -1354,6 +1355,45 @@ class ModelOptFp4Config(ModelOptQuantConfig):
|
||||
)
|
||||
|
||||
|
||||
class HybridFp8NvFp4Config(Fp8Config):
|
||||
"""FP8 (linear/attention/MTP MoE) + NVFP4 (FusedMoE) hybrid quantization.
|
||||
|
||||
For checkpoints like nvidia/DeepSeek-V4-Pro-NVFP4 where
|
||||
config.json:quantization_config declares quant_method=fp8 and
|
||||
moe_quant_algo=NVFP4. FusedMoE layers route through
|
||||
ModelOptNvFp4FusedMoEMethod; linear / attention layers
|
||||
delegate to the inherited Fp8Config dispatch.
|
||||
"""
|
||||
|
||||
def __init__(self, fp8_config: Fp8Config, nvfp4_config: ModelOptFp4Config):
|
||||
# Inherit all of fp8_config's state without re-running its
|
||||
# validation / logging (already happened at fp8_config build time).
|
||||
self.__dict__.update(fp8_config.__dict__)
|
||||
self.nvfp4_config = nvfp4_config
|
||||
|
||||
def get_quant_method(
|
||||
self, layer: torch.nn.Module, prefix: str
|
||||
) -> Optional[QuantizeMethodBase]:
|
||||
from sglang.srt.layers.moe.fused_moe_triton import FusedMoE
|
||||
|
||||
if isinstance(layer, FusedMoE):
|
||||
if not self.nvfp4_config.is_layer_excluded(prefix):
|
||||
return ModelOptNvFp4FusedMoEMethod(self.nvfp4_config)
|
||||
# Fall back to MXFP4 for MTP MoE layers
|
||||
if self.is_fp4_experts:
|
||||
from sglang.srt.layers.quantization.fp8 import Fp8MoEMethod
|
||||
from sglang.srt.layers.quantization.mxfp4_flashinfer_trtllm_moe import (
|
||||
Mxfp4FlashinferTrtllmMoEMethod,
|
||||
)
|
||||
|
||||
return Mxfp4FlashinferTrtllmMoEMethod(Fp8MoEMethod(self), prefix=prefix)
|
||||
return super().get_quant_method(layer, prefix)
|
||||
|
||||
def apply_weight_name_mapper(self, hf_to_sglang_mapper: WeightsMapper):
|
||||
super().apply_weight_name_mapper(hf_to_sglang_mapper)
|
||||
self.nvfp4_config.apply_weight_name_mapper(hf_to_sglang_mapper)
|
||||
|
||||
|
||||
class ModelOptFp4LinearMethod(LinearMethodBase):
|
||||
"""Linear method for NVFP4.
|
||||
Supports loading NVFP4 checkpoints with the following structure:
|
||||
@@ -2053,6 +2093,18 @@ class ModelOptNvFp4FusedMoEMethod(FusedMoEMethodBase):
|
||||
(1 / w2_input_scale).to(torch.float32),
|
||||
)
|
||||
|
||||
swiglu_limit = layer.moe_runner_config.swiglu_limit
|
||||
if (
|
||||
swiglu_limit is not None
|
||||
and layer.moe_runner_config.is_gated
|
||||
and self.enable_flashinfer_trtllm_moe
|
||||
):
|
||||
copy_or_rebind_param(
|
||||
layer,
|
||||
"gemm1_clamp_limit",
|
||||
(swiglu_limit / layer.g1_alphas).to(torch.float32),
|
||||
)
|
||||
|
||||
# TODO: for flashinfer always do MOE_NVFP4_DISPATCH
|
||||
layer.dispatcher.set_quant_config(
|
||||
{
|
||||
@@ -2338,6 +2390,7 @@ class ModelOptNvFp4FusedMoEMethod(FusedMoEMethodBase):
|
||||
layer, "routing_method_type", RoutingMethodType.Default
|
||||
)
|
||||
|
||||
gemm1_clamp = getattr(layer, "gemm1_clamp_limit", None)
|
||||
quant_info = FlashInferTrtllmFp4MoeQuantInfo(
|
||||
w13_weight=layer.w13_weight.data,
|
||||
w2_weight=layer.w2_weight.data,
|
||||
@@ -2353,6 +2406,7 @@ class ModelOptNvFp4FusedMoEMethod(FusedMoEMethodBase):
|
||||
intermediate_size_per_partition=layer.intermediate_size_per_partition,
|
||||
routing_method_type=routing_method_type,
|
||||
use_per_token_activation=self.quant_config.use_per_token_activation,
|
||||
gemm1_clamp_limit=gemm1_clamp.data if gemm1_clamp is not None else None,
|
||||
)
|
||||
|
||||
return self.runner.run(dispatch_output, quant_info)
|
||||
|
||||
@@ -245,6 +245,27 @@ def _get_quantization_config(
|
||||
|
||||
if isinstance(quant_config, Fp8Config):
|
||||
quant_config.is_fp4_experts = model_config.is_fp4_experts
|
||||
# Handle hybrid NVFP4 moe (nvidia/DeepSeek-V4-Pro-NVFP4)
|
||||
nvfp4_meta = model_config.nvfp4_moe_meta
|
||||
if nvfp4_meta is not None:
|
||||
from sglang.srt.layers.quantization.modelopt_quant import (
|
||||
HybridFp8NvFp4Config,
|
||||
ModelOptFp4Config,
|
||||
)
|
||||
|
||||
# MTP MoE layers (model.decoder.*) are not NVFP4 quantized.
|
||||
nvfp4_exclude_modules = list(
|
||||
nvfp4_meta.get("exclude_modules") or []
|
||||
) + ["model.decoder.*"]
|
||||
nvfp4_config = ModelOptFp4Config(
|
||||
is_checkpoint_nvfp4_serialized=True,
|
||||
group_size=int(nvfp4_meta["group_size"]),
|
||||
exclude_modules=nvfp4_exclude_modules,
|
||||
packed_modules_mapping=quant_config.packed_modules_mapping,
|
||||
)
|
||||
quant_config = HybridFp8NvFp4Config(
|
||||
fp8_config=quant_config, nvfp4_config=nvfp4_config
|
||||
)
|
||||
if not _is_npu:
|
||||
major, minor = get_device_capability()
|
||||
|
||||
|
||||
@@ -2394,7 +2394,11 @@ class DeepseekV4ForCausalLM(nn.Module):
|
||||
assert len(cache_wqkv_a_weight) == 0, cache_wqkv_a_weight.keys()
|
||||
unloaded_params = params_dict.keys() - loaded_params
|
||||
|
||||
skipped_checking_patterns = ["attn_mqa.k_scale", "attn_mqa.v_scale"]
|
||||
skipped_checking_patterns = [
|
||||
"attn_mqa.k_scale",
|
||||
"attn_mqa.v_scale",
|
||||
"blockscale_swizzled",
|
||||
]
|
||||
if not self.pp_group.is_first_rank:
|
||||
skipped_checking_patterns.append("embed_tokens")
|
||||
if not self.pp_group.is_last_rank:
|
||||
|
||||
Reference in New Issue
Block a user