Skip to content

Commit 1cb48e1

Browse files
committed
feat(v4/fusion): Stage D — 12 architecture presets + 3 new bricks
Adds the architecture preset registry and the three bricks needed to compose every entry of the Raschka LLM gallery from V4 BLOCK_BUILDERS. New bricks: - gqa_sliding (cppmega_v4/nn/sliding_attention.py) Gemma 4 / Arcee Trinity 5:1 sliding-window GQA slot. Thin wrapper around mx.fast.scaled_dot_product_attention with a fixed-window causal mask, asymmetric GQA, optional Q/K RMSNorm, RoPE. - cca_attention (cppmega_v4/nn/cca_attention.py) ZAYA1 Coarse Causal Attention. Two-stream attention: a fine sliding window over recent tokens + a mean-pooled coarse stream over older blocks, causally masked, concatenated for SDPA. - mamba3 (re-export from cppmega_mlx.nn.mamba3) Thin wrapper around Mamba3ReferenceBlock so Nemotron-style Mamba+GQA+MoE presets can compose an SSM brick without modifying the plugin (read-only re-export). All three are registered in BLOCK_BUILDERS, classified in compatibility._CATEGORY_BY_KIND, and added to the BrickGraph walker's KIND_BY_CLASS map. Architecture presets (cppmega_v4/architectures/presets.py): qwen3_next, kimi_linear, kimi_k2, deepseek_v3, deepseek_v4_flash, gemma4, mistral4, ling26, longcat, nemotron3, zaya1, arcee_trinity Each preset is a callable hidden_size -> list[dict] returning one repeat-unit's brick specs ready for from_block_specs. build_preset_specs(name, hidden_size, num_layers=N) replicates with unique brick names per repeat. Params are sized to be instantiable at hidden_size=64 for unit testing; tune via spec overrides for prod. Tests (tests/v4/test_fusion_stage_d.py, 58 cases): - new-brick forward shape preservation (with and without coarse stream) - new-brick input validation - BLOCK_BUILDERS registration + category coverage - per-preset (12 each): non-empty spec list, every kind known to BLOCK_BUILDERS, instantiation via from_block_specs, fusion plan covers every brick in declared order - replication: unique names, multiple-of-unit truncation, zero-layer - system: Qwen3-Next 3:1 / Ling 2.6 7:1 / Gemma4 5:1 / ZAYA1 / Nemotron3 region-detection sanity checks Full v4 regression: 534 passed / 5 skipped / 0 failed.
1 parent 7700f3b commit 1cb48e1

8 files changed

Lines changed: 895 additions & 2 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Architecture preset registry — JSON-shaped block specs for the
2+
gallery of public hybrid LLM architectures we want to be able to compose
3+
from V4 bricks.
4+
5+
Each preset is a list of ``{"kind": ..., "name": ..., "params": ...}``
6+
dicts ready to feed to
7+
:func:`cppmega_v4.fusion.brick_graph.from_block_specs`. The preset
8+
covers ONE repeat-unit of the architecture; callers replicate the
9+
returned list ``num_layers / repeat_unit_size`` times to build a full
10+
model.
11+
12+
Public API:
13+
- :data:`PRESETS`: ``dict[str, callable(int)]`` mapping a preset name
14+
to a function ``hidden_size -> list[dict]``.
15+
- :func:`build_preset_specs(name, hidden_size, *, num_layers=None)`:
16+
return the concatenated spec list ready to instantiate.
17+
- :func:`available_presets()`: sorted list of preset names.
18+
"""
19+
20+
from __future__ import annotations
21+
22+
from cppmega_v4.architectures.presets import (
23+
PRESETS,
24+
available_presets,
25+
build_preset_specs,
26+
)
27+
28+
__all__ = [
29+
"PRESETS",
30+
"available_presets",
31+
"build_preset_specs",
32+
]
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
"""12 architecture presets — JSON spec lists for the LLM gallery.
2+
3+
Each entry produces ONE repeat-unit's worth of bricks. Replicate to
4+
build a full model. Brick params are kept generic (small head/expert
5+
counts) so unit tests can instantiate every preset in seconds; tune for
6+
production via overrides on the returned spec list.
7+
8+
References:
9+
Qwen3-Next / 3.5 / 3.6 — 3:1 GDN + Gated Attention + MoE
10+
Kimi Linear 48B-A3B — 3:1 KDA + MLA + MoE
11+
Kimi K2 / K2.5 — 100% MLA-absorb + MoE
12+
DeepSeek V3 — MLA + MoE
13+
DeepSeek V4 Flash — hash-indexed sparse MLA + MoE
14+
Gemma 4 26B-A4B — 5:1 sliding/global GQA + MoE (with QK-norm)
15+
Mistral Small 4 119B — MLA-absorb + INT4-cache + 128 sparse MoE
16+
Ling 2.6 — 7:1 Bailing Linear + Bailing MLA + Bailing MoE
17+
LongCat — MLA + Bailing MoE (shortcut routing variant)
18+
Nemotron 3 Super — Mamba-2 + GQA + MoE
19+
ZAYA1-8B — CCA + 4:1 GQA + top-1 MoE
20+
Arcee Trinity Large — 3:1 sliding/global gated GQA + MoE
21+
"""
22+
23+
from __future__ import annotations
24+
25+
from collections.abc import Callable
26+
from typing import Any
27+
28+
29+
def _qwen3_next(hidden_size: int) -> list[dict[str, Any]]:
30+
nh = max(8, hidden_size // 64)
31+
nkv = max(2, nh // 8)
32+
return [
33+
{"kind": "gdn", "name": f"qwen3_gdn_{i}"} for i in range(3)
34+
] + [
35+
{"kind": "gated_attention", "name": "qwen3_attn",
36+
"params": {"num_attention_heads": nh, "num_key_value_heads": nkv,
37+
"head_dim": 64}},
38+
{"kind": "moe", "name": "qwen3_moe",
39+
"params": {"num_experts": 4, "top_k": 2}},
40+
]
41+
42+
43+
def _mla_params(hidden_size: int) -> dict[str, Any]:
44+
"""Defaults sized to ``hidden_size`` for our MLA brick (num_heads /
45+
LoRA ranks / head-dim splits). Matches ``_build_mla``'s setdefault
46+
rules so small hidden sizes (used in unit tests) work."""
47+
return {
48+
"num_heads": max(2, hidden_size // 32),
49+
"qk_nope_head_dim": 16,
50+
"qk_rope_head_dim": 8,
51+
"v_head_dim": 16,
52+
"q_lora_rank": max(32, hidden_size // 2),
53+
"kv_lora_rank": max(16, hidden_size // 4),
54+
}
55+
56+
57+
def _kimi_linear(hidden_size: int) -> list[dict[str, Any]]:
58+
return [
59+
{"kind": "kda", "name": f"kimi_kda_{i}"} for i in range(3)
60+
] + [
61+
{"kind": "mla", "name": "kimi_mla", "params": _mla_params(hidden_size)},
62+
{"kind": "moe", "name": "kimi_moe",
63+
"params": {"num_experts": 4, "top_k": 2}},
64+
]
65+
66+
67+
def _kimi_k2(hidden_size: int) -> list[dict[str, Any]]:
68+
return [
69+
{"kind": "mla_absorb", "name": "k2_mla", "params": _mla_params(hidden_size)},
70+
{"kind": "moe", "name": "k2_moe",
71+
"params": {"num_experts": 8, "top_k": 2}},
72+
]
73+
74+
75+
def _deepseek_v3(hidden_size: int) -> list[dict[str, Any]]:
76+
return [
77+
{"kind": "mla", "name": "dsv3_mla", "params": _mla_params(hidden_size)},
78+
{"kind": "moe", "name": "dsv3_moe",
79+
"params": {"num_experts": 6, "top_k": 2}},
80+
]
81+
82+
83+
def _deepseek_v4_flash(hidden_size: int) -> list[dict[str, Any]]:
84+
return [
85+
{"kind": "dsv4_attention", "name": "dsv4_attn"},
86+
{"kind": "moe", "name": "dsv4_moe",
87+
"params": {"num_experts": 6, "top_k": 2}},
88+
]
89+
90+
91+
def _gemma4(hidden_size: int) -> list[dict[str, Any]]:
92+
nh = max(8, hidden_size // 64)
93+
nkv = max(2, nh // 8)
94+
sliding_params = {"num_attention_heads": nh, "num_key_value_heads": nkv,
95+
"head_dim": 64, "sliding_window_size": 1024}
96+
global_params = {"num_attention_heads": nh, "num_key_value_heads": nkv,
97+
"head_dim": 64}
98+
return [
99+
{"kind": "gqa_sliding", "name": f"gemma_sw_{i}",
100+
"params": dict(sliding_params)} for i in range(5)
101+
] + [
102+
{"kind": "gated_attention", "name": "gemma_global",
103+
"params": dict(global_params)},
104+
{"kind": "moe", "name": "gemma_moe",
105+
"params": {"num_experts": 4, "top_k": 2}},
106+
]
107+
108+
109+
def _mistral4(hidden_size: int) -> list[dict[str, Any]]:
110+
return [
111+
{"kind": "mistral4_mla", "name": "mistral4_attn"},
112+
{"kind": "moe", "name": "mistral4_moe",
113+
"params": {"num_experts": 8, "top_k": 2}},
114+
]
115+
116+
117+
def _ling26(hidden_size: int) -> list[dict[str, Any]]:
118+
nh = max(8, hidden_size // 64)
119+
nkv = max(2, nh // 8)
120+
return [
121+
{"kind": "bailing_linear", "name": f"ling_la_{i}",
122+
"params": {"num_attention_heads": nh, "num_key_value_heads": nkv,
123+
"head_dim": 64}}
124+
for i in range(7)
125+
] + [
126+
{"kind": "bailing_mla", "name": "ling_mla",
127+
"params": {"num_attention_heads": nh, "num_key_value_heads": nkv,
128+
"head_dim": 64, "kv_lora_rank": 32,
129+
"qk_rope_head_dim": 16, "qk_nope_head_dim": 32,
130+
"v_head_dim": 32}},
131+
{"kind": "bailing_moe", "name": "ling_moe",
132+
"params": {"num_experts": 4, "top_k": 2}},
133+
]
134+
135+
136+
def _longcat(hidden_size: int) -> list[dict[str, Any]]:
137+
return [
138+
{"kind": "mla", "name": "longcat_mla", "params": _mla_params(hidden_size)},
139+
{"kind": "bailing_moe", "name": "longcat_moe",
140+
"params": {"num_experts": 4, "top_k": 2}},
141+
]
142+
143+
144+
def _nemotron3(hidden_size: int) -> list[dict[str, Any]]:
145+
nh = max(8, hidden_size // 64)
146+
return [
147+
{"kind": "mamba3", "name": "nemo_mamba"},
148+
{"kind": "attention", "name": "nemo_attn",
149+
"params": {"num_heads": nh, "head_dim": 64}},
150+
{"kind": "moe", "name": "nemo_moe",
151+
"params": {"num_experts": 4, "top_k": 2}},
152+
]
153+
154+
155+
def _zaya1(hidden_size: int) -> list[dict[str, Any]]:
156+
nh = max(8, hidden_size // 64)
157+
nkv = max(2, nh // 8)
158+
cca_params = {"num_attention_heads": nh, "num_key_value_heads": nkv,
159+
"head_dim": 64, "fine_window": 64, "coarse_block_size": 8}
160+
gqa_params = {"num_attention_heads": nh, "num_key_value_heads": nkv,
161+
"head_dim": 64}
162+
return [
163+
{"kind": "cca_attention", "name": "zaya_cca",
164+
"params": dict(cca_params)},
165+
] + [
166+
{"kind": "gated_attention", "name": f"zaya_gqa_{i}",
167+
"params": dict(gqa_params)} for i in range(4)
168+
] + [
169+
{"kind": "moe", "name": "zaya_moe",
170+
"params": {"num_experts": 4, "top_k": 1}},
171+
]
172+
173+
174+
def _arcee_trinity(hidden_size: int) -> list[dict[str, Any]]:
175+
nh = max(8, hidden_size // 64)
176+
nkv = max(2, nh // 8)
177+
sliding_params = {"num_attention_heads": nh, "num_key_value_heads": nkv,
178+
"head_dim": 64, "sliding_window_size": 1024}
179+
global_params = {"num_attention_heads": nh, "num_key_value_heads": nkv,
180+
"head_dim": 64}
181+
return [
182+
{"kind": "gqa_sliding", "name": f"arcee_sw_{i}",
183+
"params": dict(sliding_params)} for i in range(3)
184+
] + [
185+
{"kind": "gated_attention", "name": "arcee_global",
186+
"params": dict(global_params)},
187+
{"kind": "moe", "name": "arcee_moe",
188+
"params": {"num_experts": 4, "top_k": 2}},
189+
]
190+
191+
192+
# ---------------------------------------------------------------------------
193+
# Public registry
194+
# ---------------------------------------------------------------------------
195+
196+
197+
PRESETS: dict[str, Callable[[int], list[dict[str, Any]]]] = {
198+
"qwen3_next": _qwen3_next,
199+
"kimi_linear": _kimi_linear,
200+
"kimi_k2": _kimi_k2,
201+
"deepseek_v3": _deepseek_v3,
202+
"deepseek_v4_flash": _deepseek_v4_flash,
203+
"gemma4": _gemma4,
204+
"mistral4": _mistral4,
205+
"ling26": _ling26,
206+
"longcat": _longcat,
207+
"nemotron3": _nemotron3,
208+
"zaya1": _zaya1,
209+
"arcee_trinity": _arcee_trinity,
210+
}
211+
212+
213+
def available_presets() -> list[str]:
214+
return sorted(PRESETS.keys())
215+
216+
217+
def build_preset_specs(
218+
name: str,
219+
hidden_size: int,
220+
*,
221+
num_layers: int | None = None,
222+
) -> list[dict[str, Any]]:
223+
"""Return the spec list for ``name``, optionally replicated.
224+
225+
``num_layers`` is interpreted as the **total** number of brick specs
226+
desired. If it is not a multiple of the preset's repeat-unit size,
227+
the result is truncated to the largest multiple ≤ ``num_layers``.
228+
When omitted, returns a single repeat-unit.
229+
230+
Spec names are made unique across repetitions by appending a
231+
``_rep{i}`` suffix on every repeat after the first.
232+
"""
233+
if name not in PRESETS:
234+
raise KeyError(f"unknown preset {name!r}; available={available_presets()}")
235+
unit = PRESETS[name](hidden_size)
236+
if not unit:
237+
return []
238+
if num_layers is None:
239+
return [dict(s) for s in unit]
240+
if num_layers < 0:
241+
raise ValueError("num_layers must be ≥ 0")
242+
n_reps = num_layers // len(unit)
243+
out: list[dict[str, Any]] = []
244+
for r in range(n_reps):
245+
for s in unit:
246+
spec = dict(s)
247+
if r > 0:
248+
spec["name"] = f"{spec['name']}_rep{r}"
249+
spec["params"] = dict(spec.get("params") or {})
250+
out.append(spec)
251+
return out
252+
253+
254+
__all__ = [
255+
"PRESETS",
256+
"available_presets",
257+
"build_preset_specs",
258+
]

cppmega_v4/fusion/brick_graph.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ def _brick_kind_of(module: nn.Module) -> str | None:
173173
"Gemma4DrafterLayerBlock": "gemma4_drafter",
174174
"NemotronHMTPBlockWrapper": "nemotron_h_mtp",
175175
"MLABlock": "mla",
176+
# Stage D additions
177+
"GQAWithSlidingWindowBlock": "gqa_sliding",
178+
"CCAAttentionBlock": "cca_attention",
179+
"Mamba3ReferenceBlock": "mamba3",
176180
# gdn / kda / nsa / csa_hca / moe / mlp / engram / attention /
177181
# lightning_indexer are local closures in unified_superblock_v4.py
178182
# so their _SelfAttn / _MLP class names aren't unique. Callers
@@ -203,7 +207,7 @@ def from_mlx_model(
203207
children = (
204208
list(attr_order)
205209
if attr_order is not None
206-
else [name for name, _ in model.named_modules() if "." not in name and name]
210+
else [name for name, val in model.items() if isinstance(val, nn.Module)]
207211
)
208212
used_names: set[str] = set()
209213
for child_name in children:

cppmega_v4/fusion/compatibility.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@
5454
"mistral4_mla": "sdpa_attention",
5555
"dsv4_attention": "sdpa_attention",
5656
"bailing_mla": "sdpa_attention",
57+
# Stage D — new SDPA-backed bricks (sliding GQA, coarse causal attn)
58+
"gqa_sliding": "sdpa_attention",
59+
"cca_attention": "sdpa_attention",
5760
# cross attention drafter
5861
"gemma4_drafter": "cross_attn",
5962
# MTP block — opaque, treat like attention for fusion purposes

0 commit comments

Comments
 (0)