DedeProGames commited on
Commit
70038b6
·
verified ·
1 Parent(s): 9c2ef59

DynamicMind-MoE: 30.2M total / 8.9M active sparse MoE, upcycled from DynamicMind-Mini

Browse files
README.md ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ datasets:
4
+ - HuggingFaceFW/fineweb-edu
5
+ - HuggingFaceTB/finemath
6
+ - HuggingFaceTB/smollm-corpus
7
+ language:
8
+ - en
9
+ pipeline_tag: text-generation
10
+ library_name: transformers
11
+ base_model: DedeProGames/DynamicMind-Mini
12
+ tags:
13
+ - causal-lm
14
+ - language-model
15
+ - base-model
16
+ - mixture-of-experts
17
+ - sparse-moe
18
+ - dynamicmind
19
+ - digit-tokenizer
20
+ - pytorch
21
+ - custom-code
22
+ - trust-remote-code
23
+ ---
24
+
25
+ ![Banner](https://cdn-uploads.huggingface.co/production/uploads/685ea8ff7b4139b6845ce395/0QdBKT5iKdrEy3iwLosAH.png)
26
+
27
+ # DynamicMind-MoE
28
+
29
+ DynamicMind-MoE is a sparse mixture-of-experts decoder-only model trained on [FineWeb-Edu](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu), [SmolLM-Corpus](https://huggingface.co/datasets/HuggingFaceTB/smollm-corpus) and [FineMath](https://huggingface.co/datasets/HuggingFaceTB/finemath)
30
+
31
+ The model has **30.2M total parameters but activates only 8.9M per token**, and was initialised by sparse-upcycling [DynamicMind-Mini](https://huggingface.co/DedeProGames/DynamicMind-Mini). It keeps the same custom 8k-token byte-level BPE tokenizer with digit-aware tokenization, so inference cost per token is unchanged from the dense model while total capacity is 3.4x larger.
32
+
33
+ ## Model Details
34
+
35
+ | Field | Value |
36
+ |---|---:|
37
+ | Total parameters | 30,150,912 |
38
+ | Active parameters per token | 8,917,248 |
39
+ | Architecture | Sparse MoE Llama-style decoder |
40
+ | Layers | 9 |
41
+ | Hidden size | 256 |
42
+ | Routed experts per layer | 14 |
43
+ | Shared experts per layer | 1 |
44
+ | Experts activated per token | 2 routed + 1 shared |
45
+ | Expert intermediate size | 256 |
46
+ | Attention heads | 8 |
47
+ | KV heads | 2 |
48
+ | Vocabulary size | 8,192 |
49
+ | Context length | 1,024 |
50
+ | Embeddings | Tied input/output embeddings |
51
+ | Weight format | safetensors |
52
+
53
+ ## Mixture of Experts
54
+
55
+ Each block's dense MLP is replaced by one always-on shared expert plus 14 fine-grained routed experts, of which the router selects the top 2 per token. Shared + top-2 at intermediate size 256 reproduces the dense model's exact active parameter count.
56
+
57
+ Load balancing uses an auxiliary-loss-free bias (DeepSeek-V3 style): a per-expert bias steers *selection* toward idle experts while the combining weights come from the unbiased softmax, so balancing costs no gradient interference.
58
+
59
+ Routing is **per token, per layer** — a single sequence touches many different experts, and the 9 routers are independent of each other.
60
+
61
+ ## Tokenizer
62
+
63
+ DynamicMind-MoE uses the same digit-aware 8k tokenizer as [DynamicMind-Mini](https://huggingface.co/DedeProGames/DynamicMind-Mini).
64
+
65
+ Digits are kept as separate tokens so numbers do not collapse into large number tokens during tokenization.
66
+
67
+ Digit IDs:
68
+
69
+ | Token | ID |
70
+ |---|---:|
71
+ | `1` | 9 |
72
+ | `2` | 10 |
73
+ | `3` | 11 |
74
+ | `4` | 12 |
75
+ | `5` | 13 |
76
+ | `6` | 14 |
77
+ | `7` | 15 |
78
+ | `8` | 16 |
79
+ | `9` | 17 |
80
+ | `0` | 18 |
81
+
82
+ ## Training
83
+
84
+ | Field | Value |
85
+ |---|---:|
86
+ | Initialisation | Sparse upcycling from DynamicMind-Mini |
87
+ | Tokens seen | 10,008,133,632 |
88
+ | Optimizer steps | 8,484 |
89
+ | Sequence length | 1,024 |
90
+ | Tokens per optimizer step | 1,179,648 |
91
+ | Peak learning rate | 1e-4 |
92
+ | Min learning rate | 1e-5 |
93
+ | LR schedule | Cosine decay after 200 warmup steps |
94
+ | Optimizer | AdamW (betas 0.9, 0.95) |
95
+ | Weight decay | 0.1 |
96
+ | Gradient clipping | 1.0 |
97
+ | Seed | 1337 |
98
+ | Hardware | 1x RTX 3060 12GB, 39.5h |
99
+
100
+ Data mixture: FineWeb-Edu `sample-10BT` 50.0%, Cosmopedia-v2 27.8%, FineMath-4plus 22.2%.
101
+
102
+ Embeddings, attention and norms transferred 1:1 from the dense model. The dense MLP (intermediate size 768) splits exactly into three 256-wide slices — because `down_proj` sums over the intermediate axis — and each expert was seeded from one slice plus small noise to break router symmetry.
103
+
104
+ ## Benchmarks
105
+
106
+ Self-reported results from the official [BananaMind Base Bench 1.1](https://huggingface.co/datasets/BananaMind/BananaMind-Base-Bench-1.1) script, all measured with the same runner, dtype (bfloat16) and GPU.
107
+
108
+ | Model | Total params | Active params | Overall Elo |
109
+ |---|---:|---:|---:|
110
+ | BananaMind-2-Medium | 55.9M | 55.9M | 1,037 |
111
+ | GPT-2 | 124M | 124M | 990 |
112
+ | BananaMind-2-Nano | 12.1M | 12.1M | 915 |
113
+ | **DynamicMind-MoE** | **30.2M** | **8.9M** | **912** |
114
+ | Pythia-14m-deduped | 14M | 14M | 909 |
115
+ | BananaMind-2-MoE | 26.1M | — | 903 |
116
+ | DynamicMind-Mini | 8.9M | 8.9M | 868 |
117
+
118
+ **Detailed DynamicMind-MoE result**
119
+
120
+ | Category | Passed | Elo |
121
+ |---|---:|---:|
122
+ | **Overall** | **137 / 350** | **912** |
123
+ | Language completion | 38 / 50 | 1,115 |
124
+ | Logical reasoning | 17 / 50 | 970 |
125
+ | World knowledge | 25 / 50 | 951 |
126
+ | Commonsense | 23 / 50 | 923 |
127
+ | Context tracking | 15 / 50 | 843 |
128
+ | Quantitative | 11 / 50 | 822 |
129
+ | Code completion | 8 / 50 | 793 |
130
+
131
+ Against the dense DynamicMind-Mini the MoE gains +44 Elo overall at identical inference cost, improving in six of seven categories. Quantitative is the exception (822 vs 837), despite FineMath being 22% of the corpus.
132
+
133
+ Scores are self-evaluated and may vary with the benchmark revision, Transformers version, dtype, hardware, and generation settings. Cross-tokenizer comparisons (e.g. against GPT-2's 50k vocabulary) carry a residual tokenizer effect that mean-per-token log-probability does not fully remove.
134
+
135
+ ## Usage
136
+
137
+ This model uses custom architecture code, so load it with `trust_remote_code=True`.
138
+
139
+ Install dependencies:
140
+
141
+ ```bash
142
+ pip install -U transformers safetensors torch
143
+ ```
144
+
145
+ Run inference:
146
+
147
+ ```python
148
+ import torch
149
+ from transformers import AutoTokenizer, AutoModelForCausalLM
150
+
151
+ model_id = "DedeProGames/DynamicMind-MoE"
152
+
153
+ tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
154
+
155
+ model = AutoModelForCausalLM.from_pretrained(
156
+ model_id,
157
+ trust_remote_code=True,
158
+ torch_dtype=torch.bfloat16 if torch.cuda.is_available() and torch.cuda.is_bf16_supported() else torch.float16,
159
+ ).cuda().eval()
160
+
161
+ prompt = "The meaning of life is "
162
+ input_ids = tokenizer(prompt, return_tensors="pt").input_ids.to(model.device)
163
+
164
+ with torch.no_grad():
165
+ output = model.generate(
166
+ input_ids=input_ids,
167
+ max_new_tokens=64,
168
+ do_sample=False,
169
+ repetition_penalty=1.1,
170
+ pad_token_id=tokenizer.eos_token_id,
171
+ eos_token_id=tokenizer.eos_token_id,
172
+ )
173
+
174
+ print(tokenizer.decode(output[0], skip_special_tokens=True))
175
+ ```
176
+
177
+ ## Limitations
178
+
179
+ This is a base model, not instruction-tuned — it continues text rather than following instructions. At 8.9M active parameters it reproduces register and structure well (encyclopedic text reads encyclopedic, code keeps valid indentation) but is frequently wrong on facts and arithmetic. Keep a finite generation limit and do not use it for high-stakes decisions.
180
+
181
+ ## License
182
+
183
+ Apache 2.0
config.json ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "transformers_version": "5.5.3",
3
+ "architectures": [
4
+ "DynamicMindMoEForCausalLM"
5
+ ],
6
+ "output_hidden_states": false,
7
+ "return_dict": true,
8
+ "dtype": "float32",
9
+ "chunk_size_feed_forward": 0,
10
+ "is_encoder_decoder": false,
11
+ "id2label": {
12
+ "0": "LABEL_0",
13
+ "1": "LABEL_1"
14
+ },
15
+ "label2id": {
16
+ "LABEL_0": 0,
17
+ "LABEL_1": 1
18
+ },
19
+ "problem_type": null,
20
+ "_name_or_path": "",
21
+ "bos_token_id": 0,
22
+ "eos_token_id": 0,
23
+ "pad_token_id": 1,
24
+ "tie_word_embeddings": true,
25
+ "vocab_size": 8192,
26
+ "hidden_size": 256,
27
+ "intermediate_size": 768,
28
+ "moe_intermediate_size": 256,
29
+ "num_hidden_layers": 9,
30
+ "num_attention_heads": 8,
31
+ "num_key_value_heads": 2,
32
+ "num_routed_experts": 14,
33
+ "num_shared_experts": 1,
34
+ "num_experts_per_token": 2,
35
+ "first_k_dense_layers": 0,
36
+ "norm_topk_prob": true,
37
+ "router_aux_loss_coef": 0.01,
38
+ "router_z_loss_coef": 0.001,
39
+ "router_bias_update_rate": 0.001,
40
+ "use_aux_loss_free_balancing": true,
41
+ "max_position_embeddings": 1024,
42
+ "rms_norm_eps": 1e-05,
43
+ "rope_theta": 10000.0,
44
+ "attention_dropout": 0.0,
45
+ "model_type": "dynamicmind_moe",
46
+ "output_attentions": false,
47
+ "auto_map": {
48
+ "AutoConfig": "configuration_dynamicmind_moe.DynamicMindMoEConfig",
49
+ "AutoModelForCausalLM": "modeling_dynamicmind_moe.DynamicMindMoEForCausalLM"
50
+ }
51
+ }
configuration_dynamicmind_moe.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers.configuration_utils import PretrainedConfig
2
+
3
+
4
+ class DynamicMindMoEConfig(PretrainedConfig):
5
+ """DynamicMind-MoE: sparse mixture-of-experts variant of DynamicMind-Mini.
6
+
7
+ The dense MLP (intermediate 768) is replaced by one always-on shared expert
8
+ plus `num_routed_experts` fine-grained experts (intermediate 256), of which
9
+ `num_experts_per_token` are selected. Shared + top-2 reproduces the dense
10
+ layer's exact active parameter count, so inference cost per token is
11
+ unchanged while total capacity grows ~3.4x.
12
+ """
13
+
14
+ model_type = "dynamicmind_moe"
15
+
16
+ def __init__(
17
+ self,
18
+ vocab_size=8192,
19
+ hidden_size=256,
20
+ intermediate_size=768, # kept for dense layers / upcycling source
21
+ moe_intermediate_size=256, # per-expert width (768 / 3)
22
+ num_hidden_layers=9,
23
+ num_attention_heads=8,
24
+ num_key_value_heads=2,
25
+ num_routed_experts=14,
26
+ num_shared_experts=1,
27
+ num_experts_per_token=2,
28
+ first_k_dense_layers=0, # keep the first K blocks dense if desired
29
+ norm_topk_prob=True,
30
+ router_aux_loss_coef=0.01,
31
+ router_z_loss_coef=0.001,
32
+ router_bias_update_rate=0.001, # aux-loss-free balancing (DeepSeek-V3)
33
+ use_aux_loss_free_balancing=True,
34
+ max_position_embeddings=1024,
35
+ rms_norm_eps=1e-5,
36
+ rope_theta=10000.0,
37
+ attention_dropout=0.0,
38
+ tie_word_embeddings=True,
39
+ bos_token_id=0,
40
+ eos_token_id=0,
41
+ pad_token_id=1,
42
+ **kwargs,
43
+ ):
44
+ super().__init__(
45
+ bos_token_id=bos_token_id,
46
+ eos_token_id=eos_token_id,
47
+ pad_token_id=pad_token_id,
48
+ tie_word_embeddings=tie_word_embeddings,
49
+ **kwargs,
50
+ )
51
+
52
+ self.vocab_size = vocab_size
53
+ self.hidden_size = hidden_size
54
+ self.intermediate_size = intermediate_size
55
+ self.moe_intermediate_size = moe_intermediate_size
56
+ self.num_hidden_layers = num_hidden_layers
57
+ self.num_attention_heads = num_attention_heads
58
+ self.num_key_value_heads = num_key_value_heads
59
+
60
+ self.num_routed_experts = num_routed_experts
61
+ self.num_shared_experts = num_shared_experts
62
+ self.num_experts_per_token = num_experts_per_token
63
+ self.first_k_dense_layers = first_k_dense_layers
64
+ self.norm_topk_prob = norm_topk_prob
65
+
66
+ self.router_aux_loss_coef = router_aux_loss_coef
67
+ self.router_z_loss_coef = router_z_loss_coef
68
+ self.router_bias_update_rate = router_bias_update_rate
69
+ self.use_aux_loss_free_balancing = use_aux_loss_free_balancing
70
+
71
+ self.max_position_embeddings = max_position_embeddings
72
+ self.rms_norm_eps = rms_norm_eps
73
+ self.rope_theta = rope_theta
74
+ self.attention_dropout = attention_dropout
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:28e7c84cded021b4d707f5a3535156d659d8223a3f37498735f5050a347d6d01
3
+ size 120656928
modeling_dynamicmind_moe.py ADDED
@@ -0,0 +1,339 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import torch.nn as nn
3
+ import torch.nn.functional as F
4
+
5
+ from transformers.modeling_utils import PreTrainedModel
6
+ from transformers.generation import GenerationMixin
7
+ from transformers.modeling_outputs import MoeCausalLMOutputWithPast
8
+ from transformers.cache_utils import Cache, DynamicCache
9
+
10
+ from .configuration_dynamicmind_moe import DynamicMindMoEConfig
11
+
12
+
13
+ class DynamicMindRMSNorm(nn.Module):
14
+ def __init__(self, hidden_size, eps=1e-5):
15
+ super().__init__()
16
+ self.weight = nn.Parameter(torch.ones(hidden_size))
17
+ self.eps = eps
18
+
19
+ def forward(self, x):
20
+ dtype = x.dtype
21
+ x = x.float()
22
+ x = x * torch.rsqrt(x.pow(2).mean(dim=-1, keepdim=True) + self.eps)
23
+ return (self.weight * x).to(dtype)
24
+
25
+
26
+ class DynamicMindRotaryEmbedding(nn.Module):
27
+ """RoPE with a cached inv_freq.
28
+
29
+ The dense model rebuilt inv_freq on every forward of every layer; caching it
30
+ removes 9 redundant allocations per step.
31
+
32
+ inv_freq is a constant derived from config, so persistent=False looks
33
+ correct — but from_pretrained materialises tensors straight from the
34
+ checkpoint onto meta-device modules, never running __init__'s value nor
35
+ _load_from_state_dict for it. A non-persistent buffer therefore survives
36
+ loading as uninitialised `torch.empty` garbage, silently scrambling RoPE:
37
+ measured 833 vs 908 Elo on identical weights. Persisting the 16 floats is
38
+ the only variant that loads correctly through every path.
39
+ """
40
+
41
+ def __init__(self, head_dim, rope_theta, max_position_embeddings):
42
+ super().__init__()
43
+ self.head_dim = head_dim
44
+ self.rope_theta = rope_theta
45
+ self.register_buffer("inv_freq", self._compute(), persistent=True)
46
+ self.max_seq_len_cached = 0
47
+
48
+ def _compute(self):
49
+ return 1.0 / (self.rope_theta ** (
50
+ torch.arange(0, self.head_dim, 2).float() / self.head_dim))
51
+
52
+ def _load_from_state_dict(self, state_dict, prefix, *args, **kwargs):
53
+ super()._load_from_state_dict(state_dict, prefix, *args, **kwargs)
54
+ with torch.no_grad():
55
+ self.inv_freq.copy_(self._compute().to(self.inv_freq.device))
56
+
57
+ def forward(self, x, position_ids):
58
+ freqs = position_ids[:, :, None].float() * self.inv_freq[None, None, :]
59
+ return freqs.cos().to(x.dtype), freqs.sin().to(x.dtype)
60
+
61
+
62
+ def apply_rope(q, k, cos, sin):
63
+ cos = cos[:, None, :, :]
64
+ sin = sin[:, None, :, :]
65
+
66
+ def rotate(x):
67
+ even, odd = x[..., 0::2], x[..., 1::2]
68
+ return torch.stack((even * cos - odd * sin, even * sin + odd * cos), dim=-1).flatten(-2)
69
+
70
+ return rotate(q), rotate(k)
71
+
72
+
73
+ class DynamicMindAttention(nn.Module):
74
+ def __init__(self, config, layer_idx):
75
+ super().__init__()
76
+ self.layer_idx = layer_idx
77
+ self.num_heads = config.num_attention_heads
78
+ self.num_kv_heads = config.num_key_value_heads
79
+ self.head_dim = config.hidden_size // config.num_attention_heads
80
+ self.attention_dropout = config.attention_dropout
81
+
82
+ self.q_proj = nn.Linear(config.hidden_size, self.num_heads * self.head_dim, bias=False)
83
+ self.k_proj = nn.Linear(config.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
84
+ self.v_proj = nn.Linear(config.hidden_size, self.num_kv_heads * self.head_dim, bias=False)
85
+ self.o_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
86
+
87
+ def forward(self, x, cos, sin, attention_mask=None, past_key_values=None, cache_position=None):
88
+ bsz, q_len, _ = x.shape
89
+
90
+ q = self.q_proj(x).view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)
91
+ k = self.k_proj(x).view(bsz, q_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
92
+ v = self.v_proj(x).view(bsz, q_len, self.num_kv_heads, self.head_dim).transpose(1, 2)
93
+
94
+ q, k = apply_rope(q, k, cos, sin)
95
+
96
+ if past_key_values is not None:
97
+ k, v = past_key_values.update(k, v, self.layer_idx, {"cache_position": cache_position})
98
+
99
+ if self.num_kv_heads != self.num_heads:
100
+ repeats = self.num_heads // self.num_kv_heads
101
+ k = k.repeat_interleave(repeats, dim=1)
102
+ v = v.repeat_interleave(repeats, dim=1)
103
+
104
+ is_causal = attention_mask is None and q_len > 1
105
+ y = F.scaled_dot_product_attention(
106
+ q, k, v,
107
+ attn_mask=attention_mask,
108
+ dropout_p=self.attention_dropout if self.training else 0.0,
109
+ is_causal=is_causal,
110
+ )
111
+ y = y.transpose(1, 2).contiguous().view(bsz, q_len, -1)
112
+ return self.o_proj(y)
113
+
114
+
115
+ class DynamicMindMLP(nn.Module):
116
+ def __init__(self, hidden_size, intermediate_size):
117
+ super().__init__()
118
+ self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
119
+ self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
120
+ self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)
121
+
122
+ def forward(self, x):
123
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
124
+
125
+
126
+ class DynamicMindMoE(nn.Module):
127
+ """Shared expert + top-k routed fine-grained experts.
128
+
129
+ The shared expert runs on every token and absorbs knowledge common to all
130
+ inputs, so the routed experts are free to specialise instead of each
131
+ re-learning the same basics.
132
+ """
133
+
134
+ def __init__(self, config):
135
+ super().__init__()
136
+ self.num_routed = config.num_routed_experts
137
+ self.top_k = config.num_experts_per_token
138
+ self.norm_topk_prob = config.norm_topk_prob
139
+ self.aux_free = config.use_aux_loss_free_balancing
140
+
141
+ self.experts = nn.ModuleList([
142
+ DynamicMindMLP(config.hidden_size, config.moe_intermediate_size)
143
+ for _ in range(self.num_routed)
144
+ ])
145
+ self.shared_experts = nn.ModuleList([
146
+ DynamicMindMLP(config.hidden_size, config.moe_intermediate_size)
147
+ for _ in range(config.num_shared_experts)
148
+ ])
149
+
150
+ self.router = nn.Linear(config.hidden_size, self.num_routed, bias=False)
151
+
152
+ # Aux-loss-free balancing: a per-expert bias nudged toward even load.
153
+ # It steers selection only — never the combining weights — so it costs
154
+ # no gradient interference, unlike an auxiliary loss.
155
+ self.register_buffer("expert_bias", torch.zeros(self.num_routed), persistent=True)
156
+ self.bias_update_rate = config.router_bias_update_rate
157
+
158
+ def forward(self, x):
159
+ bsz, seq_len, hidden = x.shape
160
+ flat = x.view(-1, hidden)
161
+ n_tokens = flat.size(0)
162
+
163
+ logits = self.router(flat) # [T, E]
164
+ probs = F.softmax(logits, dim=-1, dtype=torch.float)
165
+
166
+ scores = probs + self.expert_bias if self.aux_free else probs
167
+ _, topk_idx = torch.topk(scores, self.top_k, dim=-1)
168
+ topk_w = probs.gather(-1, topk_idx) # weights from unbiased probs
169
+ if self.norm_topk_prob:
170
+ topk_w = topk_w / topk_w.sum(dim=-1, keepdim=True).clamp_min(1e-9)
171
+ topk_w = topk_w.to(x.dtype)
172
+
173
+ out = torch.zeros_like(flat)
174
+ for expert in self.shared_experts:
175
+ out = out + expert(flat)
176
+
177
+ # one-hot over experts -> per-expert token lists
178
+ mask = torch.zeros(n_tokens, self.num_routed, dtype=torch.bool, device=x.device)
179
+ mask.scatter_(1, topk_idx, True)
180
+ load = mask.sum(0)
181
+
182
+ for e in range(self.num_routed):
183
+ idx = mask[:, e].nonzero(as_tuple=True)[0]
184
+ if idx.numel() == 0:
185
+ continue
186
+ slot = (topk_idx[idx] == e).float().argmax(dim=-1)
187
+ w = topk_w[idx].gather(-1, slot[:, None])
188
+ out.index_add_(0, idx, self.experts[e](flat[idx]) * w)
189
+
190
+ if self.training and self.aux_free:
191
+ with torch.no_grad():
192
+ target = n_tokens * self.top_k / self.num_routed
193
+ self.expert_bias += self.bias_update_rate * (target - load.float()).sign()
194
+
195
+ # Reported for logging even when aux-free balancing is on.
196
+ frac_tokens = load.float() / (n_tokens * self.top_k)
197
+ frac_probs = probs.mean(dim=0)
198
+ aux_loss = self.num_routed * (frac_tokens * frac_probs).sum()
199
+ z_loss = torch.logsumexp(logits.float(), dim=-1).pow(2).mean()
200
+
201
+ return out.view(bsz, seq_len, hidden), aux_loss, z_loss, load
202
+
203
+
204
+ class DynamicMindBlock(nn.Module):
205
+ def __init__(self, config, layer_idx):
206
+ super().__init__()
207
+ self.input_layernorm = DynamicMindRMSNorm(config.hidden_size, config.rms_norm_eps)
208
+ self.self_attn = DynamicMindAttention(config, layer_idx)
209
+ self.post_attention_layernorm = DynamicMindRMSNorm(config.hidden_size, config.rms_norm_eps)
210
+
211
+ self.is_moe = layer_idx >= config.first_k_dense_layers
212
+ if self.is_moe:
213
+ self.mlp = DynamicMindMoE(config)
214
+ else:
215
+ self.mlp = DynamicMindMLP(config.hidden_size, config.intermediate_size)
216
+
217
+ def forward(self, x, cos, sin, attention_mask=None, past_key_values=None, cache_position=None):
218
+ x = x + self.self_attn(self.input_layernorm(x), cos, sin,
219
+ attention_mask, past_key_values, cache_position)
220
+ h = self.post_attention_layernorm(x)
221
+ if self.is_moe:
222
+ delta, aux, z, load = self.mlp(h)
223
+ return x + delta, aux, z, load
224
+ return x + self.mlp(h), None, None, None
225
+
226
+
227
+ class DynamicMindMoEPreTrainedModel(PreTrainedModel):
228
+ config_class = DynamicMindMoEConfig
229
+ base_model_prefix = "model"
230
+ supports_gradient_checkpointing = True
231
+ _no_split_modules = ["DynamicMindBlock"]
232
+
233
+ def _init_weights(self, module):
234
+ if isinstance(module, nn.Linear):
235
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
236
+ if module.bias is not None:
237
+ nn.init.zeros_(module.bias)
238
+ elif isinstance(module, nn.Embedding):
239
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
240
+
241
+
242
+ class DynamicMindMoEForCausalLM(DynamicMindMoEPreTrainedModel, GenerationMixin):
243
+ _tied_weights_keys = {"lm_head.weight": "embed_tokens.weight"}
244
+
245
+ def __init__(self, config):
246
+ super().__init__(config)
247
+ self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size)
248
+ self.layers = nn.ModuleList([
249
+ DynamicMindBlock(config, i) for i in range(config.num_hidden_layers)
250
+ ])
251
+ self.norm = DynamicMindRMSNorm(config.hidden_size, config.rms_norm_eps)
252
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
253
+ self.rotary = DynamicMindRotaryEmbedding(
254
+ config.hidden_size // config.num_attention_heads,
255
+ config.rope_theta,
256
+ config.max_position_embeddings,
257
+ )
258
+ if config.tie_word_embeddings:
259
+ self.lm_head.weight = self.embed_tokens.weight
260
+ self.post_init()
261
+
262
+ def tie_weights(self, *args, **kwargs):
263
+ if getattr(self.config, "tie_word_embeddings", True):
264
+ self.lm_head.weight = self.embed_tokens.weight
265
+
266
+ def get_input_embeddings(self):
267
+ return self.embed_tokens
268
+
269
+ def set_input_embeddings(self, value):
270
+ self.embed_tokens = value
271
+
272
+ def forward(self, input_ids=None, attention_mask=None, position_ids=None,
273
+ past_key_values=None, labels=None, use_cache=True, **kwargs):
274
+ # cache_position is read from kwargs rather than declared: transformers
275
+ # warns about remote-code models whose signature expects it, and plans
276
+ # to stop passing it. It is derived below whenever it is absent.
277
+ cache_position = kwargs.get("cache_position")
278
+ x = self.embed_tokens(input_ids)
279
+
280
+ if use_cache and past_key_values is None:
281
+ past_key_values = DynamicCache()
282
+ past_len = past_key_values.get_seq_length() if isinstance(past_key_values, Cache) else 0
283
+
284
+ if cache_position is None:
285
+ cache_position = torch.arange(past_len, past_len + x.size(1), device=x.device)
286
+ if position_ids is None:
287
+ position_ids = cache_position[None, :]
288
+
289
+ cos, sin = self.rotary(x, position_ids)
290
+
291
+ causal_mask = None
292
+ if x.size(1) > 1:
293
+ total = past_len + x.size(1)
294
+ causal = torch.tril(torch.ones(x.size(1), total, dtype=torch.bool, device=x.device),
295
+ diagonal=past_len)
296
+ causal_mask = torch.zeros(x.size(1), total, dtype=x.dtype, device=x.device)
297
+ causal_mask.masked_fill_(~causal, torch.finfo(x.dtype).min)
298
+ causal_mask = causal_mask[None, None, :, :]
299
+
300
+ aux_total = x.new_zeros(())
301
+ z_total = x.new_zeros(())
302
+ loads = []
303
+ for layer in self.layers:
304
+ x, aux, z, load = layer(x, cos, sin, causal_mask, past_key_values, cache_position)
305
+ if aux is not None:
306
+ aux_total = aux_total + aux
307
+ z_total = z_total + z
308
+ loads.append(load)
309
+
310
+ logits = self.lm_head(self.norm(x))
311
+
312
+ loss = None
313
+ if labels is not None:
314
+ shift_labels = torch.cat(
315
+ [labels[:, 1:], labels.new_full((labels.size(0), 1), -100)], dim=1
316
+ )
317
+ loss = F.cross_entropy(logits.view(-1, logits.size(-1)), shift_labels.view(-1))
318
+ n_moe = max(len(loads), 1)
319
+ loss = loss + self.config.router_aux_loss_coef * aux_total / n_moe
320
+ loss = loss + self.config.router_z_loss_coef * z_total / n_moe
321
+
322
+ return MoeCausalLMOutputWithPast(
323
+ loss=loss,
324
+ logits=logits,
325
+ past_key_values=past_key_values if use_cache else None,
326
+ aux_loss=aux_total / max(len(loads), 1) if loads else None,
327
+ )
328
+
329
+ def expert_load(self):
330
+ """Per-layer expert token counts from the last forward, for monitoring."""
331
+ return [m.expert_bias for m in self.modules() if isinstance(m, DynamicMindMoE)]
332
+
333
+ def state_dict(self, *args, **kwargs):
334
+ sd = super().state_dict(*args, **kwargs)
335
+ if getattr(self.config, "tie_word_embeddings", True):
336
+ for k in list(sd.keys()):
337
+ if k == "lm_head.weight" or k.endswith(".lm_head.weight"):
338
+ del sd[k]
339
+ return sd
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer_config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|endoftext|>",
4
+ "eos_token": "<|endoftext|>",
5
+ "pad_token": "<|pad|>",
6
+ "unk_token": "<|unk|>",
7
+ "model_max_length": 1024,
8
+ "tokenizer_class": "TokenizersBackend"
9
+ }