Skip to content

Commit 6df92b6

Browse files
justinchubyCopilot
andcommitted
Match Dots1 routing normalization floor
Preserve the pinned llama.cpp denominator clamp for normalized Dots1 expert weights while leaving native DeepSeek routing unchanged. Add a low-score ONNX Runtime parity regression for the boundary. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Signed-off-by: Justin Chu <justinchuby@users.noreply.github.com>
1 parent 2878437 commit 6df92b6

5 files changed

Lines changed: 55 additions & 2 deletions

File tree

src/mobius/_configs/_base.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,6 +494,7 @@ class ArchitectureConfig(BaseModelConfig):
494494
n_group: int = 1
495495
topk_group: int = 1
496496
routed_scaling_factor: float = 1.0
497+
routing_weight_normalization_floor: float | None = None
497498
scoring_func: str = "softmax"
498499
topk_method: str = "greedy"
499500
first_k_dense_replace: int = 0

src/mobius/integrations/gguf/_config_mapping.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1522,6 +1522,7 @@ def _conventional_shared_moe_postprocess(
15221522
topk_method="greedy",
15231523
n_group=1,
15241524
topk_group=1,
1525+
routing_weight_normalization_floor=(6.103515625e-5 if arch == "dots1" else None),
15251526
use_expert_bias=use_expert_bias,
15261527
norm_topk_prob=norm_topk_prob,
15271528
routed_scaling_factor=route_scale,

src/mobius/integrations/gguf/_config_mapping_test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,9 @@ def test_exact_shared_moe_contract(
10301030
assert config.n_group == config.topk_group == 1
10311031
assert config.norm_topk_prob is (architecture != "deepseek")
10321032
assert config.routed_scaling_factor == pytest.approx(1.25)
1033+
assert config.routing_weight_normalization_floor == (
1034+
6.103515625e-5 if architecture == "dots1" else None
1035+
)
10331036
assert config.attn_qk_norm is qk_norm
10341037
assert config.tie_word_embeddings is False
10351038

src/mobius/models/deepseek.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def __init__(self, config: ArchitectureConfig):
7272
self.topk_group = config.topk_group
7373
self.routed_scaling_factor = config.routed_scaling_factor
7474
self.norm_topk_prob = config.norm_topk_prob
75+
self.routing_weight_normalization_floor = config.routing_weight_normalization_floor
7576
self.scoring_func = config.scoring_func
7677
self.topk_method = config.topk_method
7778
explicit_expert_bias = getattr(config, "use_expert_bias", None)
@@ -110,8 +111,13 @@ def forward(self, op: OpBuilder, hidden_states: ir.Value):
110111
# Normalize weights (V3 with norm_topk_prob=True)
111112
if self.norm_topk_prob:
112113
weight_sum = op.ReduceSum(routing_weights, [-1], keepdims=True)
113-
eps = 1e-20
114-
routing_weights = op.Div(routing_weights, op.Add(weight_sum, eps))
114+
if self.routing_weight_normalization_floor is None:
115+
denominator = op.Add(weight_sum, 1e-20)
116+
else:
117+
denominator = op.Max(
118+
weight_sum, float(self.routing_weight_normalization_floor)
119+
)
120+
routing_weights = op.Div(routing_weights, denominator)
115121

116122
# Apply routing scale
117123
routing_weights = op.Mul(routing_weights, float(self.routed_scaling_factor))

src/mobius/models/deepseek_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,48 @@ def test_sigmoid_gate_without_optional_correction_bias_has_no_bias_parameter():
112112
assert [name for name, _ in gate.named_parameters()] == ["weight"]
113113

114114

115+
def test_dots1_routing_normalization_uses_pinned_llama_floor():
116+
config = make_config(
117+
hidden_size=1,
118+
num_local_experts=2,
119+
num_experts_per_tok=2,
120+
scoring_func="sigmoid",
121+
use_expert_bias=False,
122+
norm_topk_prob=True,
123+
routing_weight_normalization_floor=6.103515625e-5,
124+
)
125+
gate = DeepSeekMoEGate(config)
126+
gate.weight.const_value = ir.tensor(np.ones((2, 1), dtype=np.float32))
127+
128+
hidden = ir.Value(
129+
name="hidden_states",
130+
shape=ir.Shape([1, 1, 1]),
131+
type=ir.TensorType(ir.DataType.FLOAT),
132+
)
133+
graph = ir.Graph(
134+
inputs=[hidden],
135+
outputs=[],
136+
nodes=[],
137+
name="dots1_routing_floor",
138+
opset_imports={"": OPSET_VERSION},
139+
)
140+
builder = GraphBuilder(graph)
141+
routing_weights, _ = gate(builder.op, hidden)
142+
routing_weights.name = "routing_weights"
143+
graph.outputs.append(routing_weights)
144+
145+
session = ort.InferenceSession(
146+
ir.to_proto(ir.Model(graph, ir_version=11)).SerializeToString(),
147+
providers=["CPUExecutionProvider"],
148+
)
149+
actual = session.run(None, {"hidden_states": np.array([[[-11.0]]], dtype=np.float32)})[0]
150+
score = 1.0 / (1.0 + np.exp(11.0))
151+
expected = np.full((1, 1, 2), score / 6.103515625e-5, dtype=np.float32)
152+
153+
np.testing.assert_allclose(actual, expected, rtol=2e-3, atol=1e-8)
154+
assert actual.sum() < 1.0
155+
156+
115157
@pytest.mark.parametrize(
116158
("scoring_func", "expects_bias"),
117159
[("softmax", False), ("sigmoid", True)],

0 commit comments

Comments
 (0)