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