Fix DSV4 DSpark shared expert loading (#33312)
This commit is contained in:
@@ -20,6 +20,7 @@ from sglang.srt.configs.deepseek_v4 import DeepSeekV4Config
|
|||||||
from sglang.srt.environ import envs
|
from sglang.srt.environ import envs
|
||||||
from sglang.srt.layers.layernorm import RMSNorm
|
from sglang.srt.layers.layernorm import RMSNorm
|
||||||
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
from sglang.srt.layers.logits_processor import LogitsProcessorOutput
|
||||||
|
from sglang.srt.layers.moe.utils import is_shared_experts_fusion_disabled
|
||||||
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
from sglang.srt.layers.quantization.base_config import QuantizationConfig
|
||||||
from sglang.srt.layers.radix_attention import RadixAttention
|
from sglang.srt.layers.radix_attention import RadixAttention
|
||||||
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
from sglang.srt.layers.vocab_parallel_embedding import VocabParallelEmbedding
|
||||||
@@ -32,6 +33,7 @@ from sglang.srt.models.dbrx import ReplicatedLinear
|
|||||||
from sglang.srt.models.deepseek_v4 import (
|
from sglang.srt.models.deepseek_v4 import (
|
||||||
DEEPSEEK_V4_STACKED_PARAMS_MAPPING,
|
DEEPSEEK_V4_STACKED_PARAMS_MAPPING,
|
||||||
DeepseekV4DecoderLayer,
|
DeepseekV4DecoderLayer,
|
||||||
|
DeepseekV4ForCausalLM,
|
||||||
MqaAttentionBase,
|
MqaAttentionBase,
|
||||||
_dequant_fp8_wo_a_streaming,
|
_dequant_fp8_wo_a_streaming,
|
||||||
hc_head_torch,
|
hc_head_torch,
|
||||||
@@ -571,6 +573,12 @@ class DSparkV4Stage(DeepseekV4DecoderLayer):
|
|||||||
|
|
||||||
class DeepseekV4ForCausalLMDSpark(nn.Module):
|
class DeepseekV4ForCausalLMDSpark(nn.Module):
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def shared_experts_fusion_disable_reason(cls, hf_config, quant_config):
|
||||||
|
return DeepseekV4ForCausalLM.shared_experts_fusion_disable_reason(
|
||||||
|
hf_config, quant_config
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
config: DeepSeekV4Config,
|
config: DeepSeekV4Config,
|
||||||
@@ -580,6 +588,9 @@ class DeepseekV4ForCausalLMDSpark(nn.Module):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.config = config
|
self.config = config
|
||||||
self.quant_config = quant_config
|
self.quant_config = quant_config
|
||||||
|
self.num_fused_shared_experts = (
|
||||||
|
0 if is_shared_experts_fusion_disabled() else config.n_shared_experts
|
||||||
|
)
|
||||||
|
|
||||||
dspark_config = parse_dspark_draft_config(draft_hf_config=config)
|
dspark_config = parse_dspark_draft_config(draft_hf_config=config)
|
||||||
if not dspark_config.require_markov():
|
if not dspark_config.require_markov():
|
||||||
@@ -796,14 +807,18 @@ class DeepseekV4ForCausalLMDSpark(nn.Module):
|
|||||||
ckpt_gate_proj_name="gate_proj",
|
ckpt_gate_proj_name="gate_proj",
|
||||||
ckpt_down_proj_name="down_proj",
|
ckpt_down_proj_name="down_proj",
|
||||||
ckpt_up_proj_name="up_proj",
|
ckpt_up_proj_name="up_proj",
|
||||||
num_experts=self.config.n_routed_experts,
|
num_experts=(self.config.n_routed_experts + self.num_fused_shared_experts),
|
||||||
)
|
)
|
||||||
|
|
||||||
for name, loaded_weight in weights:
|
for name, loaded_weight in weights:
|
||||||
mapped = self._remap_dspark_weight_name(name)
|
mapped = self._remap_dspark_weight_name(name)
|
||||||
if mapped is None:
|
if mapped is None:
|
||||||
continue
|
continue
|
||||||
|
if self.num_fused_shared_experts > 0 and ".mlp.shared_experts." in mapped:
|
||||||
|
mapped = mapped.replace(
|
||||||
|
".mlp.shared_experts.",
|
||||||
|
f".mlp.experts.{self.config.n_routed_experts}.",
|
||||||
|
)
|
||||||
for param_name, weight_name, shard_id in stacked_params_mapping:
|
for param_name, weight_name, shard_id in stacked_params_mapping:
|
||||||
if weight_name not in mapped:
|
if weight_name not in mapped:
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -220,6 +220,7 @@ class TestRunaiModelStreamerLoader(CustomTestCase):
|
|||||||
remapper = SimpleNamespace(confidence_head=None)
|
remapper = SimpleNamespace(confidence_head=None)
|
||||||
model = SimpleNamespace(
|
model = SimpleNamespace(
|
||||||
config=SimpleNamespace(n_routed_experts=1),
|
config=SimpleNamespace(n_routed_experts=1),
|
||||||
|
num_fused_shared_experts=0,
|
||||||
named_parameters=lambda: [
|
named_parameters=lambda: [
|
||||||
("stages.0.self_attn.wo_a.weight", param),
|
("stages.0.self_attn.wo_a.weight", param),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,18 +1,25 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
|
||||||
|
from sglang.srt.configs.deepseek_v4 import DeepSeekV4Config
|
||||||
from sglang.srt.layers.moe.utils import (
|
from sglang.srt.layers.moe.utils import (
|
||||||
install_shared_experts_fusion_decision,
|
install_shared_experts_fusion_decision,
|
||||||
is_shared_experts_fusion_disabled,
|
is_shared_experts_fusion_disabled,
|
||||||
)
|
)
|
||||||
from sglang.srt.models.deepseek_v4 import DeepseekV4ForCausalLM
|
from sglang.srt.models.deepseek_v4 import DeepseekV4ForCausalLM
|
||||||
|
from sglang.srt.models.deepseek_v4_dspark import DeepseekV4ForCausalLMDSpark
|
||||||
from sglang.srt.runtime_context import get_context, get_exec, get_flags
|
from sglang.srt.runtime_context import get_context, get_exec, get_flags
|
||||||
from sglang.test.ci.ci_register import register_cpu_ci
|
from sglang.test.ci.ci_register import register_cpu_ci
|
||||||
|
from sglang.test.test_utils import CustomTestCase
|
||||||
|
|
||||||
register_cpu_ci(est_time=4, suite="base-a-test-cpu")
|
register_cpu_ci(est_time=4, suite="base-a-test-cpu")
|
||||||
|
|
||||||
|
|
||||||
class TestDeepseekV4SharedExpertFusionPolicy(unittest.TestCase):
|
class TestDeepseekV4SharedExpertFusionPolicy(CustomTestCase):
|
||||||
"""V4 fuses its shared expert only when explicitly asked to.
|
"""V4 fuses its shared expert only when explicitly asked to.
|
||||||
|
|
||||||
The gate is a question the loader asks the model class before any layer
|
The gate is a question the loader asks the model class before any layer
|
||||||
@@ -31,13 +38,24 @@ class TestDeepseekV4SharedExpertFusionPolicy(unittest.TestCase):
|
|||||||
lambda: setattr(get_flags().moe, "disable_shared_experts_fusion", None)
|
lambda: setattr(get_flags().moe, "disable_shared_experts_fusion", None)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _install(self, n_shared_experts=1):
|
def _install(self, model_class=DeepseekV4ForCausalLM, n_shared_experts=1):
|
||||||
install_shared_experts_fusion_decision(
|
install_shared_experts_fusion_decision(
|
||||||
DeepseekV4ForCausalLM,
|
model_class,
|
||||||
SimpleNamespace(n_shared_experts=n_shared_experts),
|
SimpleNamespace(n_shared_experts=n_shared_experts),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _make_dspark_config(self):
|
||||||
|
return DeepSeekV4Config(
|
||||||
|
architectures=["DeepseekV4ForCausalLMDSpark"],
|
||||||
|
quantization_config={},
|
||||||
|
rope_scaling={},
|
||||||
|
compress_ratios=[],
|
||||||
|
n_shared_experts=1,
|
||||||
|
dspark_markov_rank=1,
|
||||||
|
num_nextn_predict_layers=1,
|
||||||
|
)
|
||||||
|
|
||||||
def test_disables_shared_fusion_without_enforce(self):
|
def test_disables_shared_fusion_without_enforce(self):
|
||||||
self._publish(enforce=False)
|
self._publish(enforce=False)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@@ -68,6 +86,89 @@ class TestDeepseekV4SharedExpertFusionPolicy(unittest.TestCase):
|
|||||||
SimpleNamespace(n_shared_experts=2), None
|
SimpleNamespace(n_shared_experts=2), None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_dspark_entry_class_uses_the_v4_gate(self):
|
||||||
|
"""A DSV4 DSpark draft must inherit the target's default fusion policy."""
|
||||||
|
self._publish(enforce=False)
|
||||||
|
|
||||||
|
self._install(DeepseekV4ForCausalLMDSpark)
|
||||||
|
|
||||||
|
self.assertTrue(is_shared_experts_fusion_disabled())
|
||||||
|
|
||||||
|
def test_dspark_records_explicitly_forced_fusion(self):
|
||||||
|
"""A forced DSpark build must retain its fused shared-expert count."""
|
||||||
|
self._publish(enforce=True)
|
||||||
|
self._install(DeepseekV4ForCausalLMDSpark)
|
||||||
|
|
||||||
|
class Stage(nn.Module):
|
||||||
|
def __init__(self, **_kwargs):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
class MarkovHead(nn.Module):
|
||||||
|
def __init__(self, **_kwargs):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"sglang.srt.models.deepseek_v4_dspark.DSparkV4Stage",
|
||||||
|
Stage,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"sglang.srt.models.deepseek_v4_dspark.DSparkV4MarkovHead",
|
||||||
|
MarkovHead,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"sglang.srt.models.deepseek_v4_dspark.build_dspark_v4_confidence_head",
|
||||||
|
return_value=None,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
model = DeepseekV4ForCausalLMDSpark(self._make_dspark_config())
|
||||||
|
|
||||||
|
self.assertEqual(model.num_fused_shared_experts, 1)
|
||||||
|
|
||||||
|
def test_dspark_loads_forced_shared_expert_into_fused_slot(self):
|
||||||
|
"""Forced DSpark shared tensors must load instead of being skipped."""
|
||||||
|
config = self._make_dspark_config()
|
||||||
|
loaded = []
|
||||||
|
|
||||||
|
class Param:
|
||||||
|
def weight_loader(
|
||||||
|
self,
|
||||||
|
_param,
|
||||||
|
loaded_weight,
|
||||||
|
candidate,
|
||||||
|
*,
|
||||||
|
shard_id,
|
||||||
|
expert_id,
|
||||||
|
):
|
||||||
|
loaded.append((loaded_weight, candidate, shard_id, expert_id))
|
||||||
|
|
||||||
|
class DraftModel:
|
||||||
|
num_fused_shared_experts = 1
|
||||||
|
confidence_head = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
def named_parameters(self):
|
||||||
|
return [("stages.0.mlp.experts.w13_weight", Param())]
|
||||||
|
|
||||||
|
def _remap_dspark_weight_name(self, name):
|
||||||
|
return DeepseekV4ForCausalLMDSpark._remap_dspark_weight_name(self, name)
|
||||||
|
|
||||||
|
def _assert_confidence_head_loaded(self, **_kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
weight = torch.ones(1)
|
||||||
|
DeepseekV4ForCausalLMDSpark.load_weights(
|
||||||
|
DraftModel(),
|
||||||
|
[("mtp.0.ffn.shared_experts.w1.weight", weight)],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(len(loaded), 1)
|
||||||
|
self.assertIs(loaded[0][0], weight)
|
||||||
|
self.assertEqual(loaded[0][1], "stages.0.mlp.experts.w13_weight")
|
||||||
|
self.assertEqual(loaded[0][2:], ("w1", config.n_routed_experts))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user