Soham Jain commited on
Initial commit of GPT-2 46M SwiGLU Dense baseline model architecture and configs
Browse files- configuration_gpt2.py +40 -0
- generate_plots_and_report.py +208 -0
- modeling_gpt2.py +225 -0
- train.py +1086 -0
- upload_all_to_hf.py +144 -0
- upload_project_hf.py +84 -0
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)
|