DedeProGames commited on
Commit
8d4372c
·
verified ·
1 Parent(s): 15a76fb

Add DynamicMind-MoE-Instruct: 2-epoch SFT on smol-smoltalk, custom MoE code, EOS-weighted loss

Browse files
README.md ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: apache-2.0
3
+ language:
4
+ - en
5
+ library_name: transformers
6
+ pipeline_tag: text-generation
7
+ base_model: DedeProGames/DynamicMind-MoE
8
+ datasets:
9
+ - HuggingFaceTB/smol-smoltalk
10
+ tags:
11
+ - dynamicmind
12
+ - mixture-of-experts
13
+ - sparse-moe
14
+ - instruct
15
+ - sft
16
+ - conversational
17
+ - custom-code
18
+ - trust-remote-code
19
+ ---
20
+
21
+ # DynamicMind-MoE-Instruct
22
+
23
+ An instruction-tuned version of [DynamicMind-MoE](https://huggingface.co/DedeProGames/DynamicMind-MoE), a sparse mixture-of-experts model with 30.2M total parameters and 8.9M active per token. It was fully fine-tuned for two epochs on [smol-smoltalk](https://huggingface.co/datasets/HuggingFaceTB/smol-smoltalk) with the loss computed only on assistant tokens (end-of-turn tokens weighted 5×, so the model learns to stop).
24
+
25
+ At this size it is a small research model. It follows the chat format and answers simple requests, but factual knowledge, reasoning and arithmetic are weak. See the limitations below.
26
+
27
+ ## Quickstart
28
+
29
+ This model uses custom architecture code, so load it with `trust_remote_code=True`. Use the recommended sampling settings (stored in `generation_config.json`). With near-greedy decoding this model can fall into repetition loops.
30
+
31
+ ```python
32
+ import torch
33
+ from transformers import AutoModelForCausalLM, AutoTokenizer
34
+
35
+ repo = "DedeProGames/DynamicMind-MoE-Instruct"
36
+ tokenizer = AutoTokenizer.from_pretrained(repo)
37
+ model = AutoModelForCausalLM.from_pretrained(repo, trust_remote_code=True, torch_dtype=torch.float32).eval()
38
+
39
+ messages = [{"role": "user", "content": "What is photosynthesis?"}]
40
+ input_ids = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt", return_dict=False)
41
+ out = model.generate(input_ids, max_new_tokens=256) # temperature 0.7, top-p 0.9, repetition penalty 1.1 from generation_config.json
42
+ print(tokenizer.decode(out[0, input_ids.shape[1]:], skip_special_tokens=True))
43
+ ```
44
+
45
+ Keep prompt + answer within the 1,024-token context.
46
+
47
+ ## Chat format
48
+
49
+ This vocabulary has no chat tokens, so the format is plain text. `apply_chat_template` renders it:
50
+
51
+ ```text
52
+ <|endoftext|>### User:
53
+ {user message}
54
+ ### Assistant:
55
+ {assistant reply}
56
+ <|endoftext|>### User:
57
+ ...
58
+ ```
59
+
60
+ `<|endoftext|>` (id 0) is the BOS, the end-of-turn token and the EOS.
61
+
62
+ ## Architecture
63
+
64
+ Same as DynamicMind-MoE: 9 layers, hidden size 256, 8 attention heads with 2 KV heads, one always-on shared expert plus 14 routed experts per layer with the top 2 selected per token, auxiliary-loss-free balancing, tied embeddings, 8,192-token digit-aware vocabulary, 1,024-token context. 30,150,912 total parameters, 8,917,248 active per token. Weights in BF16 (`model.safetensors`, 60 MB).
65
+
66
+ ## Training
67
+
68
+ | | |
69
+ |---|---|
70
+ | Base | DedeProGames/DynamicMind-MoE |
71
+ | Data | smol-smoltalk train split, conversations of up to 1,024 tokens (221,811 with this tokenizer; 68.9M supervised tokens) |
72
+ | Epochs | 2 (13,864 steps, batch 32 = 16 × 2 gradient accumulation) |
73
+ | Optimizer | AdamW (β = 0.9, 0.95), weight decay 0.1 on 2D matrices, grad clip 1.0 |
74
+ | Schedule | lr 1e-4 (the base model's pretraining peak), 300 warmup steps, cosine to 1e-5 |
75
+ | Loss | assistant tokens; `<|endoftext|>` weighted 5×; router balance loss (coefficient 0.01) added |
76
+ | Precision | bf16 autocast, fp32 master weights |
77
+ | Hardware | 1× NVIDIA RTX 3060 12 GB, 239 minutes |
78
+
79
+ Held-out loss on assistant tokens (smol-smoltalk test split): 1.422 at the end of training.
80
+
81
+ ## Evaluation
82
+
83
+ Benchmarks follow the protocol of the [Kiyo-230M-Instruct](https://huggingface.co/DedeProGames/Kiyo-230M-Instruct) card: FP16, the model's own chat template, no system prompt, new-token limits IFEval 1,024, GPQA 384, LiveBench 512. The context is 1,024 tokens, so the generation limit is capped at 1,024 minus the prompt length. The main setting uses temperature 0.1, top-k 50 and repetition penalty 1.05, as on the card (GPQA is sampled at T = 0.6, as on the card). The second setting is the recommended one shipped in `generation_config.json`.
84
+
85
+ | Benchmark | Metric | DynamicMind-MoE-Instruct, protocol | DynamicMind-MoE-Instruct, recommended sampling |
86
+ |---|---|---:|---:|
87
+ | GPQA Diamond | pass@1 (198 × 5 samples, T = 0.6) | 12.32 | not run |
88
+ | IFEval | prompt-level strict | 18.85 | 14.97 |
89
+ | LiveBench instruction following | strict, 200 questions | 18.66 | not run |
90
+ | CaseReportBench | token F1 | not run (not on the Hub) | not run |
91
+ | EOS rate (IFEval) | share of answers that stop | 0.88 | 0.99 |
92
+
93
+ For reference, the Kiyo card reports (not re-run here): Kiyo-230M-Instruct GPQA 21.11, IFEval 17.38, LiveBench 18.77; SmolLM2-135M-Instruct GPQA 16.57, IFEval 20.89, LiveBench 24.23; BananaMind-2-Pro GPQA 15.05, IFEval 20.15, LiveBench 19.75; Supra2-100M-Instruct GPQA 5.56, IFEval 9.06, LiveBench 11.28.
94
+
95
+ Notes:
96
+
97
+ - With the protocol setting, about 12% of IFEval answers never stop and loop. Under the recommended sampling this drops to about 1%, at a cost of about 4 IFEval points.
98
+ - GPQA: only 46% of answers contain an identifiable option letter, and 40 prompts were truncated to fit the 1,024-token context. Among answered questions, accuracy is 26.6%, close to the 25% chance level.
99
+ - The GPQA prompt was written for this run, not taken from the Kiyo card, so the number is not strictly comparable.
100
+ - Single run, single seed. Differences of a point or two are within noise.
101
+
102
+ Per-sample outputs and summaries are in [`eval/`](eval/).
103
+
104
+ ## Limitations
105
+
106
+ - It is small (8.9M active parameters per token). Expect factual errors, invented details, arithmetic mistakes, weak multi-step reasoning, and repetitive text in long answers.
107
+ - Under near-greedy decoding it can loop. Keep the recommended sampling settings.
108
+ - Context is 1,024 tokens.
109
+ - English only.
110
+ - Requires `trust_remote_code=True`, since the architecture is custom.
111
+ - No safety tuning and no safety evaluation. Do not use it for medical, legal, financial or other high-stakes decisions.
112
+
113
+ ## License
114
+
115
+ Apache-2.0, as for the base model. The SFT data (smol-smoltalk) is also Apache-2.0.
config.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "DynamicMindMoEForCausalLM"
4
+ ],
5
+ "attention_dropout": 0.0,
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_dynamicmind_moe.DynamicMindMoEConfig",
8
+ "AutoModelForCausalLM": "modeling_dynamicmind_moe.DynamicMindMoEForCausalLM"
9
+ },
10
+ "bos_token_id": 0,
11
+ "dtype": "bfloat16",
12
+ "eos_token_id": 0,
13
+ "first_k_dense_layers": 0,
14
+ "hidden_size": 256,
15
+ "intermediate_size": 768,
16
+ "max_position_embeddings": 1024,
17
+ "model_type": "dynamicmind_moe",
18
+ "moe_intermediate_size": 256,
19
+ "norm_topk_prob": true,
20
+ "num_attention_heads": 8,
21
+ "num_experts_per_token": 2,
22
+ "num_hidden_layers": 9,
23
+ "num_key_value_heads": 2,
24
+ "num_routed_experts": 14,
25
+ "num_shared_experts": 1,
26
+ "pad_token_id": 1,
27
+ "rms_norm_eps": 1e-05,
28
+ "rope_theta": 10000.0,
29
+ "router_aux_loss_coef": 0.01,
30
+ "router_bias_update_rate": 0.001,
31
+ "router_z_loss_coef": 0.001,
32
+ "torch_dtype": "bfloat16",
33
+ "transformers_version": "4.53.3",
34
+ "use_aux_loss_free_balancing": true,
35
+ "vocab_size": 8192
36
+ }
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
eval/protocol/gpqa_generations.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/protocol/ifeval_generations.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/protocol/livebench_generations.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/protocol/summary.json ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ifeval": {
3
+ "metric": "prompt-level strict",
4
+ "score": 18.853974121996302,
5
+ "n": 541,
6
+ "eos_rate": 0.8835489833641405,
7
+ "minutes": 29.08
8
+ },
9
+ "gpqa": {
10
+ "metric": "pass@1 (990 attempts, no-letter counts wrong)",
11
+ "score": 12.323232323232324,
12
+ "n_attempts": 990,
13
+ "answered_rate": 46.26262626262626,
14
+ "accuracy_among_answered": 26.637554585152838,
15
+ "prompts_truncated": 40,
16
+ "minutes": 25.69
17
+ },
18
+ "livebench": {
19
+ "metric": "per-question mean(all followed, fraction followed), strict",
20
+ "score": 18.658333333333335,
21
+ "n": 200,
22
+ "eos_rate": 0.835,
23
+ "minutes": 9.9
24
+ }
25
+ }
eval/recommended_sampling/ifeval_generations.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
eval/recommended_sampling/summary.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ifeval": {
3
+ "metric": "prompt-level strict",
4
+ "score": 14.972273567467653,
5
+ "n": 541,
6
+ "eos_rate": 0.988909426987061,
7
+ "minutes": 21.12
8
+ }
9
+ }
generation_config.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 0,
3
+ "pad_token_id": 1,
4
+ "eos_token_id": 0,
5
+ "do_sample": true,
6
+ "temperature": 0.7,
7
+ "top_p": 0.9,
8
+ "repetition_penalty": 1.1,
9
+ "max_new_tokens": 512
10
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7cba2db1a17845e805ce8806b7c31d3b5bc88ba3f566f5f43be69dec195629f1
3
+ size 60355044
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
+ "bos_token": "<|endoftext|>",
3
+ "eos_token": "<|endoftext|>",
4
+ "pad_token": "<|pad|>",
5
+ "unk_token": "<|unk|>",
6
+ "model_max_length": 1024,
7
+ "tokenizer_class": "PreTrainedTokenizerFast",
8
+ "chat_template": "{{- '<|endoftext|>' -}}{%- for message in messages -%}{%- if message['role'] == 'system' -%}{{ '### System:\n' + message['content'].strip() + '\n' }}{%- elif message['role'] == 'user' -%}{{ '### User:\n' + message['content'].strip() + '\n' }}{%- elif message['role'] == 'assistant' -%}{{ '### Assistant:\n' + message['content'].strip() + '\n<|endoftext|>' }}{%- endif -%}{%- endfor -%}{%- if add_generation_prompt -%}{{ '### Assistant:\n' }}{%- endif -%}"
9
+ }