Neithanvarvas commited on
Commit
db7b729
·
verified ·
1 Parent(s): c7e2bd3

[RELEASE] Upload ScaledGPT 18.95M champion model weights and artifacts

Browse files
Files changed (7) hide show
  1. README.md +110 -0
  2. champion_final.pt +3 -0
  3. config.json +31 -0
  4. dataset.py +976 -0
  5. interactive.py +321 -0
  6. model.py +421 -0
  7. tokenizer.py +109 -0
README.md ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ language:
3
+ - en
4
+ license: mit
5
+ tags:
6
+ - arithmetic
7
+ - length-generalization
8
+ - recurrent-transformers
9
+ - 2d-rope
10
+ - tool-use
11
+ - function-calling
12
+ - reasoning
13
+ datasets:
14
+ - synthetic
15
+ metrics:
16
+ - accuracy
17
+ pipeline_tag: text-generation
18
+ ---
19
+
20
+ # ScaledGPT (~18.95M): Recurrent 2D-RoPE Architecture for Algorithmic Length Extrapolation
21
+
22
+ ## 1. Model Overview
23
+
24
+ **ScaledGPT** is an autoregressive Transformer model engineered to overcome the empirical limits of standard self-attention on sequential state machines and multi-tool agent execution.
25
+
26
+ By combining **Decoupled 2D Rotary Position Embeddings (2D-RoPE)** with **Weight-Tied Recurrent Depth ($R=3$)**, ScaledGPT achieves:
27
+ - **100.0% Exact Match Accuracy** on 1 to 50 digit column arithmetic length extrapolation (including 50-digit cascading carry $99\dots99 + 1$).
28
+ - **100.0% Multi-Tool Routing Accuracy** across 9 tool schemas (single-tool, multi-tool chaining, nested JSON schemas, and error recovery).
29
+ - **Dual NVIDIA T4 GPU Efficiency**: Native Turing `sm_75` FP16 AMP and SDPA memory-efficient attention.
30
+
31
+ ---
32
+
33
+ ## 2. Model Specifications
34
+
35
+ | Parameter | Specification |
36
+ | :--- | :--- |
37
+ | **Total Parameters** | 18,944,512 (~18.95M) |
38
+ | **Non-Embedding Parameters** | 18,887,680 (~18.89M) |
39
+ | **Physical Layers ($L$)** | 6 |
40
+ | **Recurrent Passes ($R$)** | 3 (Effective Depth = 18 layers) |
41
+ | **Hidden Dimension ($d_{\text{model}}$)** | 512 |
42
+ | **Attention Heads ($H$)** | 8 (Head dimension = 64) |
43
+ | **Max Sequence Length ($T$)** | 1024 |
44
+ | **Positional Embedding** | Decoupled 2D-RoPE ($d/2$ macro column, $d/2$ micro step) |
45
+ | **Vocabulary Size** | 115 (Special discourse markers + ASCII characters) |
46
+ | **Weight Tying** | $W_{\text{te}} == W_{\text{lm\_head}}$ |
47
+
48
+ ---
49
+
50
+ ## 3. Empirical Evaluation Benchmark
51
+
52
+ | Benchmark Suite | Metric | Score | Status |
53
+ | :--- | :--- | :--- | :--- |
54
+ | **1-15 Digit In-Distribution Math** | Exact Answer Match | 100.0% (6/6) | [PASS] |
55
+ | **16-25 Digit Intermediate Extrapolation** | Exact Answer Match | 100.0% (6/6) | [PASS] |
56
+ | **30-40 Digit High Extrapolation** | Exact Answer Match | 100.0% (4/4) | [PASS] |
57
+ | **45-50 Digit Ultra Extrapolation** | Exact Answer Match | 100.0% (4/4) | [PASS] |
58
+ | **50-Digit Cascade Carry ($99\dots99 + 1$)** | 50 Sequential Transitions | 100.0% (1/1) | [PASS] |
59
+ | **Multi-Tool Routing Suite (9 Schemas)** | Correct Tool Selection | 100.0% (26/26) | [PASS] |
60
+ | **Multi-Tool Exact Argument Extraction** | Exact Parameter Copy Rate | 96.2% (25/26) | [PASS] |
61
+
62
+ ---
63
+
64
+ ## 4. Usage & Inference
65
+
66
+ ### Local Quickstart
67
+
68
+ ```python
69
+ import torch
70
+ from model import ScaledGPT, ScaledGPTConfig
71
+ from tokenizer import ScaledTokenizer
72
+
73
+ # 1. Load configuration and model
74
+ config = ScaledGPTConfig(
75
+ block_size=1024,
76
+ vocab_size=115,
77
+ n_layer=6,
78
+ n_head=8,
79
+ n_embd=512,
80
+ n_recur=3,
81
+ pos_emb_type="rope_2d"
82
+ )
83
+ model = ScaledGPT(config)
84
+
85
+ # 2. Load weights
86
+ state_dict = torch.load("champion_final.pt", map_location="cpu")
87
+ model.load_state_dict(state_dict)
88
+ model.eval()
89
+
90
+ # 3. Initialize tokenizer
91
+ tokenizer = ScaledTokenizer()
92
+ ```
93
+
94
+ ### Interactive CLI
95
+
96
+ ```bash
97
+ python interactive.py --ckpt champion_final.pt
98
+ ```
99
+
100
+ ---
101
+
102
+ ## 5. Citation
103
+
104
+ ```bibtex
105
+ @article{vargas2026attention,
106
+ title={Attention Is Not All You Need for AGI: Empirical Limits of Pure Self-Attention on Algorithmic Length Generalization},
107
+ author={Vargas, Neithan},
108
+ year={2026}
109
+ }
110
+ ```
champion_final.pt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:79d908d328d9aeac319129b3816fc2de0ada6722c2a5f9ee1ea7f4919e87ccaa
3
+ size 75800724
config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model_type": "scaled_t4",
3
+ "vocab_size": 115,
4
+ "block_size": 1024,
5
+ "n_layer": 6,
6
+ "n_head": 8,
7
+ "n_embd": 512,
8
+ "n_recur": 3,
9
+ "dropout": 0.0,
10
+ "bias": false,
11
+ "pos_emb_type": "rope_2d",
12
+ "rope_theta": 10000.0,
13
+ "max_coords": 4096,
14
+ "k_rand_shift": 32,
15
+ "effective_depth": 18,
16
+ "head_dim": 64,
17
+ "parameters": {
18
+ "total": 18953216,
19
+ "non_embedding": 18887680,
20
+ "target_scale": "~10M to ~20M",
21
+ "status": "[OK] Scaled 18.95M parameter architecture with 2D-RoPE and R=3 recurrence"
22
+ },
23
+ "t4_optimization": {
24
+ "precision": "FP16 AMP (torch.amp.autocast)",
25
+ "scaler": "torch.amp.GradScaler('cuda')",
26
+ "sdpa": "Scaled Dot-Product Attention with O(1) activation memory",
27
+ "distributed": "PyTorch DistributedDataParallel (DDP) across 2x T4 GPUs",
28
+ "bfloat16_allowed": false,
29
+ "tf32_allowed": false
30
+ }
31
+ }
dataset.py ADDED
@@ -0,0 +1,976 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Joint Balanced Dataset & Batch Encoder for ScaledGPT (Dual T4 Optimization).
3
+ Supports:
4
+ 1. Multi-digit column scratchpad arithmetic (1 to 50+ digits curriculum).
5
+ 2. Expanded tool repertoire:
6
+ - Single-tool calling (calculator, weather, search, time, unit_convert)
7
+ - Multi-tool sequential chaining
8
+ - Nested JSON parameter schemas
9
+ - Error handling and recovery traces
10
+ - Multi-turn conversational dialogues
11
+ - Direct conversational routing (no-tool)
12
+ 3. Strict prompt and environment output loss masking.
13
+ """
14
+
15
+ import os
16
+ import sys
17
+ import json
18
+ import random
19
+ from typing import List, Dict, Tuple, Any, Optional
20
+ import torch
21
+
22
+ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
23
+ if ROOT_DIR not in sys.path:
24
+ sys.path.insert(0, ROOT_DIR)
25
+
26
+ from champion_model.scaled_t4.tokenizer import ScaledTokenizer
27
+
28
+
29
+ def format_addition_char_scratchpad(a: int, b: int) -> str:
30
+ """Formats addition into column-by-column carry scratchpad for character tokenizer."""
31
+ s_a, s_b = str(a), str(b)
32
+ len_a, len_b = len(s_a), len(s_b)
33
+ max_len = max(len_a, len_b)
34
+
35
+ carry = 0
36
+ steps, digits = [], []
37
+
38
+ for col in range(max_len):
39
+ da = int(s_a[len_a - 1 - col]) if col < len_a else 0
40
+ db = int(s_b[len_b - 1 - col]) if col < len_b else 0
41
+ col_sum = carry + da + db
42
+ next_carry = col_sum // 10
43
+ res_digit = col_sum % 10
44
+ steps.append(f"({carry}+{da}+{db})={next_carry}{res_digit}")
45
+ digits.append(str(res_digit))
46
+ carry = next_carry
47
+
48
+ if carry > 0:
49
+ steps.append(f"({carry}+0+0)=0{carry}")
50
+ digits.append(str(carry))
51
+
52
+ ans = "".join(reversed(digits))
53
+ return f"{a}+{b}=<THINK>" + "".join(steps) + f"</THINK>={ans}"
54
+
55
+
56
+ def extract_scratchpad_answer(text: str) -> Tuple[str, str]:
57
+ """Extracts reasoning scratchpad and predicted answer from generated output."""
58
+ reasoning = ""
59
+ ans = ""
60
+ if "<THINK>" in text and "</THINK>" in text:
61
+ parts = text.split("</THINK>")
62
+ reasoning = parts[0].replace("<THINK>", "")
63
+ after = parts[1]
64
+ if "=" in after:
65
+ ans = after.split("=")[-1].replace("<EOS>", "").replace("<PAD>", "").strip()
66
+ else:
67
+ ans = after.replace("<EOS>", "").replace("<PAD>", "").strip()
68
+ elif "=" in text:
69
+ ans = text.split("=")[-1].replace("<EOS>", "").replace("<PAD>", "").strip()
70
+ return reasoning, ans
71
+
72
+ # Factual databases for synthetic generation
73
+ CITIES = [
74
+ "New York", "London", "Tokyo", "Paris", "Berlin", "Sydney", "Toronto", "Madrid",
75
+ "Rome", "Seoul", "Singapore", "Zurich", "Honolulu", "Reykjavik", "Ouagadougou",
76
+ "Vancouver", "Santiago", "Auckland", "Casablanca", "Vienna", "Stockholm", "Kyoto",
77
+ "Dublin", "Oslo", "Helsinki", "Bangkok", "Cairo", "Lisbon", "Amsterdam", "Athens",
78
+ ]
79
+ CONDITIONS = ["Sunny", "Rainy", "Cloudy", "Snowy", "Windy", "Foggy", "Clear"]
80
+ TIMEZONES = ["UTC", "EST", "PST", "GMT", "JST", "CET", "AEST", "CST", "IST", "HST"]
81
+
82
+ # Nonce word phonetic generators to force pure Attention Induction Copying
83
+ NONCE_ONSETS = ["bl", "br", "ch", "cl", "cr", "dr", "fl", "fr", "gl", "gr", "pl", "pr", "sc", "sk", "sl", "sm", "sn", "sp", "st", "sw", "th", "tr", "qu", "v", "z", "k", "j"]
84
+ NONCE_NUCLEI = ["a", "e", "i", "o", "u", "y", "ai", "ea", "ee", "oa", "oo", "ou"]
85
+ NONCE_CODAS = ["b", "d", "f", "g", "k", "l", "m", "n", "p", "r", "s", "t", "x", "z", "ck", "nk", "mp", "sh", "th", "nt", "rt"]
86
+
87
+
88
+ def generate_nonce_entity(rng: random.Random) -> Tuple[str, str, str]:
89
+ """Generates an arbitrary pseudo-word entity (singular, plural, type)."""
90
+ n_syl = rng.choice([1, 2, 2, 3])
91
+ parts = []
92
+ for _ in range(n_syl):
93
+ parts.append(rng.choice(NONCE_ONSETS) + rng.choice(NONCE_NUCLEI) + rng.choice(NONCE_CODAS))
94
+ singular = "".join(parts)
95
+ plural = singular + ("es" if singular[-1] in "sxz" else "s")
96
+ ent_type = rng.choice(["concept", "plant", "creature", "artifact", "substance"])
97
+ return singular, plural, ent_type
98
+ SEARCH_FACTS = [
99
+ ("Python creator", "Guido van Rossum", "creator"),
100
+ ("capital of Australia", "Canberra", "capital"),
101
+ ("speed of light", "299792 km/s", "constant"),
102
+ ("tallest mountain on Earth", "Mount Everest", "geography"),
103
+ ("deepest ocean trench", "Mariana Trench", "geography"),
104
+ ("inventor of the telephone", "Alexander Graham Bell", "inventor"),
105
+ ("currency of Japan", "Yen", "currency"),
106
+ ("largest planet in solar system", "Jupiter", "astronomy"),
107
+ ("author of Hamlet", "William Shakespeare", "literature"),
108
+ ("chemical symbol for Gold", "Au", "chemistry"),
109
+ ("Tokyo population", "14000000", "demographics"),
110
+ ("Paris land area", "105 sq km", "geography"),
111
+ ("distance from Earth to Moon", "384400 km", "astronomy"),
112
+ ("human body temperature", "37 Celsius", "biology"),
113
+ ]
114
+
115
+ # Open-domain entity database for Universal Search Delegation
116
+ SEARCH_ENTITIES = [
117
+ # Foods & Plants
118
+ {"entity": "tomato", "plural": "tomatoes", "type": "plant", "snippet": "the edible berry of the plant Solanum lycopersicum, commonly prepared as a culinary vegetable."},
119
+ {"entity": "apple", "plural": "apples", "type": "fruit", "snippet": "a round, edible fruit produced by an apple tree, cultivated worldwide."},
120
+ {"entity": "potato", "plural": "potatoes", "type": "vegetable", "snippet": "a starchy, tuberous root crop of the nightshade family Solanaceae."},
121
+ {"entity": "coffee", "plural": "coffee beans", "type": "plant", "snippet": "a brewed beverage prepared from roasted coffee beans of flowering Coffea species."},
122
+ {"entity": "banana", "plural": "bananas", "type": "fruit", "snippet": "an elongated, edible fruit produced by large herbaceous plants in the genus Musa."},
123
+ {"entity": "onion", "plural": "onions", "type": "vegetable", "snippet": "a pungent vegetable that is the most widely cultivated species of the genus Allium."},
124
+ {"entity": "wheat", "plural": "wheat grains", "type": "crop", "snippet": "a cereal grain cultivated worldwide as a major dietary staple."},
125
+ {"entity": "rice", "plural": "rice grains", "type": "crop", "snippet": "the seed of the grass species Oryza sativa, a primary staple food for billions of people."},
126
+ {"entity": "carrot", "plural": "carrots", "type": "vegetable", "snippet": "a root vegetable, typically orange, rich in beta-carotene and dietary fiber."},
127
+ {"entity": "garlic", "plural": "garlic cloves", "type": "plant", "snippet": "a species of bulbous flowering plant in the genus Allium, used for culinary seasoning."},
128
+ # Animals
129
+ {"entity": "dog", "plural": "dogs", "type": "animal", "snippet": "a domesticated descendant of the wolf, widely kept as a companion and working animal."},
130
+ {"entity": "cat", "plural": "cats", "type": "animal", "snippet": "a small, carnivorous mammal known for its agility, flexible body, and companionship with humans."},
131
+ {"entity": "dolphin", "plural": "dolphins", "type": "animal", "snippet": "a highly intelligent marine mammal belonging to the cetacean order, known for echolocation."},
132
+ {"entity": "elephant", "plural": "elephants", "type": "animal", "snippet": "the largest existing land animal, characterized by a long trunk, tusks, and large ear flaps."},
133
+ {"entity": "penguin", "plural": "penguins", "type": "animal", "snippet": "an aquatic, flightless bird living almost exclusively in the Southern Hemisphere."},
134
+ {"entity": "lion", "plural": "lions", "type": "animal", "snippet": "a large cat of the genus Panthera, native to Africa and India, known for social prides."},
135
+ {"entity": "tiger", "plural": "tigers", "type": "animal", "snippet": "the largest living cat species, identifiable by its reddish-orange coat with dark vertical stripes."},
136
+ {"entity": "whale", "plural": "whales", "type": "animal", "snippet": "a widely distributed and diverse group of fully aquatic placental marine mammals."},
137
+ # Science & Nature
138
+ {"entity": "gravity", "plural": "gravitational forces", "type": "physics", "snippet": "a fundamental interaction which causes mutual attraction between all things with mass or energy."},
139
+ {"entity": "photosynthesis", "plural": "photosynthetic processes", "type": "biology", "snippet": "a biological process used by plants to convert light energy into chemical energy stored in glucose."},
140
+ {"entity": "atom", "plural": "atoms", "type": "physics", "snippet": "the basic unit of a chemical element, consisting of a dense central nucleus surrounded by electrons."},
141
+ {"entity": "DNA", "plural": "DNA molecules", "type": "genetics", "snippet": "deoxyribonucleic acid, a polymer composed of polynucleotide chains carrying genetic instructions."},
142
+ {"entity": "black hole", "plural": "black holes", "type": "astronomy", "snippet": "a region of spacetime where gravity is so strong that nothing, including light, can escape from it."},
143
+ {"entity": "water", "plural": "water molecules", "type": "chemistry", "snippet": "a transparent, odorless chemical substance with chemical formula H2O, essential for life."},
144
+ {"entity": "oxygen", "plural": "oxygen molecules", "type": "chemistry", "snippet": "a chemical element with symbol O and atomic number 8, essential for aerobic respiration."},
145
+ {"entity": "electricity", "plural": "electric currents", "type": "physics", "snippet": "physical phenomena associated with the presence and motion of matter possessing electric charge."},
146
+ # Geography & Landmarks
147
+ {"entity": "Eiffel Tower", "plural": "monuments", "type": "landmark", "snippet": "a wrought-iron lattice tower on the Champ de Mars in Paris, France, named after Gustave Eiffel."},
148
+ {"entity": "Great Wall of China", "plural": "ancient walls", "type": "history", "snippet": "a series of fortifications built across the historical northern borders of ancient Chinese states."},
149
+ {"entity": "Mount Everest", "plural": "peaks", "type": "geography", "snippet": "Earth's highest mountain above sea level, located in the Mahalangur Himal sub-range of the Himalayas."},
150
+ {"entity": "Amazon River", "plural": "rivers", "type": "geography", "snippet": "the largest river by discharge volume of water in the world, located in South America."},
151
+ {"entity": "Sahara Desert", "plural": "deserts", "type": "geography", "snippet": "the largest hot desert in the world and the third largest desert overall, covering North Africa."},
152
+ # Technology & Society
153
+ {"entity": "computer", "plural": "computers", "type": "technology", "snippet": "an electronic device for storing and processing data, typically according to binary instructions."},
154
+ {"entity": "internet", "plural": "computer networks", "type": "technology", "snippet": "a global system of interconnected computer networks that uses the Internet protocol suite (TCP/IP)."},
155
+ {"entity": "algorithm", "plural": "algorithms", "type": "computer science", "snippet": "a finite sequence of rigorous instructions used to solve problems or perform a computation."},
156
+ {"entity": "telescope", "plural": "telescopes", "type": "instrument", "snippet": "an optical instrument that makes distant objects appear magnified using lenses or curved mirrors."},
157
+ {"entity": "airplane", "plural": "airplanes", "type": "vehicle", "snippet": "a powered, fixed-wing aircraft that is propelled forward by thrust from an engine."},
158
+ # Historical Figures & Creators
159
+ {"entity": "Guido van Rossum", "plural": "programmers", "type": "person", "snippet": "the Dutch programmer best known as the creator of the Python programming language."},
160
+ {"entity": "Albert Einstein", "plural": "physicists", "type": "person", "snippet": "a theoretical physicist who developed the theory of relativity, a pillar of modern physics."},
161
+ {"entity": "William Shakespeare", "plural": "playwrights", "type": "person", "snippet": "an English playwright, poet, and actor widely regarded as the greatest writer in the English language."},
162
+ {"entity": "Alexander Graham Bell", "plural": "inventors", "type": "person", "snippet": "an inventor and scientist credited with patenting the first practical telephone."},
163
+ {"entity": "Isaac Newton", "plural": "scientists", "type": "person", "snippet": "an English mathematician and physicist who formulated the laws of motion and universal gravitation."},
164
+ {"entity": "Nikola Tesla", "plural": "engineers", "type": "person", "snippet": "a Serbian-American engineer known for his contributions to alternating current electricity."},
165
+ {"entity": "Marie Curie", "plural": "scientists", "type": "person", "snippet": "a Polish-French physicist who pioneered radioactivity research and won two Nobel Prizes."},
166
+ {"entity": "Alan Turing", "plural": "mathematicians", "type": "person", "snippet": "an English mathematician widely considered the father of modern computer science and AI."},
167
+ {"entity": "Ada Lovelace", "plural": "mathematicians", "type": "person", "snippet": "an English mathematician celebrated for writing the first computer algorithm."},
168
+ {"entity": "Aristotle", "plural": "philosophers", "type": "person", "snippet": "an ancient Greek philosopher whose work shaped centuries of Western logic and metaphysics."},
169
+ {"entity": "Leonardo da Vinci", "plural": "polymaths", "type": "person", "snippet": "an Italian polymath active as a painter, draughtsman, engineer, and scientist."},
170
+ # Additional Produce & Animals
171
+ {"entity": "lemon", "plural": "lemons", "type": "fruit", "snippet": "a species of small evergreen tree native to Asia, known for its yellow ellipsoidal citrus fruit."},
172
+ {"entity": "strawberry", "plural": "strawberries", "type": "fruit", "snippet": "a widely grown hybrid species of the genus Fragaria, cultivated for its sweet red fruit."},
173
+ {"entity": "grape", "plural": "grapes", "type": "fruit", "snippet": "a botanical berry of the deciduous woody vines of the flowering plant genus Vitis."},
174
+ {"entity": "spinach", "plural": "spinach leaves", "type": "vegetable", "snippet": "an edible flowering plant in the family Amaranthaceae, rich in iron and vitamins."},
175
+ {"entity": "broccoli", "plural": "broccoli florets", "type": "vegetable", "snippet": "an edible green plant in the cabbage family whose large flower head is eaten as a vegetable."},
176
+ {"entity": "avocado", "plural": "avocados", "type": "fruit", "snippet": "a medium-sized, evergreen tree native to the Americas, harvested for its buttery fruit."},
177
+ {"entity": "wolf", "plural": "wolves", "type": "animal", "snippet": "a large canine native to Eurasia and North America, known for pack hunting behavior."},
178
+ {"entity": "eagle", "plural": "eagles", "type": "animal", "snippet": "a large, powerfully built bird of prey belonging to the family Accipitridae."},
179
+ {"entity": "shark", "plural": "sharks", "type": "animal", "snippet": "a group of elasmobranch fish characterized by a cartilaginous skeleton and multiple gill slits."},
180
+ {"entity": "octopus", "plural": "octopuses", "type": "animal", "snippet": "a soft-bodied, eight-limbed mollusc of the order Octopoda, famous for high intelligence."},
181
+ # Science & Computing
182
+ {"entity": "quantum entanglement", "plural": "entangled states", "type": "physics", "snippet": "a physical phenomenon occurring when pairs of particles interact such that quantum states cannot be described independently."},
183
+ {"entity": "thermodynamics", "plural": "thermodynamic laws", "type": "physics", "snippet": "a branch of physics that deals with heat, work, temperature, and statistical mechanics."},
184
+ {"entity": "compiler", "plural": "compilers", "type": "computer science", "snippet": "a computer program that translates computer code written in one language into another."},
185
+ {"entity": "operating system", "plural": "operating systems", "type": "computer science", "snippet": "system software that manages computer hardware, software resources, and common services."},
186
+ {"entity": "neural network", "plural": "neural networks", "type": "computer science", "snippet": "a computational model inspired by the biological neural networks of animal brains."},
187
+ ]
188
+ DEPARTMENTS = ["engineering", "marketing", "sales", "finance", "legal", "product"]
189
+
190
+
191
+ # -----------------------------------------------------------------------------
192
+ # 1. Search Delegation & Copy Induction Dataset Generator
193
+ # -----------------------------------------------------------------------------
194
+
195
+ def generate_search_delegation_dataset(
196
+ n_samples: int = 15000,
197
+ seed: int = 42,
198
+ ) -> List[Dict[str, str]]:
199
+ """
200
+ Generates concentrated Search Delegation samples for Induction Head copy training.
201
+ Curriculum:
202
+ - 65% Nonce Words (1-3 syllables) forcing pure Attention Pointer Copying (memorization impossible).
203
+ - 35% Real-World Entities (tomatoes, photosynthesis, etc.) establishing lexical alignment.
204
+ - Symmetric punctuation curriculum (50% with '?', '.', 50% raw text without punctuation).
205
+ - Diverse question framing: What are, What is a, Define, Tell me about, Search for, Who is, Explain.
206
+ """
207
+ rng = random.Random(seed)
208
+ samples: List[Dict[str, str]] = []
209
+
210
+ for _ in range(n_samples):
211
+ is_nonce = (rng.random() < 0.65)
212
+ has_punct = (rng.random() < 0.50)
213
+
214
+ if is_nonce:
215
+ ent, plural, ent_type = generate_nonce_entity(rng)
216
+ snippet = f"a hypothetical {ent_type} characterized in verified theoretical studies."
217
+ else:
218
+ ent_item = rng.choice(SEARCH_ENTITIES)
219
+ ent = ent_item["entity"]
220
+ plural = ent_item.get("plural", ent + "s")
221
+ snippet = ent_item["snippet"]
222
+ ent_type = ent_item.get("type", "concept")
223
+
224
+ # Choose query framing
225
+ frame = rng.choice([
226
+ "what_are_plural",
227
+ "what_is_singular",
228
+ "what_is_concept",
229
+ "define",
230
+ "tell_me_about",
231
+ "search_for",
232
+ "who_is",
233
+ "explain",
234
+ "lookup",
235
+ ])
236
+
237
+ if frame == "what_are_plural":
238
+ q_mark = "?" if has_punct else ""
239
+ user_prompt = f"What are {plural}{q_mark}"
240
+ search_q = plural
241
+ thought = rng.choice([
242
+ "The user is asking a factual question. I will query the search tool.",
243
+ "The user requests factual information. Querying search.",
244
+ "The user is seeking knowledge on this topic. I will search the web.",
245
+ ])
246
+ answer = f"{plural.capitalize()} are {snippet}"
247
+ elif frame == "what_is_singular" and ent_type in ["plant", "fruit", "vegetable", "animal", "creature", "artifact", "instrument", "vehicle"]:
248
+ article = "an" if ent[0].lower() in "aeiou" else "a"
249
+ q_mark = "?" if has_punct else ""
250
+ user_prompt = f"What is {article} {ent}{q_mark}"
251
+ search_q = ent
252
+ thought = rng.choice([
253
+ "The user is asking a factual question. I will query the search tool.",
254
+ "The user is inquiring about this entity. I will query the search tool.",
255
+ "The user requests factual information. Querying search.",
256
+ ])
257
+ answer = f"{ent.capitalize()} is {snippet}"
258
+ elif frame == "define":
259
+ p_mark = "." if has_punct else ""
260
+ user_prompt = f"Define {ent}{p_mark}"
261
+ search_q = ent
262
+ thought = rng.choice([
263
+ "The user asks for a definition. Calling search tool.",
264
+ "The user is asking a factual question. I will query the search tool.",
265
+ "The user requests factual information. Querying search.",
266
+ ])
267
+ answer = f"{ent.capitalize()} is defined as {snippet}"
268
+ elif frame == "tell_me_about":
269
+ p_mark = "." if has_punct else ""
270
+ user_prompt = f"Tell me about {ent}{p_mark}"
271
+ search_q = ent
272
+ thought = rng.choice([
273
+ "The user requests factual background information. Querying search.",
274
+ "The user is asking a factual question. I will query the search tool.",
275
+ "The user is seeking knowledge on this topic. I will search the web.",
276
+ ])
277
+ answer = f"According to verified search results, {ent} is {snippet}"
278
+ elif frame == "search_for":
279
+ p_mark = "." if has_punct else ""
280
+ user_prompt = f"Search for {ent}{p_mark}"
281
+ search_q = ent
282
+ thought = rng.choice([
283
+ "The user explicitly requests a search. Calling search tool.",
284
+ "The user requests factual information. Querying search.",
285
+ "The user is asking a factual question. I will query the search tool.",
286
+ ])
287
+ answer = f"Search results indicate {ent} is {snippet}"
288
+ elif frame == "who_is" and ent_type in ["person", "creature"]:
289
+ q_mark = "?" if has_punct else ""
290
+ user_prompt = f"Who was {ent}{q_mark}" if rng.random() < 0.5 else f"Who is {ent}{q_mark}"
291
+ search_q = ent
292
+ thought = rng.choice([
293
+ "The user requests biographical information. Calling search.",
294
+ "The user is asking a factual question. I will query the search tool.",
295
+ "The user requests factual information. Querying search.",
296
+ ])
297
+ answer = f"{ent} was {snippet}" if "was" in user_prompt else f"{ent} is {snippet}"
298
+ elif frame == "explain":
299
+ p_mark = "." if has_punct else ""
300
+ user_prompt = f"Explain {ent}{p_mark}"
301
+ search_q = ent
302
+ thought = rng.choice([
303
+ "The user asks for an explanation of this subject. I will query the search tool.",
304
+ "The user is asking a factual question. I will query the search tool.",
305
+ "The user requests factual information. Querying search.",
306
+ ])
307
+ answer = f"{ent.capitalize()} is {snippet}"
308
+ else:
309
+ q_mark = "?" if has_punct else ""
310
+ user_prompt = f"What is {ent}{q_mark}"
311
+ search_q = ent
312
+ thought = rng.choice([
313
+ "The user is asking a factual question. I will query the search tool.",
314
+ "The user requests factual information. Querying search.",
315
+ "The user is seeking knowledge on this topic. I will search the web.",
316
+ ])
317
+ answer = f"{ent.capitalize()} is {snippet}"
318
+
319
+ call = f"search(query=\"{search_q}\")"
320
+ result_json = json.dumps({"snippet": snippet})
321
+ full_text = (
322
+ f"<USER>{user_prompt}</USER>"
323
+ f"<THINK>{thought}</THINK>"
324
+ f"<CALL>{call}</CALL>"
325
+ f"<RESULT>{result_json}</RESULT>"
326
+ f"<ANSWER>{answer}</ANSWER>"
327
+ )
328
+
329
+ samples.append({
330
+ "task_type": "tool",
331
+ "category": "search_delegation",
332
+ "prompt": f"<USER>{user_prompt}</USER>",
333
+ "full_text": full_text,
334
+ })
335
+
336
+ return samples
337
+
338
+
339
+ # -----------------------------------------------------------------------------
340
+ # 1. High-Digit Arithmetic Dataset Generator
341
+ # -----------------------------------------------------------------------------
342
+
343
+ def generate_arithmetic_equations(
344
+ n_samples: int = 4000,
345
+ seed: int = 42,
346
+ max_train_digits: int = 15,
347
+ ) -> List[Dict[str, str]]:
348
+ """
349
+ Generates balanced multi-digit addition equations with column carry scratchpad.
350
+ Curriculum: 1 to max_train_digits (default 15) digits, plus edge cases:
351
+ - Zero padding
352
+ - Cascade carries (e.g. 9999 + 1)
353
+ - Equal length and asymmetric lengths
354
+ """
355
+ rng = random.Random(seed)
356
+ samples: List[Dict[str, str]] = []
357
+
358
+ for _ in range(n_samples):
359
+ mode = rng.random()
360
+
361
+ if mode < 0.20:
362
+ # Cascade carry stress cases (999... + 1 or 999... + small)
363
+ digits = rng.randint(2, max_train_digits)
364
+ a = int("9" * digits)
365
+ b = rng.randint(1, 9)
366
+ elif mode < 0.35:
367
+ # Round numbers / power of 10 zero tracking
368
+ d1 = rng.randint(2, max_train_digits)
369
+ d2 = rng.randint(2, max_train_digits)
370
+ a = rng.randint(1, 9) * (10 ** (d1 - 1))
371
+ b = rng.randint(1, 9) * (10 ** (d2 - 1))
372
+ elif mode < 0.60:
373
+ # Uniform length random digits (1 to max_train_digits)
374
+ d = rng.randint(1, max_train_digits)
375
+ min_v = 10 ** (d - 1) if d > 1 else 0
376
+ max_v = (10 ** d) - 1
377
+ a = rng.randint(min_v, max_v)
378
+ b = rng.randint(min_v, max_v)
379
+ else:
380
+ # Asymmetric length random digits
381
+ d1 = rng.randint(1, max_train_digits)
382
+ d2 = rng.randint(1, max_train_digits)
383
+ min_v1 = 10 ** (d1 - 1) if d1 > 1 else 0
384
+ max_v1 = (10 ** d1) - 1
385
+ min_v2 = 10 ** (d2 - 1) if d2 > 1 else 0
386
+ max_v2 = (10 ** d2) - 1
387
+ a = rng.randint(min_v1, max_v1)
388
+ b = rng.randint(min_v2, max_v2)
389
+
390
+ full_text = format_addition_char_scratchpad(a, b)
391
+ prompt = full_text.split("<THINK>")[0]
392
+ samples.append({
393
+ "task_type": "math",
394
+ "prompt": prompt,
395
+ "full_text": full_text,
396
+ })
397
+
398
+ return samples
399
+
400
+
401
+ # -----------------------------------------------------------------------------
402
+ # 2. Expanded Tool Repertoire Generator
403
+ # -----------------------------------------------------------------------------
404
+
405
+ def generate_tool_example(rng: random.Random) -> Dict[str, str]:
406
+ """
407
+ Generates a structured synthetic tool interaction from 6 expanded categories:
408
+ 1. Single-tool calling (calculator, weather, search, time, unit_convert)
409
+ 2. Multi-tool sequential chaining
410
+ 3. Nested JSON parameter schemas
411
+ 4. Error handling and recovery
412
+ 5. Multi-turn dialogues
413
+ 6. Direct conversational routing (no-tool)
414
+
415
+ Enforces 1-Hop Induction Copying:
416
+ - <THINK> emits a clean, entity-agnostic routing intent (no variable copying in <THINK>).
417
+ - <CALL> copies arguments directly from <USER> via 1-hop attention pointers.
418
+ - Symmetric punctuation curriculum (50% with '?'/'.', 50% raw conversational text).
419
+ """
420
+ category = rng.choices(
421
+ [
422
+ "single_tool",
423
+ "multi_tool_chain",
424
+ "nested_json_schema",
425
+ "error_recovery",
426
+ "multi_turn",
427
+ "no_tool",
428
+ ],
429
+ weights=[0.55, 0.12, 0.12, 0.08, 0.06, 0.07],
430
+ k=1,
431
+ )[0]
432
+ has_punct = (rng.random() < 0.50)
433
+ q_mark = "?" if has_punct else ""
434
+ p_mark = "." if has_punct else ""
435
+
436
+ if category == "single_tool":
437
+ sub = rng.choices(
438
+ ["weather", "calculator", "time", "unit_convert", "search"],
439
+ weights=[0.28, 0.24, 0.20, 0.20, 0.08],
440
+ k=1,
441
+ )[0]
442
+ if sub == "calculator":
443
+ op = rng.choice(["+", "-", "*", "/"])
444
+ if op == "+":
445
+ a, b = rng.randint(10, 9999), rng.randint(10, 9999)
446
+ res = a + b
447
+ elif op == "-":
448
+ a, b = rng.randint(100, 9999), rng.randint(10, 99)
449
+ res = a - b
450
+ elif op == "*":
451
+ a, b = rng.randint(2, 250), rng.randint(2, 99)
452
+ res = a * b
453
+ else:
454
+ b = rng.randint(2, 50)
455
+ res = rng.randint(2, 200)
456
+ a = res * b
457
+
458
+ use_spaces = (rng.random() < 0.50)
459
+ expr_prompt = f"{a} {op} {b}" if use_spaces else f"{a}{op}{b}"
460
+ # Keep call expression identical to prompt expression or normalized so 1-hop pointer copies cleanly
461
+ call_expr = expr_prompt if rng.random() < 0.65 else f"{a}{op}{b}"
462
+ user_prompt = rng.choice([
463
+ f"Calculate {expr_prompt}{p_mark}",
464
+ f"What is {expr_prompt}{q_mark}",
465
+ f"Compute {expr_prompt}{p_mark}",
466
+ f"Evaluate {expr_prompt}{p_mark}",
467
+ f"Evaluate the mathematical expression {expr_prompt}{p_mark}",
468
+ f"Solve {expr_prompt}{p_mark}",
469
+ ])
470
+ thought = rng.choice([
471
+ "The user requests an arithmetic calculation. I will invoke the calculator tool.",
472
+ "The user is asking to evaluate a mathematical expression. Calling calculator.",
473
+ ])
474
+ call = f"calculator(expr=\"{call_expr}\")"
475
+ result = json.dumps({"result": res})
476
+ answer = f"The evaluated result of {call_expr} is {res}."
477
+
478
+ elif sub == "weather":
479
+ if rng.random() < 0.35:
480
+ # Nonce city to force pure copy induction
481
+ city = generate_nonce_entity(rng)[0].capitalize()
482
+ else:
483
+ city = rng.choice(CITIES)
484
+ temp = rng.randint(-5, 35)
485
+ cond = rng.choice(CONDITIONS)
486
+ user_prompt = rng.choice([
487
+ f"What is the weather in {city}{q_mark}",
488
+ f"What is the weather on {city}{q_mark}",
489
+ f"What is the weather for {city}{q_mark}",
490
+ f"What is the current weather forecast for {city}{q_mark}",
491
+ f"What is the weather like in {city}{q_mark}",
492
+ f"How is the weather in {city}{q_mark}",
493
+ f"How is the weather on {city}{q_mark}",
494
+ f"Weather in {city}{p_mark}",
495
+ f"Weather on {city}{p_mark}",
496
+ f"Weather for {city}{p_mark}",
497
+ f"Check the weather in {city}{p_mark}",
498
+ f"Check the weather for {city}{p_mark}",
499
+ f"Tell me the weather in {city}{p_mark}",
500
+ f"Forecast for {city}{p_mark}",
501
+ ])
502
+ thought = rng.choice([
503
+ "The user requests current weather information. I will query the get_weather tool.",
504
+ "The user is asking for weather conditions. Querying get_weather.",
505
+ ])
506
+ call = f"get_weather(city=\"{city}\")"
507
+ result = json.dumps({"temp": temp, "condition": cond, "units": "celsius"})
508
+ answer = f"The weather in {city} is currently {cond} with a temperature of {temp} degrees Celsius."
509
+
510
+ elif sub == "search":
511
+ search_mode = rng.random()
512
+ if search_mode < 0.50:
513
+ ent, plural, ent_type = generate_nonce_entity(rng)
514
+ snippet = f"a hypothetical {ent_type} characterized in general knowledge."
515
+ elif search_mode < 0.85 and bool(SEARCH_ENTITIES):
516
+ ent_item = rng.choice(SEARCH_ENTITIES)
517
+ ent = ent_item["entity"]
518
+ plural = ent_item.get("plural", ent + "s")
519
+ snippet = ent_item["snippet"]
520
+ ent_type = ent_item.get("type", "concept")
521
+ else:
522
+ ent = None
523
+
524
+ if ent is not None:
525
+ variant = rng.choice([
526
+ "what_are_plural",
527
+ "what_is_a",
528
+ "what_is",
529
+ "tell_me_about",
530
+ "explain",
531
+ "define",
532
+ ])
533
+
534
+ if variant == "what_are_plural":
535
+ user_prompt = f"What are {plural}{q_mark}"
536
+ search_q = plural
537
+ answer = f"{plural.capitalize()} are {snippet}"
538
+ elif variant == "what_is_a" and ent_type in ["plant", "fruit", "vegetable", "animal", "creature", "artifact", "instrument", "vehicle"]:
539
+ article = "an" if ent[0].lower() in "aeiou" else "a"
540
+ user_prompt = f"What is {article} {ent}{q_mark}"
541
+ search_q = ent
542
+ answer = f"{ent.capitalize()} is {snippet}"
543
+ elif variant == "tell_me_about":
544
+ user_prompt = f"Tell me about {ent}{p_mark}"
545
+ search_q = ent
546
+ answer = f"According to verified search results, {ent} is {snippet}"
547
+ elif variant == "define":
548
+ user_prompt = f"Define {ent}{p_mark}"
549
+ search_q = ent
550
+ answer = f"{ent.capitalize()} is defined as {snippet}"
551
+ elif variant == "explain":
552
+ user_prompt = f"Explain {ent}{p_mark}"
553
+ search_q = ent
554
+ answer = f"{ent.capitalize()} is {snippet}"
555
+ else:
556
+ user_prompt = f"What is {ent}{q_mark}"
557
+ search_q = ent
558
+ answer = f"{ent.capitalize()} is {snippet}"
559
+
560
+ thought = rng.choice([
561
+ "The user is asking a factual question. I will query the search tool.",
562
+ "The user requests factual information. Querying search.",
563
+ "The user is seeking knowledge on this topic. I will search the web.",
564
+ ])
565
+ call = f"search(query=\"{search_q}\")"
566
+ result = json.dumps({"snippet": snippet})
567
+ else:
568
+ topic, fact, _ = rng.choice(SEARCH_FACTS)
569
+ user_prompt = rng.choice([
570
+ f"Who or what is {topic}{q_mark}",
571
+ f"What is {topic}{q_mark}",
572
+ f"Tell me about {topic}{p_mark}",
573
+ ])
574
+ thought = "The user is asking a factual question. I will query the search tool."
575
+ call = f"search(query=\"{topic}\")"
576
+ result = json.dumps({"snippet": fact})
577
+ answer = f"According to verified search results, {topic} is {fact}."
578
+
579
+ elif sub == "time":
580
+ tz = rng.choice(TIMEZONES)
581
+ hr = rng.randint(0, 23)
582
+ mn = rng.randint(0, 59)
583
+ time_str = f"{hr:02d}:{mn:02d}"
584
+ user_prompt = rng.choice([
585
+ f"What is the current time in {tz}{q_mark}",
586
+ f"What is the time in {tz}{q_mark}",
587
+ f"What time is it in {tz}{q_mark}",
588
+ f"Current time in {tz}{p_mark}",
589
+ f"Time in {tz}{p_mark}",
590
+ f"Tell me the time in {tz}{p_mark}",
591
+ f"Check the time for {tz}{p_mark}",
592
+ ])
593
+ thought = rng.choice([
594
+ "The user requests the current time for a timezone. I will query the get_time tool.",
595
+ "The user is asking for the current time. Querying get_time.",
596
+ ])
597
+ call = f"get_time(timezone=\"{tz}\")"
598
+ result = json.dumps({"time": time_str, "timezone": tz})
599
+ answer = f"The current time in {tz} is {time_str}."
600
+
601
+ else: # unit_convert
602
+ val = rng.randint(5, 500)
603
+ from_u, to_u, factor = rng.choice([
604
+ ("km", "miles", 0.621371),
605
+ ("miles", "km", 1.60934),
606
+ ("kg", "lbs", 2.20462),
607
+ ("lbs", "kg", 0.453592),
608
+ ])
609
+ converted = round(val * factor, 2)
610
+ user_prompt = rng.choice([
611
+ f"Convert {val} {from_u} to {to_u}{p_mark}",
612
+ f"How many {to_u} is {val} {from_u}{q_mark}",
613
+ f"How many {to_u} are in {val} {from_u}{q_mark}",
614
+ f"What is {val} {from_u} in {to_u}{q_mark}",
615
+ f"Change {val} {from_u} into {to_u}{p_mark}",
616
+ ])
617
+ thought = rng.choice([
618
+ "The user requests a unit conversion. I will invoke the unit_convert tool.",
619
+ "The user is asking to convert between measurement units. Calling unit_convert.",
620
+ ])
621
+ call = f"unit_convert(value={val}, from_unit=\"{from_u}\", to_unit=\"{to_u}\")"
622
+ result = json.dumps({"value": converted, "unit": to_u})
623
+ answer = f"{val} {from_u} is equal to {converted} {to_u}."
624
+
625
+ full_text = (
626
+ f"<USER>{user_prompt}</USER>"
627
+ f"<THINK>{thought}</THINK>"
628
+ f"<CALL>{call}</CALL>"
629
+ f"<RESULT>{result}</RESULT>"
630
+ f"<ANSWER>{answer}</ANSWER>"
631
+ )
632
+
633
+ elif category == "multi_tool_chain":
634
+ # Chained sequential reasoning: Step 1 -> Tool 1 -> Result 1 -> Step 2 -> Tool 2 -> Result 2 -> Final Answer
635
+ chain_type = rng.choice(["fact_calc", "dual_weather", "tz_calc"])
636
+ if chain_type == "fact_calc":
637
+ pop = 14000000
638
+ area = 2194
639
+ density = round(pop / area, 2)
640
+ user_prompt = f"What is the population density of Tokyo based on its population and area of 2194 sq km{q_mark}"
641
+ thought1 = "First, I need to search for Tokyo's population."
642
+ call1 = "search(query=\"Tokyo population\")"
643
+ result1 = json.dumps({"population": pop})
644
+ thought2 = "Now I will calculate the population density using the calculator tool."
645
+ call2 = f"calculator(expr=\"{pop} / {area}\")"
646
+ result2 = json.dumps({"result": density})
647
+ answer = f"Tokyo's population is {pop:,}. Dividing by 2,194 sq km gives a density of approximately {density} people per sq km."
648
+
649
+ elif chain_type == "dual_weather":
650
+ c1, c2 = rng.sample(CITIES, 2)
651
+ t1, t2 = rng.randint(0, 30), rng.randint(0, 30)
652
+ diff = abs(t1 - t2)
653
+ user_prompt = rng.choice([
654
+ f"Compare the temperature between {c1} and {c2}{p_mark}",
655
+ f"Compare the weather in {c1} and {c2}{p_mark}",
656
+ f"What is the temperature difference between {c1} and {c2}{q_mark}",
657
+ ])
658
+ thought1 = "I will first query the weather for the first city."
659
+ call1 = f"get_weather(city=\"{c1}\")"
660
+ result1 = json.dumps({"temp": t1, "condition": "Cloudy"})
661
+ thought2 = "Next, I will query the weather for the second city."
662
+ call2 = f"get_weather(city=\"{c2}\")"
663
+ result2 = json.dumps({"temp": t2, "condition": "Sunny"})
664
+ answer = f"{c1} is {t1}C and {c2} is {t2}C, with a temperature difference of {diff} degrees."
665
+
666
+ else: # tz_calc
667
+ tz1, tz2 = rng.sample(TIMEZONES[:6], 2)
668
+ user_prompt = f"What is the time difference between {tz1} and {tz2}{q_mark}"
669
+ thought1 = "First, I will query the current time in the first timezone."
670
+ call1 = f"get_time(timezone=\"{tz1}\")"
671
+ result1 = json.dumps({"time": "08:00", "utc_offset": -5})
672
+ thought2 = "Next, I will query the current time in the second timezone."
673
+ call2 = f"get_time(timezone=\"{tz2}\")"
674
+ result2 = json.dumps({"time": "22:00", "utc_offset": 9})
675
+ answer = f"{tz1} is UTC-5 and {tz2} is UTC+9, representing a 14-hour time difference."
676
+
677
+ full_text = (
678
+ f"<USER>{user_prompt}</USER>"
679
+ f"<THINK>{thought1}</THINK>"
680
+ f"<CALL>{call1}</CALL>"
681
+ f"<RESULT>{result1}</RESULT>"
682
+ f"<THINK>{thought2}</THINK>"
683
+ f"<CALL>{call2}</CALL>"
684
+ f"<RESULT>{result2}</RESULT>"
685
+ f"<ANSWER>{answer}</ANSWER>"
686
+ )
687
+
688
+ elif category == "nested_json_schema":
689
+ # Complex tools with nested dictionaries, lists, and boolean flags
690
+ schema_type = rng.choice(["db_query", "event_create", "file_manage", "api_fetch"])
691
+ if schema_type == "db_query":
692
+ dept = rng.choice(DEPARTMENTS)
693
+ limit = rng.randint(3, 10)
694
+ user_prompt = rng.choice([
695
+ f"Query the top {limit} active records in the {dept} department{p_mark}",
696
+ f"Query active records in the {dept} department with limit {limit}{p_mark}",
697
+ f"Fetch the top {limit} active entries from {dept}{p_mark}",
698
+ ])
699
+ thought = "I will invoke database_query with a nested filter and options schema."
700
+ call = f"database_query(filter={{\"department\": \"{dept}\", \"status\": \"active\"}}, options={{\"limit\": {limit}, \"sort\": \"desc\"}})"
701
+ result = json.dumps({"status": "success", "count": limit, "records": [{"id": 100 + i, "dept": dept} for i in range(min(limit, 3))]})
702
+ answer = f"Found {limit} active records in {dept} according to your filter specifications."
703
+
704
+ elif schema_type == "event_create":
705
+ attendees = ["alice@corp.internal", "bob@corp.internal"]
706
+ user_prompt = f"Schedule a Sprint Sync meeting for tomorrow from 10:00 to 11:00 UTC with Alice and Bob{p_mark}"
707
+ thought = "I need to call create_event with a nested event schedule and attendee list."
708
+ call = "create_event(event={\"title\": \"Sprint Sync\", \"schedule\": {\"date\": \"2026-11-01\", \"start\": \"10:00\", \"end\": \"11:00\", \"tz\": \"UTC\"}}, attendees=[\"alice@corp.internal\", \"bob@corp.internal\"])"
709
+ result = json.dumps({"event_id": "evt_98214", "status": "confirmed"})
710
+ answer = "Successfully scheduled the Sprint Sync meeting with Alice and Bob for 10:00-11:00 UTC (Event ID: evt_98214)."
711
+
712
+ elif schema_type == "file_manage":
713
+ path = "/configs/model_t4.json"
714
+ user_prompt = f"Save model configuration to {path} with fp16 enabled and 2 workers{p_mark}"
715
+ thought = "Calling file_manager with action write and a nested file payload."
716
+ call = f"file_manager(action=\"write\", file={{\"path\": \"{path}\", \"format\": \"json\"}}, content={{\"fp16\": true, \"workers\": 2}})"
717
+ result = json.dumps({"status": "ok", "bytes_written": 48})
718
+ answer = f"Configuration file has been written to {path} with fp16=True and 2 workers."
719
+
720
+ else: # api_fetch
721
+ endpoint = "/v2/metrics/latency"
722
+ user_prompt = f"Fetch latency metrics for EU region from {endpoint}{p_mark}"
723
+ thought = "Calling fetch_api with endpoint and nested filter parameters."
724
+ call = f"fetch_api(endpoint=\"{endpoint}\", params={{\"region\": \"EU\", \"metric\": \"p99\"}}, method=\"GET\")"
725
+ result = json.dumps({"status": 200, "data": {"p99_ms": 14.2}})
726
+ answer = f"Retrieved p99 latency for EU region from {endpoint}: 14.2 ms."
727
+
728
+ full_text = (
729
+ f"<USER>{user_prompt}</USER>"
730
+ f"<THINK>{thought}</THINK>"
731
+ f"<CALL>{call}</CALL>"
732
+ f"<RESULT>{result}</RESULT>"
733
+ f"<ANSWER>{answer}</ANSWER>"
734
+ )
735
+
736
+ elif category == "error_recovery":
737
+ # Robust error reasoning & recovery paths
738
+ err_type = rng.choice(["div_zero", "city_not_found", "api_503_fallback", "table_missing"])
739
+ if err_type == "div_zero":
740
+ num = rng.choice([100, 150, 250, 500])
741
+ user_prompt = f"Calculate {num} / 0"
742
+ thought1 = "The user requests an arithmetic calculation. I will invoke the calculator tool."
743
+ call1 = f"calculator(expr=\"{num}/0\")"
744
+ result1 = json.dumps({"error": "ZeroDivisionError: division by zero"})
745
+ thought2 = "The calculator returned ZeroDivisionError. Division by zero is mathematically undefined. I will explain this clearly."
746
+ answer = "Error: Division by zero is undefined in mathematics. Please specify a non-zero denominator."
747
+ full_text = (
748
+ f"<USER>{user_prompt}</USER>"
749
+ f"<THINK>{thought1}</THINK>"
750
+ f"<CALL>{call1}</CALL>"
751
+ f"<RESULT>{result1}</RESULT>"
752
+ f"<THINK>{thought2}</THINK>"
753
+ f"<ANSWER>{answer}</ANSWER>"
754
+ )
755
+
756
+ elif err_type == "city_not_found":
757
+ city = rng.choice(["Atlantis", "Narnia"])
758
+ user_prompt = f"What is the weather in {city}{q_mark}"
759
+ thought1 = "The user requests current weather information. I will query the get_weather tool."
760
+ call1 = f"get_weather(city=\"{city}\")"
761
+ result1 = json.dumps({"status": "error", "code": 404, "message": f"City '{city}' not found"})
762
+ thought2 = "get_weather returned 404. The requested city is not recognized. I must inform the user."
763
+ answer = f"I could not retrieve weather data for {city} because it is not recognized in our weather database."
764
+ full_text = (
765
+ f"<USER>{user_prompt}</USER>"
766
+ f"<THINK>{thought1}</THINK>"
767
+ f"<CALL>{call1}</CALL>"
768
+ f"<RESULT>{result1}</RESULT>"
769
+ f"<THINK>{thought2}</THINK>"
770
+ f"<ANSWER>{answer}</ANSWER>"
771
+ )
772
+
773
+ elif err_type == "api_503_fallback":
774
+ city = "Tokyo"
775
+ user_prompt = f"Check the primary API forecast for {city}{p_mark}"
776
+ thought1 = "Attempting primary weather API fetch."
777
+ call1 = f"fetch_api(endpoint=\"/v1/weather\", params={{\"city\": \"{city}\"}}, method=\"GET\")"
778
+ result1 = json.dumps({"status": "error", "code": 503, "message": "Service Unavailable"})
779
+ thought2 = "Primary API failed with 503. Recovering via fallback tool get_weather."
780
+ call2 = f"get_weather(city=\"{city}\")"
781
+ result2 = json.dumps({"temp": 22, "condition": "Sunny"})
782
+ answer = "Primary service was temporarily unavailable, but fallback weather confirmed Tokyo is Sunny at 22C."
783
+ full_text = (
784
+ f"<USER>{user_prompt}</USER>"
785
+ f"<THINK>{thought1}</THINK>"
786
+ f"<CALL>{call1}</CALL>"
787
+ f"<RESULT>{result1}</RESULT>"
788
+ f"<THINK>{thought2}</THINK>"
789
+ f"<CALL>{call2}</CALL>"
790
+ f"<RESULT>{result2}</RESULT>"
791
+ f"<ANSWER>{answer}</ANSWER>"
792
+ )
793
+
794
+ else: # table_missing
795
+ user_prompt = f"Query records from table 'legacy_metrics'{p_mark}"
796
+ thought1 = "Querying database for the requested table."
797
+ call1 = "database_query(filter={\"table\": \"legacy_metrics\"}, options={\"limit\": 5})"
798
+ result1 = json.dumps({"status": "error", "code": "TABLE_NOT_FOUND", "message": "Table does not exist"})
799
+ thought2 = "TABLE_NOT_FOUND error encountered. Informing user of invalid table name."
800
+ answer = "The requested database table 'legacy_metrics' was not found. Please verify table name."
801
+ full_text = (
802
+ f"<USER>{user_prompt}</USER>"
803
+ f"<THINK>{thought1}</THINK>"
804
+ f"<CALL>{call1}</CALL>"
805
+ f"<RESULT>{result1}</RESULT>"
806
+ f"<THINK>{thought2}</THINK>"
807
+ f"<ANSWER>{answer}</ANSWER>"
808
+ )
809
+
810
+ elif category == "multi_turn":
811
+ # Multi-turn conversational dialogue with state continuity
812
+ city = rng.choice(CITIES)
813
+ temp = rng.randint(10, 25)
814
+ w_q = rng.choice([
815
+ f"How is the weather in {city}{q_mark}",
816
+ f"What is the weather in {city}{q_mark}",
817
+ f"What is the weather on {city}{q_mark}",
818
+ ])
819
+ full_text = (
820
+ f"<USER>Hi there!</USER>"
821
+ f"<THINK>Conversational greeting without tool.</THINK>"
822
+ f"<ANSWER>Hello! How can I assist you today?</ANSWER>"
823
+ f"<USER>{w_q}</USER>"
824
+ f"<THINK>The user requests current weather information. I will query the get_weather tool.</THINK>"
825
+ f"<CALL>get_weather(city=\"{city}\")</CALL>"
826
+ f"<RESULT>{{\"temp\": {temp}, \"condition\": \"Clear\"}}</RESULT>"
827
+ f"<ANSWER>{city} is currently Clear with a temperature of {temp} degrees Celsius.</ANSWER>"
828
+ f"<USER>Thank you!</USER>"
829
+ f"<THINK>User acknowledges. Polite closing response.</THINK>"
830
+ f"<ANSWER>You are very welcome! Let me know if you need anything else.</ANSWER>"
831
+ )
832
+
833
+ else: # no_tool
834
+ greeting = rng.choice([
835
+ ("Hello! How are you?", "I am doing well, thank you! How may I assist you today?"),
836
+ ("Hi there!", "Hello! What can I help you with today?"),
837
+ ("Thank you for your assistance.", "You are welcome! Let me know if you have any more questions."),
838
+ ("Thank you!", "You are very welcome! Let me know if you need anything else."),
839
+ ("Good morning!", "Good morning! How may I help you?"),
840
+ ("Who are you?", "I am ScaledGPT, an AI assistant with structured tool-calling and high-digit arithmetic capabilities."),
841
+ ("Explain the concept of gradient descent.", "Gradient descent is an optimization algorithm that iteratively moves model weights in the direction of steepest loss decrease."),
842
+ ])
843
+ user_prompt, ans = greeting
844
+ thought = "Conversational request that does not require any tool. I will answer directly."
845
+ full_text = (
846
+ f"<USER>{user_prompt}</USER>"
847
+ f"<THINK>{thought}</THINK>"
848
+ f"<ANSWER>{ans}</ANSWER>"
849
+ )
850
+
851
+ return {
852
+ "category": category,
853
+ "full_text": full_text,
854
+ }
855
+
856
+
857
+ def generate_tool_dataset(n_samples: int = 4000, seed: int = 1337) -> List[Dict[str, str]]:
858
+ """Generates an expanded tool interaction dataset across all 6 categories."""
859
+ rng = random.Random(seed)
860
+ dataset: List[Dict[str, str]] = []
861
+ for _ in range(n_samples):
862
+ ex = generate_tool_example(rng)
863
+ full_text = ex["full_text"]
864
+ prompt = full_text.split("</USER>")[0] + "</USER>"
865
+ dataset.append({
866
+ "task_type": "tools",
867
+ "prompt": prompt,
868
+ "full_text": full_text,
869
+ "category": ex["category"],
870
+ })
871
+ return dataset
872
+
873
+
874
+ def generate_joint_dataset(
875
+ n_math: int = 4000,
876
+ n_tools: int = 4000,
877
+ seed: int = 42,
878
+ max_train_digits: int = 15,
879
+ ) -> Tuple[List[Dict[str, str]], List[Dict[str, str]]]:
880
+ """Generates dual datasets for math curriculum and expanded tools."""
881
+ math_samples = generate_arithmetic_equations(n_samples=n_math, seed=seed, max_train_digits=max_train_digits)
882
+ tool_samples = generate_tool_dataset(n_samples=n_tools, seed=seed + 100)
883
+ return math_samples, tool_samples
884
+
885
+
886
+ # -----------------------------------------------------------------------------
887
+ # 3. Batch Encoder & Strict Loss Masker
888
+ # -----------------------------------------------------------------------------
889
+
890
+ def encode_joint_batch(
891
+ math_items: List[Dict[str, str]],
892
+ tool_items: List[Dict[str, str]],
893
+ tokenizer: ScaledTokenizer,
894
+ max_len: Optional[int] = None,
895
+ device: Optional[torch.device] = None,
896
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
897
+ """
898
+ Encodes balanced joint batch of math and tool sequences.
899
+ Strict Prompt & Environment Loss Masking:
900
+ - Math: masks everything up to and including '<THINK>' (loss on scratchpad + answer).
901
+ - Tools:
902
+ - Masks everything inside '<USER>...</USER>' except at '</USER>' (where target is '<THINK>').
903
+ - Masks everything inside '<RESULT>...</RESULT>' except at '</RESULT>' (where target is '<THINK>' or '<ANSWER>').
904
+ - Supervised: reasoning traces '<THINK>', tool calls '<CALL>', and answers '<ANSWER>'.
905
+ """
906
+ items = math_items + tool_items
907
+ B = len(items)
908
+
909
+ encoded_seqs = [tokenizer.encode(item["full_text"], add_bos=True, add_eos=True) for item in items]
910
+ calc_max_len = max(len(s) for s in encoded_seqs) - 1
911
+ if max_len is not None:
912
+ batch_max = min(calc_max_len, max_len)
913
+ else:
914
+ batch_max = calc_max_len
915
+
916
+ x_tensor = torch.full((B, batch_max), tokenizer.pad_id, dtype=torch.long)
917
+ y_tensor = torch.full((B, batch_max), -100, dtype=torch.long)
918
+
919
+ think_s_id = tokenizer.think_s_id
920
+ user_s_id = tokenizer.user_s_id
921
+ user_e_id = tokenizer.user_e_id
922
+ result_s_id = tokenizer.result_s_id
923
+ result_e_id = tokenizer.result_e_id
924
+
925
+ for i, (item, tokens) in enumerate(zip(items, encoded_seqs)):
926
+ # Truncate if exceeding batch_max + 1
927
+ if len(tokens) > batch_max + 1:
928
+ tokens = tokens[: batch_max + 1]
929
+
930
+ n = len(tokens) - 1
931
+ t_tok = torch.tensor(tokens, dtype=torch.long)
932
+ x_tensor[i, :n] = t_tok[:-1]
933
+ y_tensor[i, :n] = t_tok[1:].clone()
934
+
935
+ if item["task_type"] == "math":
936
+ # Mask prompt up to '<THINK>'
937
+ think_matches = (t_tok[:-1] == think_s_id).nonzero(as_tuple=True)[0]
938
+ if len(think_matches) > 0:
939
+ mask_until = think_matches[0].item()
940
+ y_tensor[i, :mask_until] = -100
941
+ else:
942
+ # Segment-aware masking for single-turn, multi-turn, and tool responses
943
+ mask = torch.zeros(n, dtype=torch.bool)
944
+ in_user = False
945
+ in_result = False
946
+
947
+ for pos in range(n):
948
+ tok_id = t_tok[pos].item()
949
+ if tok_id == user_s_id:
950
+ in_user = True
951
+ elif tok_id == result_s_id:
952
+ in_result = True
953
+
954
+ if in_user:
955
+ if tok_id == user_e_id:
956
+ in_user = False
957
+ mask[pos] = False
958
+ else:
959
+ mask[pos] = True
960
+ elif in_result:
961
+ if tok_id == result_e_id:
962
+ in_result = False
963
+ mask[pos] = False
964
+ else:
965
+ mask[pos] = True
966
+ elif pos == 0:
967
+ # BOS token preceding <USER>
968
+ mask[pos] = True
969
+
970
+ y_tensor[i, :n][mask] = -100
971
+
972
+ if device is not None:
973
+ x_tensor = x_tensor.to(device)
974
+ y_tensor = y_tensor.to(device)
975
+
976
+ return x_tensor, y_tensor
interactive.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Interactive CLI Runner for ScaledGPT (~18.95M Parameter Recurrent 2D-RoPE Transformer).
4
+ Supports live interactive inference across:
5
+ 1. High-digit column scratchpad arithmetic (up to 50 digits).
6
+ 2. Expanded tool execution (Single-tool, Chaining, Nested JSON schemas, Error handling).
7
+ 3. Multi-turn interactive dialogues.
8
+ """
9
+
10
+ import os
11
+ import sys
12
+ import re
13
+ import json
14
+ import time
15
+ import argparse
16
+ from typing import Tuple, Optional, Dict, Any
17
+
18
+ import torch
19
+
20
+ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
21
+ if ROOT_DIR not in sys.path:
22
+ sys.path.insert(0, ROOT_DIR)
23
+ THIS_DIR = os.path.dirname(os.path.abspath(__file__))
24
+ if THIS_DIR not in sys.path:
25
+ sys.path.insert(0, THIS_DIR)
26
+
27
+ if hasattr(sys.stdout, "reconfigure"):
28
+ sys.stdout.reconfigure(line_buffering=True)
29
+
30
+ try:
31
+ from champion_model.scaled_t4.model import ScaledGPT, ScaledGPTConfig
32
+ from champion_model.scaled_t4.tokenizer import ScaledTokenizer
33
+ from champion_model.scaled_t4.dataset import extract_scratchpad_answer
34
+ except ImportError:
35
+ from model import ScaledGPT, ScaledGPTConfig
36
+ from tokenizer import ScaledTokenizer
37
+ from dataset import extract_scratchpad_answer
38
+
39
+
40
+ def mock_execute_tool(call_text: str) -> str:
41
+ """Executes or mocks tool calls parsed from <CALL>...</CALL>."""
42
+ # 1. calculator(expr="...")
43
+ calc_m = re.match(r'calculator\(expr="([^"]+)"\)', call_text)
44
+ if calc_m:
45
+ expr = calc_m.group(1)
46
+ try:
47
+ allowed = re.match(r"^[\d\+\-\*\/\(\)\s\.]+$", expr)
48
+ if allowed:
49
+ val = eval(expr)
50
+ return json.dumps({"result": val})
51
+ except ZeroDivisionError:
52
+ return json.dumps({"error": "ZeroDivisionError: division by zero"})
53
+ except Exception as e:
54
+ return json.dumps({"error": str(e)})
55
+
56
+ # 2. get_weather(city="...")
57
+ w_m = re.match(r'get_weather\(city="([^"]+)"\)', call_text)
58
+ if w_m:
59
+ city = w_m.group(1)
60
+ if city.lower() in ["atlantis", "narnia"]:
61
+ return json.dumps({"status": "error", "code": 404, "message": f"City '{city}' not found"})
62
+ return json.dumps({"temp": 22, "condition": "Sunny", "city": city})
63
+
64
+ # 3. get_time(timezone="...")
65
+ t_m = re.match(r'get_time\(timezone="([^"]+)"\)', call_text)
66
+ if t_m:
67
+ tz = t_m.group(1)
68
+ return json.dumps({"time": "14:30", "timezone": tz})
69
+
70
+ # 4. unit_convert
71
+ u_m = re.match(r'unit_convert\(value=([\d\.]+),\s*from_unit="([^"]+)",\s*to_unit="([^"]+)"\)', call_text)
72
+ if u_m:
73
+ val = float(u_m.group(1))
74
+ f_u, t_u = u_m.group(2), u_m.group(3)
75
+ factors = {
76
+ ("km", "miles"): 0.621371,
77
+ ("miles", "km"): 1.60934,
78
+ ("kg", "lbs"): 2.20462,
79
+ ("lbs", "kg"): 0.453592,
80
+ }
81
+ factor = factors.get((f_u, t_u), 1.0)
82
+ return json.dumps({"value": round(val * factor, 2), "unit": t_u})
83
+
84
+ # 5. database_query
85
+ if call_text.startswith("database_query("):
86
+ return json.dumps({"status": "success", "count": 3, "records": [{"id": 101, "status": "active"}]})
87
+
88
+ # 6. create_event
89
+ if call_text.startswith("create_event("):
90
+ return json.dumps({"event_id": "evt_98214", "status": "confirmed"})
91
+
92
+ # 7. file_manager
93
+ if call_text.startswith("file_manager("):
94
+ return json.dumps({"status": "ok", "bytes_written": 64})
95
+
96
+ # 8. fetch_api
97
+ if call_text.startswith("fetch_api("):
98
+ return json.dumps({"status": 200, "data": {"status": "healthy"}})
99
+
100
+ # 9. search(query="...")
101
+ s_m = re.match(r'search\(query="([^"]+)"\)', call_text)
102
+ if s_m:
103
+ query = s_m.group(1).strip()
104
+ q_lower = query.lower()
105
+
106
+ # Offline dictionary lookup for instant, deterministic testing
107
+ kb = {
108
+ "tomato": "the edible berry of the plant Solanum lycopersicum, commonly prepared as a culinary vegetable.",
109
+ "tomatoes": "the edible berry of the plant Solanum lycopersicum, commonly prepared as a culinary vegetable.",
110
+ "apple": "a round, edible fruit produced by an apple tree (Malus domestica), cultivated worldwide.",
111
+ "apples": "a round, edible fruit produced by an apple tree (Malus domestica), cultivated worldwide.",
112
+ "potato": "a starchy, tuberous root crop of the nightshade family Solanaceae.",
113
+ "potatoes": "a starchy, tuberous root crop of the nightshade family Solanaceae.",
114
+ "coffee": "a brewed drink prepared from roasted coffee beans of flowering Coffea species.",
115
+ "banana": "an elongated, edible fruit produced by several kinds of large herbaceous plants in the genus Musa.",
116
+ "bananas": "an elongated, edible fruit produced by several kinds of large herbaceous plants in the genus Musa.",
117
+ "gravity": "a fundamental interaction causing mutual attraction between all things with mass or energy.",
118
+ "photosynthesis": "a process used by plants to convert light energy into chemical energy stored in glucose.",
119
+ "atom": "the basic unit of a chemical element, consisting of a dense central nucleus surrounded by electrons.",
120
+ "python creator": "Guido van Rossum, a Dutch programmer.",
121
+ "speed of light": "approximately 299,792 kilometers per second in a vacuum.",
122
+ "mount everest": "Earth's highest mountain above sea level, located in the Himalayas.",
123
+ }
124
+
125
+ for k, v in kb.items():
126
+ if k in q_lower or q_lower in k:
127
+ return json.dumps({"snippet": v})
128
+
129
+ # Try live Wikipedia REST API if network is available
130
+ try:
131
+ import urllib.request
132
+ import urllib.parse
133
+ clean_q = urllib.parse.quote(query.replace(" ", "_"))
134
+ url = f"https://en.wikipedia.org/api/rest_v1/page/summary/{clean_q}"
135
+ req = urllib.request.Request(url, headers={"User-Agent": "ScaledGPT-Search/1.0"})
136
+ with urllib.request.urlopen(req, timeout=2.5) as resp:
137
+ data = json.loads(resp.read().decode("utf-8"))
138
+ if "extract" in data and data["extract"]:
139
+ return json.dumps({"snippet": data["extract"][:220]})
140
+ except Exception:
141
+ pass
142
+
143
+ return json.dumps({"snippet": f"{query.capitalize()} is an entity and topic in verified knowledge sources."})
144
+
145
+ # Fallback
146
+ return json.dumps({"status": "executed", "call": call_text})
147
+
148
+
149
+ def print_human_readable_output(text: str, is_math: bool = False):
150
+ """Formats internal model output into clean, human-readable terminal blocks."""
151
+ print("\n" + "-" * 80)
152
+ if is_math:
153
+ m = re.search(r"(?:<BOS>)?(.*?)=(?:<THINK>)?(.*?)(?:</THINK>=)(.*?)(?:<EOS>|$)", text)
154
+ if m:
155
+ problem = m.group(1).strip()
156
+ scratch = m.group(2).strip()
157
+ ans = m.group(3).strip()
158
+ print(f"Problem: {problem} = ?")
159
+ print(f"Scratchpad: {scratch}")
160
+ print(f"Answer: {ans}")
161
+ try:
162
+ if "+" in problem:
163
+ parts = problem.split("+")
164
+ if len(parts) == 2 and parts[0].isdigit() and parts[1].isdigit():
165
+ expected = int(parts[0]) + int(parts[1])
166
+ status = "[PASS]" if str(expected) == ans else "[FAIL]"
167
+ print(f"Validation: {status} (Expected: {expected})")
168
+ except Exception:
169
+ pass
170
+ else:
171
+ print(f"Result: {text}")
172
+ else:
173
+ think_m = re.search(r"<THINK>(.*?)</THINK>", text)
174
+ call_m = re.search(r"<CALL>(.*?)</CALL>", text)
175
+ result_m = re.search(r"<RESULT>(.*?)</RESULT>", text)
176
+ answer_m = re.search(r"<ANSWER>(.*?)</ANSWER>", text)
177
+
178
+ if think_m:
179
+ print(f"Reasoning: {think_m.group(1).strip()}")
180
+ if call_m:
181
+ print(f"Tool Call: {call_m.group(1).strip()}")
182
+ if result_m:
183
+ print(f"Tool Result: {result_m.group(1).strip()}")
184
+ if answer_m:
185
+ print(f"Response: {answer_m.group(1).strip()}")
186
+ if not any([think_m, call_m, result_m, answer_m]):
187
+ print(f"Response: {text}")
188
+
189
+ print("-" * 80)
190
+ print(f"[RAW TOKENS] {text}\n", flush=True)
191
+
192
+
193
+ def run_math_prompt(model: ScaledGPT, tokenizer: ScaledTokenizer, prompt: str, device: torch.device) -> str:
194
+ """Processes a multi-digit arithmetic expression."""
195
+ clean_prompt = prompt.strip()
196
+ if not clean_prompt.endswith("="):
197
+ clean_prompt += "="
198
+ full_prompt = clean_prompt + "<THINK>"
199
+
200
+ tokens = tokenizer.encode(full_prompt, add_bos=True, add_eos=False)
201
+ x = torch.tensor([tokens], dtype=torch.long, device=device)
202
+
203
+ with torch.no_grad():
204
+ out = model.generate(x, max_new_tokens=700, temperature=0.0, stop_tokens=[tokenizer.eos_id])
205
+
206
+ gen_text = tokenizer.decode(out[0].tolist(), skip_special=False)
207
+ print_human_readable_output(gen_text, is_math=True)
208
+ return gen_text
209
+
210
+
211
+ def run_dialogue_prompt(model: ScaledGPT, tokenizer: ScaledTokenizer, user_input: str, device: torch.device) -> str:
212
+ """Processes a user dialogue turn with live tool execution."""
213
+ prompt = f"<USER>{user_input}</USER><THINK>"
214
+ tokens = tokenizer.encode(prompt, add_bos=True, add_eos=False)
215
+ x = torch.tensor([tokens], dtype=torch.long, device=device)
216
+
217
+ with torch.no_grad():
218
+ stop_ids = [tokenizer.call_e_id, tokenizer.answer_e_id, tokenizer.eos_id]
219
+ out = model.generate(x, max_new_tokens=256, temperature=0.0, stop_tokens=stop_ids)
220
+
221
+ gen_text = tokenizer.decode(out[0].tolist(), skip_special=False)
222
+
223
+ if "<CALL>" in gen_text and "</CALL>" in gen_text:
224
+ call_part = gen_text.split("<CALL>")[1].split("</CALL>")[0]
225
+ result_str = mock_execute_tool(call_part)
226
+
227
+ # Continue generation with the tool result (append tokens directly to avoid double BOS)
228
+ res_suffix = f"<RESULT>{result_str}</RESULT>"
229
+ res_tokens = tokenizer.encode(res_suffix, add_bos=False, add_eos=False)
230
+ c_x = torch.cat([out, torch.tensor([res_tokens], dtype=torch.long, device=device)], dim=1)
231
+
232
+ with torch.no_grad():
233
+ out2 = model.generate(c_x, max_new_tokens=256, temperature=0.0, stop_tokens=[tokenizer.answer_e_id, tokenizer.eos_id])
234
+
235
+ full_dialogue = tokenizer.decode(out2[0].tolist(), skip_special=False)
236
+ else:
237
+ full_dialogue = gen_text
238
+
239
+ print_human_readable_output(full_dialogue, is_math=False)
240
+ return full_dialogue
241
+
242
+
243
+ def main():
244
+ parser = argparse.ArgumentParser(description="ScaledGPT Interactive CLI")
245
+ parser.add_argument("--ckpt", type=str, default="champion_model/scaled_t4/checkpoints/champion_final.pt", help="Path to checkpoint")
246
+ parser.add_argument("--prompt", type=str, default=None, help="Execute a single prompt and exit")
247
+ parser.add_argument("--threads", type=int, default=8, help="Number of CPU threads for inference")
248
+ args = parser.parse_args()
249
+
250
+ device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
251
+ if device.type == "cpu":
252
+ torch.set_num_threads(args.threads)
253
+ tokenizer = ScaledTokenizer()
254
+ config = ScaledGPTConfig(vocab_size=tokenizer.vocab_size)
255
+ model = ScaledGPT(config, tokenizer=tokenizer).to(device)
256
+
257
+ # Search candidate paths for the trained checkpoint
258
+ candidates = [
259
+ args.ckpt,
260
+ os.path.join(ROOT_DIR, args.ckpt),
261
+ os.path.join(ROOT_DIR, "champion_model/scaled_t4/checkpoints/champion_final.pt"),
262
+ os.path.join(ROOT_DIR, "champion_model/checkpoints/champion_final.pt"),
263
+ os.path.join(ROOT_DIR, "champion_model/scaled_t4/checkpoints/scaled_t4_sft_all_tools.pt"),
264
+ os.path.join(ROOT_DIR, "champion_model/scaled_t4/checkpoints/scaled_t4_sft_search.pt"),
265
+ os.path.join(ROOT_DIR, "champion_model/scaled_t4/checkpoints/scaled_t4_final.pt"),
266
+ os.path.join(ROOT_DIR, "champion_model/scaled_t4/checkpoints/scaled_t4_final_timeout_step_45059.pt"),
267
+ os.path.join(ROOT_DIR, "champion_model/checkpoints/scaled_t4_sft_search_step_7000.zip"),
268
+ os.path.join(ROOT_DIR, "champion_model/checkpoints/scaled_t4_final.zip"),
269
+ os.path.join(ROOT_DIR, "champion_model/checkpoints/scaled_t4_final_timeout_step_45059.zip"),
270
+ ]
271
+ resolved_path = None
272
+ for p in candidates:
273
+ if os.path.exists(p) and not os.path.isdir(p):
274
+ resolved_path = p
275
+ break
276
+
277
+ if resolved_path is not None:
278
+ ckpt = torch.load(resolved_path, map_location=device, weights_only=False)
279
+ model.load_state_dict(ckpt["model_state_dict"])
280
+ step_info = f" (Step {ckpt.get('step')})" if 'step' in ckpt else ""
281
+ print(f"[INFO] Successfully loaded trained checkpoint{step_info} from: {resolved_path}")
282
+ else:
283
+ print(f"[WARN] Checkpoint not found in candidate paths. Running with initialized weights.")
284
+
285
+ model.eval()
286
+
287
+ if args.prompt:
288
+ user_input = args.prompt.strip()
289
+ is_math = bool(re.match(r"^[\d\s\+\-\=]+$", user_input) and ("+" in user_input or "-" in user_input))
290
+ if is_math:
291
+ run_math_prompt(model, tokenizer, user_input, device)
292
+ else:
293
+ run_dialogue_prompt(model, tokenizer, user_input, device)
294
+ return
295
+
296
+ print("\n" + "=" * 80)
297
+ print("SCALEDGPT (~18.95M) INTERACTIVE TERMINAL")
298
+ print("Type an arithmetic equation (e.g. '123456789+987654321=') or ask a question.")
299
+ print("Type 'exit' or 'quit' to terminate.")
300
+ print("=" * 80 + "\n")
301
+
302
+ while True:
303
+ try:
304
+ user_input = input("User >> ").strip()
305
+ except (EOFError, KeyboardInterrupt):
306
+ print("\n[INFO] Exiting interactive CLI.")
307
+ break
308
+
309
+ if not user_input or user_input.lower() in ["exit", "quit"]:
310
+ break
311
+
312
+ # Check if input is arithmetic (contains only digits and + or -)
313
+ is_math = bool(re.match(r"^[\d\s\+\-\=]+$", user_input) and ("+" in user_input or "-" in user_input))
314
+ if is_math:
315
+ run_math_prompt(model, tokenizer, user_input, device)
316
+ else:
317
+ run_dialogue_prompt(model, tokenizer, user_input, device)
318
+
319
+
320
+ if __name__ == "__main__":
321
+ main()
model.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ScaledGPT: ~18.95M Parameter Recurrent 2D-RoPE Transformer.
3
+ Optimized for 25-Hour Dual NVIDIA T4 (sm_75, FP16 AMP, DDP).
4
+
5
+ Architecture:
6
+ - d_model = 512, n_head = 8 (head_dim = 64)
7
+ - Physical layers L = 6
8
+ - Recurrent passes R = 3 (Effective depth = 18 layers at zero parameter overhead)
9
+ - Residual recurrence update: x = x + (1/sqrt(R)) * (x_res - x)
10
+ - Tied embedding and output projection weights (wte.weight == lm_head.weight)
11
+ - Decoupled 2D-RoPE (Macro place p, Micro step tau)
12
+ - Selective shift masking: (place_coords > 0).long()
13
+ - O(1) SDPA causal attention
14
+ - Non-embedding params: ~18,887,680 | Total params: ~18,953,216 (~18.95M target)
15
+ """
16
+
17
+ import math
18
+ from dataclasses import dataclass
19
+ from typing import Optional, Tuple, List, Dict, Any
20
+ import torch
21
+ import torch.nn as nn
22
+ import torch.nn.functional as F
23
+
24
+ try:
25
+ from champion_model.scaled_t4.tokenizer import ScaledTokenizer
26
+ except ImportError:
27
+ try:
28
+ from tokenizer import ScaledTokenizer
29
+ except ImportError:
30
+ from champion_model.unified_200k.tokenizer import UnifiedTokenizer as ScaledTokenizer
31
+
32
+
33
+ @dataclass
34
+ class ScaledGPTConfig:
35
+ """Hyperparameters for ScaledGPT (~18.95M parameters)."""
36
+ vocab_size: int = 115
37
+ block_size: int = 1024
38
+ n_layer: int = 6
39
+ n_head: int = 8
40
+ n_embd: int = 512
41
+ n_recur: int = 3
42
+ dropout: float = 0.0
43
+ bias: bool = False
44
+ rope_theta: float = 10000.0
45
+ max_coords: int = 4096
46
+ k_rand_shift: int = 32
47
+ pos_emb_type: str = "rope_2d"
48
+
49
+
50
+ def precompute_2d_rope_freqs(
51
+ head_dim: int, max_coords: int = 4096, theta_base: float = 10000.0
52
+ ) -> Tuple[torch.Tensor, torch.Tensor]:
53
+ """Precomputes rotary frequency tables (cos, sin) for decoupled 2D-RoPE."""
54
+ half_dim = head_dim // 2
55
+ assert half_dim % 2 == 0, f"head_dim // 2 must be even, got {half_dim}"
56
+ dim_indices = torch.arange(0, half_dim, 2, dtype=torch.float32)
57
+ freqs = 1.0 / (theta_base ** (dim_indices / half_dim))
58
+ coords = torch.arange(max_coords, dtype=torch.float32)
59
+ angles = torch.outer(coords, freqs)
60
+ cos = torch.cos(angles)
61
+ sin = torch.sin(angles)
62
+ return cos, sin
63
+
64
+
65
+ def apply_2d_rope(
66
+ x: torch.Tensor,
67
+ place_coords: torch.Tensor,
68
+ step_coords: torch.Tensor,
69
+ cos: torch.Tensor,
70
+ sin: torch.Tensor,
71
+ ) -> torch.Tensor:
72
+ """Applies decoupled 2D-RoPE to tensor of shape [B, n_head, T, head_dim]."""
73
+ B, H, T, D = x.shape
74
+ half_d = D // 2
75
+
76
+ max_c = cos.size(0) - 1
77
+ p_clamped = torch.clamp(place_coords, 0, max_c)
78
+ s_clamped = torch.clamp(step_coords, 0, max_c)
79
+
80
+ cos_p = cos[p_clamped].unsqueeze(1).to(dtype=x.dtype, device=x.device)
81
+ sin_p = sin[p_clamped].unsqueeze(1).to(dtype=x.dtype, device=x.device)
82
+ cos_s = cos[s_clamped].unsqueeze(1).to(dtype=x.dtype, device=x.device)
83
+ sin_s = sin[s_clamped].unsqueeze(1).to(dtype=x.dtype, device=x.device)
84
+
85
+ x_place = x[..., :half_d]
86
+ x_step = x[..., half_d:]
87
+
88
+ # Place subspace rotation
89
+ x_p_even = x_place[..., 0::2]
90
+ x_p_odd = x_place[..., 1::2]
91
+ rot_p_even = x_p_even * cos_p - x_p_odd * sin_p
92
+ rot_p_odd = x_p_even * sin_p + x_p_odd * cos_p
93
+ rot_place = torch.empty_like(x_place)
94
+ rot_place[..., 0::2] = rot_p_even
95
+ rot_place[..., 1::2] = rot_p_odd
96
+
97
+ # Step subspace rotation
98
+ x_s_even = x_step[..., 0::2]
99
+ x_s_odd = x_step[..., 1::2]
100
+ rot_s_even = x_s_even * cos_s - x_s_odd * sin_s
101
+ rot_s_odd = x_s_even * sin_s + x_s_odd * cos_s
102
+ rot_step = torch.empty_like(x_step)
103
+ rot_step[..., 0::2] = rot_s_even
104
+ rot_step[..., 1::2] = rot_s_odd
105
+
106
+ return torch.cat([rot_place, rot_step], dim=-1)
107
+
108
+
109
+ class CausalSelfAttention(nn.Module):
110
+ """Causal multi-head self-attention with SDPA and 2D-RoPE."""
111
+
112
+ def __init__(self, config: ScaledGPTConfig):
113
+ super().__init__()
114
+ assert config.n_embd % config.n_head == 0
115
+ self.n_head = config.n_head
116
+ self.n_embd = config.n_embd
117
+ self.head_dim = config.n_embd // config.n_head
118
+ self.dropout = config.dropout
119
+ self.rope_theta = config.rope_theta
120
+
121
+ self.c_attn = nn.Linear(config.n_embd, 3 * config.n_embd, bias=config.bias)
122
+ self.c_proj = nn.Linear(config.n_embd, config.n_embd, bias=config.bias)
123
+ self.resid_dropout = nn.Dropout(config.dropout)
124
+
125
+ cos, sin = precompute_2d_rope_freqs(
126
+ self.head_dim, max_coords=config.max_coords, theta_base=config.rope_theta
127
+ )
128
+ self.register_buffer("rope_cos", cos, persistent=False)
129
+ self.register_buffer("rope_sin", sin, persistent=False)
130
+
131
+ def forward(
132
+ self,
133
+ x: torch.Tensor,
134
+ place_coords: torch.Tensor,
135
+ step_coords: torch.Tensor,
136
+ ) -> torch.Tensor:
137
+ B, T, C = x.size()
138
+ qkv = self.c_attn(x)
139
+ q, k, v = qkv.chunk(3, dim=2)
140
+
141
+ k = k.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
142
+ q = q.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
143
+ v = v.view(B, T, self.n_head, self.head_dim).transpose(1, 2)
144
+
145
+ # Dynamic frequency expansion if coordinates exceed precomputed length
146
+ max_coord = max(int(place_coords.max().item()), int(step_coords.max().item())) + 1
147
+ if max_coord > self.rope_cos.size(0):
148
+ new_max = max(max_coord * 2, 4096)
149
+ cos, sin = precompute_2d_rope_freqs(self.head_dim, max_coords=new_max, theta_base=self.rope_theta)
150
+ self.rope_cos = cos.to(x.device)
151
+ self.rope_sin = sin.to(x.device)
152
+
153
+ q = apply_2d_rope(q, place_coords, step_coords, self.rope_cos, self.rope_sin)
154
+ k = apply_2d_rope(k, place_coords, step_coords, self.rope_cos, self.rope_sin)
155
+
156
+ dropout_p = self.dropout if self.training else 0.0
157
+ y = F.scaled_dot_product_attention(
158
+ q, k, v, attn_mask=None, dropout_p=dropout_p, is_causal=True
159
+ )
160
+
161
+ y = y.transpose(1, 2).contiguous().view(B, T, C)
162
+ return self.resid_dropout(self.c_proj(y))
163
+
164
+
165
+ class MLP(nn.Module):
166
+ """Feed-forward network with 4x expansion and GELU."""
167
+
168
+ def __init__(self, config: ScaledGPTConfig):
169
+ super().__init__()
170
+ self.c_fc = nn.Linear(config.n_embd, 4 * config.n_embd, bias=config.bias)
171
+ self.gelu = nn.GELU()
172
+ self.c_proj = nn.Linear(4 * config.n_embd, config.n_embd, bias=config.bias)
173
+ self.dropout = nn.Dropout(config.dropout)
174
+
175
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
176
+ return self.dropout(self.c_proj(self.gelu(self.c_fc(x))))
177
+
178
+
179
+ class Block(nn.Module):
180
+ """Transformer block with Pre-LN residuals."""
181
+
182
+ def __init__(self, config: ScaledGPTConfig):
183
+ super().__init__()
184
+ self.ln_1 = nn.LayerNorm(config.n_embd)
185
+ self.attn = CausalSelfAttention(config)
186
+ self.ln_2 = nn.LayerNorm(config.n_embd)
187
+ self.mlp = MLP(config)
188
+
189
+ def forward(
190
+ self,
191
+ x: torch.Tensor,
192
+ place_coords: torch.Tensor,
193
+ step_coords: torch.Tensor,
194
+ ) -> torch.Tensor:
195
+ x = x + self.attn(self.ln_1(x), place_coords, step_coords)
196
+ x = x + self.mlp(self.ln_2(x))
197
+ return x
198
+
199
+
200
+ class ScaledGPT(nn.Module):
201
+ """Scaled ~18.95M Parameter Recurrent 2D-RoPE Transformer (sm_75 Dual T4)."""
202
+
203
+ def __init__(self, config: ScaledGPTConfig, tokenizer: Optional[ScaledTokenizer] = None):
204
+ super().__init__()
205
+ self.config = config
206
+ self.tokenizer = tokenizer or ScaledTokenizer()
207
+
208
+ self.transformer = nn.ModuleDict(
209
+ dict(
210
+ wte=nn.Embedding(config.vocab_size, config.n_embd),
211
+ drop=nn.Dropout(config.dropout),
212
+ h=nn.ModuleList([Block(config) for _ in range(config.n_layer)]),
213
+ ln_f=nn.LayerNorm(config.n_embd),
214
+ )
215
+ )
216
+ self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
217
+
218
+ # Weight tying: wte.weight == lm_head.weight
219
+ self.transformer.wte.weight = self.lm_head.weight
220
+
221
+ self.apply(self._init_weights)
222
+ for pn, p in self.named_parameters():
223
+ if pn.endswith("c_proj.weight"):
224
+ torch.nn.init.normal_(p, mean=0.0, std=0.02 / math.sqrt(2 * config.n_layer))
225
+
226
+ def _init_weights(self, module: nn.Module) -> None:
227
+ if isinstance(module, nn.Linear):
228
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
229
+ if module.bias is not None:
230
+ torch.nn.init.zeros_(module.bias)
231
+ elif isinstance(module, nn.Embedding):
232
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
233
+
234
+ def get_num_params(self, non_embedding: bool = False) -> int:
235
+ n_params = sum(p.numel() for p in self.parameters())
236
+ if non_embedding:
237
+ n_params -= self.transformer.wte.weight.numel()
238
+ return n_params
239
+
240
+ def compute_2d_coords(self, idx: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
241
+ """
242
+ Unified 2D coordinate mapper:
243
+ 1. If <USER> is present, routes as Discourse 2D coordinates (segment p in [0, 4], token offset tau).
244
+ In multi-turn dialogues, handles turns gracefully.
245
+ 2. Else, routes as Arithmetic 2D coordinates (column rank p up to 50+, intra-column step tau in [0, 9]).
246
+ """
247
+ B, T = idx.size()
248
+ device = idx.device
249
+ tok = self.tokenizer
250
+
251
+ p_coords = torch.zeros((B, T), dtype=torch.long, device=device)
252
+ s_coords = torch.zeros((B, T), dtype=torch.long, device=device)
253
+
254
+ user_s_id = tok.user_s_id
255
+ plus_id = tok.plus_id
256
+ eq_id = tok.eq_id
257
+ think_s_id = tok.think_s_id
258
+ think_e_id = tok.think_e_id
259
+ lparen_id = tok.lparen_id
260
+ digit_ids = set(tok.digit_ids)
261
+
262
+ segment_tags = {
263
+ tok.user_s_id: 0,
264
+ tok.think_s_id: 1,
265
+ tok.call_s_id: 2,
266
+ tok.result_s_id: 3,
267
+ tok.answer_s_id: 4,
268
+ }
269
+
270
+ for b in range(B):
271
+ row = idx[b]
272
+ is_dialogue = (row == user_s_id).any().item()
273
+
274
+ if is_dialogue:
275
+ # Discourse routing
276
+ cur_seg = 0
277
+ cur_step = 0
278
+ for t in range(T):
279
+ token_id = row[t].item()
280
+ if token_id in segment_tags:
281
+ cur_seg = segment_tags[token_id]
282
+ cur_step = 0
283
+ p_coords[b, t] = cur_seg
284
+ s_coords[b, t] = cur_step
285
+ cur_step += 1
286
+ else:
287
+ # Arithmetic scratchpad routing (extrapolates cleanly from 1 to 50+ digits)
288
+ plus_matches = (row == plus_id).nonzero(as_tuple=True)[0]
289
+ plus_idx = plus_matches[0].item() if len(plus_matches) > 0 else -1
290
+
291
+ eq_matches = (row == eq_id).nonzero(as_tuple=True)[0]
292
+ eq_idx = -1
293
+ if plus_idx != -1:
294
+ for pos in eq_matches:
295
+ if pos.item() > plus_idx:
296
+ eq_idx = pos.item()
297
+ break
298
+
299
+ think_s_matches = (row == think_s_id).nonzero(as_tuple=True)[0]
300
+ think_s = think_s_matches[0].item() if len(think_s_matches) > 0 else -1
301
+
302
+ think_e_matches = (row == think_e_id).nonzero(as_tuple=True)[0]
303
+ think_e = think_e_matches[0].item() if len(think_e_matches) > 0 else -1
304
+
305
+ # Operand 1 (before '+')
306
+ if plus_idx > 0:
307
+ for i in range(plus_idx):
308
+ if row[i].item() in digit_ids:
309
+ p_coords[b, i] = plus_idx - 1 - i
310
+ s_coords[b, i] = 0
311
+
312
+ # Operand 2 (between '+' and '=')
313
+ if eq_idx > plus_idx > -1:
314
+ for i in range(plus_idx + 1, eq_idx):
315
+ if row[i].item() in digit_ids:
316
+ p_coords[b, i] = eq_idx - 1 - i
317
+ s_coords[b, i] = 0
318
+
319
+ # Inside THINK
320
+ col_count = 0
321
+ if think_s != -1:
322
+ end_pos = think_e if think_e != -1 else T
323
+ cur_col = -1
324
+ cur_step = 0
325
+ for i in range(think_s + 1, end_pos):
326
+ if row[i].item() == lparen_id:
327
+ cur_col += 1
328
+ cur_step = 0
329
+ if cur_col >= 0:
330
+ p_coords[b, i] = cur_col
331
+ s_coords[b, i] = min(cur_step, 9)
332
+ cur_step += 1
333
+ col_count = cur_col + 1
334
+
335
+ # After THINK (final answer)
336
+ if think_e != -1:
337
+ ans_s = -1
338
+ for pos in eq_matches:
339
+ if pos.item() > think_e:
340
+ ans_s = pos.item() + 1
341
+ break
342
+ if ans_s != -1 and ans_s < T:
343
+ ans_d_idx = 0
344
+ for i in range(ans_s, T):
345
+ if row[i].item() in digit_ids:
346
+ p_coords[b, i] = max(0, col_count - 1 - ans_d_idx)
347
+ s_coords[b, i] = 0
348
+ ans_d_idx += 1
349
+
350
+ return p_coords, s_coords
351
+
352
+ def forward(
353
+ self,
354
+ idx: torch.Tensor,
355
+ targets: Optional[torch.Tensor] = None,
356
+ place_coords: Optional[torch.Tensor] = None,
357
+ step_coords: Optional[torch.Tensor] = None,
358
+ ) -> Tuple[torch.Tensor, Optional[torch.Tensor]]:
359
+ B, T = idx.size()
360
+ device = idx.device
361
+
362
+ if place_coords is None or step_coords is None:
363
+ place_coords, step_coords = self.compute_2d_coords(idx)
364
+
365
+ # Shift invariance data augmentation during training:
366
+ # Strictly applies selective shift masking (place_coords > 0).long()
367
+ if self.training and self.config.k_rand_shift > 0:
368
+ shifts = torch.randint(0, self.config.k_rand_shift + 1, (B, 1), device=device)
369
+ shift_mask = (place_coords > 0).long()
370
+ place_coords = place_coords + shifts * shift_mask
371
+
372
+ x = self.transformer.drop(self.transformer.wte(idx))
373
+
374
+ # Per-block recurrence residual updates: x = x + (1/sqrt(R)) * (x_res - x)
375
+ n_recur = max(1, self.config.n_recur)
376
+ scale_factor = 1.0 / math.sqrt(n_recur) if n_recur > 1 else 1.0
377
+ for _ in range(n_recur):
378
+ for block in self.transformer.h:
379
+ x_res = block(x, place_coords, step_coords)
380
+ if n_recur > 1:
381
+ x = x + scale_factor * (x_res - x)
382
+ else:
383
+ x = x_res
384
+
385
+ x = self.transformer.ln_f(x)
386
+ logits = self.lm_head(x)
387
+
388
+ loss = None
389
+ if targets is not None:
390
+ loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-100)
391
+
392
+ return logits, loss
393
+
394
+ @torch.no_grad()
395
+ def generate(
396
+ self,
397
+ idx: torch.Tensor,
398
+ max_new_tokens: int = 512,
399
+ temperature: float = 0.0,
400
+ stop_tokens: Optional[List[int]] = None,
401
+ ) -> torch.Tensor:
402
+ """Autoregressive token generation with greedy / multinomial sampling."""
403
+ stop_set = set(stop_tokens) if stop_tokens is not None else set()
404
+
405
+ for _ in range(max_new_tokens):
406
+ idx_cond = idx if idx.size(1) <= self.config.block_size else idx[:, -self.config.block_size:]
407
+ logits, _ = self(idx_cond)
408
+ logits = logits[:, -1, :]
409
+
410
+ if temperature == 0.0:
411
+ next_token = torch.argmax(logits, dim=-1, keepdim=True)
412
+ else:
413
+ probs = F.softmax(logits / temperature, dim=-1)
414
+ next_token = torch.multinomial(probs, num_samples=1)
415
+
416
+ idx = torch.cat((idx, next_token), dim=1)
417
+
418
+ if next_token.item() in stop_set:
419
+ break
420
+
421
+ return idx
tokenizer.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Scaled Unified Tokenizer for ScaledGPT (Dual T4 Optimization).
3
+ Supports:
4
+ 1. Multi-digit column arithmetic with scratchpad tokens (1-50+ digits).
5
+ 2. Natural language tool-calling dialogues with structured discourse tags.
6
+ 3. Multi-turn conversation markers and nested JSON syntax.
7
+ """
8
+
9
+ import string
10
+ from typing import List, Dict, Any, Optional
11
+ import torch
12
+
13
+
14
+ class ScaledTokenizer:
15
+ """Character-level tokenizer with structured discourse tokens and arithmetic symbols."""
16
+
17
+ def __init__(self):
18
+ self.special_tokens: List[str] = [
19
+ "<PAD>",
20
+ "<BOS>",
21
+ "<EOS>",
22
+ "<UNK>",
23
+ "<USER>",
24
+ "</USER>",
25
+ "<THINK>",
26
+ "</THINK>",
27
+ "<CALL>",
28
+ "</CALL>",
29
+ "<RESULT>",
30
+ "</RESULT>",
31
+ "<ANSWER>",
32
+ "</ANSWER>",
33
+ ]
34
+
35
+ self.token_to_id: Dict[str, int] = {}
36
+ self.id_to_token: Dict[int, str] = {}
37
+
38
+ for i, tok in enumerate(self.special_tokens):
39
+ self.token_to_id[tok] = i
40
+ self.id_to_token[i] = tok
41
+
42
+ ascii_chars = sorted(list(set(string.ascii_letters + string.digits + string.punctuation + " \n\t")))
43
+ cur_id = len(self.special_tokens)
44
+ for ch in ascii_chars:
45
+ if ch not in self.token_to_id:
46
+ self.token_to_id[ch] = cur_id
47
+ self.id_to_token[cur_id] = ch
48
+ cur_id += 1
49
+
50
+ self.pad_id: int = self.token_to_id["<PAD>"]
51
+ self.bos_id: int = self.token_to_id["<BOS>"]
52
+ self.eos_id: int = self.token_to_id["<EOS>"]
53
+ self.unk_id: int = self.token_to_id["<UNK>"]
54
+
55
+ # Discourse markers
56
+ self.user_s_id: int = self.token_to_id["<USER>"]
57
+ self.user_e_id: int = self.token_to_id["</USER>"]
58
+ self.think_s_id: int = self.token_to_id["<THINK>"]
59
+ self.think_e_id: int = self.token_to_id["</THINK>"]
60
+ self.call_s_id: int = self.token_to_id["<CALL>"]
61
+ self.call_e_id: int = self.token_to_id["</CALL>"]
62
+ self.result_s_id: int = self.token_to_id["<RESULT>"]
63
+ self.result_e_id: int = self.token_to_id["</RESULT>"]
64
+ self.answer_s_id: int = self.token_to_id["<ANSWER>"]
65
+ self.answer_e_id: int = self.token_to_id["</ANSWER>"]
66
+
67
+ # Arithmetic delimiters
68
+ self.plus_id: int = self.token_to_id["+"]
69
+ self.eq_id: int = self.token_to_id["="]
70
+ self.lparen_id: int = self.token_to_id["("]
71
+ self.digit_ids: List[int] = [self.token_to_id[str(d)] for d in range(10)]
72
+
73
+ self.vocab_size: int = len(self.token_to_id)
74
+
75
+ def encode(self, text: str, add_bos: bool = True, add_eos: bool = False) -> List[int]:
76
+ tokens: List[int] = []
77
+ if add_bos:
78
+ tokens.append(self.bos_id)
79
+
80
+ i = 0
81
+ n = len(text)
82
+ while i < n:
83
+ matched_special = False
84
+ for sp in self.special_tokens:
85
+ if text.startswith(sp, i):
86
+ tokens.append(self.token_to_id[sp])
87
+ i += len(sp)
88
+ matched_special = True
89
+ break
90
+ if not matched_special:
91
+ ch = text[i]
92
+ tokens.append(self.token_to_id.get(ch, self.unk_id))
93
+ i += 1
94
+
95
+ if add_eos:
96
+ tokens.append(self.eos_id)
97
+ return tokens
98
+
99
+ def decode(self, token_ids: List[int], skip_special: bool = False) -> str:
100
+ chars: List[str] = []
101
+ for tid in token_ids:
102
+ if tid in self.id_to_token:
103
+ tok_str = self.id_to_token[tid]
104
+ if skip_special and tok_str in self.special_tokens:
105
+ continue
106
+ chars.append(tok_str)
107
+ else:
108
+ chars.append("<UNK>")
109
+ return "".join(chars)