Skip to content

Commit 0fffab6

Browse files
committed
glm_moe_dsa: interleaved indexer rope default + schedule tests (M2: sparse path)
- indexer_rope_interleave defaults True for GLM-5.2 (the arch keeps the pre-#1431 interleaved convention; validated against the real 743B mxfp4 checkpoint — engaged-DSA generation at 3k context is coherent and an independent C/Metal engine reproduces the top-k selection exactly with interleaved rope). - test_all_models entry with indexer_types=["full","shared","full","shared"] (exercises shared-layer construction + asymmetric CacheList at B=1/B=2). - test_glm_moe_dsa_shared_schedule: full layers own indexers, shared layers don't (checkpoint ships no weights for them), interleaved rope flag, CacheList(kv,indexer) vs CacheList(kv), and the ENGAGED path — prefill L > index_topk then a decode step threading prev_topk full->shared, finite outputs. The sparse (>topk) path is additionally cross-validated externally: a C reference matched this implementation's engaged logits at 1e-6 on full AND shared schedules (tiny config), and per-layer parity probes on the real checkpoint at 3015-token context selected identical top-2048 sets.
1 parent 5d0847f commit 0fffab6

2 files changed

Lines changed: 105 additions & 0 deletions

File tree

mlx_lm/models/glm_moe_dsa.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ class ModelArgs(BaseModelArgs):
5757
index_share_for_mtp_iteration: bool = False
5858
index_topk_freq: int = 4
5959
index_skip_topk_offset: int = 3
60+
# GLM-5.2's indexer rope is interleaved (traditional), unlike the
61+
# DeepSeek-V3.2 default introduced in #1431. Validated against the real
62+
# 743B mxfp4 checkpoint: engaged-DSA generation at 3k context is coherent
63+
# and an independent C reference reproduces the selection exactly with
64+
# interleaved rope (glmx engine parity gates, 2026-07-03).
65+
indexer_rope_interleave: bool = True
6066

6167
def __post_init__(self):
6268
self.rope_scaling = self.rope_parameters

tests/test_models.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1966,6 +1966,68 @@ def test_gpt_oss(self):
19661966
model, args.model_type, args.vocab_size, args.num_hidden_layers
19671967
)
19681968

1969+
def test_glm_moe_dsa_shared_schedule(self):
1970+
"""GLM-5.2 full/shared indexer typing: shared layers carry no indexer
1971+
(checkpoint has no weights for them), reuse the previous full layer's
1972+
top-k, and the sparse path engages past index_topk tokens."""
1973+
from mlx_lm.models import glm_moe_dsa
1974+
1975+
args = glm_moe_dsa.ModelArgs(
1976+
model_type="glm_moe_dsa",
1977+
vocab_size=1000,
1978+
hidden_size=32,
1979+
index_head_dim=8,
1980+
index_n_heads=4,
1981+
index_topk=4,
1982+
intermediate_size=64,
1983+
moe_intermediate_size=16,
1984+
num_hidden_layers=4,
1985+
num_attention_heads=2,
1986+
num_key_value_heads=2,
1987+
n_shared_experts=1,
1988+
n_routed_experts=8,
1989+
routed_scaling_factor=2.5,
1990+
kv_lora_rank=16,
1991+
q_lora_rank=12,
1992+
qk_rope_head_dim=4,
1993+
v_head_dim=8,
1994+
qk_nope_head_dim=8,
1995+
topk_method="noaux_tc",
1996+
scoring_func="sigmoid",
1997+
norm_topk_prob=True,
1998+
n_group=1,
1999+
topk_group=1,
2000+
num_experts_per_tok=4,
2001+
moe_layer_freq=1,
2002+
first_k_dense_replace=1,
2003+
max_position_embeddings=1024,
2004+
rms_norm_eps=1e-5,
2005+
rope_parameters={"rope_theta": 8000000.0, "rope_type": "default"},
2006+
attention_bias=False,
2007+
indexer_types=["full", "shared", "full", "shared"],
2008+
)
2009+
model = glm_moe_dsa.Model(args)
2010+
2011+
# schedule construction: full layers own an indexer, shared layers don't
2012+
for i, t in enumerate(args.indexer_types):
2013+
has = hasattr(model.model.layers[i].self_attn, "indexer")
2014+
self.assertEqual(has, t == "full", f"layer {i} ({t}) indexer={has}")
2015+
2016+
# GLM-5.2's indexer rope is interleaved (post-#1431 flag default here)
2017+
self.assertTrue(model.model.layers[0].self_attn.indexer.rope.traditional)
2018+
2019+
# asymmetric cache: CacheList(kv, indexer) on full, CacheList(kv) on shared
2020+
cache = model.make_cache()
2021+
self.assertEqual(len(cache[0].caches), 2)
2022+
self.assertEqual(len(cache[1].caches), 1)
2023+
2024+
# sparse path ENGAGES past index_topk: prefill L=8 > topk=4, then a
2025+
# decode step (prev_topk threads full -> shared). Outputs stay finite.
2026+
out = model(mx.array([[1, 2, 3, 4, 5, 6, 7, 8]]), cache=cache)
2027+
out = model(mx.array([[9]]), cache=cache)
2028+
self.assertEqual(out.shape, (1, 1, args.vocab_size))
2029+
self.assertTrue(mx.isfinite(out).all())
2030+
19692031
def test_all_models(self):
19702032
test_configs = [
19712033
{
@@ -2249,6 +2311,43 @@ def test_all_models(self):
22492311
"tie_word_embeddings": False,
22502312
"num_nextn_predict_layers": 1,
22512313
},
2314+
{
2315+
# GLM-5.2: DeepSeek-V3.2 arch + full/shared indexer typing.
2316+
# ["full","shared",...] exercises the shared-layer construction
2317+
# (del'd indexer, asymmetric CacheList) at B=1 and B=2.
2318+
"model_type": "glm_moe_dsa",
2319+
"vocab_size": 1000,
2320+
"hidden_size": 32,
2321+
"index_head_dim": 8,
2322+
"index_n_heads": 4,
2323+
"index_topk": 4,
2324+
"intermediate_size": 64,
2325+
"moe_intermediate_size": 16,
2326+
"num_hidden_layers": 4,
2327+
"num_attention_heads": 2,
2328+
"num_key_value_heads": 2,
2329+
"n_shared_experts": 1,
2330+
"n_routed_experts": 8,
2331+
"routed_scaling_factor": 2.5,
2332+
"kv_lora_rank": 16,
2333+
"q_lora_rank": 12,
2334+
"qk_rope_head_dim": 4,
2335+
"v_head_dim": 8,
2336+
"qk_nope_head_dim": 8,
2337+
"topk_method": "noaux_tc",
2338+
"scoring_func": "sigmoid",
2339+
"norm_topk_prob": True,
2340+
"n_group": 1,
2341+
"topk_group": 1,
2342+
"num_experts_per_tok": 4,
2343+
"moe_layer_freq": 1,
2344+
"first_k_dense_replace": 1,
2345+
"max_position_embeddings": 1024,
2346+
"rms_norm_eps": 1e-5,
2347+
"rope_parameters": {"rope_theta": 8000000.0, "rope_type": "default"},
2348+
"attention_bias": False,
2349+
"indexer_types": ["full", "shared", "full", "shared"],
2350+
},
22522351
{
22532352
"model_type": "granite",
22542353
"hidden_size": 128,

0 commit comments

Comments
 (0)