cfahlgren1 HF Staff commited on
Commit
33bfb6d
·
verified ·
1 Parent(s): 793c5c9

v2: 13 MB browser build (int8 download, float compute), export/compress scripts, onnx evals

Browse files
.gitattributes CHANGED
@@ -34,3 +34,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  hero.png filter=lfs diff=lfs merge=lfs -text
 
 
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
  hero.png filter=lfs diff=lfs merge=lfs -text
37
+ v2/web/weights.q8 filter=lfs diff=lfs merge=lfs -text
v2/compress.py ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.11,<3.14"
3
+ # dependencies = ["onnx>=1.17", "numpy<3", "onnxruntime>=1.20"]
4
+ # ///
5
+ """Shrinks a float ONNX model for download without changing how it runs.
6
+
7
+ The browser computes in float32 (onnxruntime-web's int8 kernels are several times slower than its float ones), so
8
+ only the download is compressed: every large weight is stored as int8 with a float32 scale per block of 64 values.
9
+ The page expands them back to float32 and hands them to onnxruntime-web as the model's external data.
10
+
11
+ Writes, next to each other:
12
+ graph.onnx the model graph; large weights point into "weights.f32" (external data)
13
+ weights.q8 int8 values, then float32 scales, tensor by tensor
14
+ weights.json for each tensor: where its int8 values and scales start, its shape, and where it goes in weights.f32
15
+
16
+ uv run compress.py v2-release/browser/model.onnx v2-release/web
17
+ """
18
+
19
+ import json
20
+ import sys
21
+ from pathlib import Path
22
+
23
+ import numpy as np
24
+ import onnx
25
+ from onnx import numpy_helper
26
+ from onnx.external_data_helper import set_external_data
27
+
28
+ BLOCK = 64
29
+ MIN_ELEMENTS = 4096 # smaller tensors (norm weights) stay inside the graph as float32
30
+ EXTERNAL = "weights.f32"
31
+
32
+
33
+ def quantize(values: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
34
+ """Symmetric int8 with one scale per BLOCK consecutive values."""
35
+ flat = values.astype(np.float32).ravel()
36
+ padded = np.pad(flat, (0, (-len(flat)) % BLOCK)).reshape(-1, BLOCK)
37
+ scales = np.abs(padded).max(axis=1) / 127
38
+ scales[scales == 0] = 1
39
+ q = np.clip(np.round(padded / scales[:, None]), -127, 127).astype(np.int8)
40
+ return q.ravel()[: len(flat)], scales.astype(np.float32)
41
+
42
+
43
+ def dequantize(q: np.ndarray, scales: np.ndarray, count: int) -> np.ndarray:
44
+ padded = np.pad(q.astype(np.float32), (0, (-count) % BLOCK)).reshape(-1, BLOCK)
45
+ return (padded * scales[:, None]).ravel()[:count]
46
+
47
+
48
+ def dedupe_transposes(model) -> None:
49
+ """The output layer reuses the embedding table, but export stores a transposed copy. Keep one and transpose it
50
+ in the graph instead, which saves about 2.6 MB of download."""
51
+ from onnx import helper
52
+
53
+ graph = model.graph
54
+ arrays = {t.name: numpy_helper.to_array(t) for t in graph.initializer if t.dims and np.prod(t.dims) >= MIN_ELEMENTS}
55
+ for name, values in list(arrays.items()):
56
+ if values.ndim != 2:
57
+ continue
58
+ for other, candidate in arrays.items():
59
+ if other != name and candidate.shape == values.T.shape and np.array_equal(candidate, values.T):
60
+ transposed = f"{other}_transposed"
61
+ graph.node.insert(0, helper.make_node("Transpose", [other], [transposed], perm=[1, 0], name=transposed))
62
+ for node in graph.node:
63
+ node.input[:] = [transposed if i == name else i for i in node.input]
64
+ graph.initializer.remove(next(t for t in graph.initializer if t.name == name))
65
+ print(f"stored {name} as a transpose of {other}")
66
+ return
67
+
68
+
69
+ def compress(src: Path, out: Path) -> dict:
70
+ out.mkdir(parents=True, exist_ok=True)
71
+ model = onnx.load(str(src))
72
+ dedupe_transposes(model)
73
+ manifest, q8 = [], bytearray()
74
+ f32_offset = 0
75
+ for tensor in model.graph.initializer:
76
+ values = numpy_helper.to_array(tensor)
77
+ if values.dtype != np.float32 or values.size < MIN_ELEMENTS:
78
+ continue
79
+ q, scales = quantize(values)
80
+ entry = {"name": tensor.name, "shape": list(values.shape), "count": int(values.size), "f32_offset": f32_offset,
81
+ "q_offset": len(q8), "scale_offset": len(q8) + len(q) + (-len(q)) % 4}
82
+ q8 += q.tobytes()
83
+ q8 += b"\0" * ((-len(q8)) % 4) # keep scales 4-byte aligned for Float32Array views
84
+ q8 += scales.tobytes()
85
+ manifest.append(entry)
86
+ # Point the graph at weights.f32, which the browser builds by expanding weights.q8.
87
+ if not tensor.HasField("raw_data"):
88
+ tensor.raw_data = values.astype(np.float32).tobytes()
89
+ tensor.ClearField("float_data")
90
+ set_external_data(tensor, location=EXTERNAL, offset=f32_offset, length=values.size * 4)
91
+ tensor.ClearField("raw_data")
92
+ tensor.data_location = onnx.TensorProto.EXTERNAL
93
+ f32_offset += values.size * 4
94
+ onnx.save_model(model, str(out / "graph.onnx"))
95
+ (out / "weights.q8").write_bytes(bytes(q8))
96
+ (out / "weights.json").write_text(json.dumps({"block": BLOCK, "f32_bytes": f32_offset, "tensors": manifest}))
97
+ return {"graph_mb": round((out / "graph.onnx").stat().st_size / 1e6, 3), "weights_q8_mb": round(len(q8) / 1e6, 2),
98
+ "expanded_mb": round(f32_offset / 1e6, 2), "tensors": len(manifest)}
99
+
100
+
101
+ def expand(out: Path) -> Path:
102
+ """What the browser does: rebuild weights.f32 from weights.q8. Used here to check accuracy."""
103
+ meta = json.loads((out / "weights.json").read_text())
104
+ raw = (out / "weights.q8").read_bytes()
105
+ f32 = np.zeros(meta["f32_bytes"] // 4, np.float32)
106
+ for t in meta["tensors"]:
107
+ q = np.frombuffer(raw, np.int8, t["count"], t["q_offset"])
108
+ blocks = (t["count"] + meta["block"] - 1) // meta["block"]
109
+ scales = np.frombuffer(raw, np.float32, blocks, t["scale_offset"])
110
+ f32[t["f32_offset"] // 4: t["f32_offset"] // 4 + t["count"]] = dequantize(q, scales, t["count"])
111
+ path = out / EXTERNAL
112
+ f32.tofile(path)
113
+ return path
114
+
115
+
116
+ if __name__ == "__main__":
117
+ src, out = Path(sys.argv[1]), Path(sys.argv[2])
118
+ print(json.dumps(compress(src, out), indent=1))
119
+ expand(out)
120
+ print(f"expanded {out / EXTERNAL} for checking")
v2/eval.py CHANGED
@@ -352,6 +352,30 @@ def hf_causal_lm(name: str, repo: str) -> TokenLM:
352
  return TokenLM(name, next_logits, encode, texts, stops)
353
 
354
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
355
  # ---- Scoring ---------------------------------------------------------------------------------------------------
356
 
357
 
@@ -446,6 +470,8 @@ def main():
446
  parser.add_argument("--model", help="Name for the metrics file")
447
  parser.add_argument("--onnx", help="A character-level GRU exported like enron-autocomplete")
448
  parser.add_argument("--hf", help="A Hugging Face causal LM to evaluate, e.g. HuggingFaceTB/SmolLM2-135M")
 
 
449
  parser.add_argument("--max-pauses", type=int, default=1000, help="Per domain and scenario")
450
  parser.add_argument("--table", action="store_true", help="Print a comparison of every metrics/*.json")
451
  args = parser.parse_args()
@@ -457,6 +483,8 @@ def main():
457
  model: Model = CharGRU(args.model, args.onnx)
458
  elif args.hf:
459
  model = hf_causal_lm(args.model, args.hf)
 
 
460
  else:
461
  parser.error("Pass --onnx, --hf or --table. train.py evaluates its own model through score().")
462
  all_pauses = pauses(load_eval_set(), args.max_pauses)
 
352
  return TokenLM(name, next_logits, encode, texts, stops)
353
 
354
 
355
+ def onnx_token_lm(name: str, folder: str, fim: bool) -> TokenLM:
356
+ """A tinynext v2 export: graph.onnx + weights.f32 (compressed, expanded) or model.onnx, plus tokenizer.json."""
357
+ import onnxruntime as ort
358
+ from tokenizers import Tokenizer
359
+
360
+ folder = Path(folder)
361
+ graph = folder / "graph.onnx" if (folder / "graph.onnx").exists() else folder / "model.onnx"
362
+ session = ort.InferenceSession(str(graph), providers=["CPUExecutionProvider"])
363
+ config = json.loads((folder / "config.json").read_text())
364
+ tokenizer = Tokenizer.from_file(str(folder / "tokenizer.json"))
365
+ empty = np.zeros((config["layers"], 1, config["heads"], 0, config["dim"] // config["heads"]), np.float32)
366
+
367
+ def next_logits(ids, cache):
368
+ past_k, past_v = cache if cache else (empty, empty)
369
+ logits, k, v = session.run(None, {"input_ids": np.array([ids], np.int64), "past_k": past_k, "past_v": past_v})
370
+ return logits[0], (k, v)
371
+
372
+ special = len(config["special_tokens"])
373
+ texts = [tokenizer.decode([i], skip_special_tokens=False) if i >= special else "" for i in range(config["vocab"])]
374
+ encode = lambda text: tokenizer.encode(text).ids or [0]
375
+ prompt = (lambda prefix, suffix: prefix if not suffix else [2, *suffix, 1, *prefix, 3]) if fim else None
376
+ return TokenLM(name, next_logits, encode, texts, set(range(special)), prompt=prompt)
377
+
378
+
379
  # ---- Scoring ---------------------------------------------------------------------------------------------------
380
 
381
 
 
470
  parser.add_argument("--model", help="Name for the metrics file")
471
  parser.add_argument("--onnx", help="A character-level GRU exported like enron-autocomplete")
472
  parser.add_argument("--hf", help="A Hugging Face causal LM to evaluate, e.g. HuggingFaceTB/SmolLM2-135M")
473
+ parser.add_argument("--onnx-lm", help="Folder with a tinynext v2 ONNX export, config.json and tokenizer.json")
474
+ parser.add_argument("--no-fim", action="store_true", help="With --onnx-lm: the model wasn't trained to fill in the middle")
475
  parser.add_argument("--max-pauses", type=int, default=1000, help="Per domain and scenario")
476
  parser.add_argument("--table", action="store_true", help="Print a comparison of every metrics/*.json")
477
  args = parser.parse_args()
 
483
  model: Model = CharGRU(args.model, args.onnx)
484
  elif args.hf:
485
  model = hf_causal_lm(args.model, args.hf)
486
+ elif args.onnx_lm:
487
+ model = onnx_token_lm(args.model, args.onnx_lm, fim=not args.no_fim)
488
  else:
489
  parser.error("Pass --onnx, --hf or --table. train.py evaluates its own model through score().")
490
  all_pauses = pauses(load_eval_set(), args.max_pauses)
v2/export.py ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # /// script
2
+ # requires-python = ">=3.11,<3.14"
3
+ # dependencies = [
4
+ # "torch>=2.6,<2.8",
5
+ # "numpy<3",
6
+ # "tokenizers>=0.20",
7
+ # "onnx>=1.17",
8
+ # "onnxruntime>=1.20",
9
+ # "safetensors>=0.4",
10
+ # "huggingface_hub>=0.30",
11
+ # ]
12
+ # ///
13
+ """Re-exports a trained tinynext checkpoint to browser ONNX (float and int8) with train.py's export, without retraining.
14
+
15
+ uv run export.py --weights v2-release/v2 --out v2-release/browser
16
+ """
17
+
18
+ import argparse
19
+ import importlib.util
20
+ import json
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ import torch
25
+ from safetensors.torch import load_file
26
+ from tokenizers import Tokenizer
27
+
28
+ HERE = Path(__file__).parent
29
+
30
+
31
+ def load(name: str):
32
+ spec = importlib.util.spec_from_file_location(name, HERE / f"{'eval' if name == 'tinynext_eval' else name}.py")
33
+ module = importlib.util.module_from_spec(spec)
34
+ sys.modules[name] = module
35
+ spec.loader.exec_module(module)
36
+ return module
37
+
38
+
39
+ def main():
40
+ parser = argparse.ArgumentParser()
41
+ parser.add_argument("--weights", required=True, help="Folder with model.safetensors, config.json, tokenizer.json")
42
+ parser.add_argument("--out", required=True)
43
+ args = parser.parse_args()
44
+ train, ev = load("train"), load("tinynext_eval")
45
+ weights, out = Path(args.weights), Path(args.out)
46
+ out.mkdir(parents=True, exist_ok=True)
47
+
48
+ config = json.loads((weights / "config.json").read_text())
49
+ model = train.TinyNext(config["vocab"], config["dim"], config["layers"], config["heads"], config["ffn"])
50
+ model.load_state_dict(load_file(weights / "model.safetensors"))
51
+ tokenizer = Tokenizer.from_file(str(weights / "tokenizer.json"))
52
+ report = train.export(model, out, torch.device("cpu"), train.parity_prompts(ev, tokenizer))
53
+ (out / "export.json").write_text(json.dumps(report, indent=1))
54
+ print(json.dumps(report, indent=1))
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
v2/metrics/tinynext-v2-float-onnx.json ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "tinynext-v2-float-onnx",
3
+ "sees_after": true,
4
+ "cutoffs": {
5
+ "0.3": {
6
+ "articles/typing": {
7
+ "pauses": 300,
8
+ "shown": 0.1833,
9
+ "right": 0.5818,
10
+ "avg_right_chars": 6.03,
11
+ "multiword_shown": 0.0733,
12
+ "multiword_right": 0.4545,
13
+ "saved_with_tab": 0.0536,
14
+ "saved_word_by_word": 0.0594
15
+ },
16
+ "articles/editing": {
17
+ "pauses": 300,
18
+ "shown": 0.13,
19
+ "right": 0.6923,
20
+ "avg_right_chars": 5.93,
21
+ "multiword_shown": 0.0533,
22
+ "multiword_right": 0.6875,
23
+ "saved_with_tab": 0.0444,
24
+ "saved_word_by_word": 0.0464
25
+ },
26
+ "messages/typing": {
27
+ "pauses": 300,
28
+ "shown": 0.3067,
29
+ "right": 0.6087,
30
+ "avg_right_chars": 7.71,
31
+ "multiword_shown": 0.1933,
32
+ "multiword_right": 0.569,
33
+ "saved_with_tab": 0.12,
34
+ "saved_word_by_word": 0.1408
35
+ },
36
+ "messages/editing": {
37
+ "pauses": 300,
38
+ "shown": 0.2833,
39
+ "right": 0.5059,
40
+ "avg_right_chars": 6.53,
41
+ "multiword_shown": 0.14,
42
+ "multiword_right": 0.5,
43
+ "saved_with_tab": 0.0781,
44
+ "saved_word_by_word": 0.0919
45
+ },
46
+ "email/typing": {
47
+ "pauses": 300,
48
+ "shown": 0.2367,
49
+ "right": 0.5915,
50
+ "avg_right_chars": 5.43,
51
+ "multiword_shown": 0.0667,
52
+ "multiword_right": 0.45,
53
+ "saved_with_tab": 0.0633,
54
+ "saved_word_by_word": 0.0722
55
+ },
56
+ "email/editing": {
57
+ "pauses": 300,
58
+ "shown": 0.2367,
59
+ "right": 0.5915,
60
+ "avg_right_chars": 5.57,
61
+ "multiword_shown": 0.0933,
62
+ "multiword_right": 0.5357,
63
+ "saved_with_tab": 0.065,
64
+ "saved_word_by_word": 0.0703
65
+ }
66
+ },
67
+ "0.4": {
68
+ "articles/typing": {
69
+ "pauses": 300,
70
+ "shown": 0.15,
71
+ "right": 0.6889,
72
+ "avg_right_chars": 6.03,
73
+ "multiword_shown": 0.0567,
74
+ "multiword_right": 0.5882,
75
+ "saved_with_tab": 0.0519,
76
+ "saved_word_by_word": 0.055
77
+ },
78
+ "articles/editing": {
79
+ "pauses": 300,
80
+ "shown": 0.0967,
81
+ "right": 0.7586,
82
+ "avg_right_chars": 5.82,
83
+ "multiword_shown": 0.0367,
84
+ "multiword_right": 0.9091,
85
+ "saved_with_tab": 0.0356,
86
+ "saved_word_by_word": 0.0358
87
+ },
88
+ "messages/typing": {
89
+ "pauses": 300,
90
+ "shown": 0.2367,
91
+ "right": 0.7465,
92
+ "avg_right_chars": 7.23,
93
+ "multiword_shown": 0.13,
94
+ "multiword_right": 0.6667,
95
+ "saved_with_tab": 0.1064,
96
+ "saved_word_by_word": 0.1161
97
+ },
98
+ "messages/editing": {
99
+ "pauses": 300,
100
+ "shown": 0.17,
101
+ "right": 0.6275,
102
+ "avg_right_chars": 6.38,
103
+ "multiword_shown": 0.0733,
104
+ "multiword_right": 0.6364,
105
+ "saved_with_tab": 0.0567,
106
+ "saved_word_by_word": 0.0658
107
+ },
108
+ "email/typing": {
109
+ "pauses": 300,
110
+ "shown": 0.19,
111
+ "right": 0.7368,
112
+ "avg_right_chars": 5.48,
113
+ "multiword_shown": 0.0467,
114
+ "multiword_right": 0.5714,
115
+ "saved_with_tab": 0.0639,
116
+ "saved_word_by_word": 0.0678
117
+ },
118
+ "email/editing": {
119
+ "pauses": 300,
120
+ "shown": 0.16,
121
+ "right": 0.6875,
122
+ "avg_right_chars": 5.27,
123
+ "multiword_shown": 0.06,
124
+ "multiword_right": 0.6111,
125
+ "saved_with_tab": 0.0483,
126
+ "saved_word_by_word": 0.0514
127
+ }
128
+ },
129
+ "0.5": {
130
+ "articles/typing": {
131
+ "pauses": 300,
132
+ "shown": 0.1133,
133
+ "right": 0.7647,
134
+ "avg_right_chars": 5.92,
135
+ "multiword_shown": 0.0367,
136
+ "multiword_right": 0.6364,
137
+ "saved_with_tab": 0.0428,
138
+ "saved_word_by_word": 0.0436
139
+ },
140
+ "articles/editing": {
141
+ "pauses": 300,
142
+ "shown": 0.0833,
143
+ "right": 0.8,
144
+ "avg_right_chars": 5.4,
145
+ "multiword_shown": 0.0267,
146
+ "multiword_right": 0.875,
147
+ "saved_with_tab": 0.03,
148
+ "saved_word_by_word": 0.0303
149
+ },
150
+ "messages/typing": {
151
+ "pauses": 300,
152
+ "shown": 0.1767,
153
+ "right": 0.8113,
154
+ "avg_right_chars": 7.12,
155
+ "multiword_shown": 0.0967,
156
+ "multiword_right": 0.7241,
157
+ "saved_with_tab": 0.085,
158
+ "saved_word_by_word": 0.0917
159
+ },
160
+ "messages/editing": {
161
+ "pauses": 300,
162
+ "shown": 0.1233,
163
+ "right": 0.7297,
164
+ "avg_right_chars": 6.37,
165
+ "multiword_shown": 0.0567,
166
+ "multiword_right": 0.6471,
167
+ "saved_with_tab": 0.0478,
168
+ "saved_word_by_word": 0.0561
169
+ },
170
+ "email/typing": {
171
+ "pauses": 300,
172
+ "shown": 0.1367,
173
+ "right": 0.8049,
174
+ "avg_right_chars": 5.48,
175
+ "multiword_shown": 0.0267,
176
+ "multiword_right": 0.75,
177
+ "saved_with_tab": 0.0503,
178
+ "saved_word_by_word": 0.0514
179
+ },
180
+ "email/editing": {
181
+ "pauses": 300,
182
+ "shown": 0.13,
183
+ "right": 0.7436,
184
+ "avg_right_chars": 5.07,
185
+ "multiword_shown": 0.0367,
186
+ "multiword_right": 0.5455,
187
+ "saved_with_tab": 0.0408,
188
+ "saved_word_by_word": 0.0431
189
+ }
190
+ },
191
+ "0.6": {
192
+ "articles/typing": {
193
+ "pauses": 300,
194
+ "shown": 0.1,
195
+ "right": 0.8,
196
+ "avg_right_chars": 6.0,
197
+ "multiword_shown": 0.0267,
198
+ "multiword_right": 0.75,
199
+ "saved_with_tab": 0.04,
200
+ "saved_word_by_word": 0.04
201
+ },
202
+ "articles/editing": {
203
+ "pauses": 300,
204
+ "shown": 0.0533,
205
+ "right": 0.9375,
206
+ "avg_right_chars": 5.67,
207
+ "multiword_shown": 0.02,
208
+ "multiword_right": 1.0,
209
+ "saved_with_tab": 0.0236,
210
+ "saved_word_by_word": 0.0236
211
+ },
212
+ "messages/typing": {
213
+ "pauses": 300,
214
+ "shown": 0.14,
215
+ "right": 0.881,
216
+ "avg_right_chars": 6.57,
217
+ "multiword_shown": 0.0733,
218
+ "multiword_right": 0.8182,
219
+ "saved_with_tab": 0.0675,
220
+ "saved_word_by_word": 0.0708
221
+ },
222
+ "messages/editing": {
223
+ "pauses": 300,
224
+ "shown": 0.0767,
225
+ "right": 0.7391,
226
+ "avg_right_chars": 6.18,
227
+ "multiword_shown": 0.04,
228
+ "multiword_right": 0.5,
229
+ "saved_with_tab": 0.0292,
230
+ "saved_word_by_word": 0.0375
231
+ },
232
+ "email/typing": {
233
+ "pauses": 300,
234
+ "shown": 0.0967,
235
+ "right": 0.8966,
236
+ "avg_right_chars": 5.73,
237
+ "multiword_shown": 0.02,
238
+ "multiword_right": 1.0,
239
+ "saved_with_tab": 0.0414,
240
+ "saved_word_by_word": 0.0414
241
+ },
242
+ "email/editing": {
243
+ "pauses": 300,
244
+ "shown": 0.0833,
245
+ "right": 0.92,
246
+ "avg_right_chars": 4.96,
247
+ "multiword_shown": 0.0133,
248
+ "multiword_right": 0.75,
249
+ "saved_with_tab": 0.0317,
250
+ "saved_word_by_word": 0.0328
251
+ }
252
+ },
253
+ "0.7": {
254
+ "articles/typing": {
255
+ "pauses": 300,
256
+ "shown": 0.07,
257
+ "right": 0.9524,
258
+ "avg_right_chars": 5.7,
259
+ "multiword_shown": 0.0167,
260
+ "multiword_right": 1.0,
261
+ "saved_with_tab": 0.0317,
262
+ "saved_word_by_word": 0.0317
263
+ },
264
+ "articles/editing": {
265
+ "pauses": 300,
266
+ "shown": 0.03,
267
+ "right": 0.8889,
268
+ "avg_right_chars": 5.12,
269
+ "multiword_shown": 0.0033,
270
+ "multiword_right": 1.0,
271
+ "saved_with_tab": 0.0114,
272
+ "saved_word_by_word": 0.0114
273
+ },
274
+ "messages/typing": {
275
+ "pauses": 300,
276
+ "shown": 0.11,
277
+ "right": 0.9394,
278
+ "avg_right_chars": 6.23,
279
+ "multiword_shown": 0.05,
280
+ "multiword_right": 0.8667,
281
+ "saved_with_tab": 0.0536,
282
+ "saved_word_by_word": 0.0544
283
+ },
284
+ "messages/editing": {
285
+ "pauses": 300,
286
+ "shown": 0.0633,
287
+ "right": 0.7895,
288
+ "avg_right_chars": 6.13,
289
+ "multiword_shown": 0.0333,
290
+ "multiword_right": 0.6,
291
+ "saved_with_tab": 0.0256,
292
+ "saved_word_by_word": 0.0328
293
+ },
294
+ "email/typing": {
295
+ "pauses": 300,
296
+ "shown": 0.0667,
297
+ "right": 0.85,
298
+ "avg_right_chars": 5.41,
299
+ "multiword_shown": 0.0067,
300
+ "multiword_right": 1.0,
301
+ "saved_with_tab": 0.0256,
302
+ "saved_word_by_word": 0.0256
303
+ },
304
+ "email/editing": {
305
+ "pauses": 300,
306
+ "shown": 0.06,
307
+ "right": 0.9444,
308
+ "avg_right_chars": 4.71,
309
+ "multiword_shown": 0.0033,
310
+ "multiword_right": 1.0,
311
+ "saved_with_tab": 0.0222,
312
+ "saved_word_by_word": 0.0222
313
+ }
314
+ }
315
+ },
316
+ "examples": [
317
+ {
318
+ "domain": "articles",
319
+ "scenario": "typing",
320
+ "before": "elipads, IFR approaches, and a separate float plane dock alo",
321
+ "suggestion": "ng the",
322
+ "truth": "ng the Fraser Rive",
323
+ "right": true
324
+ },
325
+ {
326
+ "domain": "articles",
327
+ "scenario": "editing",
328
+ "before": " where he met his wife, Kisa, a graduate of the University o",
329
+ "suggestion": "f Illinois",
330
+ "truth": "f Colorado. Sasaki the",
331
+ "right": false
332
+ },
333
+ {
334
+ "domain": "articles",
335
+ "scenario": "editing",
336
+ "before": "ing any kind of direct discrimination that was prominent els",
337
+ "suggestion": "ewhere.",
338
+ "truth": "ewhere in the count",
339
+ "right": false
340
+ },
341
+ {
342
+ "domain": "messages",
343
+ "scenario": "typing",
344
+ "before": "T's ability to generate coherent and contextually relevant r",
345
+ "suggestion": "esponses",
346
+ "truth": "esponses is based on",
347
+ "right": true
348
+ },
349
+ {
350
+ "domain": "messages",
351
+ "scenario": "typing",
352
+ "before": " prioritize the development of tools and models that are des",
353
+ "suggestion": "igned to",
354
+ "truth": "igned to be inclusiv",
355
+ "right": true
356
+ },
357
+ {
358
+ "domain": "messages",
359
+ "scenario": "editing",
360
+ "before": "e at which a population is growing.\nL'Hopital's rule can als",
361
+ "suggestion": "o be used to",
362
+ "truth": "o be used to solve probl",
363
+ "right": true
364
+ },
365
+ {
366
+ "domain": "email",
367
+ "scenario": "typing",
368
+ "before": "lt time but am confident that I would make an excellent addi",
369
+ "suggestion": "tion to",
370
+ "truth": "tion to your team f",
371
+ "right": true
372
+ },
373
+ {
374
+ "domain": "email",
375
+ "scenario": "typing",
376
+ "before": ". I recommend we agree. Although, we are in the content deve",
377
+ "suggestion": "lopment",
378
+ "truth": "lopment phase the t",
379
+ "right": true
380
+ },
381
+ {
382
+ "domain": "email",
383
+ "scenario": "editing",
384
+ "before": "ured products and marketing of electricity for the United St",
385
+ "suggestion": "ates",
386
+ "truth": "ates ? Actively ",
387
+ "right": true
388
+ },
389
+ {
390
+ "domain": "email",
391
+ "scenario": "editing",
392
+ "before": "I'm not sure what you are looking for. The Early Termination",
393
+ "suggestion": " Log",
394
+ "truth": " Date is one of ",
395
+ "right": false
396
+ },
397
+ {
398
+ "domain": "email",
399
+ "scenario": "editing",
400
+ "before": " action is necessary as contractually we are coming to the e",
401
+ "suggestion": "nd of",
402
+ "truth": "nd of the accepta",
403
+ "right": true
404
+ }
405
+ ],
406
+ "latency_ms": {
407
+ "p50": 25.37,
408
+ "p95": 77.65,
409
+ "note": "Measured in this eval process, not in a browser."
410
+ }
411
+ }
v2/metrics/tinynext-v2-q8-onnx.json ADDED
@@ -0,0 +1,411 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "model": "tinynext-v2-q8-onnx",
3
+ "sees_after": true,
4
+ "cutoffs": {
5
+ "0.3": {
6
+ "articles/typing": {
7
+ "pauses": 300,
8
+ "shown": 0.1767,
9
+ "right": 0.5849,
10
+ "avg_right_chars": 6.0,
11
+ "multiword_shown": 0.0733,
12
+ "multiword_right": 0.4545,
13
+ "saved_with_tab": 0.0517,
14
+ "saved_word_by_word": 0.0575
15
+ },
16
+ "articles/editing": {
17
+ "pauses": 300,
18
+ "shown": 0.13,
19
+ "right": 0.6923,
20
+ "avg_right_chars": 5.93,
21
+ "multiword_shown": 0.0533,
22
+ "multiword_right": 0.6875,
23
+ "saved_with_tab": 0.0444,
24
+ "saved_word_by_word": 0.0464
25
+ },
26
+ "messages/typing": {
27
+ "pauses": 300,
28
+ "shown": 0.3,
29
+ "right": 0.6333,
30
+ "avg_right_chars": 7.65,
31
+ "multiword_shown": 0.1933,
32
+ "multiword_right": 0.569,
33
+ "saved_with_tab": 0.1211,
34
+ "saved_word_by_word": 0.1403
35
+ },
36
+ "messages/editing": {
37
+ "pauses": 300,
38
+ "shown": 0.2767,
39
+ "right": 0.5181,
40
+ "avg_right_chars": 6.53,
41
+ "multiword_shown": 0.14,
42
+ "multiword_right": 0.5,
43
+ "saved_with_tab": 0.0781,
44
+ "saved_word_by_word": 0.0919
45
+ },
46
+ "email/typing": {
47
+ "pauses": 300,
48
+ "shown": 0.2333,
49
+ "right": 0.5857,
50
+ "avg_right_chars": 5.46,
51
+ "multiword_shown": 0.0667,
52
+ "multiword_right": 0.45,
53
+ "saved_with_tab": 0.0622,
54
+ "saved_word_by_word": 0.0714
55
+ },
56
+ "email/editing": {
57
+ "pauses": 300,
58
+ "shown": 0.2333,
59
+ "right": 0.6,
60
+ "avg_right_chars": 5.57,
61
+ "multiword_shown": 0.0933,
62
+ "multiword_right": 0.5357,
63
+ "saved_with_tab": 0.065,
64
+ "saved_word_by_word": 0.0703
65
+ }
66
+ },
67
+ "0.4": {
68
+ "articles/typing": {
69
+ "pauses": 300,
70
+ "shown": 0.1467,
71
+ "right": 0.6818,
72
+ "avg_right_chars": 6.0,
73
+ "multiword_shown": 0.0567,
74
+ "multiword_right": 0.5882,
75
+ "saved_with_tab": 0.05,
76
+ "saved_word_by_word": 0.0531
77
+ },
78
+ "articles/editing": {
79
+ "pauses": 300,
80
+ "shown": 0.0967,
81
+ "right": 0.7586,
82
+ "avg_right_chars": 5.82,
83
+ "multiword_shown": 0.0367,
84
+ "multiword_right": 0.9091,
85
+ "saved_with_tab": 0.0356,
86
+ "saved_word_by_word": 0.0358
87
+ },
88
+ "messages/typing": {
89
+ "pauses": 300,
90
+ "shown": 0.24,
91
+ "right": 0.7361,
92
+ "avg_right_chars": 7.26,
93
+ "multiword_shown": 0.1333,
94
+ "multiword_right": 0.65,
95
+ "saved_with_tab": 0.1069,
96
+ "saved_word_by_word": 0.1167
97
+ },
98
+ "messages/editing": {
99
+ "pauses": 300,
100
+ "shown": 0.17,
101
+ "right": 0.6275,
102
+ "avg_right_chars": 6.38,
103
+ "multiword_shown": 0.0733,
104
+ "multiword_right": 0.6364,
105
+ "saved_with_tab": 0.0567,
106
+ "saved_word_by_word": 0.0658
107
+ },
108
+ "email/typing": {
109
+ "pauses": 300,
110
+ "shown": 0.1867,
111
+ "right": 0.75,
112
+ "avg_right_chars": 5.48,
113
+ "multiword_shown": 0.0467,
114
+ "multiword_right": 0.5714,
115
+ "saved_with_tab": 0.0639,
116
+ "saved_word_by_word": 0.0678
117
+ },
118
+ "email/editing": {
119
+ "pauses": 300,
120
+ "shown": 0.1667,
121
+ "right": 0.68,
122
+ "avg_right_chars": 5.26,
123
+ "multiword_shown": 0.06,
124
+ "multiword_right": 0.6111,
125
+ "saved_with_tab": 0.0497,
126
+ "saved_word_by_word": 0.0528
127
+ }
128
+ },
129
+ "0.5": {
130
+ "articles/typing": {
131
+ "pauses": 300,
132
+ "shown": 0.11,
133
+ "right": 0.7879,
134
+ "avg_right_chars": 5.81,
135
+ "multiword_shown": 0.0333,
136
+ "multiword_right": 0.7,
137
+ "saved_with_tab": 0.0419,
138
+ "saved_word_by_word": 0.0425
139
+ },
140
+ "articles/editing": {
141
+ "pauses": 300,
142
+ "shown": 0.0833,
143
+ "right": 0.8,
144
+ "avg_right_chars": 5.4,
145
+ "multiword_shown": 0.0267,
146
+ "multiword_right": 0.875,
147
+ "saved_with_tab": 0.03,
148
+ "saved_word_by_word": 0.0303
149
+ },
150
+ "messages/typing": {
151
+ "pauses": 300,
152
+ "shown": 0.18,
153
+ "right": 0.7963,
154
+ "avg_right_chars": 6.98,
155
+ "multiword_shown": 0.1,
156
+ "multiword_right": 0.7,
157
+ "saved_with_tab": 0.0833,
158
+ "saved_word_by_word": 0.09
159
+ },
160
+ "messages/editing": {
161
+ "pauses": 300,
162
+ "shown": 0.1233,
163
+ "right": 0.7297,
164
+ "avg_right_chars": 6.37,
165
+ "multiword_shown": 0.0567,
166
+ "multiword_right": 0.6471,
167
+ "saved_with_tab": 0.0478,
168
+ "saved_word_by_word": 0.0561
169
+ },
170
+ "email/typing": {
171
+ "pauses": 300,
172
+ "shown": 0.14,
173
+ "right": 0.7857,
174
+ "avg_right_chars": 5.48,
175
+ "multiword_shown": 0.0267,
176
+ "multiword_right": 0.75,
177
+ "saved_with_tab": 0.0503,
178
+ "saved_word_by_word": 0.0514
179
+ },
180
+ "email/editing": {
181
+ "pauses": 300,
182
+ "shown": 0.1267,
183
+ "right": 0.7632,
184
+ "avg_right_chars": 5.07,
185
+ "multiword_shown": 0.0367,
186
+ "multiword_right": 0.5455,
187
+ "saved_with_tab": 0.0408,
188
+ "saved_word_by_word": 0.0431
189
+ }
190
+ },
191
+ "0.6": {
192
+ "articles/typing": {
193
+ "pauses": 300,
194
+ "shown": 0.0967,
195
+ "right": 0.7931,
196
+ "avg_right_chars": 5.96,
197
+ "multiword_shown": 0.0267,
198
+ "multiword_right": 0.75,
199
+ "saved_with_tab": 0.0381,
200
+ "saved_word_by_word": 0.0381
201
+ },
202
+ "articles/editing": {
203
+ "pauses": 300,
204
+ "shown": 0.05,
205
+ "right": 0.9333,
206
+ "avg_right_chars": 5.79,
207
+ "multiword_shown": 0.02,
208
+ "multiword_right": 1.0,
209
+ "saved_with_tab": 0.0225,
210
+ "saved_word_by_word": 0.0225
211
+ },
212
+ "messages/typing": {
213
+ "pauses": 300,
214
+ "shown": 0.1467,
215
+ "right": 0.8864,
216
+ "avg_right_chars": 6.54,
217
+ "multiword_shown": 0.0733,
218
+ "multiword_right": 0.8182,
219
+ "saved_with_tab": 0.0708,
220
+ "saved_word_by_word": 0.0742
221
+ },
222
+ "messages/editing": {
223
+ "pauses": 300,
224
+ "shown": 0.0767,
225
+ "right": 0.7391,
226
+ "avg_right_chars": 6.18,
227
+ "multiword_shown": 0.04,
228
+ "multiword_right": 0.5,
229
+ "saved_with_tab": 0.0292,
230
+ "saved_word_by_word": 0.0375
231
+ },
232
+ "email/typing": {
233
+ "pauses": 300,
234
+ "shown": 0.1,
235
+ "right": 0.8667,
236
+ "avg_right_chars": 5.73,
237
+ "multiword_shown": 0.02,
238
+ "multiword_right": 1.0,
239
+ "saved_with_tab": 0.0414,
240
+ "saved_word_by_word": 0.0414
241
+ },
242
+ "email/editing": {
243
+ "pauses": 300,
244
+ "shown": 0.09,
245
+ "right": 0.8889,
246
+ "avg_right_chars": 4.92,
247
+ "multiword_shown": 0.0133,
248
+ "multiword_right": 0.75,
249
+ "saved_with_tab": 0.0328,
250
+ "saved_word_by_word": 0.0339
251
+ }
252
+ },
253
+ "0.7": {
254
+ "articles/typing": {
255
+ "pauses": 300,
256
+ "shown": 0.0667,
257
+ "right": 0.95,
258
+ "avg_right_chars": 5.63,
259
+ "multiword_shown": 0.0167,
260
+ "multiword_right": 1.0,
261
+ "saved_with_tab": 0.0297,
262
+ "saved_word_by_word": 0.0297
263
+ },
264
+ "articles/editing": {
265
+ "pauses": 300,
266
+ "shown": 0.03,
267
+ "right": 0.8889,
268
+ "avg_right_chars": 5.12,
269
+ "multiword_shown": 0.0033,
270
+ "multiword_right": 1.0,
271
+ "saved_with_tab": 0.0114,
272
+ "saved_word_by_word": 0.0114
273
+ },
274
+ "messages/typing": {
275
+ "pauses": 300,
276
+ "shown": 0.1133,
277
+ "right": 0.9412,
278
+ "avg_right_chars": 6.22,
279
+ "multiword_shown": 0.05,
280
+ "multiword_right": 0.8667,
281
+ "saved_with_tab": 0.0553,
282
+ "saved_word_by_word": 0.0561
283
+ },
284
+ "messages/editing": {
285
+ "pauses": 300,
286
+ "shown": 0.0633,
287
+ "right": 0.7895,
288
+ "avg_right_chars": 5.93,
289
+ "multiword_shown": 0.03,
290
+ "multiword_right": 0.5556,
291
+ "saved_with_tab": 0.0247,
292
+ "saved_word_by_word": 0.0319
293
+ },
294
+ "email/typing": {
295
+ "pauses": 300,
296
+ "shown": 0.0667,
297
+ "right": 0.85,
298
+ "avg_right_chars": 5.41,
299
+ "multiword_shown": 0.0067,
300
+ "multiword_right": 1.0,
301
+ "saved_with_tab": 0.0256,
302
+ "saved_word_by_word": 0.0256
303
+ },
304
+ "email/editing": {
305
+ "pauses": 300,
306
+ "shown": 0.06,
307
+ "right": 0.9444,
308
+ "avg_right_chars": 4.71,
309
+ "multiword_shown": 0.0033,
310
+ "multiword_right": 1.0,
311
+ "saved_with_tab": 0.0222,
312
+ "saved_word_by_word": 0.0222
313
+ }
314
+ }
315
+ },
316
+ "examples": [
317
+ {
318
+ "domain": "articles",
319
+ "scenario": "typing",
320
+ "before": "elipads, IFR approaches, and a separate float plane dock alo",
321
+ "suggestion": "ng the",
322
+ "truth": "ng the Fraser Rive",
323
+ "right": true
324
+ },
325
+ {
326
+ "domain": "articles",
327
+ "scenario": "editing",
328
+ "before": " where he met his wife, Kisa, a graduate of the University o",
329
+ "suggestion": "f Illinois",
330
+ "truth": "f Colorado. Sasaki the",
331
+ "right": false
332
+ },
333
+ {
334
+ "domain": "articles",
335
+ "scenario": "editing",
336
+ "before": "ing any kind of direct discrimination that was prominent els",
337
+ "suggestion": "ewhere.",
338
+ "truth": "ewhere in the count",
339
+ "right": false
340
+ },
341
+ {
342
+ "domain": "messages",
343
+ "scenario": "typing",
344
+ "before": "T's ability to generate coherent and contextually relevant r",
345
+ "suggestion": "esponses",
346
+ "truth": "esponses is based on",
347
+ "right": true
348
+ },
349
+ {
350
+ "domain": "messages",
351
+ "scenario": "typing",
352
+ "before": " prioritize the development of tools and models that are des",
353
+ "suggestion": "igned to",
354
+ "truth": "igned to be inclusiv",
355
+ "right": true
356
+ },
357
+ {
358
+ "domain": "messages",
359
+ "scenario": "editing",
360
+ "before": "e at which a population is growing.\nL'Hopital's rule can als",
361
+ "suggestion": "o be used to",
362
+ "truth": "o be used to solve probl",
363
+ "right": true
364
+ },
365
+ {
366
+ "domain": "email",
367
+ "scenario": "typing",
368
+ "before": "lt time but am confident that I would make an excellent addi",
369
+ "suggestion": "tion to",
370
+ "truth": "tion to your team f",
371
+ "right": true
372
+ },
373
+ {
374
+ "domain": "email",
375
+ "scenario": "typing",
376
+ "before": ". I recommend we agree. Although, we are in the content deve",
377
+ "suggestion": "lopment",
378
+ "truth": "lopment phase the t",
379
+ "right": true
380
+ },
381
+ {
382
+ "domain": "email",
383
+ "scenario": "editing",
384
+ "before": "ured products and marketing of electricity for the United St",
385
+ "suggestion": "ates",
386
+ "truth": "ates ? Actively ",
387
+ "right": true
388
+ },
389
+ {
390
+ "domain": "email",
391
+ "scenario": "editing",
392
+ "before": "I'm not sure what you are looking for. The Early Termination",
393
+ "suggestion": " Log",
394
+ "truth": " Date is one of ",
395
+ "right": false
396
+ },
397
+ {
398
+ "domain": "email",
399
+ "scenario": "editing",
400
+ "before": " action is necessary as contractually we are coming to the e",
401
+ "suggestion": "nd of",
402
+ "truth": "nd of the accepta",
403
+ "right": true
404
+ }
405
+ ],
406
+ "latency_ms": {
407
+ "p50": 25.25,
408
+ "p95": 75.94,
409
+ "note": "Measured in this eval process, not in a browser."
410
+ }
411
+ }
v2/train.py CHANGED
@@ -275,6 +275,7 @@ class TinyNext(nn.Module):
275
  self.embed = nn.Embedding(vocab, dim)
276
  self.blocks = nn.ModuleList(Block(dim, heads, ffn) for _ in range(layers))
277
  self.norm = RMSNorm(dim)
 
278
  self.head_dim = dim // heads
279
  inverse = 1.0 / (10000 ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim))
280
  self.register_buffer("inverse_frequency", inverse, persistent=False)
@@ -298,7 +299,7 @@ class TinyNext(nn.Module):
298
  x, k, v = block(x, cos, sin, None if past_k is None else past_k[index], None if past_v is None else past_v[index], mask)
299
  new_k.append(k)
300
  new_v.append(v)
301
- logits = self.norm(x) @ self.embed.weight.T
302
  return logits, torch.stack(new_k), torch.stack(new_v)
303
 
304
 
@@ -414,46 +415,79 @@ def as_token_lm(ev, name: str, model: TinyNext, tokenizer, device, fim: bool = T
414
  return ev.TokenLM(name, next_logits, encode, texts, set(range(len(SPECIAL))), prompt=prompt)
415
 
416
 
417
- def export(model: TinyNext, output: Path, device) -> dict:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
418
  import onnxruntime as ort
419
  from onnxruntime.quantization import QuantType, quantize_dynamic
420
 
421
  model = model.float().eval().to("cpu")
 
422
  cfg = model.config
423
  head_dim = cfg["dim"] // cfg["heads"]
424
- ids = torch.tensor([[5, 6, 7]], dtype=torch.long)
425
- empty = torch.zeros(cfg["layers"], 1, cfg["heads"], 0, head_dim)
426
- # Trace with a non-empty cache so no shape gets fixed at zero; the checks below run with an empty one.
427
  with torch.no_grad():
428
  _, trace_k, trace_v = model(torch.tensor([[1, 2]]))
429
  float_path, int8_path = output / "model.onnx", output / "model_int8.onnx"
430
  torch.onnx.export(
431
- BrowserStep(model), (ids, trace_k, trace_v), float_path, opset_version=17,
432
  input_names=["input_ids", "past_k", "past_v"], output_names=["logits", "present_k", "present_v"],
433
  dynamic_axes={"input_ids": {1: "length"}, "past_k": {3: "past"}, "past_v": {3: "past"},
434
  "present_k": {3: "total"}, "present_v": {3: "total"}},
435
  dynamo=False,
436
  )
437
- quantize_dynamic(float_path, int8_path, weight_type=QuantType.QInt8, op_types_to_quantize=["MatMul"])
438
 
439
- report = {}
440
- with torch.no_grad():
441
- expected, k, v = model(ids)
442
- expected_next, _, _ = model(torch.tensor([[8]]), k, v)
 
 
 
 
443
  for name, path in (("float", float_path), ("int8", int8_path)):
444
  session = ort.InferenceSession(str(path), providers=["CPUExecutionProvider"])
445
- logits, pk, pv = session.run(None, {"input_ids": ids.numpy(), "past_k": empty.numpy(), "past_v": empty.numpy()})
446
- logits2, _, _ = session.run(None, {"input_ids": np.array([[8]]), "past_k": pk, "past_v": pv})
 
 
 
 
447
  timings = []
448
  for _ in range(200):
449
  began = time.perf_counter()
450
- session.run(None, {"input_ids": np.array([[8]]), "past_k": pk, "past_v": pv})
451
  timings.append((time.perf_counter() - began) * 1000)
452
  report[name] = {
453
  "megabytes": round(path.stat().st_size / 1e6, 2),
454
- "same_top_token": bool(logits.argmax() == expected[0, -1].argmax() and logits2.argmax() == expected_next[0, -1].argmax()),
 
455
  "cached_step_ms_p50_cpu": round(float(np.median(timings)), 3),
456
  }
 
457
  model.to(device)
458
  return report
459
 
@@ -620,7 +654,7 @@ def main():
620
  (output / "metrics").mkdir(exist_ok=True)
621
  (output / "metrics" / f"{args.name}.json").write_text(json.dumps(scored, indent=1))
622
  tracker.log(headline(scored))
623
- exported = export(model, output, device)
624
 
625
  config = {
626
  **model.config, "parameters": parameters, "seq": args.seq, "special_tokens": dict(zip(SPECIAL, range(len(SPECIAL)))),
 
275
  self.embed = nn.Embedding(vocab, dim)
276
  self.blocks = nn.ModuleList(Block(dim, heads, ffn) for _ in range(layers))
277
  self.norm = RMSNorm(dim)
278
+ self.head = None # set only for export: an untied copy of the output weights, so int8 quantization covers it
279
  self.head_dim = dim // heads
280
  inverse = 1.0 / (10000 ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim))
281
  self.register_buffer("inverse_frequency", inverse, persistent=False)
 
299
  x, k, v = block(x, cos, sin, None if past_k is None else past_k[index], None if past_v is None else past_v[index], mask)
300
  new_k.append(k)
301
  new_v.append(v)
302
+ logits = self.norm(x) @ (self.embed.weight.T if self.head is None else self.head)
303
  return logits, torch.stack(new_k), torch.stack(new_v)
304
 
305
 
 
415
  return ev.TokenLM(name, next_logits, encode, texts, set(range(len(SPECIAL))), prompt=prompt)
416
 
417
 
418
+ def parity_prompts(ev, tokenizer, count: int = 300) -> list[list[int]]:
419
+ """Real text from the eval set, cut at random points, as token prompts."""
420
+ rng = random.Random(0)
421
+ texts = [p for domain in ev.load_eval_set().values() for doc in domain for p in doc]
422
+ prompts = []
423
+ for text in rng.sample(texts, min(count, len(texts))):
424
+ ids = tokenizer.encode(text[: rng.randint(40, len(text))]).ids[-300:]
425
+ if len(ids) >= 4:
426
+ prompts.append(ids)
427
+ return prompts
428
+
429
+
430
+ def greedy_tokens(step, ids: list[int], empty, steps: int) -> list[int]:
431
+ """Prompt, then `steps` cached greedy steps; returns the top token at each point."""
432
+ logits, k, v = step(np.array([ids]), empty, empty)
433
+ tops = [int(logits.argmax())]
434
+ for _ in range(steps):
435
+ logits, k, v = step(np.array([[tops[-1]]]), k, v)
436
+ tops.append(int(logits.argmax()))
437
+ return tops
438
+
439
+
440
+ def export(model: TinyNext, output: Path, device, prompts: list[list[int]]) -> dict:
441
+ """Float and int8 ONNX for the browser, checked against PyTorch on real prompts over several cached steps."""
442
  import onnxruntime as ort
443
  from onnxruntime.quantization import QuantType, quantize_dynamic
444
 
445
  model = model.float().eval().to("cpu")
446
+ model.head = nn.Parameter(model.embed.weight.detach().T.contiguous().clone())
447
  cfg = model.config
448
  head_dim = cfg["dim"] // cfg["heads"]
449
+ empty = np.zeros((cfg["layers"], 1, cfg["heads"], 0, head_dim), np.float32)
450
+ # Trace with a non-empty cache so no shape gets fixed at zero; the checks below start from an empty one.
 
451
  with torch.no_grad():
452
  _, trace_k, trace_v = model(torch.tensor([[1, 2]]))
453
  float_path, int8_path = output / "model.onnx", output / "model_int8.onnx"
454
  torch.onnx.export(
455
+ BrowserStep(model), (torch.tensor([[5, 6, 7]]), trace_k, trace_v), float_path, opset_version=17,
456
  input_names=["input_ids", "past_k", "past_v"], output_names=["logits", "present_k", "present_v"],
457
  dynamic_axes={"input_ids": {1: "length"}, "past_k": {3: "past"}, "past_v": {3: "past"},
458
  "present_k": {3: "total"}, "present_v": {3: "total"}},
459
  dynamo=False,
460
  )
461
+ quantize_dynamic(float_path, int8_path, weight_type=QuantType.QInt8, op_types_to_quantize=["MatMul", "Gather"], per_channel=True)
462
 
463
+ @torch.no_grad()
464
+ def torch_step(ids, k, v):
465
+ past = None if k.shape[3] == 0 else (torch.from_numpy(k), torch.from_numpy(v))
466
+ logits, nk, nv = model(torch.from_numpy(ids), *(past or (None, None)))
467
+ return logits[0, -1].numpy(), nk.numpy(), nv.numpy()
468
+
469
+ expected = [greedy_tokens(torch_step, ids, empty, 4) for ids in prompts]
470
+ report = {"parity_prompts": len(prompts), "parity_steps_per_prompt": 5}
471
  for name, path in (("float", float_path), ("int8", int8_path)):
472
  session = ort.InferenceSession(str(path), providers=["CPUExecutionProvider"])
473
+ onnx_step = lambda ids, k, v: (lambda out: (out[0][0], out[1], out[2]))(
474
+ session.run(None, {"input_ids": ids.astype(np.int64), "past_k": k, "past_v": v}))
475
+ got = [greedy_tokens(onnx_step, ids, empty, 4) for ids in prompts]
476
+ first = np.mean([g[0] == e[0] for g, e in zip(got, expected)])
477
+ all_steps = np.mean([g == e for g, e in zip(got, expected)])
478
+ _, pk, pv = onnx_step(np.array([prompts[0]]), empty, empty)
479
  timings = []
480
  for _ in range(200):
481
  began = time.perf_counter()
482
+ onnx_step(np.array([[8]]), pk, pv)
483
  timings.append((time.perf_counter() - began) * 1000)
484
  report[name] = {
485
  "megabytes": round(path.stat().st_size / 1e6, 2),
486
+ "same_first_token": round(float(first), 4),
487
+ "same_five_greedy_tokens": round(float(all_steps), 4),
488
  "cached_step_ms_p50_cpu": round(float(np.median(timings)), 3),
489
  }
490
+ model.head = None
491
  model.to(device)
492
  return report
493
 
 
654
  (output / "metrics").mkdir(exist_ok=True)
655
  (output / "metrics" / f"{args.name}.json").write_text(json.dumps(scored, indent=1))
656
  tracker.log(headline(scored))
657
+ exported = export(model, output, device, parity_prompts(ev, tokenizer))
658
 
659
  config = {
660
  **model.config, "parameters": parameters, "seq": args.seq, "special_tokens": dict(zip(SPECIAL, range(len(SPECIAL)))),
v2/web/config.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "vocab": 8192,
3
+ "dim": 320,
4
+ "layers": 8,
5
+ "heads": 5,
6
+ "ffn": 864,
7
+ "parameters": 12539200,
8
+ "seq": 512,
9
+ "special_tokens": {
10
+ "<|endoftext|>": 0,
11
+ "<|pre|>": 1,
12
+ "<|suf|>": 2,
13
+ "<|mid|>": 3
14
+ },
15
+ "prompt": "No text after the cursor: the text before it. Otherwise: <|suf|> after <|pre|> before <|mid|>."
16
+ }
v2/web/graph.onnx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e63c34d5f6882b6c9d7dda768ea6009edc831f26f5e7a800161a54a204f521d7
3
+ size 194807
v2/web/parity.json ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "parity_prompts": 300,
3
+ "parity_steps_per_prompt": 5,
4
+ "float": {
5
+ "megabytes": 60.81,
6
+ "same_first_token": 1.0,
7
+ "same_five_greedy_tokens": 1.0,
8
+ "cached_step_ms_p50_cpu": 1.2
9
+ },
10
+ "int8": {
11
+ "megabytes": 15.64,
12
+ "same_first_token": 0.55,
13
+ "same_five_greedy_tokens": 0.33,
14
+ "cached_step_ms_p50_cpu": 0.71
15
+ }
16
+ }
v2/web/tokenizer.json ADDED
The diff for this file is too large to render. See raw diff
 
v2/web/weights.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"block": 64, "f32_bytes": 50135040, "tensors": [{"name": "model.embed.weight", "shape": [8192, 320], "count": 2621440, "f32_offset": 0, "q_offset": 0, "scale_offset": 2621440}, {"name": "onnx::MatMul_1554", "shape": [320, 960], "count": 307200, "f32_offset": 10485760, "q_offset": 2785280, "scale_offset": 3092480}, {"name": "onnx::MatMul_1571", "shape": [320, 320], "count": 102400, "f32_offset": 11714560, "q_offset": 3111680, "scale_offset": 3214080}, {"name": "onnx::MatMul_1572", "shape": [320, 1728], "count": 552960, "f32_offset": 12124160, "q_offset": 3220480, "scale_offset": 3773440}, {"name": "onnx::MatMul_1573", "shape": [864, 320], "count": 276480, "f32_offset": 14336000, "q_offset": 3808000, "scale_offset": 4084480}, {"name": "onnx::MatMul_1574", "shape": [320, 960], "count": 307200, "f32_offset": 15441920, "q_offset": 4101760, "scale_offset": 4408960}, {"name": "onnx::MatMul_1591", "shape": [320, 320], "count": 102400, "f32_offset": 16670720, "q_offset": 4428160, "scale_offset": 4530560}, {"name": "onnx::MatMul_1592", "shape": [320, 1728], "count": 552960, "f32_offset": 17080320, "q_offset": 4536960, "scale_offset": 5089920}, {"name": "onnx::MatMul_1593", "shape": [864, 320], "count": 276480, "f32_offset": 19292160, "q_offset": 5124480, "scale_offset": 5400960}, {"name": "onnx::MatMul_1594", "shape": [320, 960], "count": 307200, "f32_offset": 20398080, "q_offset": 5418240, "scale_offset": 5725440}, {"name": "onnx::MatMul_1611", "shape": [320, 320], "count": 102400, "f32_offset": 21626880, "q_offset": 5744640, "scale_offset": 5847040}, {"name": "onnx::MatMul_1612", "shape": [320, 1728], "count": 552960, "f32_offset": 22036480, "q_offset": 5853440, "scale_offset": 6406400}, {"name": "onnx::MatMul_1613", "shape": [864, 320], "count": 276480, "f32_offset": 24248320, "q_offset": 6440960, "scale_offset": 6717440}, {"name": "onnx::MatMul_1614", "shape": [320, 960], "count": 307200, "f32_offset": 25354240, "q_offset": 6734720, "scale_offset": 7041920}, {"name": "onnx::MatMul_1631", "shape": [320, 320], "count": 102400, "f32_offset": 26583040, "q_offset": 7061120, "scale_offset": 7163520}, {"name": "onnx::MatMul_1632", "shape": [320, 1728], "count": 552960, "f32_offset": 26992640, "q_offset": 7169920, "scale_offset": 7722880}, {"name": "onnx::MatMul_1633", "shape": [864, 320], "count": 276480, "f32_offset": 29204480, "q_offset": 7757440, "scale_offset": 8033920}, {"name": "onnx::MatMul_1634", "shape": [320, 960], "count": 307200, "f32_offset": 30310400, "q_offset": 8051200, "scale_offset": 8358400}, {"name": "onnx::MatMul_1651", "shape": [320, 320], "count": 102400, "f32_offset": 31539200, "q_offset": 8377600, "scale_offset": 8480000}, {"name": "onnx::MatMul_1652", "shape": [320, 1728], "count": 552960, "f32_offset": 31948800, "q_offset": 8486400, "scale_offset": 9039360}, {"name": "onnx::MatMul_1653", "shape": [864, 320], "count": 276480, "f32_offset": 34160640, "q_offset": 9073920, "scale_offset": 9350400}, {"name": "onnx::MatMul_1654", "shape": [320, 960], "count": 307200, "f32_offset": 35266560, "q_offset": 9367680, "scale_offset": 9674880}, {"name": "onnx::MatMul_1671", "shape": [320, 320], "count": 102400, "f32_offset": 36495360, "q_offset": 9694080, "scale_offset": 9796480}, {"name": "onnx::MatMul_1672", "shape": [320, 1728], "count": 552960, "f32_offset": 36904960, "q_offset": 9802880, "scale_offset": 10355840}, {"name": "onnx::MatMul_1673", "shape": [864, 320], "count": 276480, "f32_offset": 39116800, "q_offset": 10390400, "scale_offset": 10666880}, {"name": "onnx::MatMul_1674", "shape": [320, 960], "count": 307200, "f32_offset": 40222720, "q_offset": 10684160, "scale_offset": 10991360}, {"name": "onnx::MatMul_1691", "shape": [320, 320], "count": 102400, "f32_offset": 41451520, "q_offset": 11010560, "scale_offset": 11112960}, {"name": "onnx::MatMul_1692", "shape": [320, 1728], "count": 552960, "f32_offset": 41861120, "q_offset": 11119360, "scale_offset": 11672320}, {"name": "onnx::MatMul_1693", "shape": [864, 320], "count": 276480, "f32_offset": 44072960, "q_offset": 11706880, "scale_offset": 11983360}, {"name": "onnx::MatMul_1694", "shape": [320, 960], "count": 307200, "f32_offset": 45178880, "q_offset": 12000640, "scale_offset": 12307840}, {"name": "onnx::MatMul_1711", "shape": [320, 320], "count": 102400, "f32_offset": 46407680, "q_offset": 12327040, "scale_offset": 12429440}, {"name": "onnx::MatMul_1712", "shape": [320, 1728], "count": 552960, "f32_offset": 46817280, "q_offset": 12435840, "scale_offset": 12988800}, {"name": "onnx::MatMul_1713", "shape": [864, 320], "count": 276480, "f32_offset": 49029120, "q_offset": 13023360, "scale_offset": 13299840}]}
v2/web/weights.q8 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b74d19232900fda1c0e1cdcd1113a4825cc05874805796e138edf778b8053dde
3
+ size 13317120