ucr-max commited on
Commit
5d9154e
·
verified ·
1 Parent(s): f19b511

Clean Atom2.7m package naming and benchmark entrypoint

Browse files
README.md CHANGED
@@ -90,12 +90,12 @@ with torch.no_grad():
90
  Use the included benchmark script:
91
 
92
  ```bash
93
- python benchmark_fusion_arithmark.py \
94
  --checkpoint . \
95
  --data-path arithmark_2.0.jsonl \
96
  --batch-size 64 \
97
  --device cuda \
98
- --output benchmark_results/fusion_arithmark_2.0_results.json
99
  ```
100
 
101
  ### lm-evaluation-harness
@@ -157,7 +157,7 @@ Synthetic-Arithmetic is canonical integer equation data. The training curriculum
157
  - `model.safetensors`: model weights
158
  - `config.json`, `config.py`, `configuration_gpt.py`, `model.py`: custom model code
159
  - `tokenizer.json`, `tokenization_atom.py`: tokenizer files and remote-code wrapper
160
- - `benchmark_fusion_arithmark.py`: ArithMark evaluation
161
  - `arithmark_2.0.jsonl`: local ArithMark 2.0 data for the standalone benchmark script
162
  - `pretraining_curriculum.json`: training curriculum
163
 
 
90
  Use the included benchmark script:
91
 
92
  ```bash
93
+ python benchmark_atom_arithmark.py \
94
  --checkpoint . \
95
  --data-path arithmark_2.0.jsonl \
96
  --batch-size 64 \
97
  --device cuda \
98
+ --output benchmark_results/atom_arithmark_2.0_results.json
99
  ```
100
 
101
  ### lm-evaluation-harness
 
157
  - `model.safetensors`: model weights
158
  - `config.json`, `config.py`, `configuration_gpt.py`, `model.py`: custom model code
159
  - `tokenizer.json`, `tokenization_atom.py`: tokenizer files and remote-code wrapper
160
+ - `benchmark_atom_arithmark.py`: ArithMark evaluation
161
  - `arithmark_2.0.jsonl`: local ArithMark 2.0 data for the standalone benchmark script
162
  - `pretraining_curriculum.json`: training curriculum
163
 
benchmark_atom_arithmark.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Score an Atom GPT checkpoint on ArithMark 2.0."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from collections import Counter
7
+ from contextlib import nullcontext
8
+ import json
9
+ from pathlib import Path
10
+ import re
11
+ import urllib.request
12
+
13
+ import torch
14
+ import torch.nn.functional as F
15
+ from transformers import AutoModelForCausalLM, AutoTokenizer
16
+
17
+
18
+ DATA_URL = (
19
+ "https://huggingface.co/datasets/AxiomicLabs/Arithmark-2.0/"
20
+ "resolve/main/arithmark_2.0.jsonl"
21
+ )
22
+
23
+
24
+ def ensure_data(path: Path) -> Path:
25
+ if path.exists():
26
+ return path
27
+ path.parent.mkdir(parents=True, exist_ok=True)
28
+ urllib.request.urlretrieve(DATA_URL, path)
29
+ return path
30
+
31
+
32
+ def load_examples(path: Path, *, max_examples: int = 0) -> list[dict]:
33
+ examples = []
34
+ with path.open("r", encoding="utf-8") as handle:
35
+ for line in handle:
36
+ if not line.strip():
37
+ continue
38
+ examples.append(json.loads(line))
39
+ if max_examples > 0 and len(examples) >= max_examples:
40
+ break
41
+ return examples
42
+
43
+
44
+ def _encoded_choice(
45
+ tokenizer,
46
+ context: str,
47
+ ending: str,
48
+ ) -> tuple[list[int], int]:
49
+ context_ids = tokenizer(context, add_special_tokens=False).input_ids
50
+ full_ids = tokenizer(context + ending, add_special_tokens=False).input_ids
51
+ continuation_length = len(full_ids) - len(context_ids)
52
+ return full_ids, continuation_length
53
+
54
+
55
+ @torch.inference_mode()
56
+ def evaluate(
57
+ model,
58
+ tokenizer,
59
+ examples: list[dict],
60
+ *,
61
+ device: torch.device,
62
+ batch_size: int,
63
+ dump_failures: bool = False,
64
+ failure_operator_count: int | None = None,
65
+ max_failures: int = 100,
66
+ ) -> dict:
67
+ correct = 0
68
+ total = 0
69
+ by_operator_count: dict[str, list[int]] = {}
70
+ by_topic: dict[str, list[int]] = {}
71
+ failures: list[dict] = []
72
+ failure_summary: Counter[tuple[str, str, str]] = Counter()
73
+ model.eval()
74
+ pad_id = tokenizer.pad_token_id
75
+ if pad_id is None:
76
+ pad_id = tokenizer.eos_token_id if tokenizer.eos_token_id is not None else 0
77
+
78
+ for start in range(0, len(examples), batch_size):
79
+ batch_examples = examples[start : start + batch_size]
80
+ encoded = []
81
+ offsets = []
82
+ for example in batch_examples:
83
+ flat_start = len(encoded)
84
+ encoded.extend(
85
+ _encoded_choice(tokenizer, example["ctx"], ending)
86
+ for ending in example["endings"]
87
+ )
88
+ offsets.append((flat_start, len(example["endings"])))
89
+
90
+ max_length = max(len(item[0]) for item in encoded)
91
+ input_ids = torch.full(
92
+ (len(encoded), max_length),
93
+ int(pad_id),
94
+ dtype=torch.long,
95
+ device=device,
96
+ )
97
+ attention_mask = torch.zeros_like(input_ids, dtype=torch.bool)
98
+ lengths = []
99
+ continuation_lengths = []
100
+ for row, (ids, continuation_length) in enumerate(encoded):
101
+ length = len(ids)
102
+ input_ids[row, :length] = torch.tensor(ids, device=device)
103
+ attention_mask[row, :length] = True
104
+ lengths.append(length)
105
+ continuation_lengths.append(continuation_length)
106
+
107
+ autocast = (
108
+ torch.autocast(device_type="cuda", dtype=torch.bfloat16)
109
+ if device.type == "cuda"
110
+ else nullcontext()
111
+ )
112
+ with autocast:
113
+ logits = model(
114
+ input_ids=input_ids,
115
+ attention_mask=attention_mask,
116
+ ).logits
117
+ log_probs = F.log_softmax(logits.float(), dim=-1)
118
+
119
+ for example_index, example in enumerate(batch_examples):
120
+ flat_start, choice_count = offsets[example_index]
121
+ likelihoods = []
122
+ for choice_index in range(choice_count):
123
+ row = flat_start + choice_index
124
+ length = lengths[row]
125
+ continuation_length = continuation_lengths[row]
126
+ continuation_start = length - continuation_length
127
+ likelihood = 0.0
128
+ for position in range(continuation_start, length):
129
+ likelihood += float(
130
+ log_probs[row, position - 1, input_ids[row, position]].item()
131
+ )
132
+ likelihoods.append(likelihood)
133
+
134
+ prediction = max(range(choice_count), key=likelihoods.__getitem__)
135
+ label = int(example["label"])
136
+ matched = prediction == label
137
+ correct += int(matched)
138
+ total += 1
139
+ metadata = example.get("metadata", {})
140
+ operator_count = str(metadata.get("operator_count", "unknown"))
141
+ topic = str(metadata.get("topic", "unknown"))
142
+ for grouped, key in (
143
+ (by_operator_count, operator_count),
144
+ (by_topic, topic),
145
+ ):
146
+ group = grouped.setdefault(key, [0, 0])
147
+ group[0] += int(matched)
148
+ group[1] += 1
149
+
150
+ if not matched and dump_failures:
151
+ op_count_int = None
152
+ try:
153
+ op_count_int = int(operator_count)
154
+ except ValueError:
155
+ pass
156
+ if failure_operator_count is None or op_count_int == failure_operator_count:
157
+ context = str(example["ctx"]).strip()
158
+ expression = context[:-1].strip() if context.endswith("=") else context
159
+ operands = [int(value) for value in re.findall(r"\d+", expression)]
160
+ operator = "".join(re.findall(r"[+\-*/]", expression))
161
+ predicted_answer = str(example["endings"][prediction]).strip()
162
+ correct_answer = str(example["endings"][label]).strip()
163
+ width = max((len(str(value)) for value in operands), default=0)
164
+ failure_summary[(topic, operator, f"width={width}")] += 1
165
+ if len(failures) < max_failures:
166
+ failures.append(
167
+ {
168
+ "ctx": context,
169
+ "topic": topic,
170
+ "operator_count": operator_count,
171
+ "operator": operator,
172
+ "operands": operands,
173
+ "max_operand_digits": width,
174
+ "correct_answer": correct_answer,
175
+ "predicted_answer": predicted_answer,
176
+ "choices": [str(value).strip() for value in example["endings"]],
177
+ "choice_scores": [round(value, 4) for value in likelihoods],
178
+ "score_margin_correct_minus_predicted": round(
179
+ likelihoods[label] - likelihoods[prediction],
180
+ 4,
181
+ ),
182
+ }
183
+ )
184
+
185
+ results = {
186
+ "benchmark": "arithmark_2.0",
187
+ "model_type": "atom_gpt",
188
+ "accuracy": correct / max(total, 1),
189
+ "correct": correct,
190
+ "total": total,
191
+ "by_operator_count": {
192
+ key: {
193
+ "accuracy": values[0] / max(values[1], 1),
194
+ "correct": values[0],
195
+ "total": values[1],
196
+ }
197
+ for key, values in sorted(by_operator_count.items())
198
+ },
199
+ "by_topic": {
200
+ key: {
201
+ "accuracy": values[0] / max(values[1], 1),
202
+ "correct": values[0],
203
+ "total": values[1],
204
+ }
205
+ for key, values in sorted(by_topic.items())
206
+ },
207
+ }
208
+ if dump_failures:
209
+ results["failure_summary"] = {
210
+ "|".join(key): value
211
+ for key, value in failure_summary.most_common()
212
+ }
213
+ results["failures"] = failures
214
+ return results
215
+
216
+
217
+ def parse_args() -> argparse.Namespace:
218
+ parser = argparse.ArgumentParser(description=__doc__)
219
+ parser.add_argument("--checkpoint", type=Path, default=Path("."))
220
+ parser.add_argument("--data-path", type=Path, default=Path("arithmark_2.0.jsonl"))
221
+ parser.add_argument("--batch-size", type=int, default=64)
222
+ parser.add_argument("--device", default="auto")
223
+ parser.add_argument("--dtype", default="auto", choices=("auto", "float32", "bfloat16", "float16"))
224
+ parser.add_argument("--output", type=Path)
225
+ parser.add_argument(
226
+ "--max-examples",
227
+ type=int,
228
+ default=0,
229
+ help="Evaluate only the first N examples. Default evaluates all examples.",
230
+ )
231
+ parser.add_argument(
232
+ "--dump-failures",
233
+ action="store_true",
234
+ help="Include incorrectly scored examples and grouped failure summary.",
235
+ )
236
+ parser.add_argument(
237
+ "--failure-operator-count",
238
+ type=int,
239
+ default=None,
240
+ help="Only dump failures with this operator count, e.g. 1 for easy examples.",
241
+ )
242
+ parser.add_argument("--max-failures", type=int, default=100)
243
+ return parser.parse_args()
244
+
245
+
246
+ def main() -> None:
247
+ args = parse_args()
248
+ if args.device == "auto":
249
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
250
+ else:
251
+ device = torch.device(args.device)
252
+
253
+ data_path = ensure_data(args.data_path)
254
+ examples = load_examples(data_path, max_examples=args.max_examples)
255
+ dtype = None
256
+ if args.dtype == "float32":
257
+ dtype = torch.float32
258
+ elif args.dtype == "bfloat16":
259
+ dtype = torch.bfloat16
260
+ elif args.dtype == "float16":
261
+ dtype = torch.float16
262
+ model = AutoModelForCausalLM.from_pretrained(
263
+ args.checkpoint,
264
+ dtype=dtype,
265
+ trust_remote_code=True,
266
+ ).to(device)
267
+ tokenizer = AutoTokenizer.from_pretrained(args.checkpoint, trust_remote_code=True)
268
+ if tokenizer.pad_token_id is None:
269
+ tokenizer.pad_token = tokenizer.eos_token
270
+ results = evaluate(
271
+ model,
272
+ tokenizer,
273
+ examples,
274
+ device=device,
275
+ batch_size=args.batch_size,
276
+ dump_failures=args.dump_failures,
277
+ failure_operator_count=args.failure_operator_count,
278
+ max_failures=args.max_failures,
279
+ )
280
+ print(json.dumps(results, indent=2, sort_keys=True))
281
+ if args.output:
282
+ args.output.parent.mkdir(parents=True, exist_ok=True)
283
+ args.output.write_text(
284
+ json.dumps(results, indent=2, sort_keys=True) + "\n",
285
+ encoding="utf-8",
286
+ )
287
+
288
+
289
+ if __name__ == "__main__":
290
+ main()
benchmark_results/lm_eval/results_2026-07-04T17-16-59.922847.json ADDED
@@ -0,0 +1,355 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "results": {
3
+ "hellaswag": {
4
+ "name": "hellaswag",
5
+ "alias": "hellaswag",
6
+ "sample_len": 10042,
7
+ "acc,none": 0.2669786895040829,
8
+ "acc_stderr,none": 0.004414770331224705,
9
+ "acc_norm,none": 0.2700657239593706,
10
+ "acc_norm_stderr,none": 0.0044308610336166706
11
+ },
12
+ "arc_easy": {
13
+ "name": "arc_easy",
14
+ "alias": "arc_easy",
15
+ "sample_len": 2376,
16
+ "acc,none": 0.32154882154882153,
17
+ "acc_stderr,none": 0.009584091575640627,
18
+ "acc_norm,none": 0.31607744107744107,
19
+ "acc_norm_stderr,none": 0.009540440071928294
20
+ },
21
+ "arc_challenge": {
22
+ "name": "arc_challenge",
23
+ "alias": "arc_challenge",
24
+ "sample_len": 1172,
25
+ "acc,none": 0.17235494880546076,
26
+ "acc_stderr,none": 0.011037113093461295,
27
+ "acc_norm,none": 0.2098976109215017,
28
+ "acc_norm_stderr,none": 0.011900548748047421
29
+ },
30
+ "piqa": {
31
+ "name": "piqa",
32
+ "alias": "piqa",
33
+ "sample_len": 1838,
34
+ "acc,none": 0.5565832426550599,
35
+ "acc_stderr,none": 0.011590883373666865,
36
+ "acc_norm,none": 0.529923830250272,
37
+ "acc_norm_stderr,none": 0.011644913435420158
38
+ }
39
+ },
40
+ "group_subtasks": {},
41
+ "configs": {
42
+ "arc_challenge": {
43
+ "task": "arc_challenge",
44
+ "dataset_path": "allenai/ai2_arc",
45
+ "dataset_name": "ARC-Challenge",
46
+ "training_split": "train",
47
+ "validation_split": "validation",
48
+ "test_split": "test",
49
+ "doc_to_text": "Question: {{question}}\nAnswer:",
50
+ "doc_to_target": "{{choices.label.index(answerKey)}}",
51
+ "unsafe_code": false,
52
+ "doc_to_choice": "{{choices.text}}",
53
+ "description": "",
54
+ "target_delimiter": " ",
55
+ "fewshot_delimiter": "\n\n",
56
+ "fewshot_config": {
57
+ "sampler": "default",
58
+ "split": null,
59
+ "process_docs": null,
60
+ "fewshot_indices": null,
61
+ "samples": null,
62
+ "doc_to_text": "Question: {{question}}\nAnswer:",
63
+ "doc_to_choice": "{{choices.text}}",
64
+ "doc_to_target": "{{choices.label.index(answerKey)}}",
65
+ "gen_prefix": null,
66
+ "fewshot_delimiter": "\n\n",
67
+ "target_delimiter": " "
68
+ },
69
+ "num_fewshot": 0,
70
+ "metric_list": [
71
+ {
72
+ "metric": "acc",
73
+ "aggregation": "mean",
74
+ "higher_is_better": true
75
+ },
76
+ {
77
+ "metric": "acc_norm",
78
+ "aggregation": "mean",
79
+ "higher_is_better": true
80
+ }
81
+ ],
82
+ "output_type": "multiple_choice",
83
+ "repeats": 1,
84
+ "should_decontaminate": true,
85
+ "doc_to_decontamination_query": "Question: {{question}}\nAnswer:",
86
+ "metadata": {
87
+ "version": 1.0,
88
+ "pretrained": ".",
89
+ "trust_remote_code": true,
90
+ "dtype": "bfloat16",
91
+ "max_length": 548,
92
+ "config_source": "/home/max/PROJECTS/PythonProjects/mcpterm/venv/lib/python3.11/site-packages/lm_eval/tasks/arc/arc_challenge.yaml"
93
+ }
94
+ },
95
+ "arc_easy": {
96
+ "task": "arc_easy",
97
+ "dataset_path": "allenai/ai2_arc",
98
+ "dataset_name": "ARC-Easy",
99
+ "training_split": "train",
100
+ "validation_split": "validation",
101
+ "test_split": "test",
102
+ "doc_to_text": "Question: {{question}}\nAnswer:",
103
+ "doc_to_target": "{{choices.label.index(answerKey)}}",
104
+ "unsafe_code": false,
105
+ "doc_to_choice": "{{choices.text}}",
106
+ "description": "",
107
+ "target_delimiter": " ",
108
+ "fewshot_delimiter": "\n\n",
109
+ "fewshot_config": {
110
+ "sampler": "default",
111
+ "split": null,
112
+ "process_docs": null,
113
+ "fewshot_indices": null,
114
+ "samples": null,
115
+ "doc_to_text": "Question: {{question}}\nAnswer:",
116
+ "doc_to_choice": "{{choices.text}}",
117
+ "doc_to_target": "{{choices.label.index(answerKey)}}",
118
+ "gen_prefix": null,
119
+ "fewshot_delimiter": "\n\n",
120
+ "target_delimiter": " "
121
+ },
122
+ "num_fewshot": 0,
123
+ "metric_list": [
124
+ {
125
+ "metric": "acc",
126
+ "aggregation": "mean",
127
+ "higher_is_better": true
128
+ },
129
+ {
130
+ "metric": "acc_norm",
131
+ "aggregation": "mean",
132
+ "higher_is_better": true
133
+ }
134
+ ],
135
+ "output_type": "multiple_choice",
136
+ "repeats": 1,
137
+ "should_decontaminate": true,
138
+ "doc_to_decontamination_query": "Question: {{question}}\nAnswer:",
139
+ "metadata": {
140
+ "version": 1.0,
141
+ "pretrained": ".",
142
+ "trust_remote_code": true,
143
+ "dtype": "bfloat16",
144
+ "max_length": 548,
145
+ "config_source": "/home/max/PROJECTS/PythonProjects/mcpterm/venv/lib/python3.11/site-packages/lm_eval/tasks/arc/arc_easy.yaml"
146
+ }
147
+ },
148
+ "hellaswag": {
149
+ "task": "hellaswag",
150
+ "dataset_path": "Rowan/hellaswag",
151
+ "training_split": "train",
152
+ "validation_split": "validation",
153
+ "process_docs": "def process_docs(dataset: datasets.Dataset) -> datasets.Dataset:\n def _process_doc(doc):\n ctx = doc[\"ctx_a\"] + \" \" + doc[\"ctx_b\"].capitalize()\n out_doc = {\n \"query\": preprocess(doc[\"activity_label\"] + \": \" + ctx),\n \"choices\": [preprocess(ending) for ending in doc[\"endings\"]],\n \"gold\": int(doc[\"label\"]),\n }\n return out_doc\n\n return dataset.map(_process_doc)\n",
154
+ "doc_to_text": "{{query}}",
155
+ "doc_to_target": "{{label}}",
156
+ "unsafe_code": false,
157
+ "doc_to_choice": "choices",
158
+ "description": "",
159
+ "target_delimiter": " ",
160
+ "fewshot_delimiter": "\n\n",
161
+ "fewshot_config": {
162
+ "sampler": "default",
163
+ "split": null,
164
+ "process_docs": "<function process_docs at 0x77bb53ff80e0>",
165
+ "fewshot_indices": null,
166
+ "samples": null,
167
+ "doc_to_text": "{{query}}",
168
+ "doc_to_choice": "choices",
169
+ "doc_to_target": "{{label}}",
170
+ "gen_prefix": null,
171
+ "fewshot_delimiter": "\n\n",
172
+ "target_delimiter": " "
173
+ },
174
+ "num_fewshot": 0,
175
+ "metric_list": [
176
+ {
177
+ "metric": "acc",
178
+ "aggregation": "mean",
179
+ "higher_is_better": true
180
+ },
181
+ {
182
+ "metric": "acc_norm",
183
+ "aggregation": "mean",
184
+ "higher_is_better": true
185
+ }
186
+ ],
187
+ "output_type": "multiple_choice",
188
+ "repeats": 1,
189
+ "should_decontaminate": false,
190
+ "metadata": {
191
+ "version": 1.0,
192
+ "pretrained": ".",
193
+ "trust_remote_code": true,
194
+ "dtype": "bfloat16",
195
+ "max_length": 548,
196
+ "config_source": "/home/max/PROJECTS/PythonProjects/mcpterm/venv/lib/python3.11/site-packages/lm_eval/tasks/hellaswag/hellaswag.yaml"
197
+ }
198
+ },
199
+ "piqa": {
200
+ "task": "piqa",
201
+ "dataset_path": "baber/piqa",
202
+ "training_split": "train",
203
+ "validation_split": "validation",
204
+ "doc_to_text": "Question: {{goal}}\nAnswer:",
205
+ "doc_to_target": "label",
206
+ "unsafe_code": false,
207
+ "doc_to_choice": "{{[sol1, sol2]}}",
208
+ "description": "",
209
+ "target_delimiter": " ",
210
+ "fewshot_delimiter": "\n\n",
211
+ "fewshot_config": {
212
+ "sampler": "default",
213
+ "split": null,
214
+ "process_docs": null,
215
+ "fewshot_indices": null,
216
+ "samples": null,
217
+ "doc_to_text": "Question: {{goal}}\nAnswer:",
218
+ "doc_to_choice": "{{[sol1, sol2]}}",
219
+ "doc_to_target": "label",
220
+ "gen_prefix": null,
221
+ "fewshot_delimiter": "\n\n",
222
+ "target_delimiter": " "
223
+ },
224
+ "num_fewshot": 0,
225
+ "metric_list": [
226
+ {
227
+ "metric": "acc",
228
+ "aggregation": "mean",
229
+ "higher_is_better": true
230
+ },
231
+ {
232
+ "metric": "acc_norm",
233
+ "aggregation": "mean",
234
+ "higher_is_better": true
235
+ }
236
+ ],
237
+ "output_type": "multiple_choice",
238
+ "repeats": 1,
239
+ "should_decontaminate": true,
240
+ "doc_to_decontamination_query": "goal",
241
+ "metadata": {
242
+ "version": 1.0,
243
+ "pretrained": ".",
244
+ "trust_remote_code": true,
245
+ "dtype": "bfloat16",
246
+ "max_length": 548,
247
+ "config_source": "/home/max/PROJECTS/PythonProjects/mcpterm/venv/lib/python3.11/site-packages/lm_eval/tasks/piqa/piqa.yaml"
248
+ }
249
+ }
250
+ },
251
+ "versions": {
252
+ "arc_challenge": 1.0,
253
+ "arc_easy": 1.0,
254
+ "hellaswag": 1.0,
255
+ "piqa": 1.0
256
+ },
257
+ "n-shot": {
258
+ "arc_challenge": 0,
259
+ "arc_easy": 0,
260
+ "hellaswag": 0,
261
+ "piqa": 0
262
+ },
263
+ "higher_is_better": {
264
+ "arc_challenge": {
265
+ "acc": true,
266
+ "acc_norm": true
267
+ },
268
+ "arc_easy": {
269
+ "acc": true,
270
+ "acc_norm": true
271
+ },
272
+ "hellaswag": {
273
+ "acc": true,
274
+ "acc_norm": true
275
+ },
276
+ "piqa": {
277
+ "acc": true,
278
+ "acc_norm": true
279
+ }
280
+ },
281
+ "n-samples": {
282
+ "hellaswag": {
283
+ "original": 10042,
284
+ "effective": 10042
285
+ },
286
+ "arc_easy": {
287
+ "original": 2376,
288
+ "effective": 2376
289
+ },
290
+ "arc_challenge": {
291
+ "original": 1172,
292
+ "effective": 1172
293
+ },
294
+ "piqa": {
295
+ "original": 1838,
296
+ "effective": 1838
297
+ }
298
+ },
299
+ "config": {
300
+ "model": "hf",
301
+ "model_args": {
302
+ "pretrained": ".",
303
+ "trust_remote_code": true,
304
+ "dtype": "bfloat16",
305
+ "max_length": 548
306
+ },
307
+ "model_num_parameters": 2738880,
308
+ "model_dtype": "torch.float32",
309
+ "model_revision": "main",
310
+ "model_sha": "",
311
+ "batch_size": "auto:1",
312
+ "batch_sizes": [
313
+ 64
314
+ ],
315
+ "device": "cuda:0",
316
+ "use_cache": null,
317
+ "limit": null,
318
+ "bootstrap_iters": 100000,
319
+ "gen_kwargs": {},
320
+ "random_seed": 0,
321
+ "numpy_seed": 1234,
322
+ "torch_seed": 1234,
323
+ "fewshot_seed": 1234
324
+ },
325
+ "git_hash": "f5846e8",
326
+ "date": 1783178146.3657796,
327
+ "pretty_env_info": "PyTorch version: 2.10.0+cu130\nIs debug build: False\nCUDA used to build PyTorch: 13.0\nROCM used to build PyTorch: N/A\n\nOS: Debian GNU/Linux 12 (bookworm) (x86_64)\nGCC version: (Debian 12.2.0-14+deb12u1) 12.2.0\nClang version: Could not collect\nCMake version: version 3.25.1\nLibc version: glibc-2.36\n\nPython version: 3.11.2 (main, Apr 8 2026, 01:58:00) [GCC 12.2.0] (64-bit runtime)\nPython platform: Linux-6.18.33.2-microsoft-standard-WSL2-x86_64-with-glibc2.36\nIs CUDA available: True\nCUDA runtime version: 13.2.51\nCUDA_MODULE_LOADING set to: \nGPU models and configuration: GPU 0: NVIDIA GeForce RTX 3080\nNvidia driver version: 596.36\ncuDNN version: Could not collect\nIs XPU available: False\nHIP runtime version: N/A\nMIOpen runtime version: N/A\nIs XNNPACK available: True\nCaching allocator config: N/A\n\nCPU:\nArchitecture: x86_64\nCPU op-mode(s): 32-bit, 64-bit\nAddress sizes: 48 bits physical, 48 bits virtual\nByte Order: Little Endian\nCPU(s): 12\nOn-line CPU(s) list: 0-11\nVendor ID: AuthenticAMD\nModel name: AMD Ryzen 5 7500F 6-Core Processor\nCPU family: 25\nModel: 97\nThread(s) per core: 2\nCore(s) per socket: 6\nSocket(s): 1\nStepping: 2\nBogoMIPS: 7399.82\nFlags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology tsc_reliable nonstop_tsc cpuid extd_apicid tsc_known_freq pni pclmulqdq ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm cmp_legacy svm cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw topoext perfctr_core ssbd ibrs ibpb stibp vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves avx512_bf16 clzero xsaveerptr arat npt nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avx512vbmi umip avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid fsrm\nVirtualization: AMD-V\nHypervisor vendor: Microsoft\nVirtualization type: full\nL1d cache: 192 KiB (6 instances)\nL1i cache: 192 KiB (6 instances)\nL2 cache: 6 MiB (6 instances)\nL3 cache: 32 MiB (1 instance)\nNUMA node(s): 1\nNUMA node0 CPU(s): 0-11\nVulnerability Gather data sampling: Not affected\nVulnerability Ghostwrite: Not affected\nVulnerability Indirect target selection: Not affected\nVulnerability Itlb multihit: Not affected\nVulnerability L1tf: Not affected\nVulnerability Mds: Not affected\nVulnerability Meltdown: Not affected\nVulnerability Mmio stale data: Not affected\nVulnerability Old microcode: Not affected\nVulnerability Reg file data sampling: Not affected\nVulnerability Retbleed: Not affected\nVulnerability Spec rstack overflow: Vulnerable: Safe RET, no microcode\nVulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl\nVulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization\nVulnerability Spectre v2: Mitigation; Retpolines; IBPB conditional; IBRS_FW; STIBP always-on; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected\nVulnerability Srbds: Not affected\nVulnerability Tsa: Vulnerable: No microcode\nVulnerability Tsx async abort: Not affected\nVulnerability Vmscape: Not affected\n\nVersions of relevant libraries:\n[pip3] flash_attn==2.8.3+cu130torch2.10\n[pip3] mypy_extensions==1.1.0\n[pip3] numpy==2.2.6\n[pip3] nvidia-cublas==13.1.0.3\n[pip3] nvidia-cublas-cu12==12.8.4.1\n[pip3] nvidia-cuda-cupti==13.0.85\n[pip3] nvidia-cuda-cupti-cu12==12.8.90\n[pip3] nvidia-cuda-nvrtc==13.0.88\n[pip3] nvidia-cuda-nvrtc-cu12==12.8.93\n[pip3] nvidia-cuda-runtime==13.0.96\n[pip3] nvidia-cuda-runtime-cu12==12.8.90\n[pip3] nvidia-cudnn-cu12==9.10.2.21\n[pip3] nvidia-cudnn-cu13==9.15.1.9\n[pip3] nvidia-cudnn-frontend==1.18.0\n[pip3] nvidia-cufft==12.0.0.61\n[pip3] nvidia-cufft-cu12==11.3.3.83\n[pip3] nvidia-curand==10.4.0.35\n[pip3] nvidia-curand-cu12==10.3.9.90\n[pip3] nvidia-cusolver==12.0.4.66\n[pip3] nvidia-cusolver-cu12==11.7.3.90\n[pip3] nvidia-cusparse==12.6.3.3\n[pip3] nvidia-cusparse-cu12==12.5.8.93\n[pip3] nvidia-cusparselt-cu12==0.7.1\n[pip3] nvidia-cusparselt-cu13==0.8.0\n[pip3] nvidia-nccl-cu12==2.27.5\n[pip3] nvidia-nccl-cu13==2.28.9\n[pip3] nvidia-nvjitlink==13.0.88\n[pip3] nvidia-nvjitlink-cu12==12.8.93\n[pip3] nvidia-nvtx==13.0.85\n[pip3] nvidia-nvtx-cu12==12.8.90\n[pip3] onnxruntime==1.20.1\n[pip3] rapidocr-onnxruntime==1.4.4\n[pip3] rotary-embedding-torch==0.8.8\n[pip3] torch==2.10.0+cu130\n[pip3] torch_c_dlpack_ext==0.1.5\n[pip3] torch-lr-finder==0.2.2\n[pip3] torch-tb-profiler==0.4.3\n[pip3] torchaudio==2.10.0+cu130\n[pip3] torchmetrics==1.9.0\n[pip3] torchvision==0.25.0+cu130\n[pip3] triton==3.6.0\n[conda] Could not collect",
328
+ "transformers_version": "4.57.6",
329
+ "lm_eval_version": "0.4.12",
330
+ "upper_git_hash": "f5846e8ab83c0da3653a1f7a04d470911c8f5065",
331
+ "tokenizer_pad_token": [
332
+ "<|pad|>",
333
+ "0"
334
+ ],
335
+ "tokenizer_eos_token": [
336
+ "<|eos|>",
337
+ "2"
338
+ ],
339
+ "tokenizer_bos_token": [
340
+ "<|bos|>",
341
+ "1"
342
+ ],
343
+ "eot_token_id": 2,
344
+ "max_length": 548,
345
+ "task_hashes": {},
346
+ "model_source": "hf",
347
+ "model_name": ".",
348
+ "model_name_sanitized": ".",
349
+ "system_instruction": null,
350
+ "system_instruction_sha": null,
351
+ "fewshot_as_multiturn": null,
352
+ "chat_template": null,
353
+ "chat_template_sha": null,
354
+ "total_evaluation_time_seconds": "80.63422504799973"
355
+ }
tokenization_atom.py CHANGED
@@ -1,4 +1,4 @@
1
- """Remote-code tokenizer for Atom/Fusion GPT checkpoints.
2
 
3
  The tokenizer is intentionally HF-compatible: generic callers can use
4
  ``AutoTokenizer.from_pretrained(..., trust_remote_code=True)``. Arithmetic digit
 
1
+ """Remote-code tokenizer for Atom GPT checkpoints.
2
 
3
  The tokenizer is intentionally HF-compatible: generic callers can use
4
  ``AutoTokenizer.from_pretrained(..., trust_remote_code=True)``. Arithmetic digit
tokenizer_utils.py CHANGED
@@ -28,7 +28,7 @@ MAX_OPERAND_ROLES = 9
28
 
29
 
30
  @dataclass(frozen=True)
31
- class FusionEncoding:
32
  ids: list[int]
33
  place_ids: list[int]
34
  role_ids: list[int]
@@ -46,7 +46,7 @@ class FusionEncoding:
46
 
47
  def __post_init__(self) -> None:
48
  if not (len(self.ids) == len(self.place_ids) == len(self.role_ids)):
49
- raise ValueError("Fusion tokenizer streams must have equal length")
50
 
51
 
52
  def build_tokenizer() -> Any:
@@ -56,10 +56,15 @@ def build_tokenizer() -> Any:
56
  tokenizer = Tokenizer(models.BPE(unk_token="<|unk|>"))
57
  tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
58
  [
 
 
59
  pre_tokenizers.Split(
60
  Regex(r"\s+|\d|[+\-*/=()]|[^\s\d+\-*/=()]+"),
61
  behavior="isolated",
62
  ),
 
 
 
63
  pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=False),
64
  ]
65
  )
@@ -67,7 +72,7 @@ def build_tokenizer() -> Any:
67
  return tokenizer
68
 
69
 
70
- class FusionTokenizer:
71
  """Runtime wrapper adding LSD-first digit streams to a trained BPE tokenizer."""
72
 
73
  _digit_span_re = re.compile(r"\d+")
@@ -85,11 +90,14 @@ class FusionTokenizer:
85
  if self.tokenizer.token_to_id(digit) is not None
86
  }
87
  self._equals_id = self.tokenizer.token_to_id("=")
 
88
  self._special_token_ids = frozenset(
89
  token_id
90
  for token in SPECIAL_TOKENS
91
  if (token_id := self.tokenizer.token_to_id(token)) is not None
92
  )
 
 
93
  if len(self._digit_token_ids) != 10:
94
  raise ValueError("Tokenizer vocabulary must contain atomic digit tokens 0-9")
95
  if self._equals_id is None:
@@ -123,20 +131,31 @@ class FusionTokenizer:
123
  return cls._digit_span_re.sub(lambda match: match.group(0)[::-1], text)
124
 
125
  def _decode_token_piece(self, token_id: int) -> str:
126
- return self.tokenizer.decode([int(token_id)], skip_special_tokens=False)
 
 
 
 
 
127
 
128
  @staticmethod
129
  def _is_equation_whitespace(piece: str) -> bool:
130
  return bool(piece) and piece.isspace() and "\n" not in piece and "\r" not in piece
131
 
132
  def _is_equation_piece(self, token_id: int, piece: str) -> bool:
 
 
 
133
  if token_id in self._special_token_ids:
134
- return False
135
- if token_id in self._digit_token_ids:
136
- return True
137
- if self._is_equation_whitespace(piece):
138
- return True
139
- return len(piece) == 1 and piece in set(ARITHMETIC_TOKENS)
 
 
 
140
 
141
  def _annotate_equation_span(
142
  self,
@@ -213,20 +232,25 @@ class FusionTokenizer:
213
 
214
  return place_ids, role_ids
215
 
216
- def encode(self, text: str, *args, **kwargs) -> FusionEncoding:
217
  transformed = self._reverse_digit_spans(text)
218
  encoding = self.tokenizer.encode(transformed, *args, **kwargs)
 
 
 
219
  ids = [int(token_id) for token_id in encoding.ids]
220
  place_ids, role_ids = self.annotate_ids(ids)
221
- return FusionEncoding(
222
  ids=ids,
223
  place_ids=place_ids,
224
  role_ids=role_ids,
225
  tokens=list(getattr(encoding, "tokens", [])),
226
  )
227
 
228
- def encode_batch(self, texts: list[str], *args, **kwargs) -> list[FusionEncoding]:
229
- return [self.encode(text, *args, **kwargs) for text in texts]
 
 
230
 
231
  def decode(
232
  self,
@@ -293,15 +317,11 @@ def validate_tokenizer(tokenizer_dir: Path) -> None:
293
  f"Missing {tokenizer_json}. Retrain with train_tokenizer.py so the "
294
  "whitespace and digit boundary rules are preserved."
295
  )
296
- if vocab_path.exists():
297
- with vocab_path.open("r", encoding="utf-8") as f:
298
- vocab = json.load(f)
299
- else:
300
- with tokenizer_json.open("r", encoding="utf-8") as f:
301
- tokenizer_data = json.load(f)
302
- vocab = tokenizer_data.get("model", {}).get("vocab")
303
- if not isinstance(vocab, dict):
304
- raise FileNotFoundError(f"Missing vocab.json and no embedded vocab in {tokenizer_json}")
305
 
306
  max_id = max(vocab.values())
307
  if max_id > 65_535:
@@ -325,4 +345,4 @@ def load_tokenizer(tokenizer_dir: Path) -> Any:
325
 
326
  validate_tokenizer(tokenizer_dir)
327
  tokenizer_json, _, _ = tokenizer_files(tokenizer_dir)
328
- return FusionTokenizer(Tokenizer.from_file(str(tokenizer_json)))
 
28
 
29
 
30
  @dataclass(frozen=True)
31
+ class AtomEncoding:
32
  ids: list[int]
33
  place_ids: list[int]
34
  role_ids: list[int]
 
46
 
47
  def __post_init__(self) -> None:
48
  if not (len(self.ids) == len(self.place_ids) == len(self.role_ids)):
49
+ raise ValueError("Atom tokenizer streams must have equal length")
50
 
51
 
52
  def build_tokenizer() -> Any:
 
56
  tokenizer = Tokenizer(models.BPE(unk_token="<|unk|>"))
57
  tokenizer.pre_tokenizer = pre_tokenizers.Sequence(
58
  [
59
+ # BPE may merge within one of these spans, but never across them.
60
+ # This keeps whitespace, digits, and arithmetic syntax isolated.
61
  pre_tokenizers.Split(
62
  Regex(r"\s+|\d|[+\-*/=()]|[^\s\d+\-*/=()]+"),
63
  behavior="isolated",
64
  ),
65
+ # Convert every span to bytes without applying GPT-2's prefix-space
66
+ # regex. The complete byte alphabet supplied to the trainer provides
67
+ # lossless fallback for arbitrary UTF-8 input.
68
  pre_tokenizers.ByteLevel(add_prefix_space=False, use_regex=False),
69
  ]
70
  )
 
72
  return tokenizer
73
 
74
 
75
+ class AtomTokenizer:
76
  """Runtime wrapper adding LSD-first digit streams to a trained BPE tokenizer."""
77
 
78
  _digit_span_re = re.compile(r"\d+")
 
90
  if self.tokenizer.token_to_id(digit) is not None
91
  }
92
  self._equals_id = self.tokenizer.token_to_id("=")
93
+ self._arithmetic_token_texts = set(ARITHMETIC_TOKENS)
94
  self._special_token_ids = frozenset(
95
  token_id
96
  for token in SPECIAL_TOKENS
97
  if (token_id := self.tokenizer.token_to_id(token)) is not None
98
  )
99
+ self._piece_cache: dict[int, str] = {}
100
+ self._equation_piece_cache: dict[int, bool] = {}
101
  if len(self._digit_token_ids) != 10:
102
  raise ValueError("Tokenizer vocabulary must contain atomic digit tokens 0-9")
103
  if self._equals_id is None:
 
131
  return cls._digit_span_re.sub(lambda match: match.group(0)[::-1], text)
132
 
133
  def _decode_token_piece(self, token_id: int) -> str:
134
+ token_id = int(token_id)
135
+ piece = self._piece_cache.get(token_id)
136
+ if piece is None:
137
+ piece = self.tokenizer.decode([token_id], skip_special_tokens=False)
138
+ self._piece_cache[token_id] = piece
139
+ return piece
140
 
141
  @staticmethod
142
  def _is_equation_whitespace(piece: str) -> bool:
143
  return bool(piece) and piece.isspace() and "\n" not in piece and "\r" not in piece
144
 
145
  def _is_equation_piece(self, token_id: int, piece: str) -> bool:
146
+ cached = self._equation_piece_cache.get(token_id)
147
+ if cached is not None:
148
+ return cached
149
  if token_id in self._special_token_ids:
150
+ result = False
151
+ elif token_id in self._digit_token_ids:
152
+ result = True
153
+ elif self._is_equation_whitespace(piece):
154
+ result = True
155
+ else:
156
+ result = len(piece) == 1 and piece in self._arithmetic_token_texts
157
+ self._equation_piece_cache[token_id] = result
158
+ return result
159
 
160
  def _annotate_equation_span(
161
  self,
 
232
 
233
  return place_ids, role_ids
234
 
235
+ def encode(self, text: str, *args, **kwargs) -> AtomEncoding:
236
  transformed = self._reverse_digit_spans(text)
237
  encoding = self.tokenizer.encode(transformed, *args, **kwargs)
238
+ return self._wrap_encoding(encoding)
239
+
240
+ def _wrap_encoding(self, encoding: Any) -> AtomEncoding:
241
  ids = [int(token_id) for token_id in encoding.ids]
242
  place_ids, role_ids = self.annotate_ids(ids)
243
+ return AtomEncoding(
244
  ids=ids,
245
  place_ids=place_ids,
246
  role_ids=role_ids,
247
  tokens=list(getattr(encoding, "tokens", [])),
248
  )
249
 
250
+ def encode_batch(self, texts: list[str], *args, **kwargs) -> list[AtomEncoding]:
251
+ transformed = [self._reverse_digit_spans(text) for text in texts]
252
+ encodings = self.tokenizer.encode_batch(transformed, *args, **kwargs)
253
+ return [self._wrap_encoding(encoding) for encoding in encodings]
254
 
255
  def decode(
256
  self,
 
317
  f"Missing {tokenizer_json}. Retrain with train_tokenizer.py so the "
318
  "whitespace and digit boundary rules are preserved."
319
  )
320
+ if not vocab_path.exists() or not merges_path.exists():
321
+ raise FileNotFoundError(f"Missing vocab.json or merges.txt in {tokenizer_dir}")
322
+
323
+ with vocab_path.open("r", encoding="utf-8") as f:
324
+ vocab = json.load(f)
 
 
 
 
325
 
326
  max_id = max(vocab.values())
327
  if max_id > 65_535:
 
345
 
346
  validate_tokenizer(tokenizer_dir)
347
  tokenizer_json, _, _ = tokenizer_files(tokenizer_dir)
348
+ return AtomTokenizer(Tokenizer.from_file(str(tokenizer_json)))