exnivo commited on
Commit
96be902
·
verified ·
1 Parent(s): dc6c885

Add Transformers and lm-eval compatibility

Browse files
chat_template.jinja ADDED
@@ -0,0 +1 @@
 
 
1
+ {% for message in messages %}{{ '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>\n' }}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}
config.json CHANGED
@@ -1,39 +1,27 @@
1
  {
2
- "model": {
3
- "vocab_size": 4096,
4
- "max_seq_len": 128,
5
- "d_model": 384,
6
- "n_layers": 6,
7
- "n_heads": 6,
8
- "ffn_hidden": 768,
9
- "dropout": 0.1,
10
- "pad_id": 0,
11
- "eos_id": 2
12
  },
13
- "train": {
14
- "input_jsonl": "loaflm_60k.jsonl",
15
- "data_dir": "data",
16
- "output_dir": "checkpoints",
17
- "skip_prepare": false,
18
- "vocab_size": 4096,
19
- "max_seq_len": 128,
20
- "d_model": 384,
21
- "n_layers": 6,
22
- "n_heads": 6,
23
- "ffn_hidden": 768,
24
- "dropout": 0.1,
25
- "batch_size": 32,
26
- "max_steps": 10000,
27
- "learning_rate": 0.0003,
28
- "min_lr": 3e-05,
29
- "warmup_steps": 200,
30
- "weight_decay": 0.1,
31
- "eval_ratio": 0.02,
32
- "eval_batches": 50,
33
- "eval_interval": 200,
34
- "save_interval": 500,
35
- "report_interval": 50,
36
- "seed": 42,
37
- "cpu": false
38
- }
39
- }
 
1
  {
2
+ "architectures": [
3
+ "LoafLMForCausalLM"
4
+ ],
5
+ "attention_dropout_prob": 0.1,
6
+ "auto_map": {
7
+ "AutoConfig": "configuration_loaflm.LoafLMConfig",
8
+ "AutoModelForCausalLM": "modeling_loaflm.LoafLMForCausalLM"
 
 
 
9
  },
10
+ "bos_token_id": 1,
11
+ "dtype": "float32",
12
+ "eos_token_id": 2,
13
+ "hidden_dropout_prob": 0.1,
14
+ "hidden_size": 384,
15
+ "initializer_range": 0.02,
16
+ "intermediate_size": 768,
17
+ "layer_norm_eps": 1e-05,
18
+ "max_position_embeddings": 128,
19
+ "model_type": "loaflm",
20
+ "num_attention_heads": 6,
21
+ "num_hidden_layers": 6,
22
+ "pad_token_id": 0,
23
+ "tie_word_embeddings": false,
24
+ "transformers_version": "5.14.1",
25
+ "use_cache": false,
26
+ "vocab_size": 4096
27
+ }
 
 
 
 
 
 
 
 
 
configuration_loaflm.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face configuration for LoafLM."""
2
+ from __future__ import annotations
3
+
4
+ from transformers import PreTrainedConfig
5
+
6
+
7
+ class LoafLMConfig(PreTrainedConfig):
8
+ """Configuration for the tiny decoder-only LoafLM architecture."""
9
+
10
+ model_type = "loaflm"
11
+ keys_to_ignore_at_inference = ["past_key_values"]
12
+
13
+ def __init__(
14
+ self,
15
+ vocab_size: int = 4096,
16
+ max_position_embeddings: int = 128,
17
+ hidden_size: int = 384,
18
+ num_hidden_layers: int = 6,
19
+ num_attention_heads: int = 6,
20
+ intermediate_size: int = 768,
21
+ hidden_dropout_prob: float = 0.1,
22
+ attention_dropout_prob: float = 0.1,
23
+ layer_norm_eps: float = 1e-5,
24
+ initializer_range: float = 0.02,
25
+ pad_token_id: int = 0,
26
+ bos_token_id: int = 1,
27
+ eos_token_id: int = 2,
28
+ tie_word_embeddings: bool = False,
29
+ use_cache: bool = False,
30
+ # Old MLX config aliases. These make older local config values easy to reuse.
31
+ max_seq_len: int | None = None,
32
+ d_model: int | None = None,
33
+ n_layers: int | None = None,
34
+ n_heads: int | None = None,
35
+ ffn_hidden: int | None = None,
36
+ dropout: float | None = None,
37
+ pad_id: int | None = None,
38
+ eos_id: int | None = None,
39
+ **kwargs,
40
+ ) -> None:
41
+ if max_seq_len is not None:
42
+ max_position_embeddings = max_seq_len
43
+ if d_model is not None:
44
+ hidden_size = d_model
45
+ if n_layers is not None:
46
+ num_hidden_layers = n_layers
47
+ if n_heads is not None:
48
+ num_attention_heads = n_heads
49
+ if ffn_hidden is not None:
50
+ intermediate_size = ffn_hidden
51
+ if dropout is not None:
52
+ hidden_dropout_prob = dropout
53
+ attention_dropout_prob = dropout
54
+ if pad_id is not None:
55
+ pad_token_id = pad_id
56
+ if eos_id is not None:
57
+ eos_token_id = eos_id
58
+
59
+ if hidden_size % num_attention_heads != 0:
60
+ raise ValueError("hidden_size must be divisible by num_attention_heads")
61
+ if max_position_embeddings <= 0:
62
+ raise ValueError("max_position_embeddings must be positive")
63
+
64
+ self.vocab_size = int(vocab_size)
65
+ self.max_position_embeddings = int(max_position_embeddings)
66
+ self.hidden_size = int(hidden_size)
67
+ self.num_hidden_layers = int(num_hidden_layers)
68
+ self.num_attention_heads = int(num_attention_heads)
69
+ self.intermediate_size = int(intermediate_size)
70
+ self.hidden_dropout_prob = float(hidden_dropout_prob)
71
+ self.attention_dropout_prob = float(attention_dropout_prob)
72
+ self.layer_norm_eps = float(layer_norm_eps)
73
+ self.initializer_range = float(initializer_range)
74
+ self.use_cache = bool(use_cache)
75
+
76
+ super().__init__(
77
+ pad_token_id=pad_token_id,
78
+ bos_token_id=bos_token_id,
79
+ eos_token_id=eos_token_id,
80
+ tie_word_embeddings=tie_word_embeddings,
81
+ is_encoder_decoder=False,
82
+ **kwargs,
83
+ )
84
+
85
+ # Aliases retained for LoafLM's original MLX naming.
86
+ @property
87
+ def max_seq_len(self) -> int:
88
+ return self.max_position_embeddings
89
+
90
+ @property
91
+ def d_model(self) -> int:
92
+ return self.hidden_size
93
+
94
+ @property
95
+ def n_layers(self) -> int:
96
+ return self.num_hidden_layers
97
+
98
+ @property
99
+ def n_heads(self) -> int:
100
+ return self.num_attention_heads
101
+
102
+ @property
103
+ def ffn_hidden(self) -> int:
104
+ return self.intermediate_size
105
+
106
+ @property
107
+ def dropout(self) -> float:
108
+ return self.hidden_dropout_prob
109
+
110
+ @property
111
+ def pad_id(self) -> int:
112
+ return self.pad_token_id
113
+
114
+ @property
115
+ def eos_id(self) -> int:
116
+ return self.eos_token_id
conversion_report.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "source_checkpoint": "checkpoints/best_model.safetensors",
3
+ "output_dir": "/Users/guus/Documents/Projects/LoafLM/LoafLM-10M-HF",
4
+ "source_tensor_count": 77,
5
+ "hf_tensor_count": 77,
6
+ "parameter_count_unique": 10298880,
7
+ "save_reload_max_abs_logit_difference": 0.0,
8
+ "conversion_notes": [],
9
+ "lossless": true,
10
+ "quantized": false
11
+ }
generation_config.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 1,
3
+ "do_sample": true,
4
+ "eos_token_id": 2,
5
+ "max_new_tokens": 64,
6
+ "pad_token_id": 0,
7
+ "temperature": 0.3,
8
+ "top_k": 50,
9
+ "transformers_version": "5.14.1",
10
+ "use_cache": false
11
+ }
model.safetensors CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:76630cfd68fbac0da939063b8945425655f8e8464f00a140f0a654de8755c2d7
3
- size 41202478
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b314ccfff53e39bbaf2cc4109c69e6e944720a05fcd5c7e112f4a5665d435af9
3
+ size 41202480
modeling_loaflm.py ADDED
@@ -0,0 +1,281 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Hugging Face Transformers implementation of LoafLM.
2
+
3
+ This intentionally mirrors the original MLX architecture:
4
+ - learned token and position embeddings
5
+ - pre-norm causal self-attention
6
+ - fused QKV projection
7
+ - ReLU feed-forward network
8
+ - language-model head (tied only when requested by the config)
9
+ """
10
+ from __future__ import annotations
11
+
12
+ from typing import Optional, Tuple
13
+
14
+ import torch
15
+ from torch import nn
16
+ import torch.nn.functional as F
17
+ from transformers import GenerationMixin, PreTrainedModel
18
+ from transformers.modeling_outputs import CausalLMOutputWithPast
19
+
20
+ try:
21
+ from .configuration_loaflm import LoafLMConfig
22
+ except ImportError: # Allows local direct imports during conversion/testing.
23
+ from configuration_loaflm import LoafLMConfig
24
+
25
+
26
+ class LoafAttention(nn.Module):
27
+ def __init__(self, config: LoafLMConfig) -> None:
28
+ super().__init__()
29
+ self.n_heads = config.num_attention_heads
30
+ self.head_dim = config.hidden_size // config.num_attention_heads
31
+ self.scale = self.head_dim ** -0.5
32
+ self.qkv = nn.Linear(config.hidden_size, 3 * config.hidden_size)
33
+ self.out = nn.Linear(config.hidden_size, config.hidden_size)
34
+ self.drop = nn.Dropout(config.attention_dropout_prob)
35
+
36
+ def forward(
37
+ self,
38
+ hidden_states: torch.Tensor,
39
+ attention_mask: Optional[torch.Tensor] = None,
40
+ output_attentions: bool = False,
41
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
42
+ batch_size, seq_len, channels = hidden_states.shape
43
+
44
+ qkv = self.qkv(hidden_states).reshape(
45
+ batch_size,
46
+ seq_len,
47
+ 3,
48
+ self.n_heads,
49
+ self.head_dim,
50
+ )
51
+ qkv = qkv.permute(2, 0, 3, 1, 4)
52
+ query, key, value = qkv[0], qkv[1], qkv[2]
53
+
54
+ scores = torch.matmul(query, key.transpose(-2, -1)) * self.scale
55
+
56
+ causal_mask = torch.ones(
57
+ (seq_len, seq_len),
58
+ dtype=torch.bool,
59
+ device=hidden_states.device,
60
+ ).tril()
61
+ combined_mask = causal_mask.view(1, 1, seq_len, seq_len)
62
+
63
+ query_mask = None
64
+ if attention_mask is not None:
65
+ if attention_mask.ndim != 2:
66
+ raise ValueError("attention_mask must have shape [batch, sequence]")
67
+ key_mask = attention_mask[:, None, None, :].to(torch.bool)
68
+ combined_mask = combined_mask & key_mask
69
+ query_mask = attention_mask[:, None, :, None].to(scores.dtype)
70
+
71
+ # Use a finite minimum. Fully padded query rows then remain numerically safe;
72
+ # they are zeroed by query_mask below and never influence real tokens.
73
+ scores = scores.masked_fill(~combined_mask, torch.finfo(scores.dtype).min)
74
+ attention_probs = torch.softmax(scores, dim=-1)
75
+ if query_mask is not None:
76
+ attention_probs = attention_probs * query_mask
77
+ attention_probs = self.drop(attention_probs)
78
+
79
+ context = torch.matmul(attention_probs, value)
80
+ context = context.permute(0, 2, 1, 3).reshape(batch_size, seq_len, channels)
81
+ output = self.out(context)
82
+ return output, attention_probs if output_attentions else None
83
+
84
+
85
+ class LoafFFN(nn.Module):
86
+ def __init__(self, config: LoafLMConfig) -> None:
87
+ super().__init__()
88
+ self.up = nn.Linear(config.hidden_size, config.intermediate_size)
89
+ self.down = nn.Linear(config.intermediate_size, config.hidden_size)
90
+ self.drop = nn.Dropout(config.hidden_dropout_prob)
91
+
92
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
93
+ return self.drop(self.down(F.relu(self.up(hidden_states))))
94
+
95
+
96
+ class LoafBlock(nn.Module):
97
+ def __init__(self, config: LoafLMConfig) -> None:
98
+ super().__init__()
99
+ self.norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
100
+ self.attn = LoafAttention(config)
101
+ self.norm2 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
102
+ self.ffn = LoafFFN(config)
103
+
104
+ def forward(
105
+ self,
106
+ hidden_states: torch.Tensor,
107
+ attention_mask: Optional[torch.Tensor] = None,
108
+ output_attentions: bool = False,
109
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
110
+ attn_output, attn_weights = self.attn(
111
+ self.norm1(hidden_states),
112
+ attention_mask=attention_mask,
113
+ output_attentions=output_attentions,
114
+ )
115
+ hidden_states = hidden_states + attn_output
116
+ hidden_states = hidden_states + self.ffn(self.norm2(hidden_states))
117
+ return hidden_states, attn_weights
118
+
119
+
120
+ class LoafLMPreTrainedModel(PreTrainedModel):
121
+ config_class = LoafLMConfig
122
+ base_model_prefix = ""
123
+ main_input_name = "input_ids"
124
+ supports_gradient_checkpointing = False
125
+ _no_split_modules = ["LoafBlock"]
126
+
127
+ def _init_weights(self, module: nn.Module) -> None:
128
+ if isinstance(module, nn.Linear):
129
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
130
+ if module.bias is not None:
131
+ module.bias.data.zero_()
132
+ elif isinstance(module, nn.Embedding):
133
+ module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
134
+ if module.padding_idx is not None:
135
+ module.weight.data[module.padding_idx].zero_()
136
+ elif isinstance(module, nn.LayerNorm):
137
+ module.bias.data.zero_()
138
+ module.weight.data.fill_(1.0)
139
+
140
+
141
+ class LoafLMForCausalLM(LoafLMPreTrainedModel, GenerationMixin):
142
+ """Decoder-only LoafLM with a causal language-modeling head."""
143
+
144
+ # Transformers 5 expects a target -> source mapping here (rather than the
145
+ # list of target keys accepted by Transformers 4).
146
+ _tied_weights_keys = {"lm_head.weight": "tok_emb.weight"}
147
+
148
+ def __init__(self, config: LoafLMConfig) -> None:
149
+ super().__init__(config)
150
+ self.tok_emb = nn.Embedding(
151
+ config.vocab_size,
152
+ config.hidden_size,
153
+ padding_idx=None, # Matches the original MLX embedding behavior.
154
+ )
155
+ self.pos_emb = nn.Embedding(config.max_position_embeddings, config.hidden_size)
156
+ self.drop = nn.Dropout(config.hidden_dropout_prob)
157
+ self.blocks = nn.ModuleList([LoafBlock(config) for _ in range(config.num_hidden_layers)])
158
+ self.norm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
159
+ self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
160
+ self.post_init()
161
+
162
+ def get_input_embeddings(self) -> nn.Embedding:
163
+ return self.tok_emb
164
+
165
+ def set_input_embeddings(self, value: nn.Embedding) -> None:
166
+ self.tok_emb = value
167
+
168
+ def get_output_embeddings(self) -> nn.Linear:
169
+ return self.lm_head
170
+
171
+ def set_output_embeddings(self, new_embeddings: nn.Linear) -> None:
172
+ self.lm_head = new_embeddings
173
+
174
+ def forward(
175
+ self,
176
+ input_ids: Optional[torch.LongTensor] = None,
177
+ attention_mask: Optional[torch.Tensor] = None,
178
+ position_ids: Optional[torch.LongTensor] = None,
179
+ past_key_values=None,
180
+ inputs_embeds: Optional[torch.FloatTensor] = None,
181
+ labels: Optional[torch.LongTensor] = None,
182
+ use_cache: Optional[bool] = None,
183
+ output_attentions: Optional[bool] = None,
184
+ output_hidden_states: Optional[bool] = None,
185
+ return_dict: Optional[bool] = None,
186
+ cache_position=None,
187
+ **kwargs,
188
+ ) -> CausalLMOutputWithPast | tuple:
189
+ del past_key_values, cache_position, kwargs
190
+ output_attentions = bool(output_attentions)
191
+ output_hidden_states = bool(output_hidden_states)
192
+ return_dict = self.config.use_return_dict if return_dict is None else return_dict
193
+
194
+ if (input_ids is None) == (inputs_embeds is None):
195
+ raise ValueError("Provide exactly one of input_ids or inputs_embeds")
196
+
197
+ if inputs_embeds is None:
198
+ inputs_embeds = self.tok_emb(input_ids)
199
+ batch_size, seq_len, _ = inputs_embeds.shape
200
+
201
+ if seq_len > self.config.max_position_embeddings:
202
+ raise ValueError(
203
+ f"Sequence length {seq_len} exceeds LoafLM's context length "
204
+ f"{self.config.max_position_embeddings}. Truncate the input first."
205
+ )
206
+
207
+ if attention_mask is None:
208
+ attention_mask = torch.ones(
209
+ (batch_size, seq_len),
210
+ dtype=torch.long,
211
+ device=inputs_embeds.device,
212
+ )
213
+
214
+ if position_ids is None:
215
+ # Keeps content positions starting at zero even when lm-eval left-pads a batch.
216
+ position_ids = attention_mask.long().cumsum(dim=-1) - 1
217
+ position_ids = position_ids.masked_fill(attention_mask == 0, 0)
218
+ position_ids = position_ids.clamp(min=0, max=self.config.max_position_embeddings - 1)
219
+
220
+ hidden_states = inputs_embeds + self.pos_emb(position_ids)
221
+ hidden_states = self.drop(hidden_states)
222
+
223
+ all_hidden_states = () if output_hidden_states else None
224
+ all_attentions = () if output_attentions else None
225
+
226
+ for block in self.blocks:
227
+ if output_hidden_states:
228
+ all_hidden_states = all_hidden_states + (hidden_states,)
229
+ hidden_states, attn_weights = block(
230
+ hidden_states,
231
+ attention_mask=attention_mask,
232
+ output_attentions=output_attentions,
233
+ )
234
+ if output_attentions:
235
+ all_attentions = all_attentions + (attn_weights,)
236
+
237
+ hidden_states = self.norm(hidden_states)
238
+ if output_hidden_states:
239
+ all_hidden_states = all_hidden_states + (hidden_states,)
240
+
241
+ logits = self.lm_head(hidden_states)
242
+
243
+ loss = None
244
+ if labels is not None:
245
+ shift_logits = logits[..., :-1, :].contiguous()
246
+ shift_labels = labels[..., 1:].contiguous()
247
+ loss = F.cross_entropy(
248
+ shift_logits.view(-1, self.config.vocab_size),
249
+ shift_labels.view(-1),
250
+ ignore_index=-100,
251
+ )
252
+
253
+ if not return_dict:
254
+ outputs = (logits, None, all_hidden_states, all_attentions)
255
+ return ((loss,) + outputs) if loss is not None else outputs
256
+
257
+ return CausalLMOutputWithPast(
258
+ loss=loss,
259
+ logits=logits,
260
+ past_key_values=None,
261
+ hidden_states=all_hidden_states,
262
+ attentions=all_attentions,
263
+ )
264
+
265
+ def prepare_inputs_for_generation(
266
+ self,
267
+ input_ids: torch.LongTensor,
268
+ attention_mask: Optional[torch.Tensor] = None,
269
+ **kwargs,
270
+ ) -> dict:
271
+ # LoafLM is tiny, so generation intentionally recomputes the full 128-token
272
+ # context instead of maintaining a KV cache.
273
+ if input_ids.shape[1] > self.config.max_position_embeddings:
274
+ input_ids = input_ids[:, -self.config.max_position_embeddings :]
275
+ if attention_mask is not None:
276
+ attention_mask = attention_mask[:, -self.config.max_position_embeddings :]
277
+ return {
278
+ "input_ids": input_ids,
279
+ "attention_mask": attention_mask,
280
+ "use_cache": False,
281
+ }
tokenizer_config.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|im_start|>",
4
+ "eos_token": "<|im_end|>",
5
+ "model_max_length": 128,
6
+ "pad_token": "<pad>",
7
+ "tokenizer_class": "TokenizersBackend",
8
+ "unk_token": "<unk>"
9
+ }