Theskywalker07 commited on
Commit
187c341
·
verified ·
1 Parent(s): 1fb418f

Update strict-small architecture files (SwiGLU, sliding window [64, 16, 8, 4], ln3, ln_post_moe, no res3)

Browse files
Files changed (7) hide show
  1. .DS_Store +0 -0
  2. README-2.md +90 -0
  3. configuration_xpertgpt.py +36 -0
  4. modeling_xpertgpt.py +456 -0
  5. train.py +1429 -0
  6. upload_all_hf.py +99 -0
  7. upload_project_hf.py +92 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
README-2.md ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: cc-by-nc-4.0
3
+ language:
4
+ - en
5
+ tags:
6
+ - babylm
7
+ - babylm-2026
8
+ - mixture-of-experts
9
+ - msit
10
+ - xpertgpt
11
+ - swiglu
12
+ - custom_code
13
+ - safetensors
14
+ library_name: transformers
15
+ pipeline_tag: text-generation
16
+ ---
17
+
18
+ # XpertGPT (SwiGLU & Sliding Window 64, 16, 8, 4)
19
+
20
+ XpertGPT is a sparse Mixture of Experts (MoE) language model designed for data-efficient pretraining under the **BabyLM 2026 challenge (Strict-Small 10M track)**. It leverages **Parallelized Multi-Scale Information Transmission (MSIT)** and **Expert Choice Routing** to maximize representational capacity within restricted token budgets.
21
+
22
+ This version implements **SwiGLU Feed-Forward Networks** across all global and parallel expert blocks, along with corrected LayerNorms, redundant residual removal, and **sliding window attention sizes** of `[64, 16, 8, 4]` tokens.
23
+
24
+ ---
25
+
26
+ ## 1. SwiGLU Feed-Forward Networks (FFN)
27
+
28
+ Instead of the standard Feed-Forward sequential structure (Linear -> GELU -> Linear), this model replaces FFN layers with the **SwiGLU (Swish Gated Linear Unit)** variant to improve model capacity and training stability:
29
+
30
+ $$\text{FFN}_{\text{SwiGLU}}(x) = \left(\text{Swish}(x W) \otimes x V\right) W_2$$
31
+
32
+ Where the Swish function is implemented using SiLU:
33
+ * **Gate Linear projections ($W$, $V$)**: Projects input dimension `dim` to `hidden_dim`.
34
+ * **Out Linear projection ($W_2$)**: Projects back to `dim`.
35
+ * To maintain parameter counts equivalent to standard `dim * 4` sequential FFNs, the hidden dimension is scaled to:
36
+ $$\text{hidden\_dim} = \text{round\_to\_multiple\_of\_8}\left(\frac{8}{3} \times \text{dim}\right)$$
37
+
38
+ ---
39
+
40
+ ## 2. Expert Sliding Window Layout
41
+
42
+ The four parallel MoE experts are configured with distinct sliding window attention constraints:
43
+ * **Expert 1**: Window size `64` tokens
44
+ * **Expert 2**: Window size `16` tokens
45
+ * **Expert 3**: Window size `8` tokens
46
+ * **Expert 4**: Window size `4` tokens
47
+
48
+ ---
49
+
50
+ ## 3. Architectural Layout & Changes
51
+
52
+ This model implements:
53
+ 1. **Removal of Redundant Residual (`res3`)**:
54
+ * Removed redundant residual connection around the global dense block. Gated input is now simply $X_2 = X_1$.
55
+ 2. **Introduction of Post-Block LayerNorm (`ln3`)**:
56
+ * LayerNorm `ln3` is added after the SwiGLU addition inside every `MSITBranchBlock`.
57
+ * Formulation: $X_{\text{out}} = \text{LayerNorm}(X^{(2)})$.
58
+ 3. **Introduction of Post-MoE LayerNorm (`ln_post_moe`)**:
59
+ * LayerNorm `ln_post_moe` is added after the Residual 4 MoE aggregation.
60
+ * Formulation: $X_{\text{out}} = \text{LayerNorm}(X_2 + X_{3, \text{full}})$.
61
+
62
+ ---
63
+
64
+ ## 4. How to Load and Use Checkpoints (Bypass Retraining)
65
+
66
+ ### A. Loading the Final Model (`main` branch)
67
+ ```python
68
+ import torch
69
+ from transformers import AutoModelForCausalLM, AutoTokenizer
70
+
71
+ model = AutoModelForCausalLM.from_pretrained(
72
+ "SRJ5035/sw_glu_sw_64_16_8_4_xpert_gpt",
73
+ revision="main",
74
+ trust_remote_code=True
75
+ ).eval()
76
+
77
+ tokenizer = AutoTokenizer.from_pretrained(
78
+ "SRJ5035/sw_glu_sw_64_16_8_4_xpert_gpt",
79
+ revision="main"
80
+ )
81
+ ```
82
+
83
+ ### B. Loading an Intermediate Milestone (e.g. `chck_5M`)
84
+ ```python
85
+ model_5m = AutoModelForCausalLM.from_pretrained(
86
+ "SRJ5035/sw_glu_sw_64_16_8_4_xpert_gpt",
87
+ revision="chck_5M",
88
+ trust_remote_code=True
89
+ ).eval()
90
+ ```
configuration_xpertgpt.py ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+ class XpertGPTConfig(PretrainedConfig):
4
+ model_type = "xpertgpt"
5
+
6
+ def __init__(
7
+ self,
8
+ vocab_size: int = 16384,
9
+ block_size: int = 512,
10
+ d_model: int = 256,
11
+ d_thin: int = 384,
12
+ num_layers: int = 6,
13
+ num_blocks: int = 4,
14
+ capacity_factor: float = 1.0,
15
+ dropout: float = 0.1,
16
+ **kwargs
17
+ ):
18
+ kwargs.setdefault("is_decoder", True)
19
+ kwargs.setdefault("bos_token_id", 2) # [CLS]
20
+ kwargs.setdefault("eos_token_id", 3) # [SEP]
21
+ kwargs.setdefault("pad_token_id", 1) # [PAD]
22
+
23
+ self.vocab_size = vocab_size
24
+ self.block_size = block_size
25
+ self.d_model = d_model
26
+ self.d_thin = d_thin
27
+ self.num_layers = num_layers
28
+ self.num_blocks = num_blocks
29
+ self.capacity_factor = capacity_factor
30
+ self.dropout = dropout
31
+
32
+ # Attribute parity for classification heads
33
+ self.hidden_size = d_model
34
+ self.num_hidden_layers = num_layers
35
+
36
+ super().__init__(**kwargs)
modeling_xpertgpt.py ADDED
@@ -0,0 +1,456 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import math
3
+ import random
4
+ import inspect
5
+ from typing import Optional, Tuple, Dict, Any
6
+ from dataclasses import dataclass
7
+
8
+ import torch
9
+ import torch.nn as nn
10
+ import torch.nn.functional as F
11
+
12
+ from transformers import PretrainedConfig, PreTrainedModel, GenerationMixin, AutoConfig, AutoModel, AutoModelForCausalLM
13
+ from transformers.modeling_outputs import BaseModelOutputWithPast, CausalLMOutputWithPast
14
+
15
+ # ─────────────────────────────────────────────────────────────
16
+ # Configuration Classes
17
+ # ─────────────────────────────────────────────────────────────
18
+
19
+ @dataclass
20
+ class XpertGPTModelConfig:
21
+ vocab_size: int = 16384
22
+ block_size: int = 512
23
+ d_model: int = 256
24
+ d_thin: int = 384
25
+ num_layers: int = 6
26
+ num_blocks: int = 4
27
+ capacity_factor: float = 1.0
28
+ dropout: float = 0.1
29
+
30
+ class XpertGPTConfig(PretrainedConfig):
31
+ model_type = "xpertgpt"
32
+ auto_map = {
33
+ "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
34
+ "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
35
+ "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
36
+ }
37
+
38
+ def __init__(
39
+ self,
40
+ vocab_size: int = 16384,
41
+ block_size: int = 512,
42
+ d_model: int = 256,
43
+ d_thin: int = 384,
44
+ num_layers: int = 6,
45
+ num_blocks: int = 4,
46
+ capacity_factor: float = 1.0,
47
+ dropout: float = 0.1,
48
+ **kwargs
49
+ ):
50
+ kwargs.setdefault("is_decoder", True)
51
+ kwargs.setdefault("bos_token_id", 2) # [CLS]
52
+ kwargs.setdefault("eos_token_id", 3) # [SEP]
53
+ kwargs.setdefault("pad_token_id", 1) # [PAD]
54
+
55
+ self.vocab_size = vocab_size
56
+ self.block_size = block_size
57
+ self.d_model = d_model
58
+ self.d_thin = d_thin
59
+ self.num_layers = num_layers
60
+ self.num_blocks = num_blocks
61
+ self.capacity_factor = capacity_factor
62
+ self.dropout = dropout
63
+
64
+ # Attribute parity for classification heads
65
+ self.hidden_size = d_model
66
+ self.num_hidden_layers = num_layers
67
+
68
+ super().__init__(**kwargs)
69
+
70
+ # ─────────────────────────────────────────────────────────────
71
+ # ROPE HELPERS
72
+ # ─────────────────────────────────────────────────────────────
73
+
74
+ def _precompute_rope_freqs(head_dim: int, seq_len: int, device: torch.device, theta: float = 10000.0):
75
+ assert head_dim % 2 == 0, "head_dim must be divisible by 2 for RoPE"
76
+ inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim))
77
+ t = torch.arange(seq_len, device=device).float()
78
+ freqs = torch.outer(t, inv_freq)
79
+ emb = torch.cat((freqs, freqs), dim=-1)
80
+ return emb.cos(), emb.sin()
81
+
82
+ def _apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor):
83
+ L = x.size(2)
84
+ cos = cos[:L, :].unsqueeze(0).unsqueeze(1)
85
+ sin = sin[:L, :].unsqueeze(0).unsqueeze(1)
86
+
87
+ half_dim = x.size(-1) // 2
88
+ x1 = x[..., :half_dim]
89
+ x2 = x[..., half_dim:]
90
+ rotated_x = torch.cat((-x2, x1), dim=-1)
91
+
92
+ return (x * cos) + (rotated_x * sin)
93
+
94
+ # ─────────────────────────────────────────────────────────────
95
+ # 1. SLIDING WINDOW ATTENTION
96
+ # ─────────────────────────────────────────────────────────────
97
+
98
+ class SlidingWindowAttention(nn.Module):
99
+ def __init__(self, dim: int, num_heads: int, window_size=None):
100
+ super().__init__()
101
+ assert dim % num_heads == 0, "dim must be divisible by num_heads"
102
+ self.num_heads = num_heads
103
+ self.window_size = window_size
104
+ self.head_dim = dim // num_heads
105
+
106
+ self.q_proj = nn.Linear(dim, dim, bias=False)
107
+ self.k_proj = nn.Linear(dim, dim, bias=False)
108
+ self.v_proj = nn.Linear(dim, dim, bias=False)
109
+ self.o_proj = nn.Linear(dim, dim, bias=False)
110
+
111
+ def forward(self, x: torch.Tensor,
112
+ past_kv=None,
113
+ use_cache: bool = False,
114
+ bidirectional: bool = False):
115
+ B, L, D = x.size()
116
+
117
+ q = self.q_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
118
+ k = self.k_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
119
+ v = self.v_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
120
+
121
+ if past_kv is not None:
122
+ past_k, past_v = past_kv
123
+ past_len = past_k.size(2)
124
+ q_cos, q_sin = _precompute_rope_freqs(self.head_dim, past_len + L, x.device)
125
+ q = _apply_rope(q, q_cos[past_len:, :], q_sin[past_len:, :])
126
+ k = _apply_rope(k, q_cos[past_len:, :], q_sin[past_len:, :])
127
+
128
+ k = torch.cat([past_k, k], dim=2)
129
+ v = torch.cat([past_v, v], dim=2)
130
+ else:
131
+ cos, sin = _precompute_rope_freqs(self.head_dim, L, x.device)
132
+ q = _apply_rope(q, cos, sin)
133
+ k = _apply_rope(k, cos, sin)
134
+
135
+ L_kv = k.size(2)
136
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
137
+
138
+ if bidirectional:
139
+ if self.window_size is not None:
140
+ past_len = L_kv - L
141
+ pos_i = (past_len + torch.arange(L, device=x.device)).unsqueeze(1)
142
+ pos_j = torch.arange(L_kv, device=x.device).unsqueeze(0)
143
+ dist = torch.abs(pos_i - pos_j)
144
+ win_mask = dist < self.window_size
145
+ scores = scores.masked_fill(
146
+ ~win_mask.unsqueeze(0).unsqueeze(0), float('-inf')
147
+ )
148
+ else:
149
+ past_len = L_kv - L
150
+ pos_i = (past_len + torch.arange(L, device=x.device)).unsqueeze(1)
151
+ pos_j = torch.arange(L_kv, device=x.device).unsqueeze(0)
152
+ dist = pos_i - pos_j
153
+
154
+ causal_mask = dist >= 0
155
+ if self.window_size is not None:
156
+ causal_mask = causal_mask & (dist < self.window_size)
157
+
158
+ scores = scores.masked_fill(
159
+ ~causal_mask.unsqueeze(0).unsqueeze(0), float('-inf')
160
+ )
161
+
162
+ attn = torch.softmax(scores, dim=-1)
163
+ out = torch.matmul(attn, v)
164
+ out = out.transpose(1, 2).contiguous().view(B, L, D)
165
+ out = self.o_proj(out)
166
+
167
+ if use_cache:
168
+ if self.window_size is not None:
169
+ present_kv = (
170
+ k[:, :, -self.window_size:, :],
171
+ v[:, :, -self.window_size:, :]
172
+ )
173
+ else:
174
+ present_kv = (k, v)
175
+ else:
176
+ present_kv = None
177
+
178
+ return out, present_kv
179
+
180
+ # ─────────────────────────────────────────────────────────────
181
+ # 2. MSIT BRANCH BLOCK (Pre-Norm)
182
+ # ─────────────────────────────────────────────────────────────
183
+
184
+ class SwiGLU(nn.Module):
185
+ def __init__(self, dim: int):
186
+ super().__init__()
187
+ # Keep parameter count comparable to regular FFN with dim * 4:
188
+ # 3 * dim * hidden_dim ~= 8 * dim^2 => hidden_dim = 8/3 * dim
189
+ hidden_dim = int(dim * 8 / 3)
190
+ hidden_dim = ((hidden_dim + 7) // 8) * 8
191
+ self.fc1 = nn.Linear(dim, hidden_dim, bias=False)
192
+ self.fc2 = nn.Linear(dim, hidden_dim, bias=False)
193
+ self.fc3 = nn.Linear(hidden_dim, dim, bias=False)
194
+
195
+ def forward(self, x):
196
+ return self.fc3(F.silu(self.fc1(x)) * self.fc2(x))
197
+
198
+ class MSITBranchBlock(nn.Module):
199
+ def __init__(self, dim: int, num_heads: int, window_size):
200
+ super().__init__()
201
+ self.ln1 = nn.LayerNorm(dim)
202
+ self.attn = SlidingWindowAttention(dim, num_heads, window_size)
203
+ self.ln2 = nn.LayerNorm(dim)
204
+ self.ffn = SwiGLU(dim)
205
+ self.ln3 = nn.LayerNorm(dim)
206
+
207
+ def forward(self, x: torch.Tensor,
208
+ past_kv=None,
209
+ use_cache: bool = False,
210
+ bidirectional: bool = False):
211
+ attn_out, present_kv = self.attn(
212
+ self.ln1(x), past_kv, use_cache, bidirectional
213
+ )
214
+ x = x + attn_out
215
+ x = x + self.ffn(self.ln2(x))
216
+ x = self.ln3(x)
217
+ return x, present_kv
218
+
219
+ # ─────────────────────────────────────────────────────────────
220
+ # 3. MoEP-MSIT ARCHITECTURE BLOCK (Expert Choice routing)
221
+ # ─────────────────────────────────────────────────────────────
222
+
223
+ class MoEPMSITBlock(nn.Module):
224
+ def __init__(self, d_model: int = 512, d_thin: int = 192, num_blocks: int = 14,
225
+ capacity_factor: float = 1.0):
226
+ super().__init__()
227
+ self.d_model = d_model
228
+ self.d_thin = d_thin
229
+ self.num_blocks = num_blocks
230
+ self.capacity_factor = capacity_factor
231
+
232
+ # 1. Global Block (Dense, d_model)
233
+ num_heads_global = max(1, d_model // 64)
234
+ self.global_block = MSITBranchBlock(d_model, num_heads_global, window_size=None)
235
+
236
+ # 2. Router
237
+ self.router_ln = nn.LayerNorm(d_model)
238
+ self.w_router = nn.Linear(d_model, num_blocks, bias=False)
239
+
240
+ # 3. Shrink Projection
241
+ self.w_down = nn.Linear(d_model, d_thin, bias=False)
242
+
243
+ # 4. Thin Parallel Blocks (d_thin)
244
+ self.windows = [64, 16, 8, 4] + [None] * (num_blocks - 4)
245
+ self.heads = [max(1, d_thin // 64)] * num_blocks
246
+
247
+ self.thin_blocks = nn.ModuleList([
248
+ MSITBranchBlock(d_thin, self.heads[i], self.windows[i])
249
+ for i in range(num_blocks)
250
+ ])
251
+
252
+ # 6. Grow Projection
253
+ self.w_up = nn.Linear(d_thin, d_model, bias=False)
254
+ self.last_topk_indices = None
255
+ self.ln_post_moe = nn.LayerNorm(d_model)
256
+
257
+ def forward(self, x_0: torch.Tensor, past_kvs=None, use_cache: bool = False, bidirectional: bool = False):
258
+ B, T, D = x_0.size()
259
+ n_tokens = B * T
260
+
261
+ # Step 1: Global Block
262
+ pkv_g = past_kvs[0] if past_kvs else None
263
+ x_1, nkv_g = self.global_block(x_0, pkv_g, use_cache, bidirectional)
264
+
265
+ # Step 2: Gated input stream
266
+ x_2 = x_1
267
+
268
+ # Router scores
269
+ r_logits = self.w_router(self.router_ln(x_2)).view(n_tokens, self.num_blocks)
270
+ r_probs = F.softmax(r_logits, dim=-1)
271
+
272
+ # Per-expert capacity: k = (n * c) / e
273
+ k_capacity = max(1, int(round(n_tokens * self.capacity_factor / self.num_blocks)))
274
+ k_capacity = min(k_capacity, n_tokens)
275
+
276
+ # Expert Choice routing: topk over the token axis for each expert
277
+ expert_token_scores = r_probs.transpose(0, 1) # (num_blocks, n_tokens)
278
+ topk_scores, topk_token_idx = torch.topk(expert_token_scores, k_capacity, dim=-1)
279
+ self.last_topk_indices = topk_token_idx
280
+
281
+ # Load balancing is guaranteed by construction in Expert Choice
282
+ layer_aux_loss = x_2.new_zeros(())
283
+
284
+ # Shrink projection
285
+ x_2_thin = self.w_down(x_2) # (B, T, d_thin)
286
+ x_2_thin_flat = x_2_thin.view(n_tokens, self.d_thin)
287
+
288
+ # Expert computations
289
+ new_kvs = [nkv_g]
290
+ expert_outputs_flat = torch.zeros(n_tokens, self.d_model, device=x_2.device, dtype=x_2.dtype)
291
+
292
+ for i, block in enumerate(self.thin_blocks):
293
+ sel_idx = topk_token_idx[i]
294
+ bucket_in = x_2_thin_flat[sel_idx].unsqueeze(0) # (1, k_capacity, d_thin)
295
+
296
+ pkv_i = past_kvs[i + 1] if past_kvs else None
297
+ bucket_out, nkv_i = block(bucket_in, pkv_i, use_cache, bidirectional)
298
+ if use_cache:
299
+ new_kvs.append(nkv_i)
300
+
301
+ bucket_out = bucket_out.squeeze(0) # (k_capacity, d_thin)
302
+ bucket_out_full = self.w_up(bucket_out) # (k_capacity, d_model)
303
+
304
+ gate = topk_scores[i].unsqueeze(-1) # (k_capacity, 1)
305
+ expert_outputs_flat.index_add_(0, sel_idx, bucket_out_full * gate)
306
+
307
+ x_3_full = expert_outputs_flat.view(B, T, self.d_model)
308
+ out = x_2 + x_3_full
309
+ out = self.ln_post_moe(out)
310
+
311
+ present_kvs = tuple(new_kvs) if use_cache else None
312
+ return out, present_kvs, layer_aux_loss
313
+
314
+ # ─────────────────────────────────────────────────────────────
315
+ # 4. RAW XpertGPT MODEL
316
+ # ─────────────────────────────────────────────────────────────
317
+
318
+ class XpertGPTModel(nn.Module):
319
+ def __init__(self, cfg):
320
+ super().__init__()
321
+ self.cfg = cfg
322
+ self.wte = nn.Embedding(cfg.vocab_size, cfg.d_model)
323
+ self.drop_emb = nn.Dropout(cfg.dropout)
324
+ self.blocks = nn.ModuleList([
325
+ MoEPMSITBlock(
326
+ d_model=cfg.d_model,
327
+ d_thin=cfg.d_thin,
328
+ num_blocks=cfg.num_blocks,
329
+ capacity_factor=cfg.capacity_factor
330
+ )
331
+ for _ in range(cfg.num_layers)
332
+ ])
333
+ self.ln_f = nn.LayerNorm(cfg.d_model)
334
+ self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
335
+ self.wte.weight = self.lm_head.weight
336
+
337
+ def forward(self, input_ids: torch.Tensor, targets: torch.Tensor = None, bidirectional: bool = False):
338
+ x = self.drop_emb(self.wte(input_ids))
339
+ total_aux_loss = 0.0
340
+
341
+ for block in self.blocks:
342
+ x, _, layer_aux = block(x, past_kvs=None, use_cache=False, bidirectional=bidirectional)
343
+ total_aux_loss += layer_aux
344
+
345
+ x = self.ln_f(x)
346
+ logits = self.lm_head(x)
347
+
348
+ loss = None
349
+ if targets is not None:
350
+ ce_loss = F.cross_entropy(logits.view(-1, self.cfg.vocab_size), targets.view(-1), ignore_index=-100)
351
+ avg_aux_loss = total_aux_loss / self.cfg.num_layers
352
+ loss = ce_loss + (0.01 * avg_aux_loss)
353
+
354
+ return logits, loss
355
+
356
+ # ─────────────────────────────────────────────────────────────
357
+ # 5. HUGGING FACE MODEL WRAPPERS
358
+ # ─────────────────────────────────────────────────────────────
359
+
360
+ class XpertGPTModelWrapper(PreTrainedModel):
361
+ config_class = XpertGPTConfig
362
+ base_model_prefix = "transformer"
363
+
364
+ def __init__(self, config: XpertGPTConfig):
365
+ super().__init__(config)
366
+ self.wte = nn.Embedding(config.vocab_size, config.d_model)
367
+ self.drop_emb = nn.Dropout(config.dropout)
368
+ self.blocks = nn.ModuleList([
369
+ MoEPMSITBlock(config.d_model, config.d_thin, config.num_blocks, config.capacity_factor)
370
+ for _ in range(config.num_layers)
371
+ ])
372
+ self.ln_f = nn.LayerNorm(config.d_model)
373
+ self.post_init()
374
+
375
+ def forward(self, input_ids, **kwargs):
376
+ x = self.drop_emb(self.wte(input_ids))
377
+ for block in self.blocks:
378
+ x, _, _ = block(x, past_kvs=None, use_cache=False, bidirectional=False)
379
+ x = self.ln_f(x)
380
+ return BaseModelOutputWithPast(last_hidden_state=x)
381
+
382
+
383
+ class XpertGPTForCausalLM(PreTrainedModel, GenerationMixin):
384
+ config_class = XpertGPTConfig
385
+ base_model_prefix = "transformer"
386
+ _no_split_modules = ["MoEPMSITBlock"]
387
+ _tied_weights_keys = {"transformer.lm_head.weight": "transformer.wte.weight"}
388
+
389
+ def __init__(self, config: XpertGPTConfig):
390
+ super().__init__(config)
391
+ self.transformer = XpertGPTModelWrapper(config)
392
+ self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
393
+ self.post_init()
394
+
395
+ # State-dict pre-hook for backwards compatibility with checkpoint key naming
396
+ def _prefix_cleaner(state_dict, prefix, local_metadata, Moore, missing_keys, unexpected_keys, error_msgs):
397
+ keys = list(state_dict.keys())
398
+ for k in keys:
399
+ if k.startswith("transformer."):
400
+ state_dict[k.replace("transformer.", "", 1)] = state_dict.pop(k)
401
+ elif f"{prefix}transformer." in k:
402
+ state_dict[k.replace("transformer.", "", 1)] = state_dict.pop(k)
403
+
404
+ self._register_load_state_dict_pre_hook(_prefix_cleaner)
405
+
406
+ def tie_weights(self, **kwargs):
407
+ if hasattr(self, "transformer") and hasattr(self.transformer, "wte") and hasattr(self.transformer, "lm_head"):
408
+ self.transformer.wte.weight = self.lm_head.weight
409
+
410
+ def get_input_embeddings(self):
411
+ return self.transformer.wte
412
+
413
+ def set_input_embeddings(self, new_embeddings):
414
+ self.transformer.wte = new_embeddings
415
+
416
+ def get_output_embeddings(self):
417
+ return self.lm_head
418
+
419
+ def set_output_embeddings(self, new_embeddings):
420
+ self.lm_head = new_embeddings
421
+
422
+ def forward(self,
423
+ input_ids: Optional[torch.LongTensor] = None,
424
+ attention_mask: Optional[torch.FloatTensor] = None,
425
+ labels: Optional[torch.LongTensor] = None,
426
+ **kwargs) -> CausalLMOutputWithPast:
427
+ outputs = self.transformer(input_ids)
428
+ hidden_states = outputs.last_hidden_state
429
+ logits = self.lm_head(hidden_states)
430
+
431
+ loss = None
432
+ if labels is not None:
433
+ shift_logits = logits[:, :-1, :].contiguous()
434
+ shift_labels = labels[:, 1:].contiguous()
435
+ loss = F.cross_entropy(
436
+ shift_logits.view(-1, self.config.vocab_size),
437
+ shift_labels.view(-1),
438
+ ignore_index=-100
439
+ )
440
+
441
+ return CausalLMOutputWithPast(
442
+ loss=loss,
443
+ logits=logits,
444
+ past_key_values=None,
445
+ hidden_states=None,
446
+ attentions=None,
447
+ )
448
+
449
+ def prepare_inputs_for_generation(self, input_ids, **kwargs):
450
+ return {"input_ids": input_ids}
451
+
452
+
453
+ # Register with auto-mapping
454
+ AutoConfig.register("xpertgpt", XpertGPTConfig)
455
+ AutoModel.register(XpertGPTConfig, XpertGPTModelWrapper)
456
+ AutoModelForCausalLM.register(XpertGPTConfig, XpertGPTForCausalLM)
train.py ADDED
@@ -0,0 +1,1429 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import math
3
+ import time
4
+ import json
5
+ import random
6
+ import inspect
7
+ import shutil
8
+ import subprocess
9
+ import argparse
10
+
11
+ # ─────────────────────────────────────────────────────────────
12
+ # 1. CORE PIPELINE FUNCTION AND HELPERS
13
+ # ─────────────────────────────────────────────────────────────
14
+
15
+ def is_zero_shot_cache_valid(cache_dir):
16
+ if not os.path.exists(cache_dir):
17
+ return False
18
+ pred_count = 0
19
+ for root, dirs, files in os.walk(cache_dir):
20
+ if "predictions.json" in files:
21
+ pred_count += 1
22
+ return pred_count >= 3
23
+
24
+ def is_finetune_cache_valid(cache_dir):
25
+ if not os.path.exists(cache_dir):
26
+ return False
27
+ pred_count = 0
28
+ for root, dirs, files in os.walk(cache_dir):
29
+ if "predictions.json" in files:
30
+ pred_count += 1
31
+ return pred_count >= 3
32
+
33
+ def verify_eval_run(path_to_check, description):
34
+ print(f"[Eval] Verifying {description} path: {path_to_check}...")
35
+ if not os.path.exists(path_to_check):
36
+ raise RuntimeError(f"CRITICAL ERROR: {description} directory was NOT created at: {path_to_check}")
37
+
38
+ # Check if predictions.json or results.txt or surprisal.json exists and is non-empty
39
+ found_valid = False
40
+ for root, dirs, files in os.walk(path_to_check):
41
+ for f in files:
42
+ if f in ["predictions.json", "results.txt", "surprisal.json"]:
43
+ file_path = os.path.join(root, f)
44
+ if os.path.getsize(file_path) > 0:
45
+ found_valid = True
46
+ break
47
+ if found_valid:
48
+ break
49
+
50
+ if not found_valid:
51
+ raise RuntimeError(f"CRITICAL ERROR: {description} completed but no valid prediction/result files were written in: {path_to_check}")
52
+ print(f"[Eval] Success! Verified {description} results are stored correctly.")
53
+
54
+ def run_pipeline(model_name: str, epochs: int = 10, skip_eval: bool = False, skip_aoa: bool = True, skip_glue: bool = False):
55
+ # Configure persistent cache paths locally to avoid duplicate downloads
56
+ os.environ["HF_HOME"] = os.path.abspath("./hf_cache")
57
+ os.environ["NLTK_DATA"] = os.path.abspath("./nltk_data")
58
+ os.makedirs("./hf_cache", exist_ok=True)
59
+ os.makedirs("./nltk_data", exist_ok=True)
60
+
61
+ # Programmatic Hugging Face Hub Login if HF_TOKEN is in environment
62
+ hf_token = os.environ.get("HF_TOKEN")
63
+
64
+ if hf_token:
65
+ try:
66
+ from huggingface_hub import login
67
+ login(token=hf_token)
68
+ print("[HF] Programmatic login successful using HF_TOKEN.")
69
+ except Exception as e:
70
+ print(f"[HF] Warning: Programmatic login failed: {e}")
71
+
72
+ import torch
73
+ import torch.nn as nn
74
+ import torch.nn.functional as F
75
+ from datasets import load_dataset
76
+ from tokenizers import Tokenizer
77
+ from transformers import PreTrainedTokenizerFast
78
+
79
+ # Import model architecture
80
+ from modeling_xpertgpt import (
81
+ XpertGPTModel,
82
+ XpertGPTModelConfig,
83
+ XpertGPTConfig
84
+ )
85
+
86
+ # Print GPU details
87
+ if torch.cuda.is_available():
88
+ gpu_name = torch.cuda.get_device_name(0)
89
+ print(f"\n[GPU] CUDA is available! Using GPU: {gpu_name}\n")
90
+ else:
91
+ print("\n[GPU] Warning: CUDA is NOT available! Running on CPU.\n")
92
+
93
+ # ─────────────────────────────────────────────────────────────
94
+ # GLOBAL HYPERPARAMETERS
95
+ # ─────────────────────────────────────────────────────────────
96
+ VOCAB_SIZE = 16384
97
+ MASK_TOKEN_ID = 16383
98
+ BLOCK_SIZE = 512
99
+ BATCH_SIZE = 16
100
+ GRAD_ACCUM_STEPS = 1 # grad_acc_step = 1
101
+ EPOCHS = epochs
102
+ LEARNING_RATE = 3e-4 # lr = 3e-4
103
+ LR_MIN = LEARNING_RATE * 0.05
104
+ WARMUP_STEPS = 800 # warmup_steps = 800
105
+ WEIGHT_DECAY = 0.1
106
+ GRAD_CLIP = 1.0
107
+
108
+ NUM_THIN_BLOCKS = 4
109
+ EC_CAPACITY_FACTOR = 1.0
110
+ CAUSAL_RATIO = 1 / 1
111
+
112
+ MASK_PROB_START = 0.20
113
+ MASK_PROB_END = 0.10
114
+
115
+ # Output directories locally
116
+ model_dir = os.path.abspath(f"./checkpoints/{model_name}")
117
+ os.makedirs(model_dir, exist_ok=True)
118
+ local_results_dir = os.path.abspath(f"./results/{model_name}")
119
+ os.makedirs(local_results_dir, exist_ok=True)
120
+
121
+
122
+
123
+ # ───────────────────────────────���─────────────────────────────
124
+ # Helper: Save Hugging Face Compliant Checkpoint
125
+ # ─────────────────────────────────────────────────────────────
126
+ def save_hf_checkpoint(raw_model, checkpoint_dir_name, tokenizer):
127
+ save_dir = os.path.join(model_dir, checkpoint_dir_name)
128
+ os.makedirs(save_dir, exist_ok=True)
129
+ print(f"\n[Checkpoint] Saving Hugging Face format checkpoint to '{save_dir}'...")
130
+
131
+ # A. Convert state dict keys to CausalLM wrapper naming
132
+ state_dict = raw_model.state_dict()
133
+ new_state_dict = {}
134
+ for k, v in state_dict.items():
135
+ name = k
136
+ if name.startswith("_orig_mod."):
137
+ name = name[10:]
138
+ if name.startswith("model."):
139
+ name = name[6:]
140
+
141
+ if name == "lm_head.weight":
142
+ new_state_dict["lm_head.weight"] = v
143
+ else:
144
+ new_state_dict[f"transformer.{name}"] = v
145
+
146
+ torch.save(new_state_dict, os.path.join(save_dir, "pytorch_model.bin"))
147
+
148
+ # B. Copy modeling.py and configuration.py
149
+ shutil.copy("modeling_xpertgpt.py", os.path.join(save_dir, "modeling_xpertgpt.py"))
150
+ shutil.copy("configuration_xpertgpt.py", os.path.join(save_dir, "configuration_xpertgpt.py"))
151
+
152
+ # C. Create config.json
153
+ config_dict = {
154
+ "auto_map": {
155
+ "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
156
+ "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
157
+ "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
158
+ },
159
+ "vocab_size": VOCAB_SIZE,
160
+ "block_size": BLOCK_SIZE,
161
+ "d_model": 256, # d_model = 256
162
+ "hidden_size": 256, # hidden_size = 256
163
+ "d_thin": 384,
164
+ "num_layers": 6,
165
+ "num_blocks": NUM_THIN_BLOCKS,
166
+ "capacity_factor": EC_CAPACITY_FACTOR,
167
+ "dropout": 0.1,
168
+ "model_type": "xpertgpt"
169
+ }
170
+ with open(os.path.join(save_dir, "config.json"), "w") as f:
171
+ json.dump(config_dict, f, indent=2)
172
+
173
+ # D. Save tokenizer config files
174
+ fast_tokenizer = PreTrainedTokenizerFast(
175
+ tokenizer_object=tokenizer,
176
+ bos_token="[CLS]",
177
+ eos_token="[SEP]",
178
+ unk_token="[UNK]",
179
+ pad_token="[PAD]",
180
+ mask_token="[MASK]"
181
+ )
182
+ fast_tokenizer.save_pretrained(save_dir)
183
+ print(f"[Checkpoint] Checkpoint '{checkpoint_dir_name}' successfully saved.")
184
+
185
+ # ─────────────────────────────────────────────────────────────
186
+ # Tokenizer Training
187
+ # ─────────────────────────────────────────────────────────────
188
+ def build_and_train_tokenizer(texts: list) -> Tokenizer:
189
+ from tokenizers.models import BPE
190
+ from tokenizers.trainers import BpeTrainer
191
+ from tokenizers.pre_tokenizers import Whitespace
192
+
193
+ vocab_path = os.path.join(model_dir, "bpe_vocab_16k.json")
194
+ if os.path.exists(vocab_path):
195
+ print(f"[Tokenizer] Loading trained BPE model layout from '{vocab_path}'...")
196
+ return Tokenizer.from_file(vocab_path)
197
+
198
+ print(f"[Tokenizer] Generating fresh HuggingFace BPE Tokenizer model with {VOCAB_SIZE} slots...")
199
+ tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
200
+ tokenizer.pre_tokenizer = Whitespace()
201
+
202
+ trainer = BpeTrainer(
203
+ vocab_size=VOCAB_SIZE,
204
+ special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]
205
+ )
206
+ tokenizer.train_from_iterator(texts, trainer)
207
+ tokenizer.save(vocab_path)
208
+ print(f"[Tokenizer] Tokenizer training completed and saved to '{vocab_path}'.")
209
+ return tokenizer
210
+
211
+ # ─────────────────────────────────────────────────────────────
212
+ # Data Loader Setup
213
+ # ─────────────────────────────────────────────────────────────
214
+ class DataLoaderLite:
215
+ def __init__(self, B: int, T: int, texts: list, tokenizer: Tokenizer, name: str):
216
+ self.B = B
217
+ self.T = T
218
+
219
+ print(f"[DataLoader:{name}] Tokenising dataset sequences...")
220
+ all_ids = []
221
+ for t in texts:
222
+ if t.strip():
223
+ encoded = tokenizer.encode(t).ids
224
+ all_ids.extend(encoded)
225
+
226
+ self.tokens = torch.tensor(all_ids, dtype=torch.long)
227
+ self.chunk_size = B * T
228
+ self.n_chunks = (len(self.tokens) - 1) // self.chunk_size
229
+ self.indices = list(range(self.n_chunks))
230
+ self.pos = 0
231
+ self._shuffle()
232
+
233
+ print(f"[DataLoader:{name}] Total tokens: {len(self.tokens):,} | Epoch steps: {self.n_chunks:,}")
234
+
235
+ def _shuffle(self):
236
+ random.shuffle(self.indices)
237
+ self.pos = 0
238
+
239
+ def steps_per_epoch(self) -> int:
240
+ return self.n_chunks
241
+
242
+ def next_batch(self):
243
+ B, T = self.B, self.T
244
+ if self.pos >= len(self.indices):
245
+ self._shuffle()
246
+
247
+ chunk_idx = self.indices[self.pos]
248
+ self.pos += 1
249
+
250
+ start_pos = chunk_idx * self.chunk_size
251
+ temp = self.tokens[start_pos : start_pos + self.chunk_size + 1]
252
+
253
+ x = temp[:-1].view(B, T)
254
+ y = temp[1:].view(B, T)
255
+ return x, y
256
+
257
+ # ─────────────────────────────────────────────────────────────
258
+ # Batch preparation and schedules
259
+ # ─────────────────────────────────────────────────────────────
260
+ def get_current_mask_prob(global_step: int, total_steps: int) -> float:
261
+ ratio = min(1.0, global_step / total_steps)
262
+ return MASK_PROB_START + ratio * (MASK_PROB_END - MASK_PROB_START)
263
+
264
+ def prepare_causal_batch(x: torch.Tensor, y: torch.Tensor):
265
+ return x, y, False
266
+
267
+ def prepare_masked_batch(x: torch.Tensor, y: torch.Tensor, mask_prob: float, mask_token_id: int):
268
+ B, T = x.size()
269
+ mask = torch.rand(B, T, device=x.device) < mask_prob
270
+ masked_x = x.clone()
271
+ masked_x[mask] = mask_token_id
272
+
273
+ targets = torch.full_like(y, -100)
274
+ targets[mask] = y[mask]
275
+
276
+ return masked_x, targets, True
277
+
278
+ def get_hybrid_batch(train_loader: DataLoaderLite, global_step: int, total_steps: int, device: torch.device):
279
+ x, y = train_loader.next_batch()
280
+ x, y = x.to(device), y.to(device)
281
+
282
+ if random.random() < CAUSAL_RATIO:
283
+ input_ids, targets, bidir = prepare_causal_batch(x, y)
284
+ else:
285
+ mask_prob = get_current_mask_prob(global_step, total_steps)
286
+ input_ids, targets, bidir = prepare_masked_batch(x, y, mask_prob, MASK_TOKEN_ID)
287
+
288
+ return input_ids, targets, bidir
289
+
290
+ def get_lr(it: int, total_steps: int) -> float:
291
+ if it < WARMUP_STEPS:
292
+ return LEARNING_RATE * (it + 1) / WARMUP_STEPS
293
+ if it >= total_steps:
294
+ return LR_MIN
295
+ decay_ratio = (it - WARMUP_STEPS) / (total_steps - WARMUP_STEPS)
296
+ coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))
297
+ return LR_MIN + coeff * (LEARNING_RATE - LR_MIN)
298
+
299
+ # ─────────────────────────────────────────────────────────────
300
+ # Dataset Preparation
301
+ # ─────────────────────────────────────────────────────────────
302
+ print("\n[Data] Loading BabyLM-2026-Strict-Small ...")
303
+ ds = load_dataset("BabyLM-community/BabyLM-2026-Strict-Small")
304
+ all_text = list(ds['train']['text'])
305
+
306
+ tokenizer = build_and_train_tokenizer(all_text)
307
+
308
+ split = int(len(all_text) * 0.95)
309
+ train_texts = all_text[:split]
310
+ val_texts = all_text[split:]
311
+
312
+ train_loader = DataLoaderLite(BATCH_SIZE, BLOCK_SIZE, train_texts, tokenizer, "train")
313
+
314
+ chunks_per_epoch = train_loader.steps_per_epoch()
315
+ steps_per_epoch = chunks_per_epoch // GRAD_ACCUM_STEPS
316
+ total_steps = steps_per_epoch * EPOCHS
317
+
318
+ cfg = XpertGPTModelConfig()
319
+ device = "cuda" if torch.cuda.is_available() else "cpu"
320
+ torch.manual_seed(42)
321
+ if torch.cuda.is_available():
322
+ torch.cuda.manual_seed(42)
323
+ random.seed(42)
324
+ if hasattr(torch, 'set_float32_matmul_precision'):
325
+ torch.set_float32_matmul_precision('high')
326
+
327
+ model = XpertGPTModel(cfg).to(device)
328
+
329
+ # ─────────────────────────────────────────────────────────────
330
+ # Training Resume Check
331
+ # ─────────────────────────────────────────────────────────────
332
+ words_trained = 0
333
+ next_milestone_idx = 0
334
+ global_step = 0
335
+
336
+ milestones = sorted(list(set([i * 1_000_000 for i in range(1, 11)] + [i * 10_000_000 for i in range(1, 11)])))
337
+
338
+ resume_checkpoint_dir = None
339
+ for idx in range(len(milestones) - 1, -1, -1):
340
+ m = milestones[idx]
341
+ ckpt_name = f"chck_{m // 1_000_000}M"
342
+ ckpt_path = os.path.join(model_dir, ckpt_name)
343
+ if os.path.exists(os.path.join(ckpt_path, "pytorch_model.bin")):
344
+ config_json_path = os.path.join(ckpt_path, "config.json")
345
+ if os.path.exists(config_json_path):
346
+ try:
347
+ with open(config_json_path, "r") as f:
348
+ saved_config = json.load(f)
349
+ if saved_config.get("d_model") == 256:
350
+ resume_checkpoint_dir = ckpt_path
351
+ next_milestone_idx = idx + 1
352
+ words_trained = m
353
+ global_step = words_trained // (BATCH_SIZE * BLOCK_SIZE)
354
+ print(f"[Training] Found existing milestone checkpoint '{ckpt_name}'. Resuming from step {global_step:,} ({words_trained:,} tokens trained)...")
355
+ break
356
+ else:
357
+ print(f"[Training] Found checkpoint '{ckpt_name}' but it has mismatch d_model={saved_config.get('d_model')}. Starting fresh.")
358
+ except Exception as e:
359
+ pass
360
+
361
+ # Load weights if resuming
362
+ if resume_checkpoint_dir is not None:
363
+ print(f"[Model] Loading weights from checkpoint '{resume_checkpoint_dir}'...")
364
+ state_dict = torch.load(os.path.join(resume_checkpoint_dir, "pytorch_model.bin"), map_location=device)
365
+ model_state_dict = {}
366
+ for k, v in state_dict.items():
367
+ name = k
368
+ if name.startswith("transformer."):
369
+ name = name[12:]
370
+ model_state_dict[name] = v
371
+ model.load_state_dict(model_state_dict)
372
+
373
+ # Check if final main model exists
374
+ main_ckpt_path = os.path.join(model_dir, "main")
375
+ if os.path.exists(os.path.join(main_ckpt_path, "pytorch_model.bin")):
376
+ print("\n[Pipeline] Final checkpoint 'main' already exists. Skipping training phase and transitioning directly to evaluations!")
377
+ else:
378
+ # torch.compile
379
+ try:
380
+ model = torch.compile(model)
381
+ print("[Model] torch.compile() successfully verified graph optimizations")
382
+ except Exception as e:
383
+ print(f"[Model] torch.compile() skipped ({e})")
384
+
385
+ # Optimizer
386
+ param_dict = {n: p for n, p in model.named_parameters() if p.requires_grad}
387
+ decay_params = [p for p in param_dict.values() if p.dim() >= 2]
388
+ nodecay_params = [p for p in param_dict.values() if p.dim() < 2]
389
+ groups = [
390
+ {'params': decay_params, 'weight_decay': WEIGHT_DECAY},
391
+ {'params': nodecay_params, 'weight_decay': 0.0},
392
+ ]
393
+ fused_ok = 'fused' in inspect.signature(torch.optim.AdamW).parameters
394
+ use_fused = fused_ok and ('cuda' in device)
395
+ optimizer = torch.optim.AdamW(groups, lr=LEARNING_RATE, betas=(0.9, 0.95), eps=1e-8, fused=use_fused)
396
+
397
+ # ─────────────────────────────────────────────────────────────
398
+ # Training Loop
399
+ # ─────────────────────────────────────────────────────────────
400
+ model.train()
401
+ autocast_ctx = torch.autocast(device_type="cuda" if "cuda" in device else "cpu", dtype=torch.bfloat16, enabled=True)
402
+
403
+ start_epoch = global_step // steps_per_epoch
404
+ start_chunk = (global_step % steps_per_epoch) * GRAD_ACCUM_STEPS
405
+
406
+ print(f"\n[Training] Starting XpertGPT MoEP training for {EPOCHS} epochs...")
407
+ for epoch in range(start_epoch, EPOCHS):
408
+ train_loader._shuffle()
409
+ if epoch == start_epoch and start_chunk > 0:
410
+ print(f"[Training] Fast-forwarding dataloader to chunk index {start_chunk}...")
411
+ train_loader.pos = start_chunk
412
+
413
+ optimizer.zero_grad(set_to_none=True)
414
+ loss_accum = 0.0
415
+
416
+ start_chunk_idx = start_chunk if epoch == start_epoch else 0
417
+ for chunk_step in range(start_chunk_idx, chunks_per_epoch):
418
+ t0 = time.perf_counter()
419
+
420
+ lr = get_lr(global_step, total_steps)
421
+ for pg in optimizer.param_groups:
422
+ pg['lr'] = lr
423
+
424
+ input_ids, targets, bidir = get_hybrid_batch(train_loader, global_step, total_steps, device)
425
+ words_trained += input_ids.numel()
426
+
427
+ with autocast_ctx:
428
+ _, loss = model(input_ids, targets, bidirectional=bidir)
429
+ scaled_loss = loss / GRAD_ACCUM_STEPS
430
+ loss_accum += scaled_loss.item()
431
+ scaled_loss.backward()
432
+
433
+ # Optimizer Step
434
+ if (chunk_step + 1) % GRAD_ACCUM_STEPS == 0:
435
+ norm = torch.nn.utils.clip_grad_norm_(model.parameters(), GRAD_CLIP)
436
+ optimizer.step()
437
+ optimizer.zero_grad(set_to_none=True)
438
+ if "cuda" in device:
439
+ torch.cuda.synchronize()
440
+
441
+ dt = (time.perf_counter() - t0) * 1000
442
+ mode_tag = "MLM" if bidir else "CLM"
443
+ mask_p = get_current_mask_prob(global_step, total_steps)
444
+
445
+ current_step = (chunk_step + 1) // GRAD_ACCUM_STEPS
446
+ print(
447
+ f"[E{epoch+1:02d} {current_step:>5d}/{steps_per_epoch} G{global_step:>7d}|{mode_tag}] "
448
+ f"train={loss_accum:.4f} mask={mask_p:.1%} norm={norm:.3f} lr={lr:.2e} dt={dt:6.1f}ms words={words_trained:,}"
449
+ )
450
+ loss_accum = 0.0
451
+ global_step += 1
452
+
453
+ # Check if we passed a milestone for checkpointing
454
+ if next_milestone_idx < len(milestones) and words_trained >= milestones[next_milestone_idx]:
455
+ milestone_val = milestones[next_milestone_idx]
456
+ if milestone_val < 10_000_000:
457
+ milestone_name = f"chck_{milestone_val // 1_000_000}M"
458
+ else:
459
+ milestone_name = f"chck_{(milestone_val // 10_000_000) * 10}M"
460
+
461
+ raw_model = model._orig_mod if hasattr(model, '_orig_mod') else model
462
+ save_hf_checkpoint(raw_model, milestone_name, tokenizer)
463
+ next_milestone_idx += 1
464
+
465
+ # Save final model as 'main'
466
+ raw_model = model._orig_mod if hasattr(model, '_orig_mod') else model
467
+ save_hf_checkpoint(raw_model, "main", tokenizer)
468
+ print("\n[Training] Training phase complete!")
469
+
470
+ if skip_eval:
471
+ print("[Pipeline] Skipping evaluations phase as requested.")
472
+ return
473
+
474
+ # ─────────────────────────────────────────────────────────────
475
+ # 2. RUN EVALUATION PIPELINE
476
+ # ─────────────────────────────────────────────────────────────
477
+ local_results_dir = os.path.abspath(f"./results/{model_name}")
478
+ os.makedirs(local_results_dir, exist_ok=True)
479
+ local_main_res = os.path.join(local_results_dir, "main")
480
+
481
+ # Ensure clone_dir exists and has the global_piqa files
482
+ clone_parent = os.path.abspath("./babylm_eval_repo")
483
+
484
+ # Self-healing check for global_piqa presence
485
+ has_global_piqa = False
486
+ for potential_strict in [os.path.join(clone_parent, "babylm-eval", "strict"), os.path.join(clone_parent, "strict")]:
487
+ if os.path.exists(os.path.join(potential_strict, "evaluation_pipeline", "global_piqa")):
488
+ has_global_piqa = True
489
+ break
490
+
491
+ if not has_global_piqa:
492
+ print("[Eval] Cloned repository does not contain global_piqa tasks.")
493
+ print("[Eval] Deleting and cloning official main branch...")
494
+ if os.path.exists(clone_parent):
495
+ shutil.rmtree(clone_parent)
496
+ subprocess.run([
497
+ "git", "clone", "-b", "main",
498
+ "https://github.com/babylm-org/babylm-eval.git",
499
+ clone_parent
500
+ ], check=True)
501
+
502
+ # Determine strict_dir path dynamically
503
+ if os.path.exists(os.path.join(clone_parent, "strict")):
504
+ strict_dir = os.path.join(clone_parent, "strict")
505
+ else:
506
+ strict_dir = os.path.join(clone_parent, "babylm-eval", "strict")
507
+
508
+ print(f"[Eval] Using strict directory: {strict_dir}")
509
+ os.environ["PYTHONPATH"] = strict_dir
510
+
511
+ def patch_evaluation_run_script(strict_dir):
512
+ import pathlib
513
+ run_file = os.path.join(strict_dir, "evaluation_pipeline", "sentence_zero_shot", "run.py")
514
+ if not os.path.exists(run_file):
515
+ print(f"[GlobalPIQA] Warning: {run_file} not found. Cannot patch.")
516
+ return
517
+
518
+ print(f"[GlobalPIQA] Patching local checkpoint loader in {run_file}...")
519
+ with open(run_file, "r") as f:
520
+ content = f.read()
521
+
522
+ # Check if already patched
523
+ if "Local checkpoint directory patch" in content:
524
+ print("[GlobalPIQA] Script already patched.")
525
+ return
526
+
527
+ target_str = """def main():
528
+ args = _parse_arguments()
529
+ if args.images_path is not None:
530
+ assert args.batch_size == 1, "Multimodal only works in batch size 1!"
531
+ dataset = args.data_path.stem
532
+ args.model_name = pathlib.Path(args.model_path_or_name).stem
533
+ if args.revision_name is None:
534
+ revision_name = "main"
535
+ else:
536
+ revision_name = args.revision_name"""
537
+
538
+ patch_str = """def main():
539
+ args = _parse_arguments()
540
+ if args.images_path is not None:
541
+ assert args.batch_size == 1, "Multimodal only works in batch size 1!"
542
+ dataset = args.data_path.stem
543
+
544
+ # Local checkpoint directory patch
545
+ import os
546
+ model_path = args.model_path_or_name
547
+ args.model_name = pathlib.Path(model_path).stem
548
+ revision_name = args.revision_name if args.revision_name else "main"
549
+
550
+ if os.path.isdir(model_path):
551
+ target_revision = args.revision_name if args.revision_name else "main"
552
+ if os.path.exists(os.path.join(model_path, target_revision)):
553
+ args.model_path_or_name = os.path.join(model_path, target_revision)
554
+ args.revision_name = None"""
555
+
556
+ if target_str in content:
557
+ new_content = content.replace(target_str, patch_str)
558
+ with open(run_file, "w") as f:
559
+ f.write(new_content)
560
+ print("[GlobalPIQA] Successfully patched run.py")
561
+ else:
562
+ print("[GlobalPIQA] Warning: Could not find target pattern in run.py. Manual patch may be needed.")
563
+
564
+ # Patch sentence zero shot loader inside cloned repo
565
+ patch_evaluation_run_script(strict_dir)
566
+
567
+ print("[Eval] Stripping Windows-specific packages from requirements.txt...")
568
+ req_file_path = os.path.join(strict_dir, "requirements.txt")
569
+ if os.path.exists(req_file_path):
570
+ with open(req_file_path, "r") as f:
571
+ lines = f.readlines()
572
+ with open(req_file_path, "w") as f:
573
+ for line in lines:
574
+ if "pywin" not in line.lower() and "wintypes" not in line.lower():
575
+ f.write(line)
576
+
577
+ print("[Eval] Verifying and installing evaluation dependencies programmatically...")
578
+ required_packages = {
579
+ "nltk": "nltk",
580
+ "pandas": "pandas",
581
+ "statsmodels": "statsmodels",
582
+ "sklearn": "scikit-learn",
583
+ "scipy": "scipy"
584
+ }
585
+ for pkg_import, pkg_install in required_packages.items():
586
+ try:
587
+ __import__(pkg_import)
588
+ except ImportError:
589
+ print(f"[Eval] Package '{pkg_install}' not found. Installing it programmatically...")
590
+ import sys
591
+ subprocess.run([sys.executable, "-m", "pip", "install", pkg_install], check=True)
592
+
593
+ print("[Eval] Downloading NLTK tokenizer resources...")
594
+ import nltk
595
+ nltk.download('punkt', download_dir=os.environ["NLTK_DATA"])
596
+ nltk.download('punkt_tab', download_dir=os.environ["NLTK_DATA"])
597
+
598
+ # Ensure standard zero-shot datasets are downloaded
599
+ blimp_fast_dir = os.path.join(strict_dir, "evaluation_data", "fast_eval", "blimp_fast")
600
+ if not os.path.exists(blimp_fast_dir) or not os.listdir(blimp_fast_dir):
601
+ print("[Eval] Standard zero-shot datasets not found. Downloading...")
602
+ subprocess.run(["python", "-m", "scripts.download_evals"], cwd=strict_dir, check=True)
603
+
604
+ # Unzip EWoK fast
605
+ ewok_zip = os.path.join(strict_dir, "evaluation_data/fast_eval/ewok_fast.zip")
606
+ if os.path.exists(ewok_zip):
607
+ print("[Eval] Unzipping EWoK fast data...")
608
+ bad_nested_dir = os.path.join(strict_dir, "evaluation_data/fast_eval/evaluation_data")
609
+ if os.path.exists(bad_nested_dir):
610
+ shutil.rmtree(bad_nested_dir)
611
+ subprocess.run(["unzip", "-o", "-P", "BabyLM2025", "evaluation_data/fast_eval/ewok_fast.zip", "-d", "."], cwd=strict_dir, check=True)
612
+
613
+ # Download EWoK full
614
+ print("[Eval] Downloading and filtering full EWoK dataset...")
615
+ subprocess.run(["python", "-m", "evaluation_pipeline.ewok.dl_and_filter"], cwd=strict_dir, check=True)
616
+
617
+ # Download GlobalPIQA dataset
618
+ global_piqa_parallel_dir = os.path.join(strict_dir, "evaluation_data", "fast_eval", "global_piqa_parallel")
619
+ if not os.path.exists(global_piqa_parallel_dir) or not os.listdir(global_piqa_parallel_dir):
620
+ print("[Eval] GlobalPIQA dataset not found. Downloading...")
621
+ subprocess.run(["python", "evaluation_pipeline/global_piqa/dl.py"], cwd=strict_dir, check=True)
622
+
623
+ # Ensure all scripts are executable
624
+ print("[Eval] Making evaluation shell scripts executable...")
625
+ subprocess.run("chmod +x scripts/*.sh", shell=True, cwd=strict_dir, check=True)
626
+
627
+ def run_task_with_cache(checkpoint, task, output_subpath, cmd):
628
+ # Determine paths
629
+ local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", task, output_subpath)
630
+ if task == "reading":
631
+ local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", "reading")
632
+ elif task == "comps":
633
+ local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", "comps", "comps")
634
+
635
+ target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", task, output_subpath)
636
+ if task == "reading":
637
+ target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", "reading")
638
+ elif task == "comps":
639
+ target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", "comps", "comps")
640
+
641
+ # If cached, copy it over
642
+ cache_file = os.path.join(local_cache_path, "predictions.json")
643
+
644
+ # Self-healing: invalidate old unfiltered entity_tracking caches
645
+ if task == "entity_tracking" and os.path.exists(cache_file):
646
+ try:
647
+ import json
648
+ with open(cache_file, "r") as f:
649
+ preds = json.load(f)
650
+ is_valid_cache = True
651
+ for k, v in preds.items():
652
+ if len(v.get("predictions", [])) in [605, 606, 607, 615, 529, 156, 187, 159]:
653
+ is_valid_cache = False
654
+ break
655
+ if not is_valid_cache:
656
+ print(f"[Eval] Cached entity_tracking for '{checkpoint}' has incorrect old sizes. Invalidate and re-run fresh...")
657
+ shutil.rmtree(local_cache_path, ignore_errors=True)
658
+ except Exception:
659
+ pass
660
+
661
+ if os.path.exists(cache_file):
662
+ print(f"[Eval] Task '{task}' ({output_subpath}) for checkpoint '{checkpoint}' is cached. Restoring...")
663
+ if os.path.exists(target_results_dir):
664
+ shutil.rmtree(target_results_dir)
665
+ os.makedirs(target_results_dir, exist_ok=True)
666
+ for item in os.listdir(local_cache_path):
667
+ s = os.path.join(local_cache_path, item)
668
+ d = os.path.join(target_results_dir, item)
669
+ if os.path.isdir(s):
670
+ shutil.copytree(s, d)
671
+ else:
672
+ shutil.copy2(s, d)
673
+ return
674
+
675
+ print(f"[Eval] Running task '{task}' ({output_subpath}) for checkpoint '{checkpoint}'...")
676
+ subprocess.run(cmd, cwd=strict_dir, check=True)
677
+
678
+ # Relocate from actual_model_basename, main, or checkpoint subfolder if needed
679
+ actual_model_basename = os.path.basename(model_dir.rstrip("/"))
680
+ possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", task, output_subpath)
681
+ if task == "reading":
682
+ possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", "reading")
683
+ elif task == "comps":
684
+ possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", "comps", "comps")
685
+
686
+ possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", task, output_subpath)
687
+ if task == "reading":
688
+ possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", "reading")
689
+ elif task == "comps":
690
+ possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", "comps", "comps")
691
+
692
+ possible_local_reading_path = os.path.join(strict_dir, "results", checkpoint, "main", "zero_shot", "causal", "reading")
693
+
694
+ for p_path in [possible_actual_path, possible_main_path, possible_local_reading_path]:
695
+ if os.path.exists(p_path) and p_path != target_results_dir:
696
+ print(f"[Eval] Relocating results from {p_path} to {target_results_dir}...")
697
+ if os.path.exists(target_results_dir):
698
+ shutil.rmtree(target_results_dir)
699
+ os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
700
+ shutil.move(p_path, target_results_dir)
701
+ break
702
+
703
+ # Verify
704
+ verify_eval_run(target_results_dir, f"{checkpoint} {task} ({output_subpath})")
705
+
706
+ # Save to local cache
707
+ if os.path.exists(local_cache_path):
708
+ shutil.rmtree(local_cache_path)
709
+ os.makedirs(local_cache_path, exist_ok=True)
710
+ for item in os.listdir(target_results_dir):
711
+ s = os.path.join(target_results_dir, item)
712
+ d = os.path.join(local_cache_path, item)
713
+ if os.path.isdir(s):
714
+ shutil.copytree(s, d)
715
+ else:
716
+ shutil.copy2(s, d)
717
+
718
+ def run_finetune_task_with_cache(task, cmd):
719
+ local_cache_path = os.path.join("./results", model_name, "main", "finetune", task)
720
+ target_results_dir = os.path.join(strict_dir, "results", model_name, "main", "finetune", task)
721
+
722
+ if os.path.exists(os.path.join(local_cache_path, "predictions.json")):
723
+ print(f"[Eval] GLUE task '{task}' is cached. Restoring...")
724
+ if os.path.exists(target_results_dir):
725
+ shutil.rmtree(target_results_dir)
726
+ os.makedirs(target_results_dir, exist_ok=True)
727
+ for item in os.listdir(local_cache_path):
728
+ s = os.path.join(local_cache_path, item)
729
+ d = os.path.join(target_results_dir, item)
730
+ if os.path.isdir(s):
731
+ shutil.copytree(s, d)
732
+ else:
733
+ shutil.copy2(s, d)
734
+ return
735
+
736
+ print(f"[Eval] Running GLUE task '{task}'...")
737
+ subprocess.run(cmd, cwd=strict_dir, check=True)
738
+
739
+ # Relocate from results/main/main if needed
740
+ possible_main_path = os.path.join(strict_dir, "results", "main", "main", "finetune", task)
741
+ if os.path.exists(possible_main_path) and possible_main_path != target_results_dir:
742
+ print(f"[Eval] Relocating results from {possible_main_path} to {target_results_dir}...")
743
+ if os.path.exists(target_results_dir):
744
+ shutil.rmtree(target_results_dir)
745
+ os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
746
+ shutil.move(possible_main_path, target_results_dir)
747
+
748
+ # Verify
749
+ verify_eval_run(target_results_dir, f"GLUE task {task}")
750
+
751
+ # Cache locally
752
+ if os.path.exists(local_cache_path):
753
+ shutil.rmtree(local_cache_path)
754
+ os.makedirs(local_cache_path, exist_ok=True)
755
+ for item in os.listdir(target_results_dir):
756
+ s = os.path.join(target_results_dir, item)
757
+ d = os.path.join(local_cache_path, item)
758
+ if os.path.isdir(s):
759
+ shutil.copytree(s, d)
760
+ else:
761
+ shutil.copy2(s, d)
762
+
763
+ def run_aoa_with_cache(cmd):
764
+ local_cache_path = os.path.join("./results", model_name, "main", "aoa")
765
+ target_results_dir = os.path.join(strict_dir, "results", model_name, "main", "aoa")
766
+
767
+ if os.path.exists(os.path.join(local_cache_path, "aoa_score.json")) or os.path.exists(os.path.join(local_cache_path, "surprisal.json")):
768
+ print(f"[Eval] AoA task is cached. Restoring...")
769
+ if os.path.exists(target_results_dir):
770
+ shutil.rmtree(target_results_dir)
771
+ os.makedirs(target_results_dir, exist_ok=True)
772
+ for item in os.listdir(local_cache_path):
773
+ s = os.path.join(local_cache_path, item)
774
+ d = os.path.join(target_results_dir, item)
775
+ if os.path.isdir(s):
776
+ shutil.copytree(s, d)
777
+ else:
778
+ shutil.copy2(s, d)
779
+ return
780
+
781
+ print("[Eval] Running AoA task...")
782
+ subprocess.run(cmd, cwd=strict_dir, check=True)
783
+
784
+ # Relocate from results/main/main if needed
785
+ possible_main_path = os.path.join(strict_dir, "results", "main", "main", "aoa")
786
+ if os.path.exists(possible_main_path) and possible_main_path != target_results_dir:
787
+ print(f"[Eval] Relocating results from {possible_main_path} to {target_results_dir}...")
788
+ if os.path.exists(target_results_dir):
789
+ shutil.rmtree(target_results_dir)
790
+ os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
791
+ shutil.move(possible_main_path, target_results_dir)
792
+
793
+ # Verify
794
+ verify_eval_run(target_results_dir, "AoA task")
795
+
796
+ # Cache locally
797
+ if os.path.exists(local_cache_path):
798
+ shutil.rmtree(local_cache_path)
799
+ os.makedirs(local_cache_path, exist_ok=True)
800
+ for item in os.listdir(target_results_dir):
801
+ s = os.path.join(target_results_dir, item)
802
+ d = os.path.join(local_cache_path, item)
803
+ if os.path.isdir(s):
804
+ shutil.copytree(s, d)
805
+ else:
806
+ shutil.copy2(s, d)
807
+
808
+ # ─────────────────────────────────────────────────────────────
809
+ # B. FINAL MODEL 'main' FULL ZERO-SHOT EVALUATION
810
+ # ─────────────────────────────────────────────────────────────
811
+ main_ckpt_path = os.path.join(model_dir, "main")
812
+ if os.path.exists(main_ckpt_path):
813
+ print(f"[Eval] Running full zero-shot evaluation on main...")
814
+ # blimp filtered
815
+ run_task_with_cache(
816
+ "main", "blimp", "blimp_filtered",
817
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/full_eval/blimp_filtered", "--save_predictions"]
818
+ )
819
+ # supplement filtered
820
+ run_task_with_cache(
821
+ "main", "blimp", "supplement_filtered",
822
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/full_eval/supplement_filtered", "--save_predictions"]
823
+ )
824
+ # ewok filtered
825
+ run_task_with_cache(
826
+ "main", "ewok", "ewok_filtered",
827
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "ewok", "--data_path", "evaluation_data/full_eval/ewok_filtered", "--save_predictions"]
828
+ )
829
+ # entity tracking
830
+ run_task_with_cache(
831
+ "main", "entity_tracking", "entity_tracking",
832
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "entity_tracking", "--data_path", "evaluation_data/full_eval/entity_tracking", "--save_predictions"]
833
+ )
834
+ # comps
835
+ run_task_with_cache(
836
+ "main", "comps", "comps",
837
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "comps", "--data_path", "evaluation_data/full_eval/comps", "--save_predictions"]
838
+ )
839
+ # reading
840
+ run_task_with_cache(
841
+ "main", "reading", "reading",
842
+ ["python", "-m", "evaluation_pipeline.reading.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--data_path", "evaluation_data/full_eval/reading/reading_data.csv"]
843
+ )
844
+ # global piqa parallel
845
+ run_task_with_cache(
846
+ "main", "global_piqa_parallel", "global_piqa_parallel",
847
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "global_piqa_parallel", "--data_path", "evaluation_data/full_eval/global_piqa_parallel", "--save_predictions"]
848
+ )
849
+ # global piqa nonparallel
850
+ run_task_with_cache(
851
+ "main", "global_piqa_nonparallel", "global_piqa_nonparallel",
852
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", main_ckpt_path, "--backend", "causal", "--task", "global_piqa_nonparallel", "--data_path", "evaluation_data/full_eval/global_piqa_nonparallel", "--save_predictions"]
853
+ )
854
+
855
+ # ─────────────────────────────────────────────────────────────
856
+ # C. FINAL MODEL GLUE AND AOA EVALUATION
857
+ # ─────────────────────────────────────────────────────────────
858
+ if os.path.exists(main_ckpt_path):
859
+ # 1. GLUE fine-tuning
860
+ if skip_glue:
861
+ print("[Eval] Skipping GLUE fine-tuning evaluations as requested.")
862
+ else:
863
+ print("[Eval] Running GLUE fine-tuning evaluations on main task-by-task...")
864
+ glue_tasks = {
865
+ "boolq": ["boolq", "16", "10"],
866
+ "multirc": ["multirc", "16", "10"],
867
+ "rte": ["rte", "32", "10"],
868
+ "wsc": ["wsc", "32", "30"],
869
+ "mrpc": ["mrpc", "32", "10"],
870
+ "qqp": ["qqp", "32", "10"],
871
+ "mnli": ["mnli", "32", "10"]
872
+ }
873
+ for task_name, (task, bsz, max_epochs) in glue_tasks.items():
874
+ num_labels = "3" if task == "mnli" else "2"
875
+ metric_for_valid = "accuracy"
876
+ if task in ["mrpc", "qqp"]:
877
+ metric_for_valid = "f1"
878
+ metrics = ["accuracy"]
879
+ if task != "mnli":
880
+ metrics = ["accuracy", "f1", "mcc"]
881
+
882
+ cmd = [
883
+ "python", "-m", "evaluation_pipeline.finetune.run",
884
+ "--model_name_or_path", main_ckpt_path,
885
+ "--train_data", f"evaluation_data/full_eval/glue_filtered/{task}.train.jsonl",
886
+ "--valid_data", f"evaluation_data/full_eval/glue_filtered/{task}.valid.jsonl",
887
+ "--predict_data", f"evaluation_data/full_eval/glue_filtered/{task}.valid.jsonl",
888
+ "--task", task,
889
+ "--num_labels", num_labels,
890
+ "--batch_size", bsz,
891
+ "--learning_rate", "3e-5",
892
+ "--num_epochs", max_epochs,
893
+ "--sequence_length", "512",
894
+ "--results_dir", "results",
895
+ "--save",
896
+ "--save_dir", "models",
897
+ "--metric_for_valid", metric_for_valid,
898
+ "--seed", "42",
899
+ "--verbose",
900
+ "--padding_side", "left",
901
+ "--take_final"
902
+ ]
903
+ cmd.append("--metrics")
904
+ cmd.extend(metrics)
905
+
906
+ run_finetune_task_with_cache(task_name, cmd)
907
+
908
+ # 2. AoA
909
+ if skip_aoa:
910
+ print("[Eval] Skipping AoA evaluations as requested.")
911
+ else:
912
+ run_aoa_with_cache([
913
+ "python", "-m", "evaluation_pipeline.AoA_word.run",
914
+ "--model_name", model_dir,
915
+ "--backend", "causal",
916
+ "--track_name", "strict-small",
917
+ "--word_path", "evaluation_data/full_eval/aoa/cdi_childes.json",
918
+ "--output_dir", "results"
919
+ ])
920
+
921
+ # ─────────────────────────────────────────────────────────────
922
+ # A. INTERMEDIATE CHECKPOINTS FAST EVALUATION
923
+ # ─────────────────────────────────────────────────────────────
924
+ print(f"[Eval] Running zero-shot fast evaluations on intermediate checkpoints...")
925
+ checkpoints = [f"chck_{i}M" for i in range(1, 10)] + [f"chck_{i}M" for i in range(10, 110, 10)]
926
+
927
+ eval_model_path = model_dir
928
+
929
+ for checkpoint in checkpoints:
930
+ ckpt_full_path = os.path.join(model_dir, checkpoint)
931
+ if not os.path.exists(ckpt_full_path):
932
+ continue
933
+
934
+ # blimp fast
935
+ run_task_with_cache(
936
+ checkpoint, "blimp", "blimp_fast",
937
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/fast_eval/blimp_fast", "--save_predictions", "--revision_name", checkpoint]
938
+ )
939
+ # supplement fast
940
+ run_task_with_cache(
941
+ checkpoint, "blimp", "supplement_fast",
942
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/fast_eval/supplement_fast", "--save_predictions", "--revision_name", checkpoint]
943
+ )
944
+ # ewok fast
945
+ run_task_with_cache(
946
+ checkpoint, "ewok", "ewok_fast",
947
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "ewok", "--data_path", "evaluation_data/fast_eval/ewok_fast", "--save_predictions", "--revision_name", checkpoint]
948
+ )
949
+ # entity tracking fast
950
+ run_task_with_cache(
951
+ checkpoint, "entity_tracking", "entity_tracking_fast",
952
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "entity_tracking", "--data_path", "evaluation_data/fast_eval/entity_tracking_fast", "--save_predictions", "--revision_name", checkpoint]
953
+ )
954
+ # reading fast
955
+ run_task_with_cache(
956
+ checkpoint, "reading", "reading",
957
+ ["python", "-m", "evaluation_pipeline.reading.run", "--model_path_or_name", ckpt_full_path, "--backend", "causal", "--data_path", "evaluation_data/fast_eval/reading/reading_data.csv"]
958
+ )
959
+ # global piqa parallel fast
960
+ run_task_with_cache(
961
+ checkpoint, "global_piqa_parallel", "global_piqa_parallel",
962
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "global_piqa_parallel", "--data_path", "evaluation_data/fast_eval/global_piqa_parallel", "--save_predictions", "--revision_name", checkpoint]
963
+ )
964
+ # global piqa nonparallel fast
965
+ run_task_with_cache(
966
+ checkpoint, "global_piqa_nonparallel", "global_piqa_nonparallel",
967
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "global_piqa_nonparallel", "--data_path", "evaluation_data/fast_eval/global_piqa_nonparallel", "--save_predictions", "--revision_name", checkpoint]
968
+ )
969
+
970
+ # ─────────────────────────────────────────────────────────────
971
+ # D. COLLATE RESULTS AND CLEANUP
972
+ # ─────────────────────────────────────────────────────────────
973
+ print("[Eval] Collating predictions into submission file...")
974
+ # Clean collation destination in evaluation repo
975
+ collate_results_dir = os.path.join(strict_dir, "results", model_name)
976
+ if os.path.exists(collate_results_dir):
977
+ shutil.rmtree(collate_results_dir)
978
+ os.makedirs(os.path.dirname(collate_results_dir), exist_ok=True)
979
+
980
+ # Copy from local cache results to strict results for collation
981
+ shutil.copytree(local_results_dir, collate_results_dir)
982
+
983
+ # Run collation
984
+ subprocess.run([
985
+ "python", "-m", "evaluation_pipeline.collate_preds",
986
+ "--model_path_or_name", model_name,
987
+ "--backend", "causal",
988
+ "--track", "strict-small",
989
+ "--fast"
990
+ ], cwd=strict_dir, check=True)
991
+
992
+ # Save results to local folder
993
+ results_src = os.path.join(strict_dir, "results")
994
+ results_dest = os.path.abspath("./results")
995
+ if os.path.exists(results_dest):
996
+ shutil.rmtree(results_dest)
997
+ shutil.copytree(results_src, results_dest)
998
+
999
+ # Copy final collated json to current folder
1000
+ collated_json = os.path.join(strict_dir, "all_full_preds_and_fast_scores_causal.json")
1001
+ if os.path.exists(collated_json):
1002
+ shutil.copy(collated_json, "./all_full_preds_and_fast_scores_causal.json")
1003
+ print("\n[Eval] Success! Collation completed! Final file is at './all_full_preds_and_fast_scores_causal.json'")
1004
+
1005
+ print("\n[Eval] Pipeline evaluation run finished.")
1006
+
1007
+ def upload_pipeline(model_name, repo_name, token=None):
1008
+ import os
1009
+ import shutil
1010
+ import json
1011
+ import hashlib
1012
+ from huggingface_hub import HfApi, create_repo
1013
+
1014
+ if not token:
1015
+ token = os.environ.get("HF_TOKEN")
1016
+
1017
+ api = HfApi(token=token)
1018
+ try:
1019
+ user_info = api.whoami()
1020
+ username = user_info["name"]
1021
+ print(f"[HF] Authenticated successfully as user: {username}")
1022
+ except Exception as e:
1023
+ print(f"[HF] Authentication failed. Error: {e}")
1024
+ return
1025
+
1026
+ repo_id = f"{username}/{repo_name}"
1027
+ print(f"[HF] Target Repository ID: {repo_id}")
1028
+
1029
+ # Create the repository if it doesn't exist
1030
+ try:
1031
+ create_repo(repo_id=repo_id, repo_type="model", token=token, exist_ok=True)
1032
+ print(f"[HF] Repository '{repo_id}' is ready.")
1033
+ except Exception as e:
1034
+ print(f"[HF] Failed to verify or create repository. Error: {e}")
1035
+ return
1036
+
1037
+ checkpoint_dir = os.path.abspath(f"./checkpoints/{model_name}")
1038
+
1039
+ # Resolve the main checkpoint directory using self-healing rules
1040
+ revisions = {}
1041
+ if os.path.exists("pytorch_model.bin") and os.path.exists("config.json"):
1042
+ print("[HF] Detected weight and config files in the current working directory. Using current folder as 'main' checkpoint.")
1043
+ revisions = {"main": os.getcwd()}
1044
+ elif os.path.exists(os.path.join(checkpoint_dir, "main")):
1045
+ revisions = {"main": os.path.join(checkpoint_dir, "main")}
1046
+ elif os.path.exists(os.path.abspath("./checkpoints/msit_gptbert_fresh/main")):
1047
+ print("[HF] Using fallback checkpoint folder './checkpoints/msit_gptbert_fresh/main'...")
1048
+ revisions = {"main": os.path.abspath("./checkpoints/msit_gptbert_fresh/main")}
1049
+ elif os.path.exists(os.path.abspath("./checkpoints/main")):
1050
+ revisions = {"main": os.path.abspath("./checkpoints/main")}
1051
+ else:
1052
+ print(f"[HF] Error: Could not locate the 'main' checkpoint weights. Checked: {checkpoint_dir}/main, current directory, and fallbacks.")
1053
+ return
1054
+
1055
+ # Check for intermediate checkpoints relative to the main checkpoint's parent folder
1056
+ main_dir = revisions["main"]
1057
+ parent_dir = os.path.dirname(main_dir)
1058
+
1059
+ for m in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40, 45, 50, 60, 70, 80, 90, 100]:
1060
+ ckpt_name = f"chck_{m}M"
1061
+ ckpt_path = os.path.join(parent_dir, ckpt_name)
1062
+ if os.path.exists(ckpt_path):
1063
+ revisions[ckpt_name] = ckpt_path
1064
+ else:
1065
+ # Check if there is a .pt file in the parent folder
1066
+ pt_path = os.path.join(parent_dir, f"{ckpt_name}.pt")
1067
+ if os.path.exists(pt_path):
1068
+ print(f"[HF] Found legacy checkpoint file '{ckpt_name}.pt'. Converting to HF format for upload...")
1069
+ import torch
1070
+ from transformers import AutoTokenizer
1071
+ from tokenizers import Tokenizer
1072
+ try:
1073
+ from modeling_xpertgpt import XpertGPTForCausalLM, XpertGPTConfig
1074
+ cfg = XpertGPTConfig(d_model=256, d_thin=384, num_layers=6, num_blocks=4)
1075
+ model_to_save = XpertGPTForCausalLM(cfg)
1076
+ sd = torch.load(pt_path, map_location="cpu")
1077
+ clean_sd = {}
1078
+ for k, v in sd.items():
1079
+ new_k = k.replace("module.", "")
1080
+ clean_sd[new_k] = v
1081
+ model_to_save.load_state_dict(clean_sd)
1082
+
1083
+ vocab_path = os.path.join(parent_dir, "bpe_vocab_16k.json")
1084
+ if not os.path.exists(vocab_path):
1085
+ vocab_path = os.path.join(main_dir, "bpe_vocab_16k.json")
1086
+ if os.path.exists(vocab_path):
1087
+ tok = Tokenizer.from_file(vocab_path)
1088
+ else:
1089
+ tok = None
1090
+
1091
+ os.makedirs(ckpt_path, exist_ok=True)
1092
+ state_dict = model_to_save.state_dict()
1093
+ new_state_dict = {}
1094
+ for k, v in state_dict.items():
1095
+ name = k
1096
+ if name.startswith("_orig_mod."):
1097
+ name = name[10:]
1098
+ if name.startswith("model."):
1099
+ name = name[6:]
1100
+ if name == "lm_head.weight":
1101
+ new_state_dict["lm_head.weight"] = v
1102
+ else:
1103
+ new_state_dict[f"transformer.{name}"] = v
1104
+ torch.save(new_state_dict, os.path.join(ckpt_path, "pytorch_model.bin"))
1105
+
1106
+ config_dict = {
1107
+ "auto_map": {
1108
+ "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
1109
+ "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
1110
+ "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
1111
+ },
1112
+ "vocab_size": 16384,
1113
+ "block_size": 512,
1114
+ "d_model": 256,
1115
+ "hidden_size": 256,
1116
+ "d_thin": 384,
1117
+ "num_layers": 6,
1118
+ "num_blocks": 4,
1119
+ "capacity_factor": 1.0,
1120
+ "dropout": 0.1,
1121
+ "model_type": "xpertgpt",
1122
+ "num_hidden_layers": 6
1123
+ }
1124
+ with open(os.path.join(ckpt_path, "config.json"), "w") as f:
1125
+ json.dump(config_dict, f, indent=2)
1126
+
1127
+ if tok:
1128
+ from transformers import PreTrainedTokenizerFast
1129
+ fast_tokenizer = PreTrainedTokenizerFast(
1130
+ tokenizer_object=tok,
1131
+ bos_token="[CLS]",
1132
+ eos_token="[SEP]",
1133
+ unk_token="[UNK]",
1134
+ pad_token="[PAD]",
1135
+ mask_token="[MASK]"
1136
+ )
1137
+ fast_tokenizer.save_pretrained(ckpt_path)
1138
+
1139
+ revisions[ckpt_name] = ckpt_path
1140
+ except Exception as ex:
1141
+ print(f"[HF] Failed to convert legacy checkpoint file '{ckpt_name}.pt': {ex}")
1142
+
1143
+ # Temporary directory for staging uploads
1144
+ temp_dir = os.path.abspath("./temp_hf_upload")
1145
+
1146
+ # LICENSE text
1147
+ license_text = """Creative Commons Attribution-NonCommercial 4.0 International Public License
1148
+
1149
+ By exercising the Licensed Rights (defined below), You accept and agree to be bound by the terms and conditions of this Creative Commons Attribution-NonCommercial 4.0 International Public License ("Public License"). To the extent this Public License may be interpreted as a contract, You are granted the Licensed Rights in consideration of Your acceptance of these terms and conditions, and the Licensor grants You such rights in consideration of benefits the Licensor receives from making the Licensed Material available under these terms and conditions.
1150
+
1151
+ Section 1 -- Definitions.
1152
+ a. Licensed Material means the artistic or literary work, database, or other material to which the Licensor applied this Public License.
1153
+ b. Licensed Rights means the rights granted to You subject to the terms and conditions of this Public License, which are limited to all Copyright and Similar Rights that apply to Your use of the Licensed Material and that the Licensor has authority to license.
1154
+ c. NonCommercial means not primarily intended for or directed towards commercial advantage or monetary compensation.
1155
+ d. Share means to provide material to the public by any means or process that requires permission under the Licensed Rights.
1156
+ e. You means the individual or entity exercising the Licensed Rights under this Public License.
1157
+
1158
+ Section 2 -- Scope.
1159
+ a. License grant.
1160
+ 1. Subject to the terms and conditions of this Public License, the Licensor hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, irrevocable license to exercise the Licensed Rights in the Licensed Material to:
1161
+ A. reproduce and Share the Licensed Material, in whole or in part, for NonCommercial purposes only; and
1162
+ B. Produce, reproduce, and Share Adapted Material for NonCommercial purposes only.
1163
+ 2. Attribution. As a condition of the license, You must attribute the Licensor and keep intact copyright notices.
1164
+ """
1165
+
1166
+ for revision_name, local_path in revisions.items():
1167
+ print(f"\n[HF] Staging files for revision '{revision_name}' from '{local_path}'...")
1168
+ if os.path.exists(temp_dir):
1169
+ shutil.rmtree(temp_dir)
1170
+ os.makedirs(temp_dir)
1171
+
1172
+ # 1. Copy weight file and save as model.safetensors if possible, otherwise pytorch_model.bin
1173
+ src_bin = os.path.join(local_path, "pytorch_model.bin")
1174
+ weight_file_dest = None
1175
+ if os.path.exists(src_bin):
1176
+ # Try to convert to safetensors
1177
+ try:
1178
+ import torch
1179
+ from safetensors.torch import save_file
1180
+ state_dict = torch.load(src_bin, map_location="cpu")
1181
+ # Clone tensors to break memory sharing (prevents shared weight memory error in safetensors)
1182
+ state_dict = {k: v.clone() for k, v in state_dict.items()}
1183
+ weight_file_dest = os.path.join(temp_dir, "model.safetensors")
1184
+ save_file(state_dict, weight_file_dest)
1185
+ print(f"[HF] Converted weights to safetensors format.")
1186
+ except Exception as e:
1187
+ print(f"[HF] Conversion to safetensors failed ({e}). Staging raw pytorch_model.bin...")
1188
+ weight_file_dest = os.path.join(temp_dir, "pytorch_model.bin")
1189
+ shutil.copy2(src_bin, weight_file_dest)
1190
+ else:
1191
+ print(f"[HF] Error: No weight file found in '{local_path}'!")
1192
+ continue
1193
+
1194
+ # Calculate weight file hash
1195
+ sha256_hash = hashlib.sha256()
1196
+ with open(weight_file_dest, "rb") as f:
1197
+ for byte_block in iter(lambda: f.read(4096), b""):
1198
+ sha256_hash.update(byte_block)
1199
+ weight_hash = sha256_hash.hexdigest()
1200
+ print(f"[HF] Weight file SHA-256: {weight_hash}")
1201
+
1202
+ # 2. Copy and patch config.json
1203
+ src_config = os.path.join(local_path, "config.json")
1204
+ if os.path.exists(src_config):
1205
+ with open(src_config, "r") as f:
1206
+ cfg_data = json.load(f)
1207
+ # Patch config
1208
+ cfg_data["auto_map"] = {
1209
+ "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
1210
+ "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
1211
+ "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
1212
+ }
1213
+ cfg_data["architectures"] = ["XpertGPTForCausalLM"]
1214
+ with open(os.path.join(temp_dir, "config.json"), "w") as f:
1215
+ json.dump(cfg_data, f, indent=2)
1216
+ else:
1217
+ # Fallback configuration
1218
+ cfg_data = {
1219
+ "auto_map": {
1220
+ "AutoConfig": "configuration_xpertgpt.XpertGPTConfig",
1221
+ "AutoModel": "modeling_xpertgpt.XpertGPTModelWrapper",
1222
+ "AutoModelForCausalLM": "modeling_xpertgpt.XpertGPTForCausalLM"
1223
+ },
1224
+ "architectures": ["XpertGPTForCausalLM"],
1225
+ "vocab_size": 16384,
1226
+ "block_size": 512,
1227
+ "d_model": 256,
1228
+ "hidden_size": 256,
1229
+ "d_thin": 384,
1230
+ "num_layers": 6,
1231
+ "num_blocks": 4,
1232
+ "capacity_factor": 1.0,
1233
+ "dropout": 0.1,
1234
+ "model_type": "xpertgpt"
1235
+ }
1236
+ with open(os.path.join(temp_dir, "config.json"), "w") as f:
1237
+ json.dump(cfg_data, f, indent=2)
1238
+
1239
+ # 3. Copy tokenizers
1240
+ for tok_file in ["tokenizer.json", "tokenizer_config.json", "special_tokens_map.json"]:
1241
+ src_tok = os.path.join(local_path, tok_file)
1242
+ if os.path.exists(src_tok):
1243
+ shutil.copy2(src_tok, os.path.join(temp_dir, tok_file))
1244
+
1245
+ # 4. Copy custom code
1246
+ shutil.copy2("modeling_xpertgpt.py", os.path.join(temp_dir, "modeling_xpertgpt.py"))
1247
+ shutil.copy2("configuration_xpertgpt.py", os.path.join(temp_dir, "configuration_xpertgpt.py"))
1248
+
1249
+ # 5. Write metadata files
1250
+ with open(os.path.join(temp_dir, ".gitattributes"), "w") as f:
1251
+ f.write("*.safetensors filter=lfs diff=lfs merge=lfs -text\n")
1252
+ f.write("*.bin filter=lfs diff=lfs merge=lfs -text\n")
1253
+
1254
+ with open(os.path.join(temp_dir, "LICENSE"), "w") as f:
1255
+ f.write(license_text)
1256
+
1257
+ with open(os.path.join(temp_dir, "CITATION.cff"), "w") as f:
1258
+ citation_yaml = f"""cff-version: 1.2.0
1259
+ message: "If you use this model or software, please cite it as below."
1260
+ authors:
1261
+ - family-names: "Jain"
1262
+ given-names: "Soham"
1263
+ - family-names: "Singh"
1264
+ given-names: "Harsh"
1265
+ - family-names: "Dewan"
1266
+ given-names: "Divija"
1267
+ - family-names: "Dev"
1268
+ given-names: "Atul"
1269
+ title: "XpertGPT: Mixture of Experts with Parallelized Multi-Scale Information Transmission for Data-Constrained Pretraining"
1270
+ year: 2026
1271
+ url: "https://huggingface.co/{repo_id}"
1272
+ """
1273
+ f.write(citation_yaml)
1274
+
1275
+ with open(os.path.join(temp_dir, "PROVENANCE.md"), "w") as f:
1276
+ provenance_md = f"""# Provenance and Weight Integrity Record
1277
+
1278
+ This file records the provenance, cryptographic hash, and reproducibility metadata of the model weights.
1279
+
1280
+ ## Verification Fingerprints
1281
+ - **Model Weight File**: {"model.safetensors" if weight_file_dest.endswith(".safetensors") else "pytorch_model.bin"}
1282
+ - **Weight SHA-256**: {weight_hash}
1283
+ - **Tokenizer Vocab Size**: 16,384
1284
+
1285
+ ## Training Run Details
1286
+ - **Training Word Budget**: 10M words (BabyLM 2026 Strict-Small track)
1287
+ - **Model Parameters**: ~48.4M non-embedding parameters / ~52.6M total tied parameters
1288
+ - **Optimizer**: AdamW
1289
+ - **Epochs**: 8
1290
+ """
1291
+ f.write(provenance_md)
1292
+
1293
+ # Build README
1294
+ readme_md = f"""---
1295
+ license: cc-by-nc-4.0
1296
+ language:
1297
+ - en
1298
+ tags:
1299
+ - babylm
1300
+ - babylm-2026
1301
+ - mixture-of-experts
1302
+ - msit
1303
+ - xpertgpt
1304
+ - custom_code
1305
+ - safetensors
1306
+ library_name: transformers
1307
+ pipeline_tag: text-generation
1308
+ ---
1309
+
1310
+ # XpertGPT Strict-Small
1311
+
1312
+ XpertGPT (Mixture of Experts with Parallelized Multi-Scale Information Transmission) is a sparse, data-efficient recurrent language model for the BabyLM 2026 challenge (Strict-Small (10M) track, 10M words). It combines sliding window attention global streams with sparse parallel expert blocks using Expert Choice routing. ~48.4M non-embedding parameters / ~52.6M total parameters. Custom code (`trust_remote_code=True`).
1313
+
1314
+ - **Architecture:** 6 layers of MoEP-MSIT blocks. Each block combines a lower-dimensional dense global sliding window attention layer (`dim = 256`) with 4 parallel high-dimensional sparse expert blocks (`dim = 384`) routed via Expert Choice gating (capacity factor: 1.0).
1315
+ - **Track:** BabyLM 2026 Strict-Small (10M) (10M words).
1316
+ - **Tokenizer:** Custom BPE tokenizer (vocab size: 16384).
1317
+ - **Revision / Checkpoint:** {revision_name}
1318
+
1319
+ ## Usage
1320
+
1321
+ ```python
1322
+ import torch
1323
+ from transformers import AutoModelForCausalLM, AutoTokenizer
1324
+
1325
+ model = AutoModelForCausalLM.from_pretrained("{repo_id}", revision="{revision_name}", trust_remote_code=True).eval()
1326
+ tok = AutoTokenizer.from_pretrained("{repo_id}", revision="{revision_name}")
1327
+ ids = tok("The quick brown fox", return_tensors="pt").input_ids
1328
+ with torch.no_grad():
1329
+ logits = model(ids).logits
1330
+ ```
1331
+
1332
+ ## Intermediate checkpoints
1333
+
1334
+ Intermediate training checkpoints are provided as git revisions named `chck_<N>M` for the BabyLM challenge fast-eval.
1335
+
1336
+ ## License and citation
1337
+
1338
+ Released under CC BY-NC 4.0 (attribution required, non-commercial only). If you use this model or code, please cite (see `CITATION.cff`):
1339
+
1340
+ ```bibtex
1341
+ @misc{{jain2026xpertgpt,
1342
+ title = {{XpertGPT: Mixture of Experts with Parallelized Multi-Scale Information Transmission for Data-Constrained Pretraining}},
1343
+ author = {{Jain, Soham and Singh, Harsh and Dewan, Divija and Dev, Atul}},
1344
+ year = {{2026}},
1345
+ howpublished = {{Hugging Face Repository}},
1346
+ note = {{XpertGPT MoE language model, BabyLM 2026}}
1347
+ }}
1348
+ ```
1349
+
1350
+ Provenance and integrity fingerprints are documented in `PROVENANCE.md`.
1351
+ """
1352
+ with open(os.path.join(temp_dir, "README.md"), "w") as f:
1353
+ f.write(readme_md)
1354
+
1355
+ # 6. For main branch only: also upload collated predictions & terminal log
1356
+ if revision_name == "main":
1357
+ for pred_file in ["all_full_preds_and_fast_scores_causal.json", "all_full_preds_and_fast_scores_causal (3).json"]:
1358
+ if os.path.exists(pred_file):
1359
+ shutil.copy2(pred_file, os.path.join(temp_dir, "all_full_preds_and_fast_scores_causal.json"))
1360
+ print(f"[HF] Copied predictions file '{pred_file}' to staging area.")
1361
+ break
1362
+ if os.path.exists("training_terminal.log"):
1363
+ shutil.copy2("training_terminal.log", os.path.join(temp_dir, "training_terminal.log"))
1364
+ print("[HF] Copied training terminal log 'training_terminal.log' to staging area.")
1365
+
1366
+ # 7. Create branch if it does not exist, then upload staged files to HF under the revision
1367
+ if revision_name != "main":
1368
+ try:
1369
+ api.create_branch(
1370
+ repo_id=repo_id,
1371
+ repo_type="model",
1372
+ branch=revision_name,
1373
+ exist_ok=True
1374
+ )
1375
+ print(f"[HF] Created branch/revision '{revision_name}' on repository.")
1376
+ except Exception as branch_err:
1377
+ print(f"[HF] Info: Branch creation failed or exists: {branch_err}")
1378
+
1379
+ print(f"[HF] Uploading staged folder to '{repo_id}' revision '{revision_name}'...")
1380
+ try:
1381
+ api.upload_folder(
1382
+ folder_path=temp_dir,
1383
+ repo_id=repo_id,
1384
+ repo_type="model",
1385
+ revision=revision_name
1386
+ )
1387
+ print(f"[HF] Successfully uploaded revision '{revision_name}' to repository.")
1388
+ except Exception as e:
1389
+ print(f"[HF] Failed to upload revision '{revision_name}': {e}")
1390
+
1391
+ # Cleanup temp dir
1392
+ if os.path.exists(temp_dir):
1393
+ shutil.rmtree(temp_dir)
1394
+ print(f"\n[HF] All uploads finished! View your repository at https://huggingface.co/{repo_id}")
1395
+
1396
+ if __name__ == "__main__":
1397
+ parser = argparse.ArgumentParser()
1398
+ parser.add_argument("--model-name", type=str, default="xpertgpt_fresh")
1399
+ parser.add_argument("--epochs", type=int, default=10)
1400
+ parser.add_argument("--skip-eval", action="store_true", help="Skip evaluation phase after training")
1401
+ parser.add_argument("--skip-aoa", action="store_true", default=True, help="Skip AoA evaluation")
1402
+ parser.add_argument("--skip-glue", action="store_true", default=False, help="Skip GLUE fine-tuning")
1403
+ parser.add_argument("--upload", action="store_true", help="Upload model repository to Hugging Face")
1404
+ parser.add_argument("--upload-repo", type=str, default="Normal_c1", help="Hugging Face repository name")
1405
+ parser.add_argument("--upload-token", type=str, default=None, help="Hugging Face API token")
1406
+ args = parser.parse_args()
1407
+
1408
+ if args.upload:
1409
+ upload_pipeline(args.model_name, args.upload_repo, args.upload_token)
1410
+ else:
1411
+ class Tee:
1412
+ def __init__(self, filepath, original_stream):
1413
+ self.file = open(filepath, "a", encoding="utf-8", buffering=1)
1414
+ self.original_stream = original_stream
1415
+
1416
+ def write(self, data):
1417
+ self.original_stream.write(data)
1418
+ self.file.write(data)
1419
+
1420
+ def flush(self):
1421
+ self.original_stream.flush()
1422
+ self.file.flush()
1423
+
1424
+ import sys
1425
+ sys.stdout = Tee("training_terminal.log", sys.stdout)
1426
+ sys.stderr = Tee("training_terminal.log", sys.stderr)
1427
+
1428
+ print("\n=== STARTING NEW TRAINING RUN LOGGING TO training_terminal.log ===")
1429
+ run_pipeline(args.model_name, epochs=args.epochs, skip_eval=args.skip_eval, skip_aoa=args.skip_aoa, skip_glue=args.skip_glue)
upload_all_hf.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import re
4
+ import argparse
5
+ from huggingface_hub import HfApi, create_repo
6
+
7
+ def upload_project(repo_name=None):
8
+ token = os.environ.get("HF_TOKEN")
9
+ if not token:
10
+ print("Error: HF_TOKEN environment variable not set!")
11
+ print("Please set it before running: export HF_TOKEN=your_token")
12
+ sys.exit(1)
13
+
14
+ if not repo_name:
15
+ repo_name = os.environ.get("HF_REPO_NAME", "Normal_c1")
16
+
17
+ # Set local_dir to the directory containing this script
18
+ local_dir = os.path.dirname(os.path.abspath(__file__))
19
+ print(f"[HF] Source directory: {local_dir}")
20
+
21
+ api = HfApi(token=token)
22
+ try:
23
+ user_info = api.whoami()
24
+ username = user_info["name"]
25
+ print(f"[HF] Authenticated as: {username}")
26
+ except Exception as e:
27
+ print(f"[HF] Authentication failed: {e}")
28
+ sys.exit(1)
29
+
30
+ repo_id = f"{username}/{repo_name}"
31
+
32
+ # Auto-create the repo if it does not exist
33
+ try:
34
+ create_repo(repo_id=repo_id, repo_type="model", token=token, exist_ok=True)
35
+ print(f"[HF] Repository '{repo_id}' is ready.")
36
+ except Exception as e:
37
+ print(f"[HF] Warning during repository creation: {e}")
38
+
39
+ print(f"[HF] Uploading ALL project files (including checkpoints, results, scripts) to '{repo_id}' main branch...")
40
+
41
+ # Define ignore patterns (only excluding huge library download caches)
42
+ ignore_patterns = [
43
+ "**/__pycache__/**",
44
+ "**/*.pyc",
45
+ "**/hf_cache/**",
46
+ "**/nltk_data/**",
47
+ "hf_cache/**",
48
+ "nltk_data/**",
49
+ "hf_cache",
50
+ "nltk_data",
51
+ "**/hf_cache/*",
52
+ "**/nltk_data/*"
53
+ ]
54
+
55
+ sensitive_ignores = []
56
+ token_pattern = re.compile(r"hf_[a-zA-Z0-9]{34}")
57
+ for root, dirs, files in os.walk(local_dir):
58
+ # Skip scanning cache directories for sensitive tokens
59
+ if any(d in root for d in ["__pycache__", "hf_cache", "nltk_data"]):
60
+ continue
61
+ for file in files:
62
+ file_path = os.path.join(root, file)
63
+ # Skip checking binary or archive files for tokens to keep scan fast
64
+ if file.endswith(('.bin', '.safetensors', '.zip', '.tar.gz', '.pkl', '.pt', '.pth')):
65
+ continue
66
+ try:
67
+ # Skip checking files larger than 10MB
68
+ if os.path.getsize(file_path) > 10 * 1024 * 1024:
69
+ continue
70
+ with open(file_path, "r", errors="ignore") as f:
71
+ content = f.read()
72
+ if token_pattern.search(content):
73
+ rel_path = os.path.relpath(file_path, local_dir)
74
+ rel_path_glob = rel_path.replace("\\", "/")
75
+ sensitive_ignores.append(rel_path_glob)
76
+ print(f" -> Warning: Sensitive file containing raw token excluded from upload: {rel_path_glob}")
77
+ except Exception:
78
+ pass
79
+
80
+ all_ignores = ignore_patterns + sensitive_ignores
81
+
82
+ try:
83
+ api.upload_folder(
84
+ folder_path=local_dir,
85
+ repo_id=repo_id,
86
+ repo_type="model",
87
+ revision="main",
88
+ ignore_patterns=all_ignores,
89
+ commit_message="Upload full run contents (scripts, checkpoints, and results)"
90
+ )
91
+ print(f"\n[HF] Success! All files uploaded to: https://huggingface.co/{repo_id}/tree/main")
92
+ except Exception as e:
93
+ print(f"[HF] Upload failed: {e}")
94
+
95
+ if __name__ == "__main__":
96
+ parser = argparse.ArgumentParser()
97
+ parser.add_argument("--repo-name", type=str, default=None, help="Hugging Face repository name")
98
+ args = parser.parse_args()
99
+ upload_project(args.repo_name)
upload_project_hf.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import re
4
+ import argparse
5
+ from huggingface_hub import HfApi, create_repo
6
+
7
+ def upload_project(repo_name=None):
8
+ token = os.environ.get("HF_TOKEN")
9
+ if not token:
10
+ print("Error: HF_TOKEN environment variable not set!")
11
+ print("Please set it before running: set HF_TOKEN=your_token")
12
+ sys.exit(1)
13
+
14
+ if not repo_name:
15
+ repo_name = os.environ.get("HF_REPO_NAME", "Normal_c1")
16
+
17
+ local_dir = "/Users/harshsingh/.gemini/antigravity/scratch/ablation_2-5_normal_c1"
18
+
19
+ api = HfApi(token=token)
20
+ try:
21
+ user_info = api.whoami()
22
+ username = user_info["name"]
23
+ print(f"[HF] Authenticated as: {username}")
24
+ except Exception as e:
25
+ print(f"[HF] Authentication failed: {e}")
26
+ sys.exit(1)
27
+
28
+ repo_id = f"{username}/{repo_name}"
29
+
30
+ # Auto-create the repo if it does not exist
31
+ try:
32
+ create_repo(repo_id=repo_id, repo_type="model", token=token, exist_ok=True)
33
+ print(f"[HF] Repository '{repo_id}' is ready.")
34
+ except Exception as e:
35
+ print(f"[HF] Warning during repository creation: {e}")
36
+
37
+ print(f"[HF] Uploading modified sliding-window strict-small scripts to '{repo_id}' main branch...")
38
+
39
+ ignore_patterns = [
40
+ "**/__pycache__/**",
41
+ "**/*.pyc",
42
+ "**/hf_cache/**",
43
+ "**/nltk_data/**",
44
+ "hf_cache/**",
45
+ "nltk_data/**",
46
+ "hf_cache",
47
+ "nltk_data",
48
+ "**/hf_cache/*",
49
+ "**/nltk_data/*",
50
+ "**/checkpoints/*"
51
+ ]
52
+
53
+ sensitive_ignores = []
54
+ token_pattern = re.compile(r"hf_[a-zA-Z0-9]{34}")
55
+ for root, dirs, files in os.walk(local_dir):
56
+ if "__pycache__" in root or "checkpoints" in root:
57
+ continue
58
+ for file in files:
59
+ file_path = os.path.join(root, file)
60
+ if file.endswith(('.bin', '.safetensors', '.zip', '.tar.gz', '.pkl')):
61
+ continue
62
+ try:
63
+ with open(file_path, "r", errors="ignore") as f:
64
+ content = f.read()
65
+ if token_pattern.search(content):
66
+ rel_path = os.path.relpath(file_path, local_dir)
67
+ rel_path_glob = rel_path.replace("\\", "/")
68
+ sensitive_ignores.append(rel_path_glob)
69
+ print(f" -> Warning: Sensitive file containing raw token excluded: {rel_path_glob}")
70
+ except Exception:
71
+ pass
72
+
73
+ all_ignores = ignore_patterns + sensitive_ignores
74
+
75
+ try:
76
+ api.upload_folder(
77
+ folder_path=local_dir,
78
+ repo_id=repo_id,
79
+ repo_type="model",
80
+ revision="main",
81
+ ignore_patterns=all_ignores,
82
+ commit_message="Update strict-small architecture files (SwiGLU, sliding window [64, 16, 8, 4], ln3, ln_post_moe, no res3)"
83
+ )
84
+ print(f"\n[HF] Success! Scripts uploaded to: https://huggingface.co/{repo_id}/tree/main")
85
+ except Exception as e:
86
+ print(f"[HF] Upload failed: {e}")
87
+
88
+ if __name__ == "__main__":
89
+ parser = argparse.ArgumentParser()
90
+ parser.add_argument("--repo-name", type=str, default=None, help="Hugging Face repository name")
91
+ args = parser.parse_args()
92
+ upload_project(args.repo_name)