Soham Jain commited on
Commit
9aef604
·
verified ·
1 Parent(s): eff8d46

Initial commit of GPT-2 46M SwiGLU Dense baseline model architecture and configs

Browse files
configuration_gpt2.py ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from transformers import PretrainedConfig
2
+
3
+ class GPT2CustomConfig(PretrainedConfig):
4
+ model_type = "gpt2_custom"
5
+ auto_map = {
6
+ "AutoConfig": "configuration_gpt2.GPT2CustomConfig",
7
+ "AutoModelForCausalLM": "modeling_gpt2.GPT2CustomLMHeadModel"
8
+ }
9
+
10
+ def __init__(
11
+ self,
12
+ vocab_size=16384,
13
+ n_positions=512,
14
+ n_embd=384,
15
+ n_layer=12,
16
+ n_head=6,
17
+ n_inner=1280,
18
+ activation_function="swiglu",
19
+ resid_pdrop=0.1,
20
+ embd_pdrop=0.1,
21
+ attn_pdrop=0.1,
22
+ layer_norm_epsilon=1e-5,
23
+ initializer_range=0.02,
24
+ bos_token_id=2,
25
+ eos_token_id=3,
26
+ **kwargs
27
+ ):
28
+ super().__init__(bos_token_id=bos_token_id, eos_token_id=eos_token_id, **kwargs)
29
+ self.vocab_size = vocab_size
30
+ self.n_positions = n_positions
31
+ self.n_embd = n_embd
32
+ self.n_layer = n_layer
33
+ self.n_head = n_head
34
+ self.n_inner = n_inner
35
+ self.activation_function = activation_function
36
+ self.resid_pdrop = resid_pdrop
37
+ self.embd_pdrop = embd_pdrop
38
+ self.attn_pdrop = attn_pdrop
39
+ self.layer_norm_epsilon = layer_norm_epsilon
40
+ self.initializer_range = initializer_range
generate_plots_and_report.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import math
4
+ import torch
5
+ import argparse
6
+
7
+ def main():
8
+ parser = argparse.ArgumentParser(description="Generate training reports, learning curves, and router plots.")
9
+ parser.add_argument("--model-dir", type=str, default="./checkpoints/gpt2_dense_31M", help="Path to checkpoints directory")
10
+ args = parser.parse_args()
11
+
12
+ print("=== 1. PARAMETER COUNT & MODEL METRICS REPORT ===")
13
+ model_path = os.path.join(args.model_dir, "main")
14
+ weight_file = os.path.join(model_path, "pytorch_model.bin")
15
+
16
+ if os.path.exists(weight_file):
17
+ try:
18
+ state_dict = torch.load(weight_file, map_location="cpu")
19
+ total_params = 0
20
+ non_emb_params = 0
21
+ for name, param in state_dict.items():
22
+ size = param.numel()
23
+ total_params += size
24
+ # Exclude embedding and head tie
25
+ if "wte.weight" not in name and "lm_head.weight" not in name:
26
+ non_emb_params += size
27
+ print(f"Model weights loaded successfully from: {weight_file}")
28
+ print(f"- Total Parameters (with tied embeddings): {total_params:,}")
29
+ print(f"- Non-Embedding Parameters: {non_emb_params:,}")
30
+ except Exception as e:
31
+ print(f"Warning: Could not read parameter counts from weight file: {e}")
32
+ # Fallback to config math
33
+ fallback_param_math()
34
+ else:
35
+ print(f"Info: Checked {weight_file} but weights not found. Using fallback config computation:")
36
+ fallback_param_math()
37
+
38
+ # Load validation metrics
39
+ val_file = "validation_metrics.json"
40
+ if not os.path.exists(val_file):
41
+ val_file = os.path.join(args.model_dir, "validation_metrics.json")
42
+
43
+ if os.path.exists(val_file):
44
+ with open(val_file, "r") as f:
45
+ val_logs = json.load(f)
46
+ if len(val_logs) > 0:
47
+ final_val = val_logs[-1]
48
+ print(f"- Final Validation Step: {final_val['step']}")
49
+ print(f"- Final Validation Loss: {final_val['val_loss']:.4f}")
50
+ print(f"- Final Validation Perplexity: {final_val['val_perplexity']:.2f}")
51
+ else:
52
+ print(f"Warning: {val_file} not found! Cannot report validation perplexity.")
53
+
54
+ print("\n=== 2. GENERATING LEARNING CURVES ===")
55
+ train_file = "training_metrics.json"
56
+ if not os.path.exists(train_file):
57
+ train_file = os.path.join(args.model_dir, "training_metrics.json")
58
+
59
+ try:
60
+ import matplotlib
61
+ matplotlib.use('Agg')
62
+ import matplotlib.pyplot as plt
63
+
64
+ has_plots = False
65
+
66
+ # Setup plot grid
67
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
68
+
69
+ if os.path.exists(train_file):
70
+ with open(train_file, "r") as f:
71
+ train_logs = json.load(f)
72
+ steps = [log["step"] for log in train_logs]
73
+ losses = [log["train_loss"] for log in train_logs]
74
+ ax1.plot(steps, losses, color="#ef4444", linewidth=1.5, label="Train Loss")
75
+ ax1.set_xlabel("Global Step")
76
+ ax1.set_ylabel("Cross Entropy Loss")
77
+ ax1.set_title("Training Loss Curve")
78
+ ax1.grid(True, linestyle="--", alpha=0.6)
79
+ ax1.legend()
80
+ has_plots = True
81
+ else:
82
+ ax1.text(0.5, 0.5, "training_metrics.json not found", ha="center")
83
+
84
+ if os.path.exists(val_file):
85
+ with open(val_file, "r") as f:
86
+ val_logs = json.load(f)
87
+ steps = [log["step"] for log in val_logs]
88
+ ppl = [log["val_perplexity"] for log in val_logs]
89
+ ax2.plot(steps, ppl, marker="o", color="#3b82f6", linewidth=2, label="Val Perplexity")
90
+ ax2.set_xlabel("Global Step")
91
+ ax2.set_ylabel("Perplexity")
92
+ ax2.set_title("Validation Perplexity Curve")
93
+ ax2.grid(True, linestyle="--", alpha=0.6)
94
+ ax2.legend()
95
+ has_plots = True
96
+ else:
97
+ ax2.text(0.5, 0.5, "validation_metrics.json not found", ha="center")
98
+
99
+ if has_plots:
100
+ plt.tight_layout()
101
+ plt.savefig("learning_curves.png", dpi=150)
102
+ plt.close()
103
+ print("Successfully saved learning curve diagrams to 'learning_curves.png'.")
104
+ except Exception as e:
105
+ print(f"Warning: Could not plot learning curves: {e}")
106
+
107
+ print("\n=== 3. ROUTER UTILIZATION ANALYSIS ===")
108
+ router_details_file = "router_details.json"
109
+ if not os.path.exists(router_details_file):
110
+ router_details_file = os.path.join(args.model_dir, "router_details.json")
111
+
112
+ if os.path.exists(router_details_file):
113
+ try:
114
+ with open(router_details_file, "r") as f:
115
+ records = json.load(f)
116
+
117
+ print(f"Loaded router mapping records containing {len(records)} logged steps.")
118
+
119
+ # Reconstruct selection count mapping
120
+ # records format: [{"step": s, "layer": l, "expert_assignments": [[tokens for exp1], [exp2], ...]}]
121
+ # Find the total number of unique token indices in each step
122
+ total_expert_counts = [0, 0, 0, 0, 0] # indices representing 0, 1, 2, 3, 4 experts
123
+
124
+ for record in records:
125
+ assignments = record["expert_assignments"]
126
+ # Number of experts is 4
127
+ num_blocks = len(assignments)
128
+ # Find maximum token index present in assignments to know the token range
129
+ max_token_idx = 0
130
+ for exp_list in assignments:
131
+ if len(exp_list) > 0:
132
+ max_token_idx = max(max_token_idx, max(exp_list))
133
+
134
+ n_tokens = max_token_idx + 1
135
+ token_counts = [0] * n_tokens
136
+ for exp_list in assignments:
137
+ for token_idx in exp_list:
138
+ token_counts[token_idx] += 1
139
+
140
+ for count in token_counts:
141
+ if count < 5:
142
+ total_expert_counts[count] += 1
143
+
144
+ total_tokens_sum = sum(total_expert_counts)
145
+ if total_tokens_sum > 0:
146
+ fractions = [total_expert_counts[i] / total_tokens_sum for i in range(5)]
147
+ else:
148
+ fractions = [0.0] * 5
149
+
150
+ fraction_no_expert = fractions[0]
151
+ fraction_multiple_experts = sum(fractions[2:])
152
+
153
+ report_text = f"""=== ROUTER UTILIZATION REPORT ===
154
+ Total tokens tracked: {total_tokens_sum:,}
155
+
156
+ Number of experts selected per token:
157
+ - 0 experts: {total_expert_counts[0]:,} tokens ({fractions[0]:.2%})
158
+ - 1 expert: {total_expert_counts[1]:,} tokens ({fractions[1]:.2%})
159
+ - 2 experts: {total_expert_counts[2]:,} tokens ({fractions[2]:.2%})
160
+ - 3 experts: {total_expert_counts[3]:,} tokens ({fractions[3]:.2%})
161
+ - 4 experts: {total_expert_counts[4]:,} tokens ({fractions[4]:.2%})
162
+
163
+ Summary metrics:
164
+ - Fraction of tokens receiving NO expert: {fraction_no_expert:.2%}
165
+ - Fraction of tokens receiving MULTIPLE experts (2, 3, or 4): {fraction_multiple_experts:.2%}
166
+ """
167
+ with open("router_report.txt", "w") as f:
168
+ f.write(report_text)
169
+ print(report_text)
170
+
171
+ # Plot utilization chart
172
+ try:
173
+ import matplotlib
174
+ matplotlib.use('Agg')
175
+ import matplotlib.pyplot as plt
176
+
177
+ categories = ['0', '1', '2', '3', '4']
178
+ plt.figure(figsize=(8, 5))
179
+ plt.bar(categories, [f * 100 for f in fractions], color='#10b981', edgecolor='#059669', width=0.6)
180
+ plt.xlabel('Number of Experts Selected per Token')
181
+ plt.ylabel('Percentage of Tokens (%)')
182
+ plt.title('Router Utilization / Expert Choices per Token')
183
+ plt.grid(axis='y', linestyle='--', alpha=0.6)
184
+
185
+ for i, f in enumerate(fractions):
186
+ plt.text(i, f * 100 + 1, f"{f:.2%}", ha='center', fontweight='bold')
187
+
188
+ plt.ylim(0, max([f * 100 for f in fractions]) + 10)
189
+ plt.savefig('router_utilization.png', dpi=150)
190
+ plt.close()
191
+ print("Successfully saved router utilization plot to 'router_utilization.png'.")
192
+ except Exception as plot_err:
193
+ print(f"Warning: Could not plot router utilization: {plot_err}")
194
+
195
+ except Exception as e:
196
+ print(f"Warning: Could not analyze router assignments file: {e}")
197
+ else:
198
+ print(f"Warning: {router_details_file} not found! Cannot analyze router utilization.")
199
+
200
+ def fallback_param_math():
201
+ # Fallback mathematical projection computation based on model config
202
+ print("Fallback configuration architecture math:")
203
+ print("- Config: 12 layers, d_model=384, d_inner=1280, 6 heads, tied embeddings, SwiGLU")
204
+ print("- Non-Embedding parameter count is approximately ~24.8M parameters.")
205
+ print("- Total parameter count is approximately ~31.1M parameters.")
206
+
207
+ if __name__ == "__main__":
208
+ main()
modeling_gpt2.py ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import math
2
+ from typing import Optional, Tuple, Dict, Any
3
+ import torch
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+ from transformers import PreTrainedModel, GenerationMixin, AutoConfig, AutoModelForCausalLM
7
+ from transformers import PretrainedConfig
8
+
9
+ class GPT2CustomConfig(PretrainedConfig):
10
+ model_type = "gpt2_custom"
11
+ auto_map = {
12
+ "AutoConfig": "configuration_gpt2.GPT2CustomConfig",
13
+ "AutoModelForCausalLM": "modeling_gpt2.GPT2CustomLMHeadModel"
14
+ }
15
+
16
+ def __init__(
17
+ self,
18
+ vocab_size=16384,
19
+ n_positions=512,
20
+ n_embd=384,
21
+ n_layer=12,
22
+ n_head=6,
23
+ n_inner=1280,
24
+ activation_function="gelu_new",
25
+ resid_pdrop=0.1,
26
+ embd_pdrop=0.1,
27
+ attn_pdrop=0.1,
28
+ layer_norm_epsilon=1e-5,
29
+ initializer_range=0.02,
30
+ bos_token_id=2,
31
+ eos_token_id=3,
32
+ **kwargs
33
+ ):
34
+ self.vocab_size = vocab_size
35
+ self.n_positions = n_positions
36
+ self.n_embd = n_embd
37
+ self.n_layer = n_layer
38
+ self.n_head = n_head
39
+ self.n_inner = n_inner
40
+ self.activation_function = activation_function
41
+ self.resid_pdrop = resid_pdrop
42
+ self.embd_pdrop = embd_pdrop
43
+ self.attn_pdrop = attn_pdrop
44
+ self.layer_norm_epsilon = layer_norm_epsilon
45
+ self.initializer_range = initializer_range
46
+ self.hidden_size = n_embd
47
+ self.num_attention_heads = n_head
48
+ self.num_hidden_layers = n_layer
49
+ super().__init__(bos_token_id=bos_token_id, eos_token_id=eos_token_id, **kwargs)
50
+
51
+ # ─────────────────────────────────────────────────────────────
52
+ # ROPE HELPERS
53
+ # ─────────────────────────────────────────────────────────────
54
+
55
+ def _precompute_rope_freqs(head_dim: int, seq_len: int, device: torch.device, theta: float = 10000.0):
56
+ assert head_dim % 2 == 0, "head_dim must be divisible by 2 for RoPE"
57
+ inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device).float() / head_dim))
58
+ t = torch.arange(seq_len, device=device).float()
59
+ freqs = torch.outer(t, inv_freq)
60
+ emb = torch.cat((freqs, freqs), dim=-1)
61
+ return emb.cos(), emb.sin()
62
+
63
+ def _apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor):
64
+ L = x.size(2)
65
+ cos = cos[:L, :].unsqueeze(0).unsqueeze(1)
66
+ sin = sin[:L, :].unsqueeze(0).unsqueeze(1)
67
+
68
+ half_dim = x.size(-1) // 2
69
+ x1 = x[..., :half_dim]
70
+ x2 = x[..., half_dim:]
71
+ rotated_x = torch.cat((-x2, x1), dim=-1)
72
+
73
+ return (x * cos) + (rotated_x * sin)
74
+
75
+ class CausalSelfAttention(nn.Module):
76
+ def __init__(self, config):
77
+ super().__init__()
78
+ assert config.n_embd % config.n_head == 0
79
+ self.num_heads = config.n_head
80
+ self.head_dim = config.n_embd // config.n_head
81
+
82
+ self.q_proj = nn.Linear(config.n_embd, config.n_embd, bias=False)
83
+ self.k_proj = nn.Linear(config.n_embd, config.n_embd, bias=False)
84
+ self.v_proj = nn.Linear(config.n_embd, config.n_embd, bias=False)
85
+ self.o_proj = nn.Linear(config.n_embd, config.n_embd, bias=False)
86
+
87
+ self.attn_dropout = nn.Dropout(config.attn_pdrop)
88
+ self.resid_dropout = nn.Dropout(config.resid_pdrop)
89
+
90
+ def forward(self, x, past_kv=None, use_cache: bool = False):
91
+ B, L, D = x.size()
92
+
93
+ q = self.q_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
94
+ k = self.k_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
95
+ v = self.v_proj(x).view(B, L, self.num_heads, self.head_dim).transpose(1, 2)
96
+
97
+ if past_kv is not None:
98
+ past_k, past_v = past_kv
99
+ past_len = past_k.size(2)
100
+ q_cos, q_sin = _precompute_rope_freqs(self.head_dim, past_len + L, x.device)
101
+ q = _apply_rope(q, q_cos[past_len:, :], q_sin[past_len:, :])
102
+ k = _apply_rope(k, q_cos[past_len:, :], q_sin[past_len:, :])
103
+ k = torch.cat([past_k, k], dim=2)
104
+ v = torch.cat([past_v, v], dim=2)
105
+ else:
106
+ cos, sin = _precompute_rope_freqs(self.head_dim, L, x.device)
107
+ q = _apply_rope(q, cos, sin)
108
+ k = _apply_rope(k, cos, sin)
109
+
110
+ L_kv = k.size(2)
111
+ scores = torch.matmul(q, k.transpose(-2, -1)) / math.sqrt(self.head_dim)
112
+
113
+ # Standard full context causal attention mask (no sliding window)
114
+ past_len = L_kv - L
115
+ pos_i = (past_len + torch.arange(L, device=x.device)).unsqueeze(1)
116
+ pos_j = torch.arange(L_kv, device=x.device).unsqueeze(0)
117
+ causal_mask = (pos_i - pos_j) >= 0
118
+
119
+ scores = scores.masked_fill(~causal_mask.unsqueeze(0).unsqueeze(0), float('-inf'))
120
+
121
+ attn = torch.softmax(scores, dim=-1)
122
+ attn = self.attn_dropout(attn)
123
+ out = torch.matmul(attn, v)
124
+ out = out.transpose(1, 2).contiguous().view(B, L, D)
125
+ out = self.resid_dropout(self.o_proj(out))
126
+
127
+ present_kv = (k, v) if use_cache else None
128
+ return out, present_kv
129
+
130
+ class SwiGLU(nn.Module):
131
+ def __init__(self, dim: int, hidden_dim: int):
132
+ super().__init__()
133
+ self.fc1 = nn.Linear(dim, hidden_dim, bias=False)
134
+ self.fc2 = nn.Linear(dim, hidden_dim, bias=False)
135
+ self.fc3 = nn.Linear(hidden_dim, dim, bias=False)
136
+
137
+ def forward(self, x):
138
+ return self.fc3(F.silu(self.fc1(x)) * self.fc2(x))
139
+
140
+ class GPT2Block(nn.Module):
141
+ def __init__(self, config):
142
+ super().__init__()
143
+ self.ln_1 = nn.LayerNorm(config.n_embd, eps=config.layer_norm_epsilon)
144
+ self.attn = CausalSelfAttention(config)
145
+ self.ln_2 = nn.LayerNorm(config.n_embd, eps=config.layer_norm_epsilon)
146
+ self.mlp = SwiGLU(config.n_embd, config.n_inner)
147
+
148
+ def forward(self, x, past_kv=None, use_cache: bool = False):
149
+ attn_out, present_kv = self.attn(self.ln_1(x), past_kv=past_kv, use_cache=use_cache)
150
+ x = x + attn_out
151
+ x = x + self.mlp(self.ln_2(x))
152
+ return x, present_kv
153
+
154
+ class GPT2CustomLMHeadModel(PreTrainedModel, GenerationMixin):
155
+ config_class = GPT2CustomConfig
156
+ base_model_prefix = "transformer"
157
+ _tied_weights_keys = {"lm_head.weight": "transformer.wte.weight"}
158
+
159
+ def __init__(self, config):
160
+ super().__init__(config)
161
+ self.transformer = nn.ModuleDict(dict(
162
+ wte = nn.Embedding(config.vocab_size, config.n_embd),
163
+ drop = nn.Dropout(config.embd_pdrop),
164
+ h = nn.ModuleList([GPT2Block(config) for _ in range(config.n_layer)]),
165
+ ln_f = nn.LayerNorm(config.n_embd, eps=config.layer_norm_epsilon),
166
+ ))
167
+ self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
168
+ self.post_init()
169
+
170
+ def get_input_embeddings(self):
171
+ return self.transformer.wte
172
+
173
+ def set_input_embeddings(self, new_embeddings):
174
+ self.transformer.wte = new_embeddings
175
+
176
+ def get_output_embeddings(self):
177
+ return self.lm_head
178
+
179
+ def set_output_embeddings(self, new_embeddings):
180
+ self.lm_head = new_embeddings
181
+
182
+ def _init_weights(self, module):
183
+ if isinstance(module, nn.Linear):
184
+ torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
185
+ if module.bias is not None:
186
+ torch.nn.init.zeros_(module.bias)
187
+ elif isinstance(module, nn.Embedding):
188
+ torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.initializer_range)
189
+
190
+ def forward(self, input_ids, labels=None, past_key_values=None, use_cache: bool = False, **kwargs):
191
+ device = input_ids.device
192
+
193
+ x = self.transformer.wte(input_ids)
194
+ x = self.transformer.drop(x)
195
+
196
+ new_kvs = []
197
+ for i, block in enumerate(self.transformer.h):
198
+ pkv = past_key_values[i] if past_key_values is not None else None
199
+ x, nkv = block(x, past_kv=pkv, use_cache=use_cache)
200
+ if use_cache:
201
+ new_kvs.append(nkv)
202
+
203
+ x = self.transformer.ln_f(x)
204
+ logits = self.lm_head(x)
205
+
206
+ loss = None
207
+ if labels is not None:
208
+ shift_logits = logits[..., :-1, :].contiguous()
209
+ shift_labels = labels[..., 1:].contiguous()
210
+ loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1), ignore_index=-100)
211
+
212
+ present_kvs = tuple(new_kvs) if use_cache else None
213
+ from transformers.modeling_outputs import CausalLMOutputWithPast
214
+ return CausalLMOutputWithPast(
215
+ loss=loss,
216
+ logits=logits,
217
+ past_key_values=present_kvs
218
+ )
219
+
220
+ def prepare_inputs_for_generation(self, input_ids, past_key_values=None, **kwargs):
221
+ return {"input_ids": input_ids, "past_key_values": past_key_values}
222
+
223
+ # Register configuration and model for auto mapping
224
+ AutoConfig.register("gpt2_custom", GPT2CustomConfig)
225
+ AutoModelForCausalLM.register(GPT2CustomConfig, GPT2CustomLMHeadModel)
train.py ADDED
@@ -0,0 +1,1086 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ found_valid = False
39
+ for root, dirs, files in os.walk(path_to_check):
40
+ for f in files:
41
+ if f in ["predictions.json", "results.txt", "surprisal.json"]:
42
+ file_path = os.path.join(root, f)
43
+ if os.path.getsize(file_path) > 0:
44
+ found_valid = True
45
+ break
46
+ if found_valid:
47
+ break
48
+
49
+ if not found_valid:
50
+ raise RuntimeError(f"CRITICAL ERROR: {description} completed but no valid prediction/result files were written in: {path_to_check}")
51
+ print(f"[Eval] Success! Verified {description} results are stored correctly.")
52
+
53
+ def run_pipeline(model_name: str, epochs: int = 10, skip_eval: bool = False, skip_aoa: bool = True, skip_glue: bool = False):
54
+ # Configure persistent cache paths locally
55
+ os.environ["HF_HOME"] = os.path.abspath("./hf_cache")
56
+ os.environ["NLTK_DATA"] = os.path.abspath("./nltk_data")
57
+ os.makedirs("./hf_cache", exist_ok=True)
58
+ os.makedirs("./nltk_data", exist_ok=True)
59
+
60
+ hf_token = os.environ.get("HF_TOKEN")
61
+ if hf_token:
62
+ try:
63
+ from huggingface_hub import login
64
+ login(token=hf_token)
65
+ print("[HF] Programmatic login successful using HF_TOKEN.")
66
+ except Exception as e:
67
+ print(f"[HF] Warning: Programmatic login failed: {e}")
68
+
69
+ import torch
70
+ import torch.nn as nn
71
+ import torch.nn.functional as F
72
+ from datasets import load_dataset
73
+ from tokenizers import Tokenizer
74
+ from transformers import PreTrainedTokenizerFast
75
+
76
+ # Import model architecture
77
+ from modeling_gpt2 import GPT2CustomLMHeadModel
78
+ from configuration_gpt2 import GPT2CustomConfig
79
+
80
+ # Print GPU details
81
+ if torch.cuda.is_available():
82
+ gpu_name = torch.cuda.get_device_name(0)
83
+ print(f"\n[GPU] CUDA is available! Using GPU: {gpu_name}\n")
84
+ else:
85
+ print("\n[GPU] Warning: CUDA is NOT available! Running on CPU.\n")
86
+
87
+ # ─────────────────────────────────────────────────────────────
88
+ # GLOBAL HYPERPARAMETERS
89
+ # ─────────────────────────────────────────────────────────────
90
+ VOCAB_SIZE = 16384
91
+ BLOCK_SIZE = 512
92
+ BATCH_SIZE = 16
93
+ GRAD_ACCUM_STEPS = 1
94
+ EPOCHS = epochs
95
+ LEARNING_RATE = 3e-4
96
+ LR_MIN = LEARNING_RATE * 0.05
97
+ WARMUP_STEPS = 800
98
+ WEIGHT_DECAY = 0.1
99
+ GRAD_CLIP = 1.0
100
+
101
+ # Output directories locally
102
+ model_dir = os.path.abspath(f"./checkpoints/{model_name}")
103
+ os.makedirs(model_dir, exist_ok=True)
104
+ local_results_dir = os.path.abspath(f"./results/{model_name}")
105
+ os.makedirs(local_results_dir, exist_ok=True)
106
+
107
+ # ─────────────────────────────────────────────────────────────
108
+ # Helper: Save Hugging Face Compliant Checkpoint
109
+ # ─────────────────────────────────────────────────────────────
110
+ def save_hf_checkpoint(raw_model, checkpoint_dir_name, tokenizer):
111
+ save_dir = os.path.join(model_dir, checkpoint_dir_name)
112
+ os.makedirs(save_dir, exist_ok=True)
113
+ print(f"\n[Checkpoint] Saving Hugging Face format checkpoint to '{save_dir}'...")
114
+
115
+ # Save standard HF model format (bin & configs) for Hub upload
116
+ raw_model.save_pretrained(save_dir)
117
+ tokenizer.save_pretrained(save_dir)
118
+
119
+ # Copy modeling.py and configuration.py
120
+ shutil.copy("modeling_gpt2.py", os.path.join(save_dir, "modeling_gpt2.py"))
121
+ shutil.copy("configuration_gpt2.py", os.path.join(save_dir, "configuration_gpt2.py"))
122
+
123
+ # ─────────────────────────────────────────────────────────────
124
+ # Tokenizer Training
125
+ # ─────────────────────────────────────────────────────────────
126
+ def build_and_train_tokenizer(texts: list) -> Tokenizer:
127
+ from tokenizers.models import BPE
128
+ from tokenizers.trainers import BpeTrainer
129
+ from tokenizers.pre_tokenizers import Whitespace
130
+
131
+ vocab_path = os.path.join(model_dir, "bpe_vocab_16k.json")
132
+ if os.path.exists(vocab_path):
133
+ print(f"[Tokenizer] Loading trained BPE model layout from '{vocab_path}'...")
134
+ return Tokenizer.from_file(vocab_path)
135
+
136
+ print(f"[Tokenizer] Generating fresh HuggingFace BPE Tokenizer model with {VOCAB_SIZE} slots...")
137
+ tokenizer = Tokenizer(BPE(unk_token="[UNK]"))
138
+ tokenizer.pre_tokenizer = Whitespace()
139
+
140
+ trainer = BpeTrainer(
141
+ vocab_size=VOCAB_SIZE,
142
+ special_tokens=["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]
143
+ )
144
+ tokenizer.train_from_iterator(texts, trainer)
145
+ tokenizer.save(vocab_path)
146
+ print(f"[Tokenizer] Tokenizer training completed and saved to '{vocab_path}'.")
147
+ return tokenizer
148
+
149
+ # ─────────────────────────────────────────────────────────────
150
+ # Data Loader Setup
151
+ # ─────────────────────────────────────────────────────────────
152
+ class DataLoaderLite:
153
+ def __init__(self, B: int, T: int, texts: list, tokenizer: Tokenizer, name: str):
154
+ self.B = B
155
+ self.T = T
156
+
157
+ print(f"[DataLoader:{name}] Tokenising dataset sequences...")
158
+ all_ids = []
159
+ for t in texts:
160
+ if t.strip():
161
+ encoded = tokenizer.encode(t).ids
162
+ all_ids.extend(encoded)
163
+
164
+ self.tokens = torch.tensor(all_ids, dtype=torch.long)
165
+ self.chunk_size = B * T
166
+ self.n_chunks = (len(self.tokens) - 1) // self.chunk_size
167
+ self.indices = list(range(self.n_chunks))
168
+ self.pos = 0
169
+ self._shuffle()
170
+
171
+ print(f"[DataLoader:{name}] Total tokens: {len(self.tokens):,} | Epoch steps: {self.n_chunks:,}")
172
+
173
+ def _shuffle(self):
174
+ random.shuffle(self.indices)
175
+ self.pos = 0
176
+
177
+ def steps_per_epoch(self) -> int:
178
+ return self.n_chunks
179
+
180
+ def next_batch(self):
181
+ B, T = self.B, self.T
182
+ if self.pos >= len(self.indices):
183
+ self._shuffle()
184
+
185
+ chunk_idx = self.indices[self.pos]
186
+ self.pos += 1
187
+
188
+ start_pos = chunk_idx * self.chunk_size
189
+ temp = self.tokens[start_pos : start_pos + self.chunk_size + 1]
190
+
191
+ x = temp[:-1].view(B, T)
192
+ y = temp[1:].view(B, T)
193
+ return x, y
194
+
195
+ # Learning Rate Cosine Schedule
196
+ def get_lr(it: int, total_steps: int) -> float:
197
+ if it < WARMUP_STEPS:
198
+ return LEARNING_RATE * (it + 1) / WARMUP_STEPS
199
+ if it >= total_steps:
200
+ return LR_MIN
201
+ decay_ratio = (it - WARMUP_STEPS) / (total_steps - WARMUP_STEPS)
202
+ coeff = 0.5 * (1.0 + math.cos(math.pi * decay_ratio))
203
+ return LR_MIN + coeff * (LEARNING_RATE - LR_MIN)
204
+
205
+ # ─────────────────────────────────────────────────────────────
206
+ # Dataset Preparation
207
+ # ─────────────────────────────────────────────────────────────
208
+ print("\n[Data] Loading BabyLM-2026-Strict-Small ...")
209
+ ds = load_dataset("BabyLM-community/BabyLM-2026-Strict-Small")
210
+ all_text = list(ds['train']['text'])
211
+
212
+ raw_tokenizer = build_and_train_tokenizer(all_text)
213
+ tokenizer = PreTrainedTokenizerFast(
214
+ tokenizer_object=raw_tokenizer,
215
+ bos_token="[CLS]",
216
+ eos_token="[SEP]",
217
+ unk_token="[UNK]",
218
+ pad_token="[PAD]",
219
+ mask_token="[MASK]"
220
+ )
221
+ BOS_ID = 2
222
+ EOS_ID = 3
223
+
224
+ split = int(len(all_text) * 0.95)
225
+ train_texts = all_text[:split]
226
+ val_texts = all_text[split:]
227
+
228
+ train_loader = DataLoaderLite(BATCH_SIZE, BLOCK_SIZE, train_texts, raw_tokenizer, "train")
229
+ val_loader = DataLoaderLite(BATCH_SIZE, BLOCK_SIZE, val_texts, raw_tokenizer, "val")
230
+
231
+ chunks_per_epoch = train_loader.steps_per_epoch()
232
+ steps_per_epoch = chunks_per_epoch // GRAD_ACCUM_STEPS
233
+ total_steps = steps_per_epoch * EPOCHS
234
+
235
+ cfg = GPT2CustomConfig(
236
+ vocab_size=VOCAB_SIZE,
237
+ n_positions=BLOCK_SIZE,
238
+ n_embd=384,
239
+ n_layer=12,
240
+ n_head=6,
241
+ n_inner=1280,
242
+ bos_token_id=BOS_ID,
243
+ eos_token_id=EOS_ID,
244
+ )
245
+ device = "cuda" if torch.cuda.is_available() else "cpu"
246
+ torch.manual_seed(42)
247
+ if torch.cuda.is_available():
248
+ torch.cuda.manual_seed(42)
249
+ random.seed(42)
250
+ if hasattr(torch, 'set_float32_matmul_precision'):
251
+ torch.set_float32_matmul_precision('high')
252
+
253
+ model = GPT2CustomLMHeadModel(cfg).to(device)
254
+
255
+ # ─────────────────────────────────────────────────────────────
256
+ # Training Resume Check
257
+ # ─────────────────────────────────────────────────────────────
258
+ words_trained = 0
259
+ next_milestone_idx = 0
260
+ global_step = 0
261
+
262
+ milestones = sorted(list(set([i * 1_000_000 for i in range(1, 11)] + [i * 10_000_000 for i in range(1, 11)])))
263
+
264
+ resume_checkpoint_dir = None
265
+ for idx in range(len(milestones) - 1, -1, -1):
266
+ m = milestones[idx]
267
+ ckpt_name = f"chck_{m // 1_000_000}M"
268
+ ckpt_path = os.path.join(model_dir, ckpt_name)
269
+ if os.path.exists(os.path.join(ckpt_path, "pytorch_model.bin")) or os.path.exists(os.path.join(ckpt_path, "model.safetensors")):
270
+ config_json_path = os.path.join(ckpt_path, "config.json")
271
+ if os.path.exists(config_json_path):
272
+ try:
273
+ with open(config_json_path, "r") as f:
274
+ saved_config = json.load(f)
275
+ if saved_config.get("n_embd") == 384:
276
+ resume_checkpoint_dir = ckpt_path
277
+ next_milestone_idx = idx + 1
278
+ words_trained = m
279
+ global_step = words_trained // (BATCH_SIZE * BLOCK_SIZE)
280
+ print(f"[Training] Found existing milestone checkpoint '{ckpt_name}'. Resuming from step {global_step:,} ({words_trained:,} tokens trained)...")
281
+ break
282
+ except Exception:
283
+ pass
284
+
285
+ # Load weights if resuming
286
+ if resume_checkpoint_dir is not None:
287
+ print(f"[Model] Loading weights from checkpoint '{resume_checkpoint_dir}'...")
288
+ if os.path.exists(os.path.join(resume_checkpoint_dir, "pytorch_model.bin")):
289
+ state_dict = torch.load(os.path.join(resume_checkpoint_dir, "pytorch_model.bin"), map_location=device)
290
+ else:
291
+ from safetensors.torch import load_file
292
+ state_dict = load_file(os.path.join(resume_checkpoint_dir, "model.safetensors"), device=device)
293
+ model.load_state_dict(state_dict)
294
+
295
+ # Check if final main model exists
296
+ main_ckpt_path = os.path.join(model_dir, "main")
297
+ if os.path.exists(os.path.join(main_ckpt_path, "pytorch_model.bin")) or os.path.exists(os.path.join(main_ckpt_path, "model.safetensors")):
298
+ print("\n[Pipeline] Final checkpoint 'main' already exists. Skipping training phase and transitioning directly to evaluations!")
299
+ else:
300
+ try:
301
+ model = torch.compile(model)
302
+ print("[Model] torch.compile() successfully verified graph optimizations")
303
+ except Exception as e:
304
+ print(f"[Model] torch.compile() skipped ({e})")
305
+
306
+ # Optimizer
307
+ param_dict = {n: p for n, p in model.named_parameters() if p.requires_grad}
308
+ decay_params = [p for p in param_dict.values() if p.dim() >= 2]
309
+ nodecay_params = [p for p in param_dict.values() if p.dim() < 2]
310
+ groups = [
311
+ {'params': decay_params, 'weight_decay': WEIGHT_DECAY},
312
+ {'params': nodecay_params, 'weight_decay': 0.0},
313
+ ]
314
+ fused_ok = 'fused' in inspect.signature(torch.optim.AdamW).parameters
315
+ use_fused = fused_ok and ('cuda' in device)
316
+ optimizer = torch.optim.AdamW(groups, lr=LEARNING_RATE, betas=(0.9, 0.95), eps=1e-8, fused=use_fused)
317
+
318
+ # ─────────────────────────────────────────────────────────────
319
+ # Training Loop
320
+ # ─────────────────────────────────────────────────────────────
321
+ def evaluate_validation_loss(model_eval, val_loader_eval, dev, autocast):
322
+ was_training = model_eval.training
323
+ model_eval.eval()
324
+
325
+ val_loss_accum = 0.0
326
+ val_steps = val_loader_eval.steps_per_epoch()
327
+ max_val_steps = min(val_steps, 100)
328
+
329
+ with torch.no_grad():
330
+ for _ in range(max_val_steps):
331
+ x, y = val_loader_eval.next_batch()
332
+ x, y = x.to(dev), y.to(dev)
333
+ with autocast:
334
+ out = model_eval(x, labels=y)
335
+ loss = out.loss
336
+ val_loss_accum += loss.item()
337
+
338
+ avg_val_loss = val_loss_accum / max_val_steps
339
+ val_perplexity = math.exp(avg_val_loss)
340
+
341
+ if was_training:
342
+ model_eval.train()
343
+ return avg_val_loss, val_perplexity
344
+
345
+ validation_logs = []
346
+ training_logs = []
347
+
348
+ model.train()
349
+ autocast_ctx = torch.autocast(device_type="cuda" if "cuda" in device else "cpu", dtype=torch.bfloat16, enabled=True)
350
+
351
+ start_epoch = global_step // steps_per_epoch
352
+ start_chunk = (global_step % steps_per_epoch) * GRAD_ACCUM_STEPS
353
+
354
+ print(f"\n[Training] Starting GPT-2 Dense Baseline training for {EPOCHS} epochs...")
355
+ for epoch in range(start_epoch, EPOCHS):
356
+ train_loader._shuffle()
357
+ if epoch == start_epoch and start_chunk > 0:
358
+ print(f"[Training] Fast-forwarding dataloader to chunk index {start_chunk}...")
359
+ train_loader.pos = start_chunk
360
+
361
+ optimizer.zero_grad(set_to_none=True)
362
+ loss_accum = 0.0
363
+
364
+ start_chunk_idx = start_chunk if epoch == start_epoch else 0
365
+ for chunk_step in range(start_chunk_idx, chunks_per_epoch):
366
+ t0 = time.perf_counter()
367
+
368
+ lr = get_lr(global_step, total_steps)
369
+ for pg in optimizer.param_groups:
370
+ pg['lr'] = lr
371
+
372
+ x, y = train_loader.next_batch()
373
+ input_ids, targets = x.to(device), y.to(device)
374
+ words_trained += input_ids.numel()
375
+
376
+ with autocast_ctx:
377
+ out = model(input_ids, labels=targets)
378
+ loss = out.loss
379
+ scaled_loss = loss / GRAD_ACCUM_STEPS
380
+ loss_accum += scaled_loss.item()
381
+ scaled_loss.backward()
382
+
383
+ if (chunk_step + 1) % GRAD_ACCUM_STEPS == 0:
384
+ norm = torch.nn.utils.clip_grad_norm_(model.parameters(), GRAD_CLIP)
385
+ optimizer.step()
386
+ optimizer.zero_grad(set_to_none=True)
387
+ if "cuda" in device:
388
+ torch.cuda.synchronize()
389
+
390
+ dt = (time.perf_counter() - t0) * 1000
391
+
392
+ current_step = (chunk_step + 1) // GRAD_ACCUM_STEPS
393
+ print(
394
+ f"[E{epoch+1:02d} {current_step:>5d}/{steps_per_epoch} G{global_step:>7d}] "
395
+ f"train={loss_accum:.4f} norm={norm:.3f} lr={lr:.2e} dt={dt:6.1f}ms words={words_trained:,}"
396
+ )
397
+
398
+ training_logs.append({
399
+ "step": global_step,
400
+ "train_loss": loss_accum,
401
+ "lr": lr,
402
+ "words": words_trained
403
+ })
404
+ loss_accum = 0.0
405
+ global_step += 1
406
+
407
+ # Validation Loss every 100 steps
408
+ if global_step > 0 and global_step % 100 == 0:
409
+ v_loss, v_ppl = evaluate_validation_loss(model, val_loader, device, autocast_ctx)
410
+ validation_logs.append({
411
+ "step": global_step,
412
+ "val_loss": v_loss,
413
+ "val_perplexity": v_ppl
414
+ })
415
+ with open(os.path.join(model_dir, "validation_metrics.json"), "w") as f:
416
+ json.dump(validation_logs, f, indent=2)
417
+ with open("validation_metrics.json", "w") as f:
418
+ json.dump(validation_logs, f, indent=2)
419
+ print(f"\n[Validation Step {global_step}] Loss: {v_loss:.4f} | Perplexity: {v_ppl:.2f}\n")
420
+
421
+ # Checkpoint Milestones
422
+ if next_milestone_idx < len(milestones) and words_trained >= milestones[next_milestone_idx]:
423
+ milestone_val = milestones[next_milestone_idx]
424
+ milestone_name = f"chck_{milestone_val // 1_000_000}M" if milestone_val < 10_000_000 else f"chck_{(milestone_val // 10_000_000) * 10}M"
425
+ raw_model = model._orig_mod if hasattr(model, '_orig_mod') else model
426
+ save_hf_checkpoint(raw_model, milestone_name, tokenizer)
427
+ next_milestone_idx += 1
428
+
429
+ # Save final model as 'main'
430
+ raw_model = model._orig_mod if hasattr(model, '_orig_mod') else model
431
+ save_hf_checkpoint(raw_model, "main", tokenizer)
432
+
433
+ # Save training logs
434
+ with open(os.path.join(model_dir, "training_metrics.json"), "w") as f:
435
+ json.dump(training_logs, f, indent=2)
436
+ with open("training_metrics.json", "w") as f:
437
+ json.dump(training_logs, f, indent=2)
438
+
439
+ # Generate plots
440
+ try:
441
+ import matplotlib
442
+ matplotlib.use('Agg')
443
+ import matplotlib.pyplot as plt
444
+
445
+ if len(validation_logs) > 0:
446
+ steps = [log["step"] for log in validation_logs]
447
+ perplexities = [log["val_perplexity"] for log in validation_logs]
448
+
449
+ plt.figure(figsize=(8, 5))
450
+ plt.plot(steps, perplexities, marker='o', color='#3b82f6', linewidth=2)
451
+ plt.xlabel('Global Step')
452
+ plt.ylabel('Validation Perplexity')
453
+ plt.title('Validation Perplexity Learning Curve')
454
+ plt.grid(True, linestyle='--', alpha=0.6)
455
+ plt.savefig(os.path.join(model_dir, 'validation_perplexity.png'), dpi=150)
456
+ plt.savefig('validation_perplexity.png', dpi=150)
457
+ plt.close()
458
+ print("[Plots] Validation perplexity curve saved successfully.")
459
+ except Exception as e:
460
+ print(f"[Plots] Warning: Could not generate plots: {e}")
461
+
462
+ print("\n[Training] Training phase complete!")
463
+
464
+ if skip_eval:
465
+ print("[Pipeline] Skipping evaluations phase as requested.")
466
+ return
467
+
468
+ # ─────────────────────────────────────────────────────────────
469
+ # 2. RUN EVALUATION PIPELINE
470
+ # ─────────────────────────────────────────────────────────────
471
+ local_results_dir = os.path.abspath(f"./results/{model_name}")
472
+ os.makedirs(local_results_dir, exist_ok=True)
473
+ local_main_res = os.path.join(local_results_dir, "main")
474
+
475
+ # Ensure clone_dir exists and has the global_piqa files
476
+ clone_parent = os.path.abspath("./babylm_eval_repo")
477
+
478
+ # Self-healing check for global_piqa presence
479
+ has_global_piqa = False
480
+ for potential_strict in [os.path.join(clone_parent, "babylm-eval", "strict"), os.path.join(clone_parent, "strict")]:
481
+ if os.path.exists(os.path.join(potential_strict, "evaluation_pipeline", "global_piqa")):
482
+ has_global_piqa = True
483
+ break
484
+
485
+ if not has_global_piqa:
486
+ print("[Eval] Cloned repository does not contain global_piqa tasks.")
487
+ print("[Eval] Deleting and cloning official main branch...")
488
+ if os.path.exists(clone_parent):
489
+ shutil.rmtree(clone_parent)
490
+ subprocess.run([
491
+ "git", "clone", "-b", "main",
492
+ "https://github.com/babylm-org/babylm-eval.git",
493
+ clone_parent
494
+ ], check=True)
495
+
496
+ # Determine strict_dir path dynamically
497
+ if os.path.exists(os.path.join(clone_parent, "strict")):
498
+ strict_dir = os.path.join(clone_parent, "strict")
499
+ else:
500
+ strict_dir = os.path.join(clone_parent, "babylm-eval", "strict")
501
+
502
+ print(f"[Eval] Using strict directory: {strict_dir}")
503
+ os.environ["PYTHONPATH"] = strict_dir
504
+
505
+ def patch_evaluation_run_script(strict_dir):
506
+ import pathlib
507
+ run_file = os.path.join(strict_dir, "evaluation_pipeline", "sentence_zero_shot", "run.py")
508
+ if not os.path.exists(run_file):
509
+ print(f"[GlobalPIQA] Warning: {run_file} not found. Cannot patch.")
510
+ return
511
+
512
+ print(f"[GlobalPIQA] Patching local checkpoint loader in {run_file}...")
513
+ with open(run_file, "r") as f:
514
+ content = f.read()
515
+
516
+ if "Local checkpoint directory patch" in content:
517
+ print("[GlobalPIQA] Script already patched.")
518
+ return
519
+
520
+ target_str = """def main():
521
+ args = _parse_arguments()
522
+ if args.images_path is not None:
523
+ assert args.batch_size == 1, "Multimodal only works in batch size 1!"
524
+ dataset = args.data_path.stem
525
+ args.model_name = pathlib.Path(args.model_path_or_name).stem
526
+ if args.revision_name is None:
527
+ revision_name = "main"
528
+ else:
529
+ revision_name = args.revision_name"""
530
+
531
+ patch_str = """def main():
532
+ args = _parse_arguments()
533
+ if args.images_path is not None:
534
+ assert args.batch_size == 1, "Multimodal only works in batch size 1!"
535
+ dataset = args.data_path.stem
536
+
537
+ # Local checkpoint directory patch
538
+ import os
539
+ model_path = args.model_path_or_name
540
+ args.model_name = pathlib.Path(model_path).stem
541
+ revision_name = args.revision_name if args.revision_name else "main"
542
+
543
+ if os.path.isdir(model_path):
544
+ target_revision = args.revision_name if args.revision_name else "main"
545
+ if os.path.exists(os.path.join(model_path, target_revision)):
546
+ args.model_path_or_name = os.path.join(model_path, target_revision)
547
+ args.revision_name = None"""
548
+
549
+ if target_str in content:
550
+ new_content = content.replace(target_str, patch_str)
551
+ with open(run_file, "w") as f:
552
+ f.write(new_content)
553
+ print("[GlobalPIQA] Successfully patched run.py")
554
+ else:
555
+ print("[GlobalPIQA] Warning: Could not find target pattern in run.py. Manual patch may be needed.")
556
+
557
+ # Patch sentence zero shot loader inside cloned repo
558
+ patch_evaluation_run_script(strict_dir)
559
+
560
+ print("[Eval] Stripping Windows-specific packages from requirements.txt...")
561
+ req_file_path = os.path.join(strict_dir, "requirements.txt")
562
+ if os.path.exists(req_file_path):
563
+ with open(req_file_path, "r") as f:
564
+ lines = f.readlines()
565
+ with open(req_file_path, "w") as f:
566
+ for line in lines:
567
+ if "pywin" not in line.lower() and "wintypes" not in line.lower():
568
+ f.write(line)
569
+
570
+ print("[Eval] Verifying and installing evaluation dependencies programmatically...")
571
+ required_packages = {
572
+ "nltk": "nltk",
573
+ "pandas": "pandas",
574
+ "statsmodels": "statsmodels",
575
+ "sklearn": "scikit-learn",
576
+ "scipy": "scipy"
577
+ }
578
+ for pkg_import, pkg_install in required_packages.items():
579
+ try:
580
+ __import__(pkg_import)
581
+ except ImportError:
582
+ print(f"[Eval] Package '{pkg_install}' not found. Installing it programmatically...")
583
+ import sys
584
+ subprocess.run([sys.executable, "-m", "pip", "install", pkg_install], check=True)
585
+
586
+ print("[Eval] Downloading NLTK tokenizer resources...")
587
+ import nltk
588
+ nltk.download('punkt', download_dir=os.environ["NLTK_DATA"])
589
+ nltk.download('punkt_tab', download_dir=os.environ["NLTK_DATA"])
590
+
591
+ # Ensure standard zero-shot datasets are downloaded
592
+ blimp_fast_dir = os.path.join(strict_dir, "evaluation_data", "fast_eval", "blimp_fast")
593
+ if not os.path.exists(blimp_fast_dir) or not os.listdir(blimp_fast_dir):
594
+ print("[Eval] Standard zero-shot datasets not found. Downloading...")
595
+ subprocess.run(["python", "-m", "scripts.download_evals"], cwd=strict_dir, check=True)
596
+
597
+ ewok_zip = os.path.join(strict_dir, "evaluation_data/fast_eval/ewok_fast.zip")
598
+ if os.path.exists(ewok_zip):
599
+ print("[Eval] Unzipping EWoK fast data...")
600
+ bad_nested_dir = os.path.join(strict_dir, "evaluation_data/fast_eval/evaluation_data")
601
+ if os.path.exists(bad_nested_dir):
602
+ shutil.rmtree(bad_nested_dir)
603
+ subprocess.run(["unzip", "-o", "-P", "BabyLM2025", "evaluation_data/fast_eval/ewok_fast.zip", "-d", "."], cwd=strict_dir, check=True)
604
+
605
+ print("[Eval] Downloading and filtering full EWoK dataset...")
606
+ subprocess.run(["python", "-m", "evaluation_pipeline.ewok.dl_and_filter"], cwd=strict_dir, check=True)
607
+
608
+ # Download GlobalPIQA dataset
609
+ global_piqa_parallel_dir = os.path.join(strict_dir, "evaluation_data", "fast_eval", "global_piqa_parallel")
610
+ if not os.path.exists(global_piqa_parallel_dir) or not os.listdir(global_piqa_parallel_dir):
611
+ print("[Eval] GlobalPIQA dataset not found. Downloading...")
612
+ subprocess.run(["python", "evaluation_pipeline/global_piqa/dl.py"], cwd=strict_dir, check=True)
613
+
614
+ # Ensure all scripts are executable
615
+ print("[Eval] Making evaluation shell scripts executable...")
616
+ subprocess.run("chmod +x scripts/*.sh", shell=True, cwd=strict_dir, check=True)
617
+
618
+ def run_task_with_cache(checkpoint, task, output_subpath, cmd):
619
+ local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", task, output_subpath)
620
+ if task == "reading":
621
+ local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", "reading")
622
+ elif task == "comps":
623
+ local_cache_path = os.path.join("./results", model_name, checkpoint, "zero_shot", "causal", "comps", "comps")
624
+
625
+ target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", task, output_subpath)
626
+ if task == "reading":
627
+ target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", "reading")
628
+ elif task == "comps":
629
+ target_results_dir = os.path.join(strict_dir, "results", model_name, checkpoint, "zero_shot", "causal", "comps", "comps")
630
+
631
+ cache_file = os.path.join(local_cache_path, "predictions.json")
632
+
633
+ # Self-healing for entity_tracking cache
634
+ if task == "entity_tracking" and os.path.exists(cache_file):
635
+ try:
636
+ import json
637
+ with open(cache_file, "r") as f:
638
+ preds = json.load(f)
639
+ is_valid_cache = True
640
+ for k, v in preds.items():
641
+ if len(v.get("predictions", [])) in [605, 606, 607, 615, 529, 156, 187, 159]:
642
+ is_valid_cache = False
643
+ break
644
+ if not is_valid_cache:
645
+ print(f"[Eval] Cached entity_tracking for '{checkpoint}' has incorrect old sizes. Invalidate and re-run fresh...")
646
+ shutil.rmtree(local_cache_path, ignore_errors=True)
647
+ except Exception:
648
+ pass
649
+
650
+ if os.path.exists(cache_file):
651
+ print(f"[Eval] Task '{task}' ({output_subpath}) for checkpoint '{checkpoint}' is cached. Restoring...")
652
+ if os.path.exists(target_results_dir):
653
+ shutil.rmtree(target_results_dir)
654
+ os.makedirs(target_results_dir, exist_ok=True)
655
+ for item in os.listdir(local_cache_path):
656
+ s = os.path.join(local_cache_path, item)
657
+ d = os.path.join(target_results_dir, item)
658
+ if os.path.isdir(s):
659
+ shutil.copytree(s, d)
660
+ else:
661
+ shutil.copy2(s, d)
662
+ return
663
+
664
+ print(f"[Eval] Running task '{task}' ({output_subpath}) for checkpoint '{checkpoint}'...")
665
+ subprocess.run(cmd, cwd=strict_dir, check=True)
666
+
667
+ # Relocate results
668
+ actual_model_basename = os.path.basename(model_dir.rstrip("/"))
669
+ possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", task, output_subpath)
670
+ if task == "reading":
671
+ possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", "reading")
672
+ elif task == "comps":
673
+ possible_actual_path = os.path.join(strict_dir, "results", actual_model_basename, checkpoint, "zero_shot", "causal", "comps", "comps")
674
+
675
+ possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", task, output_subpath)
676
+ if task == "reading":
677
+ possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", "reading")
678
+ elif task == "comps":
679
+ possible_main_path = os.path.join(strict_dir, "results", "main", checkpoint, "zero_shot", "causal", "comps", "comps")
680
+
681
+ possible_local_reading_path = os.path.join(strict_dir, "results", checkpoint, "main", "zero_shot", "causal", "reading")
682
+
683
+ for p_path in [possible_actual_path, possible_main_path, possible_local_reading_path]:
684
+ if os.path.exists(p_path) and p_path != target_results_dir:
685
+ print(f"[Eval] Relocating results from {p_path} to {target_results_dir}...")
686
+ if os.path.exists(target_results_dir):
687
+ shutil.rmtree(target_results_dir)
688
+ os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
689
+ shutil.move(p_path, target_results_dir)
690
+ break
691
+
692
+ verify_eval_run(target_results_dir, f"{checkpoint} {task} ({output_subpath})")
693
+
694
+ # Save to local cache
695
+ if os.path.exists(local_cache_path):
696
+ shutil.rmtree(local_cache_path)
697
+ os.makedirs(local_cache_path, exist_ok=True)
698
+ for item in os.listdir(target_results_dir):
699
+ s = os.path.join(target_results_dir, item)
700
+ d = os.path.join(local_cache_path, item)
701
+ if os.path.isdir(s):
702
+ shutil.copytree(s, d)
703
+ else:
704
+ shutil.copy2(s, d)
705
+
706
+ def run_finetune_task_with_cache(task, cmd):
707
+ local_cache_path = os.path.join("./results", model_name, "main", "finetune", task)
708
+ target_results_dir = os.path.join(strict_dir, "results", model_name, "main", "finetune", task)
709
+
710
+ if os.path.exists(os.path.join(local_cache_path, "predictions.json")):
711
+ print(f"[Eval] GLUE task '{task}' is cached. Restoring...")
712
+ if os.path.exists(target_results_dir):
713
+ shutil.rmtree(target_results_dir)
714
+ os.makedirs(target_results_dir, exist_ok=True)
715
+ for item in os.listdir(local_cache_path):
716
+ s = os.path.join(local_cache_path, item)
717
+ d = os.path.join(target_results_dir, item)
718
+ if os.path.isdir(s):
719
+ shutil.copytree(s, d)
720
+ else:
721
+ shutil.copy2(s, d)
722
+ return
723
+
724
+ print(f"[Eval] Running GLUE task '{task}'...")
725
+ subprocess.run(cmd, cwd=strict_dir, check=True)
726
+
727
+ possible_main_path = os.path.join(strict_dir, "results", "main", "main", "finetune", task)
728
+ if os.path.exists(possible_main_path) and possible_main_path != target_results_dir:
729
+ print(f"[Eval] Relocating results from {possible_main_path} to {target_results_dir}...")
730
+ if os.path.exists(target_results_dir):
731
+ shutil.rmtree(target_results_dir)
732
+ os.makedirs(os.path.dirname(target_results_dir), exist_ok=True)
733
+ shutil.move(possible_main_path, target_results_dir)
734
+
735
+ verify_eval_run(target_results_dir, f"GLUE fine-tuning {task}")
736
+
737
+ if os.path.exists(local_cache_path):
738
+ shutil.rmtree(local_cache_path)
739
+ os.makedirs(local_cache_path, exist_ok=True)
740
+ for item in os.listdir(target_results_dir):
741
+ s = os.path.join(target_results_dir, item)
742
+ d = os.path.join(local_cache_path, item)
743
+ if os.path.isdir(s):
744
+ shutil.copytree(s, d)
745
+ else:
746
+ shutil.copy2(s, d)
747
+
748
+ # ─────────────────────────────────────────────────────────────
749
+ # A. ZERO-SHOT EVALUATIONS
750
+ # ─────────────────────────────────────────────────────────────
751
+ eval_checkpoints = ["main"] + [f"chck_{m}M" 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]]
752
+ existing_eval_checkpoints = []
753
+ for checkpoint in eval_checkpoints:
754
+ checkpoint_path = os.path.join(model_dir, checkpoint)
755
+ if os.path.exists(os.path.join(checkpoint_path, "pytorch_model.bin")) or os.path.exists(os.path.join(checkpoint_path, "model.safetensors")):
756
+ existing_eval_checkpoints.append(checkpoint)
757
+
758
+ print(f"\n[Eval] Found {len(existing_eval_checkpoints)} checkpoints ready for zero-shot evaluation: {existing_eval_checkpoints}")
759
+
760
+ # Self-healing: Patch model_type and auto_map in existing checkpoints' config.json
761
+ # and copy code files if missing.
762
+ for checkpoint in existing_eval_checkpoints:
763
+ checkpoint_path = os.path.join(model_dir, checkpoint)
764
+
765
+ # A. Overwrite modeling_gpt2.py and configuration_gpt2.py unconditionally
766
+ for code_file in ["modeling_gpt2.py", "configuration_gpt2.py"]:
767
+ dest_code_file = os.path.join(checkpoint_path, code_file)
768
+ if os.path.exists(code_file):
769
+ try:
770
+ shutil.copy(code_file, dest_code_file)
771
+ print(f"[Self-Healing] Copied and updated {code_file} in checkpoint '{checkpoint}' directory.")
772
+ except Exception as e:
773
+ print(f"[Self-Healing] Warning: Failed to copy {code_file} to {checkpoint}: {e}")
774
+
775
+ # B. Patch config.json
776
+ config_path = os.path.join(checkpoint_path, "config.json")
777
+ if os.path.exists(config_path):
778
+ try:
779
+ with open(config_path, "r") as f:
780
+ config_dict = json.load(f)
781
+
782
+ # Check if it needs patching
783
+ if config_dict.get("model_type") != "gpt2_custom" or "auto_map" not in config_dict:
784
+ config_dict["model_type"] = "gpt2_custom"
785
+ config_dict["auto_map"] = {
786
+ "AutoConfig": "configuration_gpt2.GPT2CustomConfig",
787
+ "AutoModelForCausalLM": "modeling_gpt2.GPT2CustomLMHeadModel"
788
+ }
789
+ with open(config_path, "w") as f:
790
+ json.dump(config_dict, f, indent=2)
791
+ print(f"[Self-Healing] Successfully patched 'auto_map' and 'model_type' in checkpoint '{checkpoint}' config.json.")
792
+ except Exception as e:
793
+ print(f"[Self-Healing] Warning: Failed to patch {config_path}: {e}")
794
+
795
+ for checkpoint in existing_eval_checkpoints:
796
+ eval_model_path = os.path.join(model_dir, checkpoint)
797
+ print(f"\n{'='*65}\n RUNNING EVALUATIONS FOR CHECKPOINT: {checkpoint}\n{'='*65}")
798
+
799
+ # blimp filtered
800
+ run_task_with_cache(
801
+ checkpoint, "blimp", "blimp_filtered",
802
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/full_eval/blimp_filtered", "--save_predictions", "--revision_name", checkpoint]
803
+ )
804
+ # supplement filtered
805
+ run_task_with_cache(
806
+ checkpoint, "blimp", "supplement_filtered",
807
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "blimp", "--data_path", "evaluation_data/full_eval/supplement_filtered", "--save_predictions", "--revision_name", checkpoint]
808
+ )
809
+ # ewok filtered
810
+ run_task_with_cache(
811
+ checkpoint, "ewok", "ewok_filtered",
812
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "ewok", "--data_path", "evaluation_data/full_eval/ewok_filtered", "--save_predictions", "--revision_name", checkpoint]
813
+ )
814
+ # entity tracking
815
+ run_task_with_cache(
816
+ checkpoint, "entity_tracking", "entity_tracking",
817
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "entity_tracking", "--data_path", "evaluation_data/full_eval/entity_tracking", "--save_predictions", "--revision_name", checkpoint]
818
+ )
819
+ # comps
820
+ run_task_with_cache(
821
+ checkpoint, "comps", "comps",
822
+ ["python", "-m", "evaluation_pipeline.sentence_zero_shot.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--task", "comps", "--data_path", "evaluation_data/full_eval/comps", "--save_predictions", "--revision_name", checkpoint]
823
+ )
824
+ # reading
825
+ run_task_with_cache(
826
+ checkpoint, "reading", "reading",
827
+ ["python", "-m", "evaluation_pipeline.reading.run", "--model_path_or_name", eval_model_path, "--backend", "causal", "--data_path", "evaluation_data/full_eval/reading/reading_data.csv"]
828
+ )
829
+ # global piqa parallel
830
+ run_task_with_cache(
831
+ checkpoint, "global_piqa_parallel", "global_piqa_parallel",
832
+ ["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/full_eval/global_piqa_parallel", "--save_predictions", "--revision_name", checkpoint]
833
+ )
834
+ # global piqa nonparallel
835
+ run_task_with_cache(
836
+ checkpoint, "global_piqa_nonparallel", "global_piqa_nonparallel",
837
+ ["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/full_eval/global_piqa_nonparallel", "--save_predictions", "--revision_name", checkpoint]
838
+ )
839
+
840
+ # ─────────────────────────────────────────────────────────────
841
+ # B. GLUE FINE-TUNING EVALUATIONS
842
+ # ─────────────────────────────────────────────────────────────
843
+ if not skip_glue:
844
+ main_ckpt_path = os.path.join(model_dir, "main")
845
+ if os.path.exists(main_ckpt_path):
846
+ print(f"\n{'='*65}\n RUNNING GLUE FINE-TUNING FOR CHECKPOINT: main\n{'='*65}")
847
+
848
+ glue_tasks = {
849
+ "boolq": ["boolq", "16", "10"],
850
+ "multirc": ["multirc", "16", "10"],
851
+ "rte": ["rte", "32", "10"],
852
+ "wsc": ["wsc", "32", "30"],
853
+ "mrpc": ["mrpc", "32", "10"],
854
+ "qqp": ["qqp", "32", "10"],
855
+ "mnli": ["mnli", "32", "10"]
856
+ }
857
+ for task_name, (task, bsz, max_epochs) in glue_tasks.items():
858
+ num_labels = "3" if task == "mnli" else "2"
859
+ metric_for_valid = "accuracy"
860
+ if task in ["mrpc", "qqp"]:
861
+ metric_for_valid = "f1"
862
+ metrics = ["accuracy"]
863
+ if task != "mnli":
864
+ metrics = ["accuracy", "f1", "mcc"]
865
+
866
+ cmd = [
867
+ "python", "-m", "evaluation_pipeline.finetune.run",
868
+ "--model_name_or_path", main_ckpt_path,
869
+ "--train_data", f"evaluation_data/full_eval/glue_filtered/{task}.train.jsonl",
870
+ "--valid_data", f"evaluation_data/full_eval/glue_filtered/{task}.valid.jsonl",
871
+ "--predict_data", f"evaluation_data/full_eval/glue_filtered/{task}.valid.jsonl",
872
+ "--task", task,
873
+ "--num_labels", num_labels,
874
+ "--batch_size", bsz,
875
+ "--learning_rate", "3e-5",
876
+ "--num_epochs", max_epochs,
877
+ "--sequence_length", "512",
878
+ "--results_dir", "results",
879
+ "--save",
880
+ "--save_dir", "models",
881
+ "--metric_for_valid", metric_for_valid,
882
+ "--seed", "42",
883
+ "--verbose",
884
+ "--padding_side", "left",
885
+ "--take_final"
886
+ ]
887
+ cmd.append("--metrics")
888
+ cmd.extend(metrics)
889
+
890
+ run_finetune_task_with_cache(task_name, cmd)
891
+
892
+ # ─────────────────────────────────────────────────────────────
893
+ # C. INTERMEDIATE CHECKPOINTS FAST EVALUATION
894
+ # ─────────────────────────────────────────────────────────────
895
+ print(f"[Eval] Running zero-shot fast evaluations on intermediate checkpoints...")
896
+ checkpoints = [f"chck_{i}M" for i in range(1, 10)] + [f"chck_{i}M" for i in range(10, 110, 10)]
897
+
898
+ eval_model_path = model_dir
899
+
900
+ for checkpoint in checkpoints:
901
+ ckpt_full_path = os.path.join(model_dir, checkpoint)
902
+ if not os.path.exists(ckpt_full_path):
903
+ continue
904
+
905
+ # blimp fast
906
+ run_task_with_cache(
907
+ checkpoint, "blimp", "blimp_fast",
908
+ ["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]
909
+ )
910
+ # supplement fast
911
+ run_task_with_cache(
912
+ checkpoint, "blimp", "supplement_fast",
913
+ ["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]
914
+ )
915
+ # ewok fast
916
+ run_task_with_cache(
917
+ checkpoint, "ewok", "ewok_fast",
918
+ ["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]
919
+ )
920
+ # entity tracking fast
921
+ run_task_with_cache(
922
+ checkpoint, "entity_tracking", "entity_tracking_fast",
923
+ ["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]
924
+ )
925
+ # reading fast
926
+ run_task_with_cache(
927
+ checkpoint, "reading", "reading",
928
+ ["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"]
929
+ )
930
+ # global piqa parallel fast
931
+ run_task_with_cache(
932
+ checkpoint, "global_piqa_parallel", "global_piqa_parallel",
933
+ ["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]
934
+ )
935
+ # global piqa nonparallel fast
936
+ run_task_with_cache(
937
+ checkpoint, "global_piqa_nonparallel", "global_piqa_nonparallel",
938
+ ["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]
939
+ )
940
+
941
+ # ─────────────────────────────────────────────────────────────
942
+ # D. COLLATE RESULTS AND CLEANUP
943
+ # ─────────────────────────────────────────────────────────────
944
+ print("[Eval] Collating predictions into submission file...")
945
+ collate_results_dir = os.path.join(strict_dir, "results", model_name)
946
+ if os.path.exists(collate_results_dir):
947
+ shutil.rmtree(collate_results_dir)
948
+ os.makedirs(os.path.dirname(collate_results_dir), exist_ok=True)
949
+
950
+ shutil.copytree(local_results_dir, collate_results_dir)
951
+
952
+ subprocess.run([
953
+ "python", "-m", "evaluation_pipeline.collate_preds",
954
+ "--model_path_or_name", model_name,
955
+ "--backend", "causal",
956
+ "--track", "strict-small",
957
+ "--fast"
958
+ ], cwd=strict_dir, check=True)
959
+
960
+ results_src = os.path.join(strict_dir, "results")
961
+ results_dest = os.path.abspath("./results")
962
+ if os.path.exists(results_dest):
963
+ shutil.rmtree(results_dest)
964
+ shutil.copytree(results_src, results_dest)
965
+
966
+ collated_json = os.path.join(strict_dir, "all_full_preds_and_fast_scores_causal.json")
967
+ if os.path.exists(collated_json):
968
+ shutil.copy(collated_json, "./all_full_preds_and_fast_scores_causal.json")
969
+ print("\n[Eval] Success! Collation completed! Final file is at './all_full_preds_and_fast_scores_causal.json'")
970
+
971
+ print("\n[Eval] Pipeline evaluation run finished.")
972
+
973
+ def upload_pipeline(model_name, repo_name, token=None):
974
+ from huggingface_hub import HfApi, create_repo
975
+ if not token:
976
+ token = os.environ.get("HF_TOKEN")
977
+
978
+ api = HfApi(token=token)
979
+ try:
980
+ user_info = api.whoami()
981
+ username = user_info["name"]
982
+ print(f"[HF] Authenticated successfully as user: {username}")
983
+ except Exception as e:
984
+ print(f"[HF] Authentication failed. Error: {e}")
985
+ return
986
+
987
+ repo_id = f"{username}/{repo_name}"
988
+ print(f"[HF] Target Repository ID: {repo_id}")
989
+
990
+ try:
991
+ create_repo(repo_id=repo_id, repo_type="model", token=token, exist_ok=True)
992
+ print(f"[HF] Repository '{repo_id}' is ready.")
993
+ except Exception as e:
994
+ print(f"[HF] Failed to create repository: {e}")
995
+ return
996
+
997
+ checkpoint_dir = os.path.abspath(f"./checkpoints/{model_name}")
998
+
999
+ revisions = {}
1000
+ if os.path.exists(os.path.join(checkpoint_dir, "main")):
1001
+ revisions = {"main": os.path.join(checkpoint_dir, "main")}
1002
+ else:
1003
+ print(f"[HF] Error: Could not locate 'main' checkpoint weights under {checkpoint_dir}/main")
1004
+ return
1005
+
1006
+ main_dir = revisions["main"]
1007
+ parent_dir = os.path.dirname(main_dir)
1008
+
1009
+ 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]:
1010
+ ckpt_name = f"chck_{m}M"
1011
+ ckpt_path = os.path.join(parent_dir, ckpt_name)
1012
+ if os.path.exists(ckpt_path):
1013
+ revisions[ckpt_name] = ckpt_path
1014
+
1015
+ temp_dir = os.path.abspath("./hf_upload_temp")
1016
+
1017
+ for revision_name, local_path in revisions.items():
1018
+ print(f"\n[HF] Preparing revision '{revision_name}' from folder: {local_path}")
1019
+ if os.path.exists(temp_dir):
1020
+ shutil.rmtree(temp_dir)
1021
+ os.makedirs(temp_dir, exist_ok=True)
1022
+
1023
+ for item in os.listdir(local_path):
1024
+ shutil.copy2(os.path.join(local_path, item), os.path.join(temp_dir, item))
1025
+
1026
+ try:
1027
+ api.create_branch(
1028
+ repo_id=repo_id,
1029
+ repo_type="model",
1030
+ branch=revision_name,
1031
+ exist_ok=True
1032
+ )
1033
+ print(f"[HF] Created branch/revision '{revision_name}' on repository.")
1034
+ except Exception as branch_err:
1035
+ print(f"[HF] Info: Branch creation failed or exists: {branch_err}")
1036
+
1037
+ print(f"[HF] Uploading staged folder to '{repo_id}' revision '{revision_name}'...")
1038
+ try:
1039
+ api.upload_folder(
1040
+ folder_path=temp_dir,
1041
+ repo_id=repo_id,
1042
+ repo_type="model",
1043
+ revision=revision_name
1044
+ )
1045
+ print(f"[HF] Successfully uploaded revision '{revision_name}' to repository.")
1046
+ except Exception as e:
1047
+ print(f"[HF] Failed to upload revision '{revision_name}': {e}")
1048
+
1049
+ if os.path.exists(temp_dir):
1050
+ shutil.rmtree(temp_dir)
1051
+ print(f"\n[HF] All uploads finished! View your repository at https://huggingface.co/{repo_id}")
1052
+
1053
+ if __name__ == "__main__":
1054
+ parser = argparse.ArgumentParser()
1055
+ parser.add_argument("--model-name", type=str, default="gpt2_dense_31M")
1056
+ parser.add_argument("--epochs", type=int, default=10)
1057
+ parser.add_argument("--skip-eval", action="store_true", help="Skip evaluation phase after training")
1058
+ parser.add_argument("--skip-aoa", action="store_true", default=True, help="Skip AoA evaluation")
1059
+ parser.add_argument("--skip-glue", action="store_true", default=False, help="Skip GLUE fine-tuning")
1060
+ parser.add_argument("--upload", action="store_true", help="Upload model repository to Hugging Face")
1061
+ parser.add_argument("--upload-repo", type=str, default="active_param_gpt2_31M", help="Hugging Face repository name")
1062
+ parser.add_argument("--upload-token", type=str, default=None, help="Hugging Face API token")
1063
+ args = parser.parse_args()
1064
+
1065
+ if args.upload:
1066
+ upload_pipeline(args.model_name, args.upload_repo, args.upload_token)
1067
+ else:
1068
+ class Tee:
1069
+ def __init__(self, filepath, original_stream):
1070
+ self.file = open(filepath, "a", encoding="utf-8", buffering=1)
1071
+ self.original_stream = original_stream
1072
+
1073
+ def write(self, data):
1074
+ self.original_stream.write(data)
1075
+ self.file.write(data)
1076
+
1077
+ def flush(self):
1078
+ self.original_stream.flush()
1079
+ self.file.flush()
1080
+
1081
+ import sys
1082
+ sys.stdout = Tee("training_terminal.log", sys.stdout)
1083
+ sys.stderr = Tee("training_terminal.log", sys.stderr)
1084
+
1085
+ print("\n=== STARTING NEW TRAINING RUN LOGGING TO training_terminal.log ===")
1086
+ 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_to_hf.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import shutil
4
+ import re
5
+ import argparse
6
+ from huggingface_hub import HfApi, create_repo
7
+
8
+ def upload_all(repo_name="active_param_gpt2_31M", model_name="gpt2_dense_fresh"):
9
+ token = os.environ.get("HF_TOKEN")
10
+ if not token:
11
+ print("Error: HF_TOKEN environment variable not set!")
12
+ print("Please set it before running: export HF_TOKEN=your_token")
13
+ sys.exit(1)
14
+
15
+ api = HfApi(token=token)
16
+ try:
17
+ user_info = api.whoami()
18
+ username = user_info["name"]
19
+ print(f"[HF] Authenticated as: {username}")
20
+ except Exception as e:
21
+ print(f"[HF] Authentication failed: {e}")
22
+ sys.exit(1)
23
+
24
+ repo_id = f"{username}/{repo_name}"
25
+
26
+ # Auto-create the repo if it does not exist
27
+ try:
28
+ create_repo(repo_id=repo_id, repo_type="model", token=token, exist_ok=True)
29
+ print(f"[HF] Repository '{repo_id}' is ready.")
30
+ except Exception as e:
31
+ print(f"[HF] Warning during repository creation: {e}")
32
+
33
+ local_dir = os.path.dirname(os.path.abspath(__file__))
34
+
35
+ # ─────────────────────────────────────────────────────────────
36
+ # Step 1: Upload scripts, metrics, results, and plots to main branch
37
+ # ─────────────────────────────────────────────────────────────
38
+ print(f"\n[HF] Step 1: Uploading codebase, plots, metrics, and results to main branch...")
39
+ ignore_patterns = [
40
+ "**/__pycache__/*",
41
+ "**/*.pyc",
42
+ "**/hf_cache/*",
43
+ "**/nltk_data/*",
44
+ "**/checkpoints/*",
45
+ "**/babylm_eval_repo/*"
46
+ ]
47
+
48
+ # Exclude files containing HF Token
49
+ sensitive_ignores = []
50
+ token_pattern = re.compile(r"hf_[a-zA-Z0-9]{34}")
51
+ for root, dirs, files in os.walk(local_dir):
52
+ if "__pycache__" in root or "checkpoints" in root or "hf_cache" in root or "nltk_data" in root or "babylm_eval_repo" in root:
53
+ continue
54
+ for file in files:
55
+ file_path = os.path.join(root, file)
56
+ try:
57
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
58
+ content = f.read()
59
+ if token_pattern.search(content):
60
+ rel_path = os.path.relpath(file_path, local_dir)
61
+ rel_path_glob = rel_path.replace("\\", "/")
62
+ sensitive_ignores.append(rel_path_glob)
63
+ print(f" -> Warning: Sensitive file containing raw token excluded: {rel_path_glob}")
64
+ except Exception:
65
+ pass
66
+
67
+ all_ignores = ignore_patterns + sensitive_ignores
68
+
69
+ try:
70
+ api.upload_folder(
71
+ folder_path=local_dir,
72
+ repo_id=repo_id,
73
+ repo_type="model",
74
+ revision="main",
75
+ ignore_patterns=all_ignores,
76
+ commit_message="Upload dense baseline codebase, metrics, and plots"
77
+ )
78
+ print(f"[HF] Codebase, plots, and results uploaded successfully to main branch.")
79
+ except Exception as e:
80
+ print(f"[HF] Failed to upload codebase: {e}")
81
+
82
+ # ─────────────────────────────────────────────────────────────
83
+ # Step 2: Upload checkpoints to their respective revisions/branches
84
+ # ─────────────────────────────────────────────────────────────
85
+ checkpoint_dir = os.path.abspath(f"./checkpoints/{model_name}")
86
+ if not os.path.exists(checkpoint_dir):
87
+ print(f"[HF] Error: Checkpoint directory not found at: {checkpoint_dir}")
88
+ return
89
+
90
+ revisions = {}
91
+ if os.path.exists(os.path.join(checkpoint_dir, "main")):
92
+ revisions["main"] = os.path.join(checkpoint_dir, "main")
93
+
94
+ 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]:
95
+ ckpt_name = f"chck_{m}M"
96
+ ckpt_path = os.path.join(checkpoint_dir, ckpt_name)
97
+ if os.path.exists(ckpt_path):
98
+ revisions[ckpt_name] = ckpt_path
99
+
100
+ print(f"\n[HF] Step 2: Uploading {len(revisions)} checkpoint revisions to their respective branches...")
101
+ temp_dir = os.path.abspath("./hf_upload_temp")
102
+
103
+ for revision_name, local_path in revisions.items():
104
+ print(f"\n[HF] Preparing revision '{revision_name}' from folder: {local_path}")
105
+ if os.path.exists(temp_dir):
106
+ shutil.rmtree(temp_dir)
107
+ os.makedirs(temp_dir, exist_ok=True)
108
+
109
+ for item in os.listdir(local_path):
110
+ shutil.copy2(os.path.join(local_path, item), os.path.join(temp_dir, item))
111
+
112
+ try:
113
+ api.create_branch(
114
+ repo_id=repo_id,
115
+ repo_type="model",
116
+ branch=revision_name,
117
+ exist_ok=True
118
+ )
119
+ print(f"[HF] Created branch/revision '{revision_name}' on repository.")
120
+ except Exception as branch_err:
121
+ print(f"[HF] Info: Branch creation failed or exists: {branch_err}")
122
+
123
+ print(f"[HF] Uploading staged folder to '{repo_id}' revision '{revision_name}'...")
124
+ try:
125
+ api.upload_folder(
126
+ folder_path=temp_dir,
127
+ repo_id=repo_id,
128
+ repo_type="model",
129
+ revision=revision_name
130
+ )
131
+ print(f"[HF] Successfully uploaded revision '{revision_name}' to repository.")
132
+ except Exception as e:
133
+ print(f"[HF] Failed to upload revision '{revision_name}': {e}")
134
+
135
+ if os.path.exists(temp_dir):
136
+ shutil.rmtree(temp_dir)
137
+ print(f"\n[HF] All uploads finished! View your repository at https://huggingface.co/{repo_id}")
138
+
139
+ if __name__ == "__main__":
140
+ parser = argparse.ArgumentParser()
141
+ parser.add_argument("--repo-name", type=str, default="active_param_gpt2_31M", help="Hugging Face repository name")
142
+ parser.add_argument("--model-name", type=str, default="gpt2_dense_fresh", help="Local model checkpoints folder name")
143
+ args = parser.parse_args()
144
+ upload_all(args.repo_name, args.model_name)
upload_project_hf.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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", "active_param_gpt2_31M")
16
+
17
+ local_dir = os.path.dirname(os.path.abspath(__file__))
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 GPT-2 46M SwiGLU Dense baseline scripts to '{repo_id}' main branch...")
38
+
39
+ ignore_patterns = [
40
+ "**/__pycache__/*",
41
+ "**/*.pyc",
42
+ "**/hf_cache/*",
43
+ "**/nltk_data/*",
44
+ "**/checkpoints/*"
45
+ ]
46
+
47
+ sensitive_ignores = []
48
+ token_pattern = re.compile(r"hf_[a-zA-Z0-9]{34}")
49
+ for root, dirs, files in os.walk(local_dir):
50
+ if "__pycache__" in root or "checkpoints" in root:
51
+ continue
52
+ for file in files:
53
+ file_path = os.path.join(root, file)
54
+ try:
55
+ with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
56
+ content = f.read()
57
+ if token_pattern.search(content):
58
+ rel_path = os.path.relpath(file_path, local_dir)
59
+ rel_path_glob = rel_path.replace("\\", "/")
60
+ sensitive_ignores.append(rel_path_glob)
61
+ print(f" -> Warning: Sensitive file containing raw token excluded: {rel_path_glob}")
62
+ except Exception:
63
+ pass
64
+
65
+ all_ignores = ignore_patterns + sensitive_ignores
66
+
67
+ try:
68
+ api.upload_folder(
69
+ folder_path=local_dir,
70
+ repo_id=repo_id,
71
+ repo_type="model",
72
+ revision="main",
73
+ ignore_patterns=all_ignores,
74
+ commit_message="Initial commit of GPT-2 46M SwiGLU Dense baseline model architecture and configs"
75
+ )
76
+ print(f"\n[HF] Success! Scripts uploaded to: https://huggingface.co/{repo_id}/tree/main")
77
+ except Exception as e:
78
+ print(f"[HF] Upload failed: {e}")
79
+
80
+ if __name__ == "__main__":
81
+ parser = argparse.ArgumentParser()
82
+ parser.add_argument("--repo-name", type=str, default=None, help="Hugging Face repository name")
83
+ args = parser.parse_args()
84
+ upload_project(args.repo_name)