bgub commited on
Commit
4eb89a5
·
verified ·
1 Parent(s): 36202c9

Initial Maccy-106M release

Browse files

Publish the 106M-parameter (70M active) base checkpoint, tokenizer, portable Transformers implementation, and model card.

README.md ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ library_name: transformers
4
+ pipeline_tag: text-generation
5
+ datasets:
6
+ - karpathy/climbmix-400b-shuffle
7
+ tags:
8
+ - custom_code
9
+ - mixture-of-experts
10
+ - kimi-delta-attention
11
+ - multi-head-latent-attention
12
+ ---
13
+
14
+ # Maccy 106M (70M active)
15
+
16
+ Maccy is a compact, from-scratch base language model trained on Apple Silicon. It has
17
+ **106,017,561 total parameters** and activates approximately **70,185,753 parameters per
18
+ token** through top-2 routing across four SwiGLU experts.
19
+
20
+ This is a base completion model, not a chat or instruction-following model.
21
+
22
+ ## Architecture
23
+
24
+ | Property | Value |
25
+ | --- | ---: |
26
+ | Total parameters | 106.0M |
27
+ | Active parameters per token | 70.2M |
28
+ | Layers | 12 |
29
+ | Model width | 576 |
30
+ | Sequence mixers | 9 KDA, 3 MLA |
31
+ | Channel mixers | 4-expert sparse MoE, top-2 routing |
32
+ | Context length | 1,024 tokens |
33
+ | Vocabulary | 32,768 byte-level BPE tokens |
34
+
35
+ Maccy combines Kimi Delta Attention (KDA), Multi-head Latent Attention (MLA), and a sparse
36
+ mixture of experts. Input and output embeddings are tied.
37
+
38
+ ## Usage
39
+
40
+ The repository includes a portable Transformers reference implementation built for
41
+ Transformers 5.14 or newer. Because Maccy is a custom architecture, loading the model
42
+ requires `trust_remote_code=True`.
43
+
44
+ ```python
45
+ import torch
46
+ from transformers import AutoModelForCausalLM, AutoTokenizer
47
+
48
+ model_id = "bgub/maccy-106m-base"
49
+ tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
50
+ model = AutoModelForCausalLM.from_pretrained(
51
+ model_id,
52
+ trust_remote_code=True,
53
+ dtype=torch.float32,
54
+ )
55
+
56
+ inputs = tokenizer("Once upon a time", return_tensors="pt")
57
+ output = model.generate(
58
+ **inputs,
59
+ max_new_tokens=100,
60
+ do_sample=True,
61
+ temperature=0.8,
62
+ top_k=50,
63
+ use_cache=False,
64
+ )
65
+ print(tokenizer.decode(output[0], skip_special_tokens=True))
66
+ ```
67
+
68
+ For the optimized Apple-Silicon kernels and training code, see
69
+ [https://github.com/bgub/mokka](https://github.com/bgub/mokka).
70
+
71
+ ## Training
72
+
73
+ - Training data: [Karpathy's shuffled ClimbMix repack](https://huggingface.co/datasets/karpathy/climbmix-400b-shuffle), derived from [NVIDIA Nemotron-ClimbMix](https://huggingface.co/datasets/nvidia/Nemotron-ClimbMix)
74
+ - Tokens processed: 2,120,089,600
75
+ - Optimizer steps: 64,700
76
+ - Training context: 1,024 tokens
77
+ - Effective batch: 32 sequences / 32,768 tokens per optimizer step
78
+ - Precision: bfloat16 activations with float32 master weights
79
+
80
+ The tokenizer was trained from scratch on two billion characters of the same corpus. It is
81
+ an NFC-normalized byte-level BPE with complete UTF-8 byte fallback.
82
+
83
+ NVIDIA's source dataset card designates ClimbMix for research and development under CC
84
+ BY-NC 4.0. Users are responsible for reviewing both the source-dataset terms and this
85
+ model's license before use.
86
+
87
+ ## Evaluation
88
+
89
+ On the full held-out ClimbMix validation shard, Maccy reached **0.9881 bits per byte** over
90
+ 20,971,520 target tokens. Treat this as an in-domain pretraining metric rather than a broad
91
+ capability benchmark.
92
+
93
+ In a small blind side-by-side generation evaluation against Pythia-70M, graders preferred
94
+ Maccy in all 15 non-tied comparisons (one additional comparison was tied). Both models were
95
+ still weak in absolute terms, especially on code and mathematics.
96
+
97
+ ## Limitations
98
+
99
+ - This checkpoint has not been post-trained for conversation or instruction following.
100
+ - The 1,024-token context is short by modern standards.
101
+ - Code, mathematics, factual reliability, and long-form coherence are limited.
102
+ - The portable Transformers implementation does not yet include a recurrent generation
103
+ cache and is slower than Mokka's native Metal implementation.
104
+ - Training data may contain errors, biases, and objectionable material that the model can
105
+ reproduce.
config.json ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "MaccyForCausalLM"
4
+ ],
5
+ "auto_map": {
6
+ "AutoConfig": "configuration_maccy.MaccyConfig",
7
+ "AutoModelForCausalLM": "modeling_maccy.MaccyForCausalLM"
8
+ },
9
+ "bias": false,
10
+ "bos_token_id": 32759,
11
+ "channel_mixer_pattern": "moe",
12
+ "context_length": 1024,
13
+ "d_model": 576,
14
+ "dtype": "float32",
15
+ "eos_token_id": 32763,
16
+ "mixer_pattern": "kda,kda,kda,mla",
17
+ "mla": {
18
+ "content_head_dim": 64,
19
+ "gated": false,
20
+ "kv_rank": 72,
21
+ "query_rank": 144,
22
+ "rope_head_dim": 32,
23
+ "value_head_dim": 64
24
+ },
25
+ "mlp_expansion": 3,
26
+ "model_type": "maccy",
27
+ "moe": {
28
+ "capacity_factor": 1.0,
29
+ "expert_expansion": 1.5,
30
+ "experts_per_token": 2,
31
+ "load_balancing_weight": 0.01,
32
+ "n_experts": 4
33
+ },
34
+ "n_heads": 9,
35
+ "n_layers": 12,
36
+ "pad_token_id": 32759,
37
+ "tie_word_embeddings": true,
38
+ "transformers_version": "5.14.1",
39
+ "use_cache": false,
40
+ "vocab_size": 32768
41
+ }
configuration_maccy.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face configuration for Maccy models."""
2
+
3
+ from typing import Any
4
+
5
+ from transformers import PretrainedConfig
6
+
7
+
8
+ class MaccyConfig(PretrainedConfig):
9
+ """Describe Maccy's KDA/MLA/MoE decoder architecture."""
10
+
11
+ model_type = "maccy"
12
+ keys_to_ignore_at_inference = ["router_loss"]
13
+
14
+ def __init__(
15
+ self,
16
+ vocab_size: int = 32_768,
17
+ context_length: int = 1_024,
18
+ d_model: int = 576,
19
+ n_heads: int = 9,
20
+ n_layers: int = 12,
21
+ mlp_expansion: int = 3,
22
+ bias: bool = False,
23
+ mixer_pattern: str = "kda,kda,kda,mla",
24
+ channel_mixer_pattern: str = "moe",
25
+ mla: dict[str, Any] | None = None,
26
+ moe: dict[str, Any] | None = None,
27
+ **kwargs: Any,
28
+ ) -> None:
29
+ self.vocab_size = vocab_size
30
+ self.context_length = context_length
31
+ self.d_model = d_model
32
+ self.n_heads = n_heads
33
+ self.n_layers = n_layers
34
+ self.mlp_expansion = mlp_expansion
35
+ self.bias = bias
36
+ self.mixer_pattern = mixer_pattern
37
+ self.channel_mixer_pattern = channel_mixer_pattern
38
+ self.mla = mla or {
39
+ "query_rank": 144,
40
+ "kv_rank": 72,
41
+ "content_head_dim": 64,
42
+ "rope_head_dim": 32,
43
+ "value_head_dim": 64,
44
+ "gated": False,
45
+ }
46
+ self.moe = moe or {
47
+ "n_experts": 4,
48
+ "experts_per_token": 2,
49
+ "expert_expansion": 1.5,
50
+ "capacity_factor": 1.0,
51
+ "load_balancing_weight": 0.01,
52
+ }
53
+
54
+ # Standard aliases make generic Transformers tooling more useful.
55
+ self.hidden_size = d_model
56
+ self.num_attention_heads = n_heads
57
+ self.num_hidden_layers = n_layers
58
+ self.max_position_embeddings = context_length
59
+ self.use_cache = False
60
+
61
+ kwargs.setdefault("tie_word_embeddings", True)
62
+ super().__init__(**kwargs)
generation_config.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_from_model_config": true,
3
+ "bos_token_id": 32759,
4
+ "do_sample": true,
5
+ "eos_token_id": 32763,
6
+ "pad_token_id": 32759,
7
+ "temperature": 0.8,
8
+ "top_k": 50,
9
+ "transformers_version": "5.14.1",
10
+ "use_cache": false
11
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc58f747cdee65a716181398b4fd445fa8bb899b037510534ed0a0831a07e554
3
+ size 424091684
modeling_maccy.py ADDED
@@ -0,0 +1,401 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Portable Transformers implementation of the Maccy architecture."""
2
+
3
+ from collections.abc import Sequence
4
+ from typing import Any, cast
5
+
6
+ import torch
7
+ from torch import Tensor, nn
8
+ from torch.nn import functional as F
9
+ from transformers import PreTrainedModel
10
+ from transformers.generation.utils import GenerationMixin
11
+ from transformers.modeling_outputs import CausalLMOutputWithPast
12
+
13
+ from .configuration_maccy import MaccyConfig
14
+
15
+ _NORM_EPSILON = 1e-6
16
+ _MINIMUM_RETENTION = 0.125
17
+
18
+
19
+ class RMSNorm(nn.Module):
20
+ """Normalize vector magnitude without subtracting its mean."""
21
+
22
+ def __init__(self, width: int) -> None:
23
+ super().__init__()
24
+ self.weight = nn.Parameter(torch.ones(width))
25
+
26
+ def forward(self, inputs: Tensor) -> Tensor:
27
+ inverse_rms = torch.rsqrt(
28
+ inputs.float().square().mean(dim=-1, keepdim=True) + _NORM_EPSILON
29
+ )
30
+ return inputs * inverse_rms.to(inputs.dtype) * self.weight.to(inputs.dtype)
31
+
32
+
33
+ class RotaryEmbedding(nn.Module):
34
+ """Apply rotary position embeddings over the penultimate dimension."""
35
+
36
+ def __init__(self, width: int, maximum_length: int) -> None:
37
+ super().__init__()
38
+ self.width = width
39
+ self.maximum_length = maximum_length
40
+
41
+ def forward(self, inputs: Tensor) -> Tensor:
42
+ sequence_length = inputs.shape[-2]
43
+ if sequence_length > self.maximum_length:
44
+ raise ValueError("sequence length exceeds the rotary embedding limit")
45
+ pair_indices = torch.arange(0, self.width, 2, dtype=torch.float32, device=inputs.device)
46
+ inverse_frequencies = 1.0 / (10_000.0 ** (pair_indices / self.width))
47
+ positions = torch.arange(sequence_length, dtype=torch.float32, device=inputs.device)
48
+ angles = torch.outer(positions, inverse_frequencies)
49
+ cosines = angles.cos().to(inputs.dtype)
50
+ sines = angles.sin().to(inputs.dtype)
51
+ even, odd = inputs[..., 0::2], inputs[..., 1::2]
52
+ return torch.stack(
53
+ (even * cosines - odd * sines, even * sines + odd * cosines), dim=-1
54
+ ).flatten(start_dim=-2)
55
+
56
+
57
+ def recurrent_kda(
58
+ queries: Tensor,
59
+ keys: Tensor,
60
+ values: Tensor,
61
+ retention: Tensor,
62
+ update_rate: Tensor,
63
+ ) -> Tensor:
64
+ """Evaluate the delta-rule memory recurrence in float32."""
65
+ output_dtype = values.dtype
66
+ queries = queries.float() * (keys.shape[-1] ** -0.5)
67
+ keys = keys.float()
68
+ values = values.float()
69
+ retention = retention.float()
70
+ update_rate = update_rate.float()
71
+
72
+ batch_size, _, n_heads, key_dim = keys.shape
73
+ state = keys.new_zeros(batch_size, n_heads, key_dim, values.shape[-1])
74
+ outputs = []
75
+ for token_index in range(keys.shape[1]):
76
+ query = queries[:, token_index]
77
+ key = keys[:, token_index]
78
+ value = values[:, token_index]
79
+ state = state * retention[:, token_index].unsqueeze(-1)
80
+ prediction = torch.einsum("bhkv,bhk->bhv", state, key)
81
+ error = value - prediction
82
+ beta = update_rate[:, token_index, :, None, None]
83
+ state = state + beta * key.unsqueeze(-1) * error.unsqueeze(-2)
84
+ outputs.append(torch.einsum("bhkv,bhk->bhv", state, query))
85
+ return torch.stack(outputs, dim=1).to(output_dtype)
86
+
87
+
88
+ class CausalDepthwiseConvolution(nn.Module):
89
+ """Mix a four-token local history independently within each channel."""
90
+
91
+ def __init__(self, d_model: int) -> None:
92
+ super().__init__()
93
+ self.width = 4
94
+ self.convolution = nn.Conv1d(
95
+ d_model, d_model, kernel_size=self.width, groups=d_model, bias=False, padding=3
96
+ )
97
+
98
+ def forward(self, inputs: Tensor) -> Tensor:
99
+ sequence_length = inputs.shape[1]
100
+ convolved = self.convolution(inputs.transpose(1, 2))[..., :sequence_length]
101
+ return F.silu(convolved.transpose(1, 2))
102
+
103
+
104
+ class KimiDeltaAttention(nn.Module):
105
+ """Kimi Delta Attention with a portable recurrent implementation."""
106
+
107
+ def __init__(self, d_model: int, n_heads: int) -> None:
108
+ super().__init__()
109
+ self.n_heads = n_heads
110
+ self.head_dim = d_model // n_heads
111
+ self.qkv_projection = nn.Linear(d_model, 3 * d_model, bias=False)
112
+ self.query_convolution = CausalDepthwiseConvolution(d_model)
113
+ self.key_convolution = CausalDepthwiseConvolution(d_model)
114
+ self.value_convolution = CausalDepthwiseConvolution(d_model)
115
+ self.control_down = nn.Linear(d_model, 2 * self.head_dim, bias=False)
116
+ self.update_projection = nn.Linear(d_model, n_heads, bias=False)
117
+ self.retention_up = nn.Linear(self.head_dim, d_model, bias=False)
118
+ self.retention_bias = nn.Parameter(torch.zeros(d_model))
119
+ self.log_decay_scale = nn.Parameter(torch.zeros(n_heads))
120
+ self.output_gate_up = nn.Linear(self.head_dim, d_model, bias=True)
121
+ self.output_norm_weight = nn.Parameter(torch.ones(self.head_dim))
122
+ self.output = nn.Linear(d_model, d_model, bias=False)
123
+
124
+ def _split_heads(self, inputs: Tensor) -> Tensor:
125
+ return inputs.view(inputs.shape[0], inputs.shape[1], self.n_heads, self.head_dim)
126
+
127
+ def forward(self, inputs: Tensor) -> Tensor:
128
+ queries, keys, values = self.qkv_projection(inputs).chunk(3, dim=-1)
129
+ queries = F.normalize(
130
+ self._split_heads(self.query_convolution(queries)), dim=-1, eps=_NORM_EPSILON
131
+ )
132
+ keys = F.normalize(self._split_heads(self.key_convolution(keys)), dim=-1, eps=_NORM_EPSILON)
133
+ values = self._split_heads(self.value_convolution(values))
134
+
135
+ retention_latent, gate_latent = self.control_down(inputs).chunk(2, dim=-1)
136
+ retention_logits = self.retention_up(retention_latent) + self.retention_bias
137
+ retention_logits = self._split_heads(retention_logits).float()
138
+ decay_scale = self.log_decay_scale.exp().view(1, 1, self.n_heads, 1)
139
+ retention = (-decay_scale * F.softplus(retention_logits)).exp()
140
+ retention = retention.clamp_min(_MINIMUM_RETENTION)
141
+ update_rate = self.update_projection(inputs).float().sigmoid()
142
+ output_gate = self._split_heads(self.output_gate_up(gate_latent)).float().sigmoid()
143
+
144
+ mixed = recurrent_kda(queries, keys, values, retention, update_rate).float()
145
+ inverse_rms = torch.rsqrt(mixed.square().mean(dim=-1, keepdim=True) + _NORM_EPSILON)
146
+ mixed = mixed * inverse_rms * self.output_norm_weight.float()
147
+ mixed = mixed * output_gate
148
+ mixed = mixed.to(self.output.weight.dtype)
149
+ return self.output(mixed.flatten(start_dim=-2))
150
+
151
+
152
+ class MultiHeadLatentAttention(nn.Module):
153
+ """Causal attention through compressed query and key-value latents."""
154
+
155
+ def __init__(self, config: MaccyConfig) -> None:
156
+ super().__init__()
157
+ mla = config.mla
158
+ self.n_heads = config.n_heads
159
+ self.query_rank = mla["query_rank"]
160
+ self.kv_rank = mla["kv_rank"]
161
+ self.content_head_dim = mla["content_head_dim"]
162
+ self.rope_head_dim = mla["rope_head_dim"]
163
+ self.value_head_dim = mla["value_head_dim"]
164
+ query_head_dim = self.content_head_dim + self.rope_head_dim
165
+
166
+ self.input_down = nn.Linear(
167
+ config.d_model,
168
+ self.query_rank + self.kv_rank + self.rope_head_dim,
169
+ bias=config.bias,
170
+ )
171
+ self.query_norm = RMSNorm(self.query_rank)
172
+ self.query_up = nn.Linear(self.query_rank, self.n_heads * query_head_dim, bias=config.bias)
173
+ self.kv_norm = RMSNorm(self.kv_rank)
174
+ self.kv_up = nn.Linear(
175
+ self.kv_rank,
176
+ self.n_heads * (self.content_head_dim + self.value_head_dim),
177
+ bias=config.bias,
178
+ )
179
+ self.rotary_embedding = RotaryEmbedding(self.rope_head_dim, config.context_length)
180
+ self.gate = (
181
+ nn.Linear(config.d_model, self.n_heads * self.value_head_dim, bias=True)
182
+ if mla["gated"]
183
+ else None
184
+ )
185
+ self.output = nn.Linear(
186
+ self.n_heads * self.value_head_dim, config.d_model, bias=config.bias
187
+ )
188
+
189
+ def forward(self, inputs: Tensor) -> Tensor:
190
+ batch_size, sequence_length, _ = inputs.shape
191
+ compressed_queries, compressed_kv, rotary_keys = self.input_down(inputs).split(
192
+ (self.query_rank, self.kv_rank, self.rope_head_dim), dim=-1
193
+ )
194
+ expanded_queries = self.query_up(self.query_norm(compressed_queries)).view(
195
+ batch_size,
196
+ sequence_length,
197
+ self.n_heads,
198
+ self.content_head_dim + self.rope_head_dim,
199
+ )
200
+ expanded_kv = self.kv_up(self.kv_norm(compressed_kv)).view(
201
+ batch_size,
202
+ sequence_length,
203
+ self.n_heads,
204
+ self.content_head_dim + self.value_head_dim,
205
+ )
206
+ content_queries, rotary_queries = expanded_queries.split(
207
+ (self.content_head_dim, self.rope_head_dim), dim=-1
208
+ )
209
+ content_keys, values = expanded_kv.split(
210
+ (self.content_head_dim, self.value_head_dim), dim=-1
211
+ )
212
+ rotary_queries = self.rotary_embedding(rotary_queries.transpose(1, 2))
213
+ rotary_keys = self.rotary_embedding(rotary_keys.unsqueeze(1)).expand(
214
+ -1, self.n_heads, -1, -1
215
+ )
216
+ queries = torch.cat((content_queries.transpose(1, 2), rotary_queries), dim=-1)
217
+ keys = torch.cat((content_keys.transpose(1, 2), rotary_keys), dim=-1)
218
+ values = values.transpose(1, 2)
219
+ mixed = F.scaled_dot_product_attention(queries, keys, values, is_causal=True)
220
+ mixed = mixed.transpose(1, 2)
221
+ if self.gate is not None:
222
+ gate = self.gate(inputs).view(
223
+ batch_size, sequence_length, self.n_heads, self.value_head_dim
224
+ )
225
+ mixed = mixed * gate.sigmoid()
226
+ return self.output(mixed.flatten(start_dim=-2))
227
+
228
+
229
+ class PackedSwiGLUExperts(nn.Module):
230
+ """Store equal-shaped experts in two packed parameter tensors."""
231
+
232
+ def __init__(self, n_experts: int, d_model: int, hidden_dim: int, *, bias: bool) -> None:
233
+ super().__init__()
234
+ self.input_weight = nn.Parameter(torch.empty(n_experts, d_model, 2 * hidden_dim))
235
+ self.output_weight = nn.Parameter(torch.empty(n_experts, hidden_dim, d_model))
236
+ if bias:
237
+ self.input_bias = nn.Parameter(torch.zeros(n_experts, 2 * hidden_dim))
238
+ self.output_bias = nn.Parameter(torch.zeros(n_experts, d_model))
239
+ else:
240
+ self.register_parameter("input_bias", None)
241
+ self.register_parameter("output_bias", None)
242
+
243
+ def forward_expert(self, expert_index: int, inputs: Tensor) -> Tensor:
244
+ projected = inputs @ self.input_weight[expert_index]
245
+ if self.input_bias is not None:
246
+ projected = projected + self.input_bias[expert_index]
247
+ gate, values = projected.chunk(2, dim=-1)
248
+ updates = (F.silu(gate) * values) @ self.output_weight[expert_index]
249
+ if self.output_bias is not None:
250
+ updates = updates + self.output_bias[expert_index]
251
+ return updates
252
+
253
+
254
+ class SparseMoE(nn.Module):
255
+ """Route each token to a weighted top-k subset of SwiGLU experts."""
256
+
257
+ def __init__(self, config: MaccyConfig) -> None:
258
+ super().__init__()
259
+ moe = config.moe
260
+ self.n_experts = moe["n_experts"]
261
+ self.experts_per_token = moe["experts_per_token"]
262
+ self.router = nn.Linear(config.d_model, self.n_experts, bias=False)
263
+ hidden_dim = round(moe["expert_expansion"] * config.d_model)
264
+ self.experts = PackedSwiGLUExperts(
265
+ self.n_experts, config.d_model, hidden_dim, bias=config.bias
266
+ )
267
+
268
+ def forward(self, inputs: Tensor) -> Tensor:
269
+ input_shape = inputs.shape
270
+ flat_inputs = inputs.flatten(0, -2)
271
+ probabilities = self.router(flat_inputs).float().softmax(dim=-1)
272
+ weights, expert_indices = probabilities.topk(self.experts_per_token, dim=-1)
273
+ weights = (weights / weights.sum(dim=-1, keepdim=True)).to(inputs.dtype)
274
+ updates = torch.zeros_like(flat_inputs)
275
+ for expert_index in range(self.n_experts):
276
+ token_indices, choice_indices = torch.where(expert_indices == expert_index)
277
+ if token_indices.numel() == 0:
278
+ continue
279
+ expert_updates = self.experts.forward_expert(
280
+ expert_index, flat_inputs.index_select(0, token_indices)
281
+ )
282
+ expert_weights = weights[token_indices, choice_indices].unsqueeze(-1)
283
+ updates = updates.index_add(0, token_indices, expert_updates * expert_weights)
284
+ return updates.view(input_shape)
285
+
286
+
287
+ class TransformerBlock(nn.Module):
288
+ """Apply one pre-normalized sequence mixer and sparse channel mixer."""
289
+
290
+ def __init__(self, config: MaccyConfig, mixer_kind: str) -> None:
291
+ super().__init__()
292
+ self.attention_norm = RMSNorm(config.d_model)
293
+ self.mixer = (
294
+ KimiDeltaAttention(config.d_model, config.n_heads)
295
+ if mixer_kind == "kda"
296
+ else MultiHeadLatentAttention(config)
297
+ )
298
+ self.feed_forward_norm = RMSNorm(config.d_model)
299
+ self.feed_forward = SparseMoE(config)
300
+
301
+ def forward(self, inputs: Tensor) -> Tensor:
302
+ inputs = inputs + self.mixer(self.attention_norm(inputs))
303
+ return inputs + self.feed_forward(self.feed_forward_norm(inputs))
304
+
305
+
306
+ class MaccyPreTrainedModel(PreTrainedModel):
307
+ """Shared Transformers metadata for Maccy models."""
308
+
309
+ config_class = MaccyConfig
310
+ base_model_prefix = ""
311
+ _no_split_modules = ["TransformerBlock"]
312
+ _supports_sdpa = True
313
+
314
+ def _init_weights(self, module: nn.Module) -> None:
315
+ if isinstance(module, (nn.Linear, nn.Embedding, nn.Conv1d)):
316
+ nn.init.normal_(module.weight, mean=0.0, std=0.02)
317
+ if isinstance(module, nn.Linear) and module.bias is not None:
318
+ nn.init.zeros_(module.bias)
319
+
320
+
321
+ class MaccyForCausalLM(MaccyPreTrainedModel, GenerationMixin):
322
+ """Maccy decoder with a tied next-token language-modeling head."""
323
+
324
+ _tied_weights_keys = {"lm_head.weight": "token_embedding.weight"}
325
+
326
+ def __init__(self, config: MaccyConfig) -> None:
327
+ super().__init__(config)
328
+ self.token_embedding = nn.Embedding(config.vocab_size, config.d_model)
329
+ mixers = tuple(part.strip() for part in config.mixer_pattern.split(","))
330
+ repeated_mixers = mixers * (config.n_layers // len(mixers))
331
+ self.blocks = nn.ModuleList(
332
+ TransformerBlock(config, mixer_kind) for mixer_kind in repeated_mixers
333
+ )
334
+ self.output_norm = RMSNorm(config.d_model)
335
+ self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=config.bias)
336
+ self.post_init()
337
+
338
+ def get_input_embeddings(self) -> nn.Embedding:
339
+ return self.token_embedding
340
+
341
+ def set_input_embeddings(self, value: nn.Module) -> None:
342
+ if not isinstance(value, nn.Embedding):
343
+ raise TypeError("input embeddings must be an nn.Embedding")
344
+ self.token_embedding = value
345
+
346
+ def get_output_embeddings(self) -> nn.Linear:
347
+ return self.lm_head
348
+
349
+ def set_output_embeddings(self, new_embeddings: nn.Module) -> None:
350
+ if not isinstance(new_embeddings, nn.Linear):
351
+ raise TypeError("output embeddings must be an nn.Linear")
352
+ self.lm_head = new_embeddings
353
+
354
+ def forward(
355
+ self,
356
+ input_ids: Tensor | None = None,
357
+ attention_mask: Tensor | None = None,
358
+ inputs_embeds: Tensor | None = None,
359
+ labels: Tensor | None = None,
360
+ use_cache: bool | None = None,
361
+ logits_to_keep: int | Tensor = 0,
362
+ return_dict: bool | None = None,
363
+ **_: Any,
364
+ ) -> CausalLMOutputWithPast | tuple[Tensor, ...]:
365
+ del attention_mask, use_cache
366
+ if (input_ids is None) == (inputs_embeds is None):
367
+ raise ValueError("pass exactly one of input_ids or inputs_embeds")
368
+ hidden_states = self.token_embedding(input_ids) if inputs_embeds is None else inputs_embeds
369
+ if hidden_states.shape[1] > self.config.context_length:
370
+ raise ValueError(f"Maccy's context length is {self.config.context_length} tokens")
371
+ for block in self.blocks:
372
+ hidden_states = block(hidden_states)
373
+ hidden_states = self.output_norm(hidden_states)
374
+ indices = (
375
+ slice(None)
376
+ if labels is not None or (isinstance(logits_to_keep, int) and logits_to_keep == 0)
377
+ else slice(-logits_to_keep, None)
378
+ if isinstance(logits_to_keep, int)
379
+ else logits_to_keep
380
+ )
381
+ logits = self.lm_head(hidden_states[:, indices, :])
382
+
383
+ loss = None
384
+ if labels is not None:
385
+ shift_logits = logits[:, :-1].contiguous().float()
386
+ shift_labels = labels[:, 1:].contiguous()
387
+ loss = F.cross_entropy(
388
+ shift_logits.view(-1, self.config.vocab_size),
389
+ shift_labels.view(-1),
390
+ ignore_index=-100,
391
+ )
392
+
393
+ output = CausalLMOutputWithPast(
394
+ loss=cast(torch.FloatTensor | None, loss),
395
+ logits=logits,
396
+ past_key_values=None,
397
+ )
398
+ if return_dict is False:
399
+ values: Sequence[Tensor | None] = (loss, logits) if loss is not None else (logits,)
400
+ return tuple(value for value in values if value is not None)
401
+ return output
tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
tokenizer.model ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:405aa6d2b1540ecd07f72ea181084d5df0f1592fe933d8eb1f43a99e59d744a2
3
+ size 549431
tokenizer_config.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|bos|>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|assistant_end|>",
6
+ "extra_special_tokens": [
7
+ "<|user_start|>",
8
+ "<|user_end|>",
9
+ "<|assistant_start|>",
10
+ "<|assistant_end|>",
11
+ "<|python_start|>",
12
+ "<|python_end|>",
13
+ "<|output_start|>",
14
+ "<|output_end|>"
15
+ ],
16
+ "model_max_length": 1024,
17
+ "pad_token": "<|bos|>",
18
+ "tokenizer_class": "TokenizersBackend"
19
+ }