tmickleydoyle commited on
Commit
ab4f92b
·
verified ·
1 Parent(s): 9b3e87e

Compiled (not trained) 91M-param Llama autocomplete model, TypeScript runtime and UI, docs

Browse files
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2026 tmickleydoyle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: mit
3
+ language:
4
+ - en
5
+ library_name: transformers
6
+ pipeline_tag: text-generation
7
+ tags:
8
+ - llama
9
+ - hand-crafted
10
+ - compiled
11
+ - autocomplete
12
+ - no-training
13
+ - n-gram
14
+ widget:
15
+ - text: Once upon a
16
+ - text: I hope this email
17
+ - text: The quick brown
18
+ ---
19
+
20
+ # tiny-autocomplete
21
+
22
+ A 91M-parameter Llama-format model whose weights were **compiled, not trained**.
23
+ No gradient descent was run, and no other model or pretrained weights were used. Every number
24
+ comes from a closed-form rule in [`build_weights.py`](build_weights.py), applied to ~1,100
25
+ common English phrases in [`phrases/`](phrases). Full details are in
26
+ [docs/how-it-works.md](docs/how-it-works.md).
27
+
28
+ | Prompt | Greedy completion |
29
+ |---|---|
30
+ | Once upon a | time. |
31
+ | The quick brown | fox jumps over the lazy dog. |
32
+ | I hope this email | finds you well. |
33
+ | Thank you for your | help. |
34
+ | Can I have the | check, please? |
35
+ | Looking forward to | hearing from you. |
36
+ | Actions speak | louder than words. |
37
+ | How are | you doing? |
38
+
39
+ ## Use it with transformers
40
+
41
+ ```python
42
+ from transformers import pipeline
43
+
44
+ gen = pipeline("text-generation", model="tmickleydoyle/tiny-autocomplete")
45
+ gen("Once upon a")[0]["generated_text"] # 'Once upon a time.'
46
+ ```
47
+
48
+ Next-word suggestions with probabilities:
49
+
50
+ ```python
51
+ import torch
52
+ from transformers import AutoModelForCausalLM, AutoTokenizer
53
+
54
+ tok = AutoTokenizer.from_pretrained("tmickleydoyle/tiny-autocomplete")
55
+ model = AutoModelForCausalLM.from_pretrained("tmickleydoyle/tiny-autocomplete")
56
+
57
+ ids = tok("Thank you for", return_tensors="pt")
58
+ with torch.no_grad():
59
+ probs = model(**ids).logits[0, -1].softmax(-1)
60
+ for p, i in zip(*probs.topk(5)):
61
+ print(repr(tok.decode(i)), f"{p:.1%}") # ' your' 37.6%, ' the' 16.7%, ...
62
+
63
+ out = model.generate(**ids, max_new_tokens=16)
64
+ print(tok.decode(out[0], skip_special_tokens=True)) # ' thank you for your help.'
65
+ ```
66
+
67
+ The tokenizer lowercases input, so output is lowercase. A BOS token is prepended
68
+ automatically.
69
+
70
+ ## Use it without Python (TypeScript, zero dependencies)
71
+
72
+ `model.ts` reads `config.json`, `model.safetensors` and `tokenizer.json` and runs the Llama
73
+ forward pass in plain TypeScript. It needs no ML library. It runs on [Bun](https://bun.sh)
74
+ or Node ≥ 23.6.
75
+
76
+ ```bash
77
+ git lfs install
78
+ git clone https://huggingface.co/tmickleydoyle/tiny-autocomplete
79
+ cd tiny-autocomplete
80
+
81
+ bun ui.ts # web UI at http://localhost:3000
82
+ bun autocomplete.ts # terminal REPL
83
+ bun autocomplete.ts "Once upon a" "Can you"
84
+ ```
85
+
86
+ In the web UI, type a prompt to see the predicted completion as gray ghost text and the top
87
+ 5 next words with probabilities. **Tab** accepts the completion, **Enter** accepts the
88
+ top next word, and clicking a word appends it.
89
+
90
+ ## What it computes
91
+
92
+ The network is an exact transformer implementation of an **interpolated Witten-Bell 5-gram
93
+ language model**. It predicts the next word from the current word plus up to 3 before it,
94
+ falling back to shorter contexts when a longer one is unknown. Its log-probabilities match the
95
+ reference n-gram to within 1e-3 nats.
96
+
97
+ ## How the weights are built (summary)
98
+
99
+ The residual stream is split into named slots: a constant 1, the current token's code, the
100
+ codes of the 3 previous tokens, and a logit slot.
101
+
102
+ - **Embeddings:** each token gets a seeded random unit vector of size 256, plus the constant.
103
+ - **Attention:** 3 heads with constant queries and keys. Their RoPE phases make each head look
104
+ exactly 1, 2 or 3 tokens back, with a margin of ≥ 30 nats. The head copies that token's code
105
+ into its slot.
106
+ - **MLP:** one neuron per context of 1–4 tokens seen in the phrases (13,148 neurons in total).
107
+ A neuron fires only when every token in its context matches. It adds
108
+ `log p(next | context) − log p(next | shorter context)` to the logits. These additions
109
+ telescope to the log-probability under the longest known context.
110
+ - **Norms and head:** RMSNorm weights cancel the fixed residual norms. `lm_head` reads the
111
+ logit slot.
112
+
113
+ The [docs](docs/how-it-works.md) have the formulas, the proof of exactness, and how to
114
+ recompile with your own phrases.
115
+
116
+ ## Architecture
117
+
118
+ `LlamaForCausalLM` with standard modules only, so no `trust_remote_code` is needed. The settings
119
+ are 1 layer, hidden 2088, 3 query heads, 1 KV head, head_dim 256, MLP width 13,148, vocab 1,053,
120
+ rope_theta 2, float32.
121
+
122
+ | Tensor | Params |
123
+ |---|---|
124
+ | `embed_tokens`, `lm_head` | 4,397,328 |
125
+ | attention q/k/v/o | 4,276,224 |
126
+ | MLP gate/up/down | 82,359,072 |
127
+ | RMSNorms | 6,264 |
128
+ | **total** | **91,038,888** |
129
+
130
+ ## Limitations
131
+
132
+ - It only knows its ~1,100 source phrases: 1,053 words, at most 4 tokens of context, and
133
+ 128 positions. Unknown words become `<unk>`, and the model backs off past them.
134
+ - It's a lookup machine, not a reasoning model. On novel text it predicts common next
135
+ words, not meaningful continuations.
136
+ - The prompt is treated as the start of a sentence.
137
+ - The phrase list was written by an AI coding assistant (Claude) for this project. No
138
+ corpus was scraped.
139
+
140
+ ## Rebuild and verify
141
+
142
+ ```bash
143
+ pip install -r requirements.txt
144
+ python build_weights.py # writes config, weights and tokenizer from phrases/*.txt
145
+ python test_model.py # HF loading, n-gram exactness, pipeline, TypeScript parity
146
+ ```
147
+
148
+ Tested with transformers 4.52.4 and 5.22.1, Bun 1.3 and Node 24.
149
+
150
+ ## Files
151
+
152
+ | File | Purpose |
153
+ |---|---|
154
+ | `config.json`, `generation_config.json`, `model.safetensors` | model |
155
+ | `tokenizer.json`, `tokenizer_config.json` | tokenizer |
156
+ | `build_weights.py` | the compiler that writes every file above |
157
+ | `phrases/*.txt` | source phrases |
158
+ | `test_model.py` | verification suite |
159
+ | `model.ts`, `autocomplete.ts`, `ui.ts`, `ui.html` | zero-dependency TypeScript runtime, CLI and web UI |
160
+ | `docs/how-it-works.md` | full construction details |
autocomplete.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Command-line autocomplete tester (zero dependencies).
2
+ //
3
+ // bun autocomplete.ts interactive prompt
4
+ // bun autocomplete.ts "once upon a" complete one or more prompts
5
+ // bun autocomplete.ts --json "how are" machine-readable output (used by tests)
6
+ //
7
+ // Also runs on Node >= 23.6 (`node autocomplete.ts`). For a browser UI see ui.ts.
8
+
9
+ import { createInterface } from "node:readline";
10
+ import { Autocompleter } from "./model.ts";
11
+
12
+ const DIR = import.meta.dirname ?? new URL(".", import.meta.url).pathname;
13
+ const ac = new Autocompleter(DIR);
14
+
15
+ function show(prompt: string): void {
16
+ const t0 = performance.now();
17
+ const r = ac.complete(prompt);
18
+ const ms = Math.round(performance.now() - t0);
19
+ console.log(`${prompt}\x1b[36m${r.completion}\x1b[0m (${ms} ms)`);
20
+ console.log(" next: " + r.top.map((t) => `${JSON.stringify(t.token)} ${(Math.exp(t.logprob) * 100).toFixed(1)}%`).join(" "));
21
+ }
22
+
23
+ const args = process.argv.slice(2);
24
+ if (args[0] === "--json") {
25
+ console.log(JSON.stringify(args.slice(1).map((p) => ac.complete(p))));
26
+ } else if (args.length) {
27
+ args.forEach(show);
28
+ } else {
29
+ const cfg = ac.model.cfg;
30
+ console.log(`Loaded ${cfg.num_hidden_layers}-layer model, vocab ${cfg.vocab_size}. Type a prefix (Ctrl+D to quit).`);
31
+ const rl = createInterface({ input: process.stdin, output: process.stdout, prompt: "> " });
32
+ rl.prompt();
33
+ rl.on("line", (line) => {
34
+ if (line.trim()) show(line);
35
+ rl.prompt();
36
+ });
37
+ }
build_weights.py ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compile phrases/*.txt into a Llama-format autocomplete model. No training.
2
+
3
+ Every weight is set by a closed-form rule; nothing is fit by gradient descent
4
+ and no other model is used. The result is an exact transformer implementation
5
+ of an interpolated Witten-Bell 5-gram language model over the phrase list.
6
+
7
+ Residual stream layout (hidden_size = 1 + 4*CODE_DIM + vocab):
8
+ [0] constant 1 (acts as a bias, Llama has none)
9
+ cur slot (CODE_DIM) code of the current token <- embedding
10
+ prev1..3 (CODE_DIM) codes of the 1..3 previous tokens <- attention
11
+ out slot (vocab) next-token logits, scaled by OUT_SCALE <- MLP
12
+
13
+ Token codes are seeded random unit vectors (nearly orthogonal). Attention head
14
+ a (a = 1..3) has constant query/key vectors whose RoPE phases make the score
15
+ peak at exactly a positions back; its value copies that token's code into
16
+ slot prev_a. Positions before the start fall back to position 0 (BOS).
17
+
18
+ The MLP has one neuron per context seen in the phrases (1-4 tokens, plus one
19
+ always-on unigram neuron). A neuron's gate sums code matches across slots
20
+ minus (k - 0.5), so it fires only when all k tokens match. It writes
21
+ log p(next | ctx) - log p(next | shorter ctx) into the out slot. All suffixes
22
+ of the true context fire together, so the sum telescopes to the log
23
+ probability under the longest matching context.
24
+ """
25
+
26
+ import json
27
+ import math
28
+ from collections import Counter, defaultdict
29
+ from functools import lru_cache
30
+ from pathlib import Path
31
+
32
+ import torch
33
+ from safetensors.torch import save_file
34
+ from tokenizers import Tokenizer, decoders, models, normalizers, pre_tokenizers, processors
35
+ from transformers import GenerationConfig, LlamaConfig, PreTrainedTokenizerFast
36
+
37
+ ROOT = Path(__file__).parent
38
+ PHRASE_DIR = ROOT / "phrases"
39
+ EOS, UNK = "<|endoftext|>", "<unk>" # EOS doubles as BOS and pad, like GPT-2
40
+
41
+ SEED = 1234
42
+ CODE_DIM = 256
43
+ N_PREV = 3 # previous tokens attended to; context = N_PREV + current
44
+ ORDER = N_PREV + 1
45
+ MAX_POS = 128
46
+ ATTN_MARGIN = 30.0 # min attention logit gap (nats) between target and any other position
47
+ BETA = 100.0 # gate sharpness; matched neurons sit at z = BETA / 2
48
+ OUT_SCALE = 1e-4 # keeps the out slot tiny so the final RMSNorm scale stays fixed
49
+ UNK_LOGIT = -30.0
50
+ ROPE_THETA_CANDIDATES = [2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0, 1000.0, 10000.0]
51
+
52
+ NORMALIZER = normalizers.Sequence([normalizers.NFKC(), normalizers.Lowercase()])
53
+ PRE_TOKENIZER = pre_tokenizers.ByteLevel(add_prefix_space=True)
54
+
55
+
56
+ def pieces(text: str) -> list[str]:
57
+ return [p for p, _ in PRE_TOKENIZER.pre_tokenize_str(NORMALIZER.normalize_str(text))]
58
+
59
+
60
+ def load_phrases() -> list[list[str]]:
61
+ seen, out = set(), []
62
+ for path in sorted(PHRASE_DIR.glob("*.txt")):
63
+ for line in path.read_text().splitlines():
64
+ line = line.strip()
65
+ if not line or line.startswith("#"):
66
+ continue
67
+ toks = pieces(line)
68
+ if tuple(toks) not in seen:
69
+ seen.add(tuple(toks))
70
+ out.append(toks)
71
+ return out
72
+
73
+
74
+ class NGram:
75
+ """Interpolated Witten-Bell n-gram; the reference the weights must reproduce."""
76
+
77
+ def __init__(self, sequences: list[list[int]], vocab_size: int):
78
+ self.V = vocab_size
79
+ self.counts = [defaultdict(Counter) for _ in range(ORDER + 1)]
80
+ for seq in sequences:
81
+ # ORDER BOS tokens: the last one is the real BOS position the model sees.
82
+ padded = [0] * ORDER + seq + [0]
83
+ for i in range(ORDER - 1, len(padded) - 1):
84
+ for k in range(ORDER + 1):
85
+ ctx = tuple(padded[i - k + 1 : i + 1]) if k else ()
86
+ self.counts[k][ctx][padded[i + 1]] += 1
87
+
88
+ uni = torch.zeros(vocab_size, dtype=torch.float64)
89
+ for j, c in self.counts[0][()].items():
90
+ uni[j] = c
91
+ logits = torch.log(uni + 1.0) # add-one smoothing
92
+ logits[1] = UNK_LOGIT
93
+ self.base_logits = logits - logits[2:].mean() # constant shift; softmax-invariant
94
+ self.p0 = torch.softmax(self.base_logits, 0)
95
+
96
+ @lru_cache(maxsize=None)
97
+ def prob(self, j: int, ctx: tuple) -> float:
98
+ if not ctx:
99
+ return self.p0[j].item()
100
+ lower = self.prob(j, ctx[1:])
101
+ table = self.counts[len(ctx)].get(ctx)
102
+ if table is None:
103
+ return lower
104
+ total, uniq = sum(table.values()), len(table)
105
+ return (table.get(j, 0) + uniq * lower) / (total + uniq)
106
+
107
+ def delta(self, ctx: tuple) -> dict[int, float]:
108
+ """log p_k - log p_{k-1}, minus the constant log(u/(c+u)) shared by unseen tokens."""
109
+ table = self.counts[len(ctx)][ctx]
110
+ uniq = len(table)
111
+ out = {}
112
+ for j, c in table.items():
113
+ q = self.prob(j, ctx[1:])
114
+ out[j] = math.log(c + uniq * q) - math.log(uniq * q)
115
+ return out
116
+
117
+ def contexts(self) -> list[tuple]:
118
+ return [ctx for k in range(1, ORDER + 1) for ctx in sorted(self.counts[k])]
119
+
120
+ def reference_logits(self, ids: list[int]) -> torch.Tensor:
121
+ """Logits (up to a constant) the model should emit after the last token of ids."""
122
+ t = len(ids) - 1
123
+ window = [ids[max(t - a, 0)] for a in range(N_PREV, -1, -1)] # oldest .. current
124
+ logits = self.base_logits.clone()
125
+ for k in range(1, ORDER + 1):
126
+ ctx = tuple(window[-k:])
127
+ if ctx in self.counts[k]:
128
+ for j, d in self.delta(ctx).items():
129
+ logits[j] += d
130
+ return logits
131
+
132
+
133
+ def rope_attention_scale(head_dim: int) -> tuple[float, torch.Tensor, float]:
134
+ """Pick rope_theta maximizing the worst-case attention margin; return (theta, inv_freq, q_amp)."""
135
+ best = None
136
+ for theta in ROPE_THETA_CANDIDATES:
137
+ inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2, dtype=torch.int64).double() / head_dim))
138
+ offsets = torch.arange(-MAX_POS, MAX_POS + 1, dtype=torch.float64)
139
+ kernel = torch.cos(offsets[:, None] * inv_freq[None, :]).sum(1) # K(x), index x + MAX_POS
140
+ worst = math.inf
141
+ for lag in range(1, N_PREV + 1):
142
+ for t in range(MAX_POS):
143
+ s_target = max(t - lag, 0)
144
+ xs = torch.tensor([t - s - lag for s in range(t + 1)]) + MAX_POS
145
+ scores = kernel[xs]
146
+ others = torch.cat([scores[:s_target], scores[s_target + 1 :]])
147
+ if len(others):
148
+ worst = min(worst, (scores[s_target] - others.max()).item())
149
+ if best is None or worst > best[0]:
150
+ best = (worst, theta, inv_freq)
151
+ worst, theta, inv_freq = best
152
+ assert worst > 0, "no rope_theta separates positions"
153
+ # score = q_amp * sum_i cos(...) / sqrt(head_dim) (key amplitude 1)
154
+ q_amp = ATTN_MARGIN * math.sqrt(head_dim) / worst
155
+ return theta, inv_freq, q_amp
156
+
157
+
158
+ def build(phrases: list[list[str]]):
159
+ freq = Counter(p for toks in phrases for p in toks)
160
+ vocab = [EOS, UNK] + sorted(freq, key=lambda p: (-freq[p], p))
161
+ index = {p: i for i, p in enumerate(vocab)}
162
+ V, m = len(vocab), CODE_DIM
163
+ ngram = NGram([[index[p] for p in toks] for toks in phrases], V)
164
+ contexts = ngram.contexts()
165
+
166
+ CONST, CUR = 0, 1
167
+ prev_slot = [1 + a * m for a in range(N_PREV + 1)] # prev_slot[0] is the cur slot
168
+ OUT = 1 + (N_PREV + 1) * m
169
+ n_heads, h = N_PREV, m
170
+ # transformers validates hidden_size % num_heads == 0; pad with unused zero dims.
171
+ D = -(-(OUT + V) // (8 * n_heads)) * (8 * n_heads)
172
+ N = 1 + len(contexts)
173
+
174
+ gen = torch.Generator().manual_seed(SEED)
175
+ codes = torch.randn(V, m, generator=gen, dtype=torch.float64)
176
+ codes /= codes.norm(dim=1, keepdim=True)
177
+ gram = codes @ codes.T
178
+ gram.fill_diagonal_(0)
179
+ max_coherence = gram.abs().max().item()
180
+ assert max_coherence < 0.4, max_coherence
181
+
182
+ embed = torch.zeros(V, D, dtype=torch.float64)
183
+ embed[:, CONST] = 1.0
184
+ embed[:, CUR : CUR + m] = codes
185
+
186
+ theta, inv_freq, q_amp = rope_attention_scale(h)
187
+ half = h // 2
188
+ q_proj = torch.zeros(n_heads * h, D, dtype=torch.float64)
189
+ for a in range(n_heads):
190
+ lag = a + 1
191
+ q_proj[a * h : a * h + half, CONST] = q_amp * torch.cos(lag * inv_freq)
192
+ q_proj[a * h + half : (a + 1) * h, CONST] = -q_amp * torch.sin(lag * inv_freq)
193
+ k_proj = torch.zeros(h, D, dtype=torch.float64)
194
+ k_proj[:half, CONST] = 1.0
195
+ v_proj = torch.zeros(h, D, dtype=torch.float64)
196
+ v_proj[:, CUR : CUR + m] = torch.eye(m, dtype=torch.float64)
197
+ o_proj = torch.zeros(D, n_heads * h, dtype=torch.float64)
198
+ for a in range(n_heads):
199
+ o_proj[prev_slot[a + 1] : prev_slot[a + 1] + m, a * h : (a + 1) * h] = torch.eye(m, dtype=torch.float64)
200
+
201
+ gate = torch.zeros(N, D, dtype=torch.float64)
202
+ up = torch.zeros(N, D, dtype=torch.float64)
203
+ down = torch.zeros(D, N, dtype=torch.float64)
204
+ on = (BETA / 2) * torch.sigmoid(torch.tensor(BETA / 2, dtype=torch.float64)).item() # silu(BETA/2)
205
+ up[:, CONST] = 1.0
206
+ gate[0, CONST] = BETA / 2 # always-on unigram neuron
207
+ down[OUT : OUT + V, 0] = OUT_SCALE * ngram.base_logits / on
208
+ for n, ctx in enumerate(contexts, start=1):
209
+ k = len(ctx)
210
+ for a, tok in enumerate(reversed(ctx)): # a = 0 current, a = 1 previous, ...
211
+ gate[n, prev_slot[a] : prev_slot[a] + m] = BETA * codes[tok]
212
+ gate[n, CONST] = -BETA * (k - 0.5)
213
+ for j, d in ngram.delta(ctx).items():
214
+ down[OUT + j, n] = OUT_SCALE * d / on
215
+
216
+ lm_head = torch.zeros(V, D, dtype=torch.float64)
217
+ lm_head[torch.arange(V), OUT + torch.arange(V)] = 1.0 / OUT_SCALE
218
+
219
+ # RMSNorm weights undo the norm for the (fixed) input norms: |x|^2 = 2 before
220
+ # attention, 1 + 1 + N_PREV after it.
221
+ attn_norm = math.sqrt(2 / D)
222
+ mlp_norm = math.sqrt((2 + N_PREV) / D)
223
+ state = {
224
+ "model.embed_tokens.weight": embed,
225
+ "model.layers.0.input_layernorm.weight": torch.full((D,), attn_norm),
226
+ "model.layers.0.self_attn.q_proj.weight": q_proj,
227
+ "model.layers.0.self_attn.k_proj.weight": k_proj,
228
+ "model.layers.0.self_attn.v_proj.weight": v_proj,
229
+ "model.layers.0.self_attn.o_proj.weight": o_proj,
230
+ "model.layers.0.post_attention_layernorm.weight": torch.full((D,), mlp_norm),
231
+ "model.layers.0.mlp.gate_proj.weight": gate,
232
+ "model.layers.0.mlp.up_proj.weight": up,
233
+ "model.layers.0.mlp.down_proj.weight": down,
234
+ "model.norm.weight": torch.full((D,), mlp_norm),
235
+ "lm_head.weight": lm_head,
236
+ }
237
+ state = {k: v.to(torch.float32).contiguous() for k, v in state.items()}
238
+
239
+ config = LlamaConfig(
240
+ vocab_size=V,
241
+ hidden_size=D,
242
+ intermediate_size=N,
243
+ num_hidden_layers=1,
244
+ num_attention_heads=n_heads,
245
+ num_key_value_heads=1,
246
+ head_dim=h,
247
+ hidden_act="silu",
248
+ max_position_embeddings=MAX_POS,
249
+ rms_norm_eps=1e-10,
250
+ rope_theta=theta,
251
+ tie_word_embeddings=False,
252
+ bos_token_id=0,
253
+ eos_token_id=0,
254
+ pad_token_id=0,
255
+ )
256
+ info = {
257
+ "vocab": V,
258
+ "hidden": D,
259
+ "neurons": N,
260
+ "rope_theta": theta,
261
+ "max_code_coherence": round(max_coherence, 3),
262
+ "params": sum(t.numel() for t in state.values()),
263
+ }
264
+ return vocab, ngram, state, config, info
265
+
266
+
267
+ def build_tokenizer(vocab: list[str]) -> PreTrainedTokenizerFast:
268
+ tok = Tokenizer(models.WordLevel({p: i for i, p in enumerate(vocab)}, unk_token=UNK))
269
+ tok.normalizer = NORMALIZER
270
+ tok.pre_tokenizer = PRE_TOKENIZER
271
+ tok.decoder = decoders.ByteLevel()
272
+ tok.post_processor = processors.TemplateProcessing(single=f"{EOS} $A", special_tokens=[(EOS, 0)])
273
+ return PreTrainedTokenizerFast(
274
+ tokenizer_object=tok,
275
+ bos_token=EOS,
276
+ eos_token=EOS,
277
+ unk_token=UNK,
278
+ pad_token=EOS,
279
+ clean_up_tokenization_spaces=False,
280
+ model_max_length=MAX_POS,
281
+ )
282
+
283
+
284
+ def compile_model():
285
+ """Build everything in memory (used by the tests for the n-gram reference)."""
286
+ phrases = load_phrases()
287
+ return (len(phrases), *build(phrases))
288
+
289
+
290
+ def main() -> None:
291
+ n_phrases, vocab, _, state, config, info = compile_model()
292
+ assert info["params"] <= 1_000_000_000, info
293
+
294
+ save_file(state, ROOT / "model.safetensors", metadata={"format": "pt"})
295
+ config.save_pretrained(ROOT)
296
+ cfg_path = ROOT / "config.json"
297
+ cfg = json.loads(cfg_path.read_text())
298
+ cfg["architectures"] = ["LlamaForCausalLM"]
299
+ cfg["rope_theta"] = info["rope_theta"] # transformers 4.x reads this key, 5.x reads rope_parameters
300
+ cfg["torch_dtype"] = "float32"
301
+ cfg_path.write_text(json.dumps(cfg, indent=2, sort_keys=True) + "\n")
302
+ GenerationConfig(bos_token_id=0, eos_token_id=0, pad_token_id=0, do_sample=False, max_new_tokens=16).save_pretrained(ROOT)
303
+
304
+ build_tokenizer(vocab).save_pretrained(ROOT)
305
+ # transformers 5 writes "TokenizersBackend", which 4.x can't resolve;
306
+ # PreTrainedTokenizerFast loads on both. 4.x also emits token_type_ids
307
+ # by default, which Llama's generate() rejects, and overrides the
308
+ # pre-tokenizer's add_prefix_space unless the config states it.
309
+ tok_cfg_path = ROOT / "tokenizer_config.json"
310
+ tok_cfg = json.loads(tok_cfg_path.read_text())
311
+ tok_cfg["tokenizer_class"] = "PreTrainedTokenizerFast"
312
+ tok_cfg["add_prefix_space"] = True
313
+ tok_cfg["model_input_names"] = ["input_ids", "attention_mask"]
314
+ tok_cfg_path.write_text(json.dumps(tok_cfg, indent=2) + "\n")
315
+
316
+ print(json.dumps({"phrases": n_phrases, **info}, indent=2))
317
+
318
+
319
+ if __name__ == "__main__":
320
+ main()
config.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "LlamaForCausalLM"
4
+ ],
5
+ "attention_bias": false,
6
+ "attention_dropout": 0.0,
7
+ "bos_token_id": 0,
8
+ "eos_token_id": 0,
9
+ "head_dim": 256,
10
+ "hidden_act": "silu",
11
+ "hidden_size": 2088,
12
+ "initializer_range": 0.02,
13
+ "intermediate_size": 13148,
14
+ "max_position_embeddings": 128,
15
+ "mlp_bias": false,
16
+ "model_type": "llama",
17
+ "num_attention_heads": 3,
18
+ "num_hidden_layers": 1,
19
+ "num_key_value_heads": 1,
20
+ "pad_token_id": 0,
21
+ "pretraining_tp": 1,
22
+ "rms_norm_eps": 1e-10,
23
+ "rope_parameters": {
24
+ "rope_theta": 2.0,
25
+ "rope_type": "default"
26
+ },
27
+ "rope_theta": 2.0,
28
+ "tie_word_embeddings": false,
29
+ "torch_dtype": "float32",
30
+ "transformers_version": "5.22.1",
31
+ "use_cache": true,
32
+ "vocab_size": 1053
33
+ }
docs/how-it-works.md ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How tiny-autocomplete was built
2
+
3
+ This model was **compiled, not trained**. No gradient descent, no optimizer, no
4
+ pretrained weights, and no other model's outputs were used. `build_weights.py` turns
5
+ a phrase list into a standard `LlamaForCausalLM` checkpoint using closed-form rules.
6
+ Running it twice gives byte-identical weights.
7
+
8
+ The network computes an **interpolated Witten-Bell 5-gram language model** exactly. It
9
+ predicts the next token from the current token and up to 3 before it.
10
+
11
+ ## 1. Phrases and tokenizer
12
+
13
+ `phrases/*.txt` holds about 1,100 common English sentences, one per line, in four files:
14
+ social phrases, everyday requests, idioms and proverbs, and opinions and work phrases. The
15
+ list was written for this project by an AI coding assistant (Claude). No corpus was scraped.
16
+
17
+ The tokenizer is word-level with GPT-2 style byte-level pre-tokenization:
18
+
19
+ - NFKC normalization, then lowercase.
20
+ - Split on the GPT-2 regex. Words keep their leading space as `Ġ`, and punctuation and
21
+ contractions (`'s`, `'t`, ...) become their own tokens.
22
+ - Each piece found in the phrases is one vocabulary entry (1,053 total), plus two specials:
23
+ `<|endoftext|>` (id 0, used as BOS, EOS and pad) and `<unk>` (id 1).
24
+ - A post-processor prepends BOS, so every prompt starts at position 0 with `<|endoftext|>`.
25
+
26
+ ## 2. The reference n-gram
27
+
28
+ Each phrase becomes `[BOS, BOS, BOS, BOS, w1, ..., wn, EOS]`. For every position, the
29
+ compiler counts which token follows each context of length k = 0..4 that ends there.
30
+
31
+ - **Order 0:** add-one-smoothed unigram, with `<unk>` pinned at logit -30.
32
+ - **Order k ≥ 1:** Witten-Bell interpolation with the next-shorter context (drop the
33
+ oldest token):
34
+
35
+ ```
36
+ p_k(j | ctx) = (c(ctx, j) + u(ctx) * p_{k-1}(j | ctx[1:])) / (c(ctx) + u(ctx))
37
+ ```
38
+
39
+ Here `c` is a count and `u(ctx)` is the number of distinct tokens seen after `ctx`.
40
+ An unseen context backs off: `p_k = p_{k-1}`.
41
+
42
+ The model must reproduce `p_K(· | longest known context)`, where K is the longest context
43
+ that appears in the phrase list.
44
+
45
+ ## 3. The residual stream as named slots
46
+
47
+ | Dims | Slot | Written by |
48
+ |---|---|---|
49
+ | 0 | constant `1` (Llama has no biases; this plays that role) | embedding |
50
+ | 1–256 | code of the **current** token | embedding |
51
+ | 257–512 | code of the token **1 back** | attention head 1 |
52
+ | 513–768 | code of the token **2 back** | attention head 2 |
53
+ | 769–1024 | code of the token **3 back** | attention head 3 |
54
+ | 1025–2077 | **logits**, one dim per vocab entry, scaled by 1e-4 | MLP |
55
+ | 2078–2087 | unused padding (transformers wants hidden % heads == 0) | — |
56
+
57
+ **Token codes** are seeded Gaussian vectors in 256 dimensions, normalized to unit
58
+ length. With 1,053 of them, the largest |cosine| between two different codes is 0.30.
59
+ That's far enough below 1 that a "match" (dot = 1) and a "non-match" (dot ≤ 0.3) can't
60
+ be confused.
61
+
62
+ ## 4. Attention: previous-token heads from RoPE alone
63
+
64
+ Llama has no absolute positions. The only position signal is RoPE, which rotates query
65
+ and key pairs by `position * inv_freq[i]`.
66
+
67
+ - The **key** for every token is the same constant (read from the constant dim): 1 on
68
+ the first half of each RoPE pair.
69
+ - The **query** for head *a* is also constant, with each pair's phase set to
70
+ `-a * inv_freq[i]`. The attention score between positions t and s is then
71
+
72
+ ```
73
+ score(t, s) = A * Σ_i cos((t - s - a) * inv_freq[i]) / sqrt(head_dim)
74
+ ```
75
+
76
+ which peaks exactly when `s = t - a`.
77
+ - When `t < a` there is no position `a` back, and the closest position is 0, which is BOS.
78
+ The model then "sees" BOS in that slot, matching the BOS padding used when counting.
79
+ - `rope_theta` is picked from a candidate list to maximize the worst-case gap between the
80
+ target position and every other position, for all t < 128 and a ∈ {1, 2, 3}. It came out
81
+ to 2. The amplitude `A` then scales that worst gap to **30 nats**, so softmax puts
82
+ > 0.9999999 weight on the target.
83
+ - **Value** = the current-token code. **Output projection** = identity into slot *a*.
84
+
85
+ All 3 heads share one key/value head (`num_key_value_heads = 1`).
86
+
87
+ ## 5. MLP: one neuron per context
88
+
89
+ Llama's MLP is `down(silu(gate(x)) * up(x))`. The compiler creates one neuron for each
90
+ context seen in the phrases (13,147 of them, lengths 1–4) and one always-on unigram neuron.
91
+
92
+ For a context `(c_{k-1}, ..., c_1, c_0)` of length k (c_0 = current token):
93
+
94
+ - **gate** = `100 * (code(c_0)·cur + code(c_1)·prev1 + ... - (k - 0.5) * const)`.
95
+ If all k tokens match, this is `+50`. If any one fails (dot ≤ 0.3), it's at most
96
+ `100 * (0.3 - 0.5) = -20`, and `silu(-20)` ≈ -4e-8, effectively off.
97
+ - **up** = 1 (the constant dim).
98
+ - **down** writes, for each token j seen after this context,
99
+
100
+ ```
101
+ delta_j = log p_k(j | ctx) - log p_{k-1}(j | ctx[1:])
102
+ ```
103
+
104
+ into the logit slot. The constant `log(u/(c+u))` shared by every unseen token is
105
+ dropped, since softmax ignores constant shifts. That makes the column sparse.
106
+
107
+ **Why this is exact.** If a context of length k is in the table, so are all its shorter
108
+ suffixes, and their neurons fire too. The unigram neuron writes `log p_0`, and the
109
+ neurons for lengths 1..K add their deltas. The sum telescopes to `log p_K(· | longest
110
+ known context)` plus a constant. No neuron has to know which context is the longest.
111
+
112
+ ## 6. Norms and output head
113
+
114
+ - The residual has a known fixed norm at each point: |x|² = 2 before attention and 5
115
+ after it. Each RMSNorm weight is set to `sqrt(norm² / hidden)`, so the norms leave the
116
+ stream unchanged.
117
+ - The MLP writes logits scaled by 1e-4, so they barely change the final norm (< 0.02%).
118
+ - `lm_head` is `1e4` times the identity on the logit slot.
119
+
120
+ ## 7. Verification (`test_model.py`)
121
+
122
+ The tests check the following:
123
+ - **Hugging Face loading:** the files load with `AutoModelForCausalLM`, `AutoTokenizer` and
124
+ `pipeline("text-generation")` on transformers 4.52 and 5.22.
125
+ - **Exactness:** next-token log-probs match the reference n-gram within 5e-3 nats (observed
126
+ ~8e-4). This is checked on 200 phrase prefixes and 50 random word-salad sequences of up to
127
+ 80 tokens.
128
+ - **Completions:** a set of prompts produce the expected greedy completions.
129
+ - **Unknown words:** out-of-vocabulary words back off cleanly.
130
+ - **TypeScript parity:** the zero-dependency TypeScript runtime (`model.ts`) gives the same
131
+ top-3 tokens and log-probs as PyTorch.
132
+
133
+ ## 8. Changing it
134
+
135
+ Edit or add lines in `phrases/*.txt`, then run `python build_weights.py` and
136
+ `python test_model.py`. The size grows with the number of distinct contexts. The MLP
137
+ dominates at about 3 × hidden × contexts parameters. Settings at the top of
138
+ `build_weights.py`:
139
+
140
+ | Constant | Meaning |
141
+ |---|---|
142
+ | `CODE_DIM` | token-code size (bigger means fewer near-collisions and a larger model) |
143
+ | `N_PREV` | how many previous tokens are attended to (n-gram order = N_PREV + 2) |
144
+ | `MAX_POS` | longest sequence the attention margin is guaranteed for |
145
+ | `ATTN_MARGIN`, `BETA` | sharpness of attention and of the neuron gates |
146
+
147
+ ## History
148
+
149
+ The first version was a 120-parameter model (hidden size 2) that only knew a single next word
150
+ for each of 22 words. That size can't hold any context, so the design was scaled up to this
151
+ compiled n-gram, staying under a 1B-parameter budget.
generation_config.json ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 0,
3
+ "do_sample": false,
4
+ "eos_token_id": 0,
5
+ "max_new_tokens": 16,
6
+ "pad_token_id": 0,
7
+ "transformers_version": "5.22.1"
8
+ }
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:d7719627f88eaaaa8cbab54d1452596d952e68fdfd1a07b57b51f14209ed3e7b
3
+ size 364156872
model.ts ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Zero-dependency inference for this model: reads config.json, model.safetensors
2
+ // and tokenizer.json and runs the Llama forward pass in plain TypeScript.
3
+ // No ML library, no other model. Used by autocomplete.ts (CLI) and ui.ts (web UI).
4
+
5
+ import { readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+
8
+ export type Config = {
9
+ vocab_size: number;
10
+ hidden_size: number;
11
+ intermediate_size: number;
12
+ num_hidden_layers: number;
13
+ num_attention_heads: number;
14
+ num_key_value_heads: number;
15
+ head_dim?: number;
16
+ rms_norm_eps: number;
17
+ rope_theta?: number;
18
+ rope_parameters?: { rope_theta: number };
19
+ max_position_embeddings: number;
20
+ eos_token_id: number;
21
+ };
22
+
23
+ // ---------- safetensors ----------
24
+
25
+ function loadSafetensors(path: string): Map<string, Float32Array> {
26
+ const buf = readFileSync(path);
27
+ const headerLen = Number(buf.readBigUInt64LE(0));
28
+ const header = JSON.parse(buf.subarray(8, 8 + headerLen).toString("utf8"));
29
+ const base = 8 + headerLen;
30
+ const tensors = new Map<string, Float32Array>();
31
+ for (const [name, info] of Object.entries<any>(header)) {
32
+ if (name === "__metadata__") continue;
33
+ if (info.dtype !== "F32") throw new Error(`${name}: unsupported dtype ${info.dtype}`);
34
+ const [begin, end] = info.data_offsets as [number, number];
35
+ const offset = buf.byteOffset + base + begin;
36
+ const length = (end - begin) / 4;
37
+ tensors.set(
38
+ name,
39
+ offset % 4 === 0
40
+ ? new Float32Array(buf.buffer, offset, length)
41
+ : new Float32Array(buf.buffer.slice(offset, offset + length * 4)),
42
+ );
43
+ }
44
+ return tensors;
45
+ }
46
+
47
+ // ---------- tokenizer (WordLevel + ByteLevel, as in tokenizer.json) ----------
48
+
49
+ function bytesToUnicode(): Map<number, string> {
50
+ const bs: number[] = [];
51
+ for (let b = 33; b <= 126; b++) bs.push(b);
52
+ for (let b = 161; b <= 172; b++) bs.push(b);
53
+ for (let b = 174; b <= 255; b++) bs.push(b);
54
+ const cs = [...bs];
55
+ let n = 0;
56
+ for (let b = 0; b < 256; b++) {
57
+ if (!bs.includes(b)) {
58
+ bs.push(b);
59
+ cs.push(256 + n++);
60
+ }
61
+ }
62
+ return new Map(bs.map((b, i) => [b, String.fromCodePoint(cs[i])]));
63
+ }
64
+
65
+ const GPT2_SPLIT = /'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu;
66
+
67
+ export class Tokenizer {
68
+ vocab: Map<string, number>;
69
+ inverse: string[];
70
+ special = new Set<number>();
71
+ unkId: number;
72
+ bosId: number;
73
+ private byteEnc = bytesToUnicode();
74
+ private byteDec = new Map([...this.byteEnc].map(([b, c]) => [c, b]));
75
+
76
+ constructor(path: string) {
77
+ const json = JSON.parse(readFileSync(path, "utf8"));
78
+ this.vocab = new Map(Object.entries<number>(json.model.vocab));
79
+ this.inverse = [];
80
+ for (const [tok, id] of this.vocab) this.inverse[id] = tok;
81
+ for (const t of json.added_tokens ?? []) if (t.special) this.special.add(t.id);
82
+ this.unkId = this.vocab.get(json.model.unk_token)!;
83
+ const bos = json.post_processor?.single?.find((p: any) => p.SpecialToken)?.SpecialToken?.id;
84
+ this.bosId = this.vocab.get(bos)!;
85
+ }
86
+
87
+ encode(text: string): number[] {
88
+ let norm = text.normalize("NFKC").toLowerCase();
89
+ if (!/^\s/.test(norm)) norm = " " + norm; // ByteLevel add_prefix_space
90
+ const ids = [this.bosId];
91
+ for (const piece of norm.match(GPT2_SPLIT) ?? []) {
92
+ const mapped = [...new TextEncoder().encode(piece)].map((b) => this.byteEnc.get(b)!).join("");
93
+ ids.push(this.vocab.get(mapped) ?? this.unkId);
94
+ }
95
+ return ids;
96
+ }
97
+
98
+ decode(ids: number[]): string {
99
+ const chars = ids.filter((id) => !this.special.has(id)).map((id) => this.inverse[id]).join("");
100
+ return new TextDecoder().decode(new Uint8Array([...chars].map((c) => this.byteDec.get(c) ?? 63)));
101
+ }
102
+ }
103
+
104
+ // ---------- Llama forward pass ----------
105
+
106
+ function matvec(w: Float32Array, x: Float64Array, rows: number, cols: number): Float64Array {
107
+ const y = new Float64Array(rows);
108
+ for (let r = 0; r < rows; r++) {
109
+ let s = 0;
110
+ const off = r * cols;
111
+ for (let c = 0; c < cols; c++) s += w[off + c] * x[c];
112
+ y[r] = s;
113
+ }
114
+ return y;
115
+ }
116
+
117
+ function rmsnorm(x: Float64Array, w: Float32Array, eps: number): Float64Array {
118
+ let ss = 0;
119
+ for (const v of x) ss += v * v;
120
+ const inv = 1 / Math.sqrt(ss / x.length + eps);
121
+ return x.map((v, i) => v * inv * w[i]);
122
+ }
123
+
124
+ export class Llama {
125
+ cfg: Config;
126
+ w: Map<string, Float32Array>;
127
+ headDim: number;
128
+ invFreq: Float64Array;
129
+
130
+ constructor(dir: string) {
131
+ this.cfg = JSON.parse(readFileSync(join(dir, "config.json"), "utf8"));
132
+ this.w = loadSafetensors(join(dir, "model.safetensors"));
133
+ const c = this.cfg;
134
+ this.headDim = c.head_dim ?? c.hidden_size / c.num_attention_heads;
135
+ const theta = c.rope_parameters?.rope_theta ?? c.rope_theta ?? 10000;
136
+ const half = this.headDim / 2;
137
+ this.invFreq = new Float64Array(half).map((_, i) => 1 / theta ** ((2 * i) / this.headDim));
138
+ }
139
+
140
+ get numParams(): number {
141
+ let n = 0;
142
+ for (const t of this.w.values()) n += t.length;
143
+ return n;
144
+ }
145
+
146
+ private t(name: string): Float32Array {
147
+ const t = this.w.get(name);
148
+ if (!t) throw new Error(`missing tensor ${name}`);
149
+ return t;
150
+ }
151
+
152
+ private rope(vec: Float64Array, offset: number, pos: number): void {
153
+ const half = this.headDim / 2;
154
+ for (let i = 0; i < half; i++) {
155
+ const a = pos * this.invFreq[i];
156
+ const cos = Math.cos(a), sin = Math.sin(a);
157
+ const x1 = vec[offset + i], x2 = vec[offset + i + half];
158
+ vec[offset + i] = x1 * cos - x2 * sin;
159
+ vec[offset + i + half] = x2 * cos + x1 * sin;
160
+ }
161
+ }
162
+
163
+ /** Log-probabilities of the next token after `ids`. */
164
+ nextLogprobs(ids: number[]): Float64Array {
165
+ const c = this.cfg, D = c.hidden_size, H = c.num_attention_heads, KV = c.num_key_value_heads;
166
+ const hd = this.headDim, I = c.intermediate_size, L = c.num_hidden_layers;
167
+ const embed = this.t("model.embed_tokens.weight");
168
+ const keys: Float64Array[][] = Array.from({ length: L }, () => []);
169
+ const vals: Float64Array[][] = Array.from({ length: L }, () => []);
170
+ let x = new Float64Array(D);
171
+
172
+ for (let pos = 0; pos < ids.length; pos++) {
173
+ x = Float64Array.from(embed.subarray(ids[pos] * D, (ids[pos] + 1) * D));
174
+ const last = pos === ids.length - 1;
175
+ for (let l = 0; l < L; l++) {
176
+ const p = `model.layers.${l}.`;
177
+ const xn = rmsnorm(x, this.t(p + "input_layernorm.weight"), c.rms_norm_eps);
178
+ const q = matvec(this.t(p + "self_attn.q_proj.weight"), xn, H * hd, D);
179
+ const k = matvec(this.t(p + "self_attn.k_proj.weight"), xn, KV * hd, D);
180
+ const v = matvec(this.t(p + "self_attn.v_proj.weight"), xn, KV * hd, D);
181
+ for (let h = 0; h < H; h++) this.rope(q, h * hd, pos);
182
+ for (let h = 0; h < KV; h++) this.rope(k, h * hd, pos);
183
+ keys[l].push(k);
184
+ vals[l].push(v);
185
+
186
+ const attn = new Float64Array(H * hd);
187
+ for (let h = 0; h < H; h++) {
188
+ const kvh = Math.floor(h / (H / KV)) * hd;
189
+ const scores = keys[l].map((kk) => {
190
+ let s = 0;
191
+ for (let d = 0; d < hd; d++) s += q[h * hd + d] * kk[kvh + d];
192
+ return s / Math.sqrt(hd);
193
+ });
194
+ const max = Math.max(...scores);
195
+ const exps = scores.map((s) => Math.exp(s - max));
196
+ const z = exps.reduce((a, b) => a + b, 0);
197
+ exps.forEach((e, s) => {
198
+ for (let d = 0; d < hd; d++) attn[h * hd + d] += (e / z) * vals[l][s][kvh + d];
199
+ });
200
+ }
201
+ const o = matvec(this.t(p + "self_attn.o_proj.weight"), attn, D, H * hd);
202
+ for (let i = 0; i < D; i++) x[i] += o[i];
203
+
204
+ // The last layer's MLP only matters where we read logits.
205
+ if (l === L - 1 && !last) continue;
206
+ const xn2 = rmsnorm(x, this.t(p + "post_attention_layernorm.weight"), c.rms_norm_eps);
207
+ const g = matvec(this.t(p + "mlp.gate_proj.weight"), xn2, I, D);
208
+ const u = matvec(this.t(p + "mlp.up_proj.weight"), xn2, I, D);
209
+ for (let i = 0; i < I; i++) g[i] = (g[i] / (1 + Math.exp(-g[i]))) * u[i];
210
+ const dn = matvec(this.t(p + "mlp.down_proj.weight"), g, D, I);
211
+ for (let i = 0; i < D; i++) x[i] += dn[i];
212
+ }
213
+ }
214
+
215
+ const logits = matvec(this.t("lm_head.weight"), rmsnorm(x, this.t("model.norm.weight"), c.rms_norm_eps), c.vocab_size, D);
216
+ const max = Math.max(...logits);
217
+ const lse = max + Math.log(logits.reduce((a, v) => a + Math.exp(v - max), 0));
218
+ return logits.map((v) => v - lse);
219
+ }
220
+ }
221
+
222
+ // ---------- autocomplete ----------
223
+
224
+ export type Completion = {
225
+ prompt: string;
226
+ completion: string;
227
+ top: { id: number; token: string; logprob: number }[];
228
+ };
229
+
230
+ export class Autocompleter {
231
+ tok: Tokenizer;
232
+ model: Llama;
233
+ maxNew: number;
234
+
235
+ constructor(dir: string) {
236
+ this.tok = new Tokenizer(join(dir, "tokenizer.json"));
237
+ this.model = new Llama(dir);
238
+ const gen = JSON.parse(readFileSync(join(dir, "generation_config.json"), "utf8"));
239
+ this.maxNew = gen.max_new_tokens ?? 16;
240
+ }
241
+
242
+ /** Greedy completion plus the top-k candidates for the next token. */
243
+ complete(prompt: string, topK = 3): Completion {
244
+ const { tok, model } = this;
245
+ const ids = tok.encode(prompt);
246
+ const first = model.nextLogprobs(ids);
247
+ const top = [...first.keys()]
248
+ .sort((a, b) => first[b] - first[a])
249
+ .slice(0, topK)
250
+ .map((id) => ({ id, token: tok.decode([id]) || tok.inverse[id], logprob: first[id] }));
251
+ const out: number[] = [];
252
+ let next = argmax(first);
253
+ while (next !== model.cfg.eos_token_id && out.length < this.maxNew && ids.length < model.cfg.max_position_embeddings) {
254
+ out.push(next);
255
+ ids.push(next);
256
+ next = argmax(model.nextLogprobs(ids));
257
+ }
258
+ return { prompt, completion: tok.decode(out), top };
259
+ }
260
+ }
261
+
262
+ function argmax(a: Float64Array): number {
263
+ let best = 0;
264
+ for (let i = 1; i < a.length; i++) if (a[i] > a[best]) best = i;
265
+ return best;
266
+ }
phrases/everyday.txt ADDED
@@ -0,0 +1,275 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Can you help me?
2
+ Can you help me with this?
3
+ Can you help me, please?
4
+ Can you do me a favor?
5
+ Can you call me back?
6
+ Can you call me back later?
7
+ Can you say that again?
8
+ Can you repeat that, please?
9
+ Can you speak more slowly, please?
10
+ Can you send me the file?
11
+ Can you pick me up?
12
+ Can you pick me up at the airport?
13
+ Can you turn off the lights?
14
+ Can you turn on the lights?
15
+ Can you turn down the music?
16
+ Can you open the window?
17
+ Can you close the door?
18
+ Can you pass the salt, please?
19
+ Can you give me a hand?
20
+ Can you tell me the way to the station?
21
+ Can I have the check, please?
22
+ Can I have a glass of water?
23
+ Can I have the menu, please?
24
+ Can I get a glass of water?
25
+ Can I get a cup of coffee?
26
+ Can I get the bill, please?
27
+ Can I pay by card?
28
+ Can I pay with cash?
29
+ Can I try this on?
30
+ Can I borrow your pen?
31
+ Can I use your phone?
32
+ Can I use the bathroom?
33
+ Can I ask a question?
34
+ Can I talk to you for a minute?
35
+ Can I come with you?
36
+ Could you help me, please?
37
+ Could you please help me?
38
+ Could you tell me where the bathroom is?
39
+ Could you please send me the details?
40
+ Could you please call me back?
41
+ Could I have some water, please?
42
+ Would you like some coffee?
43
+ Would you like some tea?
44
+ Would you like something to drink?
45
+ Would you like to join us?
46
+ Would you like to come over?
47
+ Would you mind closing the door?
48
+ Would you mind helping me?
49
+ Do you have a minute?
50
+ Do you have any questions?
51
+ Do you have the time?
52
+ Do you have a table for two?
53
+ Do you know what time it is?
54
+ Do you know where the station is?
55
+ Do you know how to get there?
56
+ Do you speak English?
57
+ Do you need any help?
58
+ Do you need a ride?
59
+ Do you want something to eat?
60
+ Do you want to go out tonight?
61
+ Do you accept credit cards?
62
+ What time is it?
63
+ What time is it now?
64
+ What time do you open?
65
+ What time do you close?
66
+ What time does the train leave?
67
+ What time does the meeting start?
68
+ What time should we meet?
69
+ What day is it today?
70
+ What is the date today?
71
+ What's the weather like today?
72
+ What's the weather like tomorrow?
73
+ What do you want to eat?
74
+ What do you want to do?
75
+ What do you mean?
76
+ What do you think?
77
+ What happened?
78
+ What is this?
79
+ What is that?
80
+ What's wrong?
81
+ What's for dinner?
82
+ What would you like to order?
83
+ What would you recommend?
84
+ Where is the bathroom?
85
+ Where is the restroom?
86
+ Where is the nearest hospital?
87
+ Where is the train station?
88
+ Where is the bus stop?
89
+ Where is the exit?
90
+ Where are you?
91
+ Where are you going?
92
+ Where are my keys?
93
+ Where did you put it?
94
+ Where do you live?
95
+ Where should we meet?
96
+ How much is this?
97
+ How much does it cost?
98
+ How much is it?
99
+ How much do I owe you?
100
+ How do I get there?
101
+ How do I get to the airport?
102
+ How do you spell that?
103
+ How do you say that in English?
104
+ How do you know?
105
+ How long does it take?
106
+ How long will it take?
107
+ How long have you been here?
108
+ How far is it?
109
+ How many people are coming?
110
+ How old are you?
111
+ How was the movie?
112
+ When is the meeting?
113
+ When are you coming home?
114
+ When will you be back?
115
+ When does the store open?
116
+ When does the store close?
117
+ Why are you late?
118
+ Why not?
119
+ Who is calling, please?
120
+ Who is it?
121
+ Is it going to rain today?
122
+ Is there a bank near here?
123
+ Is there a pharmacy near here?
124
+ Is this the right way?
125
+ Is breakfast included?
126
+ It's going to rain today.
127
+ It's very hot today.
128
+ It's really cold outside.
129
+ It's raining outside.
130
+ It's snowing outside.
131
+ It looks like rain.
132
+ Don't forget your umbrella.
133
+ Don't forget to call me.
134
+ Don't forget to lock the door.
135
+ I'm hungry.
136
+ I'm thirsty.
137
+ I'm tired.
138
+ I'm so tired.
139
+ I'm busy right now.
140
+ I'm running late.
141
+ I'm going to be late.
142
+ I'll be home late tonight.
143
+ I'm going to bed.
144
+ I'm going to the store.
145
+ I'm going to the gym.
146
+ I'm going home.
147
+ I'm leaving now.
148
+ I'm looking for the bathroom.
149
+ I'm looking for a job.
150
+ I'm just looking, thank you.
151
+ I'm not feeling well.
152
+ I don't feel well.
153
+ I have a headache.
154
+ I have a cold.
155
+ I have a doctor's appointment.
156
+ I need to see a doctor.
157
+ I need to go to the hospital.
158
+ I need to go to the store.
159
+ I need to go to work.
160
+ I need to go to bed.
161
+ I need to talk to you.
162
+ I need some help.
163
+ I need your help.
164
+ I need a break.
165
+ I need more time.
166
+ I need to make a reservation.
167
+ I would like to make a reservation.
168
+ I would like to order a pizza.
169
+ I would like a cup of coffee, please.
170
+ I would like the chicken, please.
171
+ I'd like to book a table for two.
172
+ I'd like to check in, please.
173
+ I'd like to check out, please.
174
+ I'll have the same.
175
+ I'll have a coffee, please.
176
+ I'll take it.
177
+ I'll pay for dinner.
178
+ I'll call you back.
179
+ I'll see you there.
180
+ I'll be there in five minutes.
181
+ I'll get back to you.
182
+ I'll do it later.
183
+ I'll think about it.
184
+ I lost my keys.
185
+ I lost my phone.
186
+ I forgot my password.
187
+ I can't find my keys.
188
+ I can't find my phone.
189
+ I can't hear you.
190
+ I don't understand.
191
+ I don't know.
192
+ I don't know what to do.
193
+ I don't have time.
194
+ I didn't know that.
195
+ I have a question.
196
+ I have an idea.
197
+ I have to go.
198
+ I have to go now.
199
+ I have to work tomorrow.
200
+ I got it.
201
+ I understand.
202
+ I agree.
203
+ Let's go.
204
+ Let's go home.
205
+ Let's go out for dinner.
206
+ Let's eat.
207
+ Let's get started.
208
+ Let's meet at the coffee shop.
209
+ Let's talk about it later.
210
+ Let's take a break.
211
+ Let's watch a movie.
212
+ Let me check.
213
+ Let me help you.
214
+ Let me think about it.
215
+ Let me see.
216
+ Let me call you back.
217
+ Please call me back.
218
+ Please wait a moment.
219
+ Please hold on.
220
+ Please sit down.
221
+ Please turn off your phone.
222
+ Please be quiet.
223
+ Please close the door.
224
+ Please wait here.
225
+ Just a moment, please.
226
+ Just a minute.
227
+ One moment, please.
228
+ Hold on a second.
229
+ Wait for me.
230
+ Hurry up.
231
+ Come here.
232
+ Come with me.
233
+ Follow me.
234
+ Turn left at the corner.
235
+ Turn right at the light.
236
+ Go straight ahead.
237
+ It's on the left.
238
+ It's on the right.
239
+ It's right over there.
240
+ It's not far from here.
241
+ It's just around the corner.
242
+ The meeting is at noon.
243
+ The meeting has been canceled.
244
+ The train is late.
245
+ The store is closed.
246
+ The food is delicious.
247
+ The bill, please.
248
+ Check, please.
249
+ Table for two, please.
250
+ A cup of coffee, please.
251
+ A glass of water, please.
252
+ Water, please.
253
+ Keep the change.
254
+ Here you go.
255
+ Here it is.
256
+ This is delicious.
257
+ This is too expensive.
258
+ That sounds good.
259
+ That sounds great.
260
+ That works for me.
261
+ See you at the meeting.
262
+ Call me if you need anything.
263
+ Call an ambulance.
264
+ Help me, please.
265
+ Help!
266
+ Watch out!
267
+ Be careful.
268
+ Drive safely.
269
+ Did you eat yet?
270
+ Did you sleep well?
271
+ Did you get my message?
272
+ Did you see that?
273
+ Have you eaten yet?
274
+ Have you seen my keys?
275
+ Have you been here before?
phrases/idioms.txt ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Once upon a time.
2
+ The quick brown fox jumps over the lazy dog.
3
+ Better late than never.
4
+ Actions speak louder than words.
5
+ It's raining cats and dogs.
6
+ Practice makes perfect.
7
+ When in Rome, do as the Romans do.
8
+ Better safe than sorry.
9
+ Every cloud has a silver lining.
10
+ A piece of cake.
11
+ A blessing in disguise.
12
+ A penny saved is a penny earned.
13
+ A penny for your thoughts.
14
+ A watched pot never boils.
15
+ A stitch in time saves nine.
16
+ A picture is worth a thousand words.
17
+ A friend in need is a friend indeed.
18
+ A chip off the old block.
19
+ A leopard can't change its spots.
20
+ A bird in the hand is worth two in the bush.
21
+ A rolling stone gathers no moss.
22
+ A dime a dozen.
23
+ A diamond in the rough.
24
+ A fish out of water.
25
+ A jack of all trades.
26
+ A drop in the bucket.
27
+ A needle in a haystack.
28
+ A wild goose chase.
29
+ A lost cause.
30
+ A heart of gold.
31
+ A chip on your shoulder.
32
+ A clean slate.
33
+ A hard pill to swallow.
34
+ A shot in the dark.
35
+ A rule of thumb.
36
+ A red herring.
37
+ A dog eat dog world.
38
+ A light at the end of the tunnel.
39
+ A sight for sore eyes.
40
+ Absence makes the heart grow fonder.
41
+ All that glitters is not gold.
42
+ All's fair in love and war.
43
+ All's well that ends well.
44
+ All good things must come to an end.
45
+ All roads lead to Rome.
46
+ All bark and no bite.
47
+ All in a day's work.
48
+ All thumbs.
49
+ An apple a day keeps the doctor away.
50
+ An eye for an eye.
51
+ An ounce of prevention is worth a pound of cure.
52
+ An eager beaver.
53
+ As easy as pie.
54
+ As American as apple pie.
55
+ As cool as a cucumber.
56
+ As fit as a fiddle.
57
+ As happy as a clam.
58
+ As light as a feather.
59
+ As old as the hills.
60
+ As plain as the nose on your face.
61
+ As quiet as a mouse.
62
+ As right as rain.
63
+ As sly as a fox.
64
+ As stubborn as a mule.
65
+ As white as a ghost.
66
+ As busy as a bee.
67
+ As dead as a doornail.
68
+ As clear as mud.
69
+ As good as gold.
70
+ As mad as a hatter.
71
+ As strong as an ox.
72
+ As cute as a button.
73
+ As blind as a bat.
74
+ Back to the drawing board.
75
+ Barking up the wrong tree.
76
+ Beauty is in the eye of the beholder.
77
+ Beggars can't be choosers.
78
+ Better the devil you know.
79
+ Birds of a feather flock together.
80
+ Bite off more than you can chew.
81
+ Bite the bullet.
82
+ Bite the hand that feeds you.
83
+ Blood is thicker than water.
84
+ Break a leg.
85
+ Burn the midnight oil.
86
+ Burn the candle at both ends.
87
+ By the skin of your teeth.
88
+ Beat around the bush.
89
+ Bend over backwards.
90
+ The best of both worlds.
91
+ Beyond the pale.
92
+ Bite the dust.
93
+ Blow off steam.
94
+ Break the ice.
95
+ Burn your bridges.
96
+ Bury the hatchet.
97
+ His bark is worse than his bite.
98
+ Break even.
99
+ Caught between a rock and a hard place.
100
+ Charity begins at home.
101
+ Cleanliness is next to godliness.
102
+ Curiosity killed the cat.
103
+ Cut corners.
104
+ Cut to the chase.
105
+ Call it a day.
106
+ Caught red-handed.
107
+ Come rain or shine.
108
+ Cost an arm and a leg.
109
+ Cross that bridge when you come to it.
110
+ Cry wolf.
111
+ Call a spade a spade.
112
+ Close but no cigar.
113
+ Cut your losses.
114
+ The calm before the storm.
115
+ The cold shoulder.
116
+ Don't judge a book by its cover.
117
+ Don't cry over spilled milk.
118
+ Don't count your chickens before they hatch.
119
+ Don't look a gift horse in the mouth.
120
+ Don't put all your eggs in one basket.
121
+ Don't rock the boat.
122
+ Don't make a mountain out of a molehill.
123
+ Down in the dumps.
124
+ Draw the line.
125
+ Easy come, easy go.
126
+ Every dog has its day.
127
+ Every man for himself.
128
+ Every rose has its thorn.
129
+ Easier said than done.
130
+ The elephant in the room.
131
+ Familiarity breeds contempt.
132
+ Fight fire with fire.
133
+ First come, first served.
134
+ Fortune favors the bold.
135
+ Face the music.
136
+ A fly on the wall.
137
+ Fool me once, shame on you, fool me twice, shame on me.
138
+ Give him an inch and he'll take a mile.
139
+ Go against the grain.
140
+ Good things come to those who wait.
141
+ Great minds think alike.
142
+ Get a taste of your own medicine.
143
+ Get the ball rolling.
144
+ Get up on the wrong side of the bed.
145
+ Give the benefit of the doubt.
146
+ Go back to square one.
147
+ Go the extra mile.
148
+ Go with the flow.
149
+ It goes without saying.
150
+ Grasping at straws.
151
+ Haste makes waste.
152
+ Health is wealth.
153
+ Herding cats.
154
+ History repeats itself.
155
+ Hit the nail on the head.
156
+ Honesty is the best policy.
157
+ Hope for the best, prepare for the worst.
158
+ Hang in there.
159
+ Head in the clouds.
160
+ Head over heels.
161
+ Hit the hay.
162
+ Hit the sack.
163
+ Hold your horses.
164
+ If it ain't broke, don't fix it.
165
+ If the shoe fits, wear it.
166
+ Ignorance is bliss.
167
+ It never rains but it pours.
168
+ It takes two to tango.
169
+ It's a small world.
170
+ It's better to give than to receive.
171
+ It's never too late.
172
+ It's the thought that counts.
173
+ In a nutshell.
174
+ In the heat of the moment.
175
+ In the nick of time.
176
+ It takes one to know one.
177
+ Jump on the bandwagon.
178
+ Jump the gun.
179
+ Keep your chin up.
180
+ Keep your eye on the ball.
181
+ Keep your friends close and your enemies closer.
182
+ Keep your fingers crossed.
183
+ Kill two birds with one stone.
184
+ Kick the bucket.
185
+ Knowledge is power.
186
+ Laughter is the best medicine.
187
+ Let bygones be bygones.
188
+ Let sleeping dogs lie.
189
+ Let the cat out of the bag.
190
+ Life is short.
191
+ Live and learn.
192
+ Look before you leap.
193
+ Love is blind.
194
+ Learn the ropes.
195
+ Let the chips fall where they may.
196
+ Live and let live.
197
+ Make hay while the sun shines.
198
+ Many hands make light work.
199
+ Measure twice, cut once.
200
+ Misery loves company.
201
+ Money can't buy happiness.
202
+ Money doesn't grow on trees.
203
+ Money is the root of all evil.
204
+ Monkey see, monkey do.
205
+ Make ends meet.
206
+ There's a method to the madness.
207
+ Miss the boat.
208
+ Make a long story short.
209
+ Necessity is the mother of invention.
210
+ Never put off until tomorrow what you can do today.
211
+ Never say never.
212
+ No news is good news.
213
+ No pain, no gain.
214
+ No man is an island.
215
+ No time like the present.
216
+ Nothing ventured, nothing gained.
217
+ There's no rest for the wicked.
218
+ Once bitten, twice shy.
219
+ One good turn deserves another.
220
+ One man's trash is another man's treasure.
221
+ Opportunity knocks but once.
222
+ Out of sight, out of mind.
223
+ Out of the frying pan and into the fire.
224
+ Out of the blue.
225
+ Off the beaten path.
226
+ Off the hook.
227
+ On the ball.
228
+ On the same page.
229
+ On thin ice.
230
+ Once in a blue moon.
231
+ Over the moon.
232
+ Patience is a virtue.
233
+ Pride comes before a fall.
234
+ Put the cart before the horse.
235
+ Par for the course.
236
+ Pass the buck.
237
+ Play it by ear.
238
+ Play devil's advocate.
239
+ Pull yourself together.
240
+ Pull your weight.
241
+ Put your best foot forward.
242
+ Peace of mind.
243
+ Rome wasn't built in a day.
244
+ Rain on your parade.
245
+ Read between the lines.
246
+ Rings a bell.
247
+ Silence is golden.
248
+ Slow and steady wins the race.
249
+ Spare the rod and spoil the child.
250
+ Speak of the devil.
251
+ Still waters run deep.
252
+ Strike while the iron is hot.
253
+ Sit on the fence.
254
+ Skeletons in the closet.
255
+ Spill the beans.
256
+ Steal someone's thunder.
257
+ Straight from the horse's mouth.
258
+ The apple doesn't fall far from the tree.
259
+ The ball is in your court.
260
+ The best things in life are free.
261
+ The customer is always right.
262
+ The early bird catches the worm.
263
+ The end justifies the means.
264
+ The exception proves the rule.
265
+ The grass is always greener on the other side.
266
+ The pen is mightier than the sword.
267
+ The pot calling the kettle black.
268
+ The proof is in the pudding.
269
+ The road to hell is paved with good intentions.
270
+ The squeaky wheel gets the grease.
271
+ The truth hurts.
272
+ The whole nine yards.
273
+ There's more than one way to skin a cat.
274
+ There's no place like home.
275
+ There's no smoke without fire.
276
+ There's no such thing as a free lunch.
277
+ Time flies when you're having fun.
278
+ Time heals all wounds.
279
+ Time is money.
280
+ Time will tell.
281
+ To each his own.
282
+ Too many cooks spoil the broth.
283
+ Two heads are better than one.
284
+ Two wrongs don't make a right.
285
+ Take it with a grain of salt.
286
+ Take the bull by the horns.
287
+ The whole kit and caboodle.
288
+ Through thick and thin.
289
+ Throw in the towel.
290
+ Tie the knot.
291
+ The tip of the iceberg.
292
+ Turn a blind eye.
293
+ Turn over a new leaf.
294
+ Tongue in cheek.
295
+ The last straw.
296
+ United we stand, divided we fall.
297
+ Under the weather.
298
+ Up in the air.
299
+ Variety is the spice of life.
300
+ Walk a mile in someone's shoes.
301
+ Waste not, want not.
302
+ What goes around comes around.
303
+ What doesn't kill you makes you stronger.
304
+ When life gives you lemons, make lemonade.
305
+ When one door closes, another opens.
306
+ When the going gets tough, the tough get going.
307
+ Where there's a will, there's a way.
308
+ Where there's smoke, there's fire.
309
+ Water under the bridge.
310
+ Wear your heart on your sleeve.
311
+ When pigs fly.
312
+ Word of mouth.
313
+ You can't have your cake and eat it too.
314
+ You can't make an omelet without breaking eggs.
315
+ You can't please everyone.
316
+ You can't teach an old dog new tricks.
317
+ You reap what you sow.
318
+ You scratch my back, I'll scratch yours.
319
+ You win some, you lose some.
320
+ Add insult to injury.
321
+ Don't kill the messenger.
phrases/opinions_work.txt ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ I think so.
2
+ I think you're right.
3
+ I think we should go.
4
+ I think it's a good idea.
5
+ I think it's a great idea.
6
+ I think that's a good idea.
7
+ I don't think so.
8
+ I don't think that's a good idea.
9
+ I agree with you.
10
+ I completely agree.
11
+ I totally agree.
12
+ I couldn't agree more.
13
+ I disagree.
14
+ I'm not sure.
15
+ I'm not sure about that.
16
+ I'm not so sure.
17
+ I see what you mean.
18
+ I see your point.
19
+ I know what you mean.
20
+ I know how you feel.
21
+ I understand how you feel.
22
+ I believe you.
23
+ I believe in you.
24
+ I trust you.
25
+ I love you.
26
+ I love you so much.
27
+ I love you too.
28
+ I love it.
29
+ I like it.
30
+ I really like it.
31
+ I don't like it.
32
+ I miss you.
33
+ I miss you so much.
34
+ I miss you too.
35
+ I need you.
36
+ I'm proud of you.
37
+ I'm so proud of you.
38
+ I'm happy for you.
39
+ I'm so happy for you.
40
+ I'm so happy.
41
+ I'm happy to help.
42
+ I'm glad to hear that.
43
+ I'm glad you're here.
44
+ I'm glad you liked it.
45
+ I'm excited.
46
+ I'm so excited.
47
+ I'm worried about you.
48
+ I'm here for you.
49
+ I'm always here for you.
50
+ I'm on your side.
51
+ I'm sorry you feel that way.
52
+ I feel the same way.
53
+ I feel great.
54
+ I feel sick.
55
+ I can't believe it.
56
+ I can't wait.
57
+ I can't wait to see you.
58
+ I can't do this anymore.
59
+ I can do it.
60
+ You can do it.
61
+ You can do this.
62
+ You've got this.
63
+ You did a great job.
64
+ You did it.
65
+ You look great.
66
+ You look beautiful.
67
+ You look tired.
68
+ You're right.
69
+ You're so right.
70
+ You're the best.
71
+ You're amazing.
72
+ You're so kind.
73
+ You're very kind.
74
+ You're welcome to join us.
75
+ You deserve it.
76
+ You should try it.
77
+ You know what I mean?
78
+ You never know.
79
+ You're kidding.
80
+ You're kidding me.
81
+ Don't worry.
82
+ Don't worry about it.
83
+ Don't worry, be happy.
84
+ Don't give up.
85
+ Don't be afraid.
86
+ Don't be sad.
87
+ Don't be late.
88
+ Don't be silly.
89
+ Don't do that.
90
+ Don't touch that.
91
+ Don't tell anyone.
92
+ Never give up.
93
+ Keep up the good work.
94
+ Keep going.
95
+ Keep it up.
96
+ Hang in there.
97
+ Take your time.
98
+ Take it easy.
99
+ Calm down.
100
+ Cheer up.
101
+ It's okay.
102
+ It's not your fault.
103
+ It's not a big deal.
104
+ It's going to be okay.
105
+ It's going to be fine.
106
+ Everything is going to be okay.
107
+ Everything will be fine.
108
+ It doesn't matter.
109
+ It's up to you.
110
+ It's my fault.
111
+ It's a good idea.
112
+ It's a deal.
113
+ It's too late.
114
+ It's time to go.
115
+ It's time for bed.
116
+ It's worth it.
117
+ That's right.
118
+ That's true.
119
+ That's not true.
120
+ That's a good idea.
121
+ That's a great idea.
122
+ That's a good question.
123
+ That's a great question.
124
+ That's fine.
125
+ That's fine with me.
126
+ That's okay.
127
+ That's not fair.
128
+ That's enough.
129
+ That's it.
130
+ That makes sense.
131
+ That doesn't make sense.
132
+ That's what I thought.
133
+ That's why I love you.
134
+ Me too.
135
+ Me neither.
136
+ So do I.
137
+ Of course.
138
+ Of course not.
139
+ Absolutely.
140
+ Definitely.
141
+ Exactly.
142
+ Maybe.
143
+ Maybe later.
144
+ Maybe next time.
145
+ Probably.
146
+ Sure.
147
+ Sure, no problem.
148
+ Sure thing.
149
+ Okay.
150
+ Okay, sounds good.
151
+ Sounds good.
152
+ Sounds good to me.
153
+ Sounds like a plan.
154
+ Good idea.
155
+ Good point.
156
+ Good question.
157
+ Fair enough.
158
+ Not really.
159
+ Not at all.
160
+ Not yet.
161
+ Not now.
162
+ I hope this email finds you well.
163
+ I hope you are doing well.
164
+ I hope you had a great weekend.
165
+ I am writing to follow up on our conversation.
166
+ I just wanted to follow up on my last email.
167
+ Just a quick reminder about the meeting.
168
+ Please find attached the report.
169
+ Please find the attached file.
170
+ Please see the attached document.
171
+ Please let me know if you have any questions.
172
+ Please let me know if you need anything else.
173
+ Please let me know your availability.
174
+ Please let me know what you think.
175
+ Please let me know as soon as possible.
176
+ Please feel free to contact me.
177
+ Please feel free to reach out.
178
+ Please don't hesitate to contact me.
179
+ Please review the document.
180
+ Please confirm your attendance.
181
+ Let me know if you need anything.
182
+ Let me know if you have any questions.
183
+ Let me know what you think.
184
+ Let me know if that works for you.
185
+ Let me know when you're free.
186
+ Let me get back to you on that.
187
+ Feel free to reach out.
188
+ Feel free to ask.
189
+ Looking forward to hearing from you.
190
+ Looking forward to seeing you.
191
+ Looking forward to working with you.
192
+ I look forward to hearing from you.
193
+ I look forward to meeting you.
194
+ I look forward to working with you.
195
+ Thank you for your email.
196
+ Thank you for your quick response.
197
+ Thank you for reaching out.
198
+ Thank you for the update.
199
+ Thanks for the update.
200
+ Thanks for getting back to me.
201
+ Thanks for reaching out.
202
+ Thanks in advance.
203
+ Thanks for your hard work.
204
+ Best regards.
205
+ Kind regards.
206
+ Warm regards.
207
+ Sincerely.
208
+ Have a great day ahead.
209
+ As soon as possible.
210
+ At your earliest convenience.
211
+ For your information.
212
+ Sorry for the inconvenience.
213
+ Sorry for the confusion.
214
+ Sorry for the delay.
215
+ I will get back to you as soon as possible.
216
+ I will send you the details.
217
+ I will look into it.
218
+ I will keep you posted.
219
+ I'll keep you updated.
220
+ I'll let you know.
221
+ I'll send it over.
222
+ I'll take care of it.
223
+ I'm working on it.
224
+ I'm in a meeting.
225
+ I'm in a meeting right now.
226
+ I'm out of the office.
227
+ Can we schedule a meeting?
228
+ Can we reschedule the meeting?
229
+ Can we talk tomorrow?
230
+ Can we move the meeting to tomorrow?
231
+ Can you send me an update?
232
+ Can you review this?
233
+ Does that work for you?
234
+ Does that make sense?
235
+ Is that okay with you?
236
+ Are you available tomorrow?
237
+ Are you available for a quick call?
238
+ Let's schedule a call.
239
+ Let's touch base next week.
240
+ Let's discuss this in the meeting.
241
+ Let's circle back on this.
242
+ Let's move forward.
243
+ Let's get to work.
244
+ The meeting is canceled.
245
+ The deadline is tomorrow.
246
+ We need to talk.
247
+ We need to finish this today.
248
+ We need more time.
249
+ We can do it.
250
+ We did it.
251
+ We are happy to help.
252
+ We apologize for the inconvenience.
253
+ We appreciate your patience.
254
+ We appreciate your business.
255
+ We look forward to hearing from you.
256
+ Your order has been shipped.
257
+ Your call is important to us.
258
+ How can I help you today?
259
+ How may I help you?
260
+ Is there anything else I can help you with?
261
+ Thank you for contacting us.
262
+ Thank you for your order.
263
+ Thank you for shopping with us.
264
+ Have a nice day, and thank you.
265
+ Great work, everyone.
266
+ Great job, team.
267
+ Good work.
268
+ Well said.
269
+ Nice work.
270
+ Nice job.
271
+ I appreciate it.
272
+ I really appreciate it.
273
+ I really appreciate your help.
274
+ I appreciate your help.
275
+ I owe you one.
276
+ I'm grateful for your help.
277
+ It means a lot to me.
278
+ It was a pleasure working with you.
phrases/social.txt ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Hello, how are you?
2
+ Hello, how are you doing?
3
+ Hi, how are you?
4
+ Hi, how's it going?
5
+ Hey, what's up?
6
+ Hey, how have you been?
7
+ How are you?
8
+ How are you doing?
9
+ How are you doing today?
10
+ How are you feeling?
11
+ How are you feeling today?
12
+ How have you been?
13
+ How's it going?
14
+ How's your day going?
15
+ How was your day?
16
+ How was your weekend?
17
+ How was your trip?
18
+ How is your family?
19
+ I'm fine, thank you.
20
+ I'm fine, thanks. And you?
21
+ I'm doing well, thank you.
22
+ I'm doing well, thanks for asking.
23
+ I'm good, thanks.
24
+ I'm good, how about you?
25
+ Not bad, how about you?
26
+ Not too bad.
27
+ Pretty good, thanks.
28
+ Good morning.
29
+ Good morning, everyone.
30
+ Good morning, how are you?
31
+ Good afternoon.
32
+ Good evening.
33
+ Good night.
34
+ Good night, sleep well.
35
+ Good night and sweet dreams.
36
+ Sweet dreams.
37
+ Sleep well.
38
+ Sleep tight.
39
+ Have a good night.
40
+ Have a good day.
41
+ Have a nice day.
42
+ Have a great day.
43
+ Have a wonderful day.
44
+ Have a good weekend.
45
+ Have a great weekend.
46
+ Have a nice weekend.
47
+ Have a good time.
48
+ Have a great time.
49
+ Have a safe trip.
50
+ Have a safe flight.
51
+ Have fun.
52
+ Have a good one.
53
+ Nice to meet you.
54
+ Nice to meet you too.
55
+ It's nice to meet you.
56
+ It was nice to meet you.
57
+ It was nice meeting you.
58
+ It was nice talking to you.
59
+ It was great to see you.
60
+ It's good to see you.
61
+ It's good to see you again.
62
+ It's been a long time.
63
+ Long time no see.
64
+ Pleased to meet you.
65
+ What is your name?
66
+ What's your name?
67
+ Where are you from?
68
+ What do you do?
69
+ What do you do for a living?
70
+ Thank you.
71
+ Thank you very much.
72
+ Thank you so much.
73
+ Thank you for your help.
74
+ Thank you for your time.
75
+ Thank you for everything.
76
+ Thank you for coming.
77
+ Thank you for listening.
78
+ Thank you for your patience.
79
+ Thank you for your understanding.
80
+ Thank you for the gift.
81
+ Thank you for the invitation.
82
+ Thank you for letting me know.
83
+ Thank you for asking.
84
+ Thanks.
85
+ Thanks a lot.
86
+ Thanks so much.
87
+ Thanks for your help.
88
+ Thanks for everything.
89
+ Thanks for coming.
90
+ Thanks for letting me know.
91
+ Thanks again.
92
+ Thanks anyway.
93
+ You're welcome.
94
+ You are welcome.
95
+ No problem.
96
+ No worries.
97
+ My pleasure.
98
+ It was my pleasure.
99
+ Don't mention it.
100
+ Anytime.
101
+ I'm sorry.
102
+ I'm so sorry.
103
+ I'm very sorry.
104
+ I'm sorry for being late.
105
+ I'm sorry to hear that.
106
+ I'm sorry for your loss.
107
+ I'm sorry, I didn't mean it.
108
+ I apologize for the inconvenience.
109
+ I apologize for the delay.
110
+ Sorry about that.
111
+ Sorry I'm late.
112
+ Sorry to bother you.
113
+ Sorry for the late reply.
114
+ Excuse me.
115
+ Excuse me, can you help me?
116
+ Excuse me, where is the bathroom?
117
+ Pardon me.
118
+ Please.
119
+ Yes, please.
120
+ No, thank you.
121
+ No, thanks.
122
+ See you later.
123
+ See you soon.
124
+ See you tomorrow.
125
+ See you next week.
126
+ See you around.
127
+ See you then.
128
+ Talk to you later.
129
+ Talk to you soon.
130
+ Catch you later.
131
+ Take care.
132
+ Take care of yourself.
133
+ Goodbye.
134
+ Bye for now.
135
+ Bye, see you later.
136
+ Welcome.
137
+ Welcome back.
138
+ Welcome home.
139
+ Welcome to the team.
140
+ Come on in.
141
+ Please come in.
142
+ Please have a seat.
143
+ Make yourself at home.
144
+ Happy birthday.
145
+ Happy birthday to you.
146
+ Happy birthday, have a great day.
147
+ Wishing you a happy birthday.
148
+ Many happy returns.
149
+ Happy new year.
150
+ Happy holidays.
151
+ Merry Christmas.
152
+ Merry Christmas and a happy new year.
153
+ Happy anniversary.
154
+ Happy Thanksgiving.
155
+ Happy Easter.
156
+ Happy Mother's Day.
157
+ Happy Father's Day.
158
+ Happy Valentine's Day.
159
+ Congratulations.
160
+ Congratulations on your new job.
161
+ Congratulations on your graduation.
162
+ Congratulations on your wedding.
163
+ Congratulations on the new baby.
164
+ Congratulations, you deserve it.
165
+ Well done.
166
+ Good job.
167
+ Great job.
168
+ Good luck.
169
+ Good luck with your exam.
170
+ Good luck with everything.
171
+ Best of luck.
172
+ Best wishes.
173
+ Get well soon.
174
+ I hope you feel better soon.
175
+ I hope you have a great day.
176
+ I hope you're doing well.
177
+ I hope everything is okay.
178
+ I hope to see you soon.
179
+ I hope so.
180
+ Cheers.
181
+ Bless you.
182
+ God bless you.
183
+ Same to you.
184
+ You too.
185
+ And you?
186
+ What about you?
187
+ How about you?
188
+ What's new?
189
+ What's going on?
190
+ What are you doing?
191
+ What are you up to?
192
+ What are you doing this weekend?
193
+ Not much, just relaxing.
194
+ Nothing much.
195
+ Are you okay?
196
+ Are you sure?
197
+ Are you ready?
198
+ Are you coming?
199
+ Are you busy?
200
+ Are you free tonight?
201
+ Do you want to hang out?
202
+ Do you want to grab lunch?
203
+ Do you want to come with me?
204
+ Let's get together soon.
205
+ Let's keep in touch.
206
+ Let's grab a coffee sometime.
207
+ Keep in touch.
208
+ Say hi to your family for me.
209
+ Give my love to your family.
210
+ Tell them I said hello.
211
+ I'll text you later.
212
+ I'll call you later.
213
+ I'll call you tomorrow.
214
+ Call me when you get home.
215
+ Text me when you get home.
216
+ Let me know when you get home.
217
+ Let me know when you're here.
218
+ I'm here.
219
+ I'm on my way.
220
+ I'm almost there.
221
+ I'll be there soon.
222
+ I'll be right back.
223
+ Be right back.
224
+ I'm home.
225
+ Just checking in.
226
+ Just wanted to say hi.
227
+ Miss you.
228
+ Love you.
229
+ Love you too.
230
+ Lol, that's so funny.
231
+ That's so funny.
232
+ That's hilarious.
233
+ Oh my god.
234
+ Oh my gosh.
235
+ No way.
236
+ Are you serious?
237
+ Really?
238
+ Wow, that's amazing.
239
+ That's awesome.
240
+ That's great.
241
+ That's great news.
242
+ That's too bad.
243
+ That's a shame.
244
+ What a surprise.
245
+ What a nice surprise.
246
+ What a beautiful day.
247
+ It's a beautiful day.
248
+ It's so nice out today.
249
+ Enjoy your meal.
250
+ Enjoy your day.
251
+ Enjoy your weekend.
252
+ Enjoy your vacation.
253
+ Enjoy the show.
254
+ Bon appetit.
255
+ Cheers to that.
256
+ After you.
257
+ Ladies and gentlemen.
258
+ Can I ask you something?
259
+ Can I help you?
260
+ May I help you?
261
+ May I come in?
262
+ How can I help you?
263
+ Is everything okay?
264
+ Is this seat taken?
265
+ Nice weather today, isn't it?
266
+ It's cold today, isn't it?
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ torch
2
+ transformers>=4.45
3
+ tokenizers
4
+ safetensors
test_model.py ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load the saved files exactly as a Hugging Face user would and check them.
2
+
3
+ Run `python build_weights.py` first.
4
+ """
5
+
6
+ import json
7
+ import random
8
+ import shutil
9
+ import subprocess
10
+ import unittest
11
+ from pathlib import Path
12
+
13
+ import torch
14
+ from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
15
+
16
+ import build_weights
17
+
18
+ MODEL_DIR = Path(__file__).parent
19
+
20
+ # prompt -> greedy completion (new text only)
21
+ CASES = {
22
+ "Once upon a": "time.",
23
+ "The quick brown": "fox jumps over the lazy dog.",
24
+ "Thank you for your": "help.",
25
+ "I hope this email": "finds you well.",
26
+ "Can I have the": "check, please?",
27
+ "Looking forward to": "hearing from you.",
28
+ "Actions speak": "louder than words.",
29
+ "Where is the": "bathroom?",
30
+ "Happy birthday": ".",
31
+ "I'm so proud": "of you.",
32
+ }
33
+
34
+
35
+ class TestModel(unittest.TestCase):
36
+ @classmethod
37
+ def setUpClass(cls):
38
+ cls.tok = AutoTokenizer.from_pretrained(MODEL_DIR)
39
+ cls.model = AutoModelForCausalLM.from_pretrained(MODEL_DIR).eval()
40
+ _, cls.vocab, cls.ngram, _, _, cls.info = build_weights.compile_model()
41
+
42
+ def complete(self, prompt: str) -> str:
43
+ ids = self.tok(prompt, return_tensors="pt")
44
+ out = self.model.generate(**ids, max_new_tokens=16)
45
+ return self.tok.decode(out[0, ids["input_ids"].shape[1] :], skip_special_tokens=True).strip()
46
+
47
+ def test_parameter_count(self):
48
+ n = sum(p.numel() for p in self.model.parameters())
49
+ self.assertEqual(n, self.info["params"])
50
+ self.assertLessEqual(n, 1_000_000_000)
51
+
52
+ def test_greedy_completions(self):
53
+ for prompt, expected in CASES.items():
54
+ with self.subTest(prompt=prompt):
55
+ self.assertEqual(self.complete(prompt), expected)
56
+
57
+ def test_matches_ngram_reference(self):
58
+ """The transformer's next-token distribution equals the compiled n-gram's."""
59
+ index = {p: i for i, p in enumerate(self.vocab)}
60
+ rng = random.Random(0)
61
+ phrases = build_weights.load_phrases()
62
+ cases = []
63
+ for toks in rng.sample(phrases, 200):
64
+ ids = [0] + [index[p] for p in toks]
65
+ cases.append(ids[: rng.randint(1, len(ids))])
66
+ for _ in range(50): # unseen word salad, up to 80 tokens
67
+ cases.append([0] + [rng.randrange(2, len(self.vocab)) for _ in range(rng.randint(1, 80))])
68
+ with torch.no_grad():
69
+ for ids in cases:
70
+ got = self.model(torch.tensor([ids])).logits[0, -1].double().log_softmax(-1)
71
+ ref = self.ngram.reference_logits(ids).log_softmax(-1)
72
+ self.assertLess((got - ref).abs().max().item(), 5e-3)
73
+
74
+ def test_unknown_words_back_off(self):
75
+ # "zebra" is out of vocabulary; the model backs off to the shorter context.
76
+ self.assertEqual(self.complete("Zebra, thank you for your"), "help.")
77
+
78
+ def test_pipeline(self):
79
+ gen = pipeline("text-generation", model=str(MODEL_DIR))
80
+ self.assertEqual(gen("Once upon a")[0]["generated_text"], "Once upon a time.")
81
+
82
+ @unittest.skipUnless(shutil.which("bun"), "bun not installed")
83
+ def test_typescript_matches(self):
84
+ prompts = ["once upon a", "how are", "zebra", "i hope this email"]
85
+ out = subprocess.run(
86
+ ["bun", "autocomplete.ts", "--json", *prompts],
87
+ cwd=MODEL_DIR, capture_output=True, text=True, check=True,
88
+ )
89
+ results = json.loads(out.stdout)
90
+ with torch.no_grad():
91
+ for prompt, res in zip(prompts, results):
92
+ with self.subTest(prompt=prompt):
93
+ ids = self.tok(prompt, return_tensors="pt")
94
+ logp = self.model(**ids).logits[0, -1].log_softmax(-1)
95
+ top = logp.topk(3)
96
+ self.assertEqual([t["id"] for t in res["top"]], top.indices.tolist())
97
+ for t, lp in zip(res["top"], top.values.tolist()):
98
+ self.assertAlmostEqual(t["logprob"], lp, places=3)
99
+ self.assertEqual(res["completion"].strip(), self.complete(prompt))
100
+
101
+
102
+ if __name__ == "__main__":
103
+ unittest.main()
tokenizer.json ADDED
@@ -0,0 +1,1149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0",
3
+ "truncation": null,
4
+ "padding": null,
5
+ "added_tokens": [
6
+ {
7
+ "id": 0,
8
+ "content": "<|endoftext|>",
9
+ "single_word": false,
10
+ "lstrip": false,
11
+ "rstrip": false,
12
+ "normalized": false,
13
+ "special": true
14
+ },
15
+ {
16
+ "id": 1,
17
+ "content": "<unk>",
18
+ "single_word": false,
19
+ "lstrip": false,
20
+ "rstrip": false,
21
+ "normalized": false,
22
+ "special": true
23
+ }
24
+ ],
25
+ "normalizer": {
26
+ "type": "Sequence",
27
+ "normalizers": [
28
+ {
29
+ "type": "NFKC"
30
+ },
31
+ {
32
+ "type": "Lowercase"
33
+ }
34
+ ]
35
+ },
36
+ "pre_tokenizer": {
37
+ "type": "ByteLevel",
38
+ "add_prefix_space": true,
39
+ "trim_offsets": true,
40
+ "use_regex": true
41
+ },
42
+ "post_processor": {
43
+ "type": "TemplateProcessing",
44
+ "single": [
45
+ {
46
+ "SpecialToken": {
47
+ "id": "<|endoftext|>",
48
+ "type_id": 0
49
+ }
50
+ },
51
+ {
52
+ "Sequence": {
53
+ "id": "A",
54
+ "type_id": 0
55
+ }
56
+ }
57
+ ],
58
+ "pair": [
59
+ {
60
+ "Sequence": {
61
+ "id": "A",
62
+ "type_id": 0
63
+ }
64
+ },
65
+ {
66
+ "Sequence": {
67
+ "id": "B",
68
+ "type_id": 1
69
+ }
70
+ }
71
+ ],
72
+ "special_tokens": {
73
+ "<|endoftext|>": {
74
+ "id": "<|endoftext|>",
75
+ "ids": [
76
+ 0
77
+ ],
78
+ "tokens": [
79
+ "<|endoftext|>"
80
+ ]
81
+ }
82
+ }
83
+ },
84
+ "decoder": {
85
+ "type": "ByteLevel",
86
+ "add_prefix_space": true,
87
+ "trim_offsets": true,
88
+ "use_regex": true
89
+ },
90
+ "model": {
91
+ "type": "WordLevel",
92
+ "vocab": {
93
+ "<|endoftext|>": 0,
94
+ "<unk>": 1,
95
+ ".": 2,
96
+ "Ġyou": 3,
97
+ "Ġthe": 4,
98
+ "Ġi": 5,
99
+ "?": 6,
100
+ "Ġa": 7,
101
+ "Ġto": 8,
102
+ "'s": 9,
103
+ "Ġit": 10,
104
+ ",": 11,
105
+ "Ġis": 12,
106
+ "Ġyour": 13,
107
+ "Ġfor": 14,
108
+ "Ġcan": 15,
109
+ "Ġme": 16,
110
+ "'t": 17,
111
+ "'m": 18,
112
+ "Ġthat": 19,
113
+ "Ġplease": 20,
114
+ "Ġas": 21,
115
+ "Ġdo": 22,
116
+ "Ġwhat": 23,
117
+ "Ġhave": 24,
118
+ "Ġin": 25,
119
+ "Ġof": 26,
120
+ "Ġgood": 27,
121
+ "Ġhow": 28,
122
+ "Ġlet": 29,
123
+ "Ġon": 30,
124
+ "Ġare": 31,
125
+ "Ġdon": 32,
126
+ "Ġtime": 33,
127
+ "Ġknow": 34,
128
+ "Ġthank": 35,
129
+ "Ġhelp": 36,
130
+ "'ll": 37,
131
+ "Ġday": 38,
132
+ "Ġlike": 39,
133
+ "Ġsee": 40,
134
+ "Ġand": 41,
135
+ "Ġget": 42,
136
+ "Ġgreat": 43,
137
+ "Ġhappy": 44,
138
+ "Ġneed": 45,
139
+ "Ġnot": 46,
140
+ "Ġthere": 47,
141
+ "Ġbe": 48,
142
+ "Ġso": 49,
143
+ "Ġwith": 50,
144
+ "Ġgo": 51,
145
+ "Ġno": 52,
146
+ "Ġthanks": 53,
147
+ "Ġwe": 54,
148
+ "Ġwhere": 55,
149
+ "Ġback": 56,
150
+ "Ġgoing": 57,
151
+ "Ġout": 58,
152
+ "Ġcall": 59,
153
+ "Ġmy": 60,
154
+ "Ġthis": 61,
155
+ "Ġup": 62,
156
+ "Ġwhen": 63,
157
+ "'re": 64,
158
+ "Ġsorry": 65,
159
+ "Ġan": 66,
160
+ "Ġmeeting": 67,
161
+ "Ġright": 68,
162
+ "Ġwell": 69,
163
+ "Ġcome": 70,
164
+ "Ġhere": 71,
165
+ "Ġnice": 72,
166
+ "Ġthink": 73,
167
+ "Ġtoday": 74,
168
+ "Ġall": 75,
169
+ "Ġat": 76,
170
+ "Ġmake": 77,
171
+ "Ġone": 78,
172
+ "Ġtoo": 79,
173
+ "Ġwould": 80,
174
+ "Ġabout": 81,
175
+ "Ġfeel": 82,
176
+ "Ġhome": 83,
177
+ "Ġkeep": 84,
178
+ "Ġlate": 85,
179
+ "Ġtake": 86,
180
+ "Ġmuch": 87,
181
+ "Ġthan": 88,
182
+ "Ġwork": 89,
183
+ "Ġbest": 90,
184
+ "Ġhope": 91,
185
+ "Ġlater": 92,
186
+ "Ġlook": 93,
187
+ "Ġlove": 94,
188
+ "Ġokay": 95,
189
+ "Ġsoon": 96,
190
+ "Ġwas": 97,
191
+ "Ġdoing": 98,
192
+ "Ġforward": 99,
193
+ "Ġidea": 100,
194
+ "Ġjust": 101,
195
+ "Ġmeet": 102,
196
+ "Ġnever": 103,
197
+ "Ġover": 104,
198
+ "Ġtomorrow": 105,
199
+ "Ġturn": 106,
200
+ "Ġwill": 107,
201
+ "Ġdid": 108,
202
+ "Ġdoes": 109,
203
+ "Ġfree": 110,
204
+ "Ġif": 111,
205
+ "Ġoff": 112,
206
+ "Ġtwo": 113,
207
+ "Ġwant": 114,
208
+ "Ġwater": 115,
209
+ "Ġway": 116,
210
+ "Ġbeen": 117,
211
+ "Ġcoffee": 118,
212
+ "Ġeverything": 119,
213
+ "Ġfrom": 120,
214
+ "Ġgive": 121,
215
+ "Ġjob": 122,
216
+ "Ġnew": 123,
217
+ "Ġnow": 124,
218
+ "Ġsure": 125,
219
+ "Ġtalk": 126,
220
+ "Ġweekend": 127,
221
+ "Ġwelcome": 128,
222
+ "Ġappreciate": 129,
223
+ "Ġbefore": 130,
224
+ "Ġbetter": 131,
225
+ "Ġbite": 132,
226
+ "Ġclose": 133,
227
+ "Ġcongratulations": 134,
228
+ "Ġcould": 135,
229
+ "Ġeat": 136,
230
+ "Ġfine": 137,
231
+ "Ġlong": 138,
232
+ "Ġlooking": 139,
233
+ "Ġmore": 140,
234
+ "Ġonce": 141,
235
+ "Ġrain": 142,
236
+ "Ġreally": 143,
237
+ "Ġsome": 144,
238
+ "Ġsounds": 145,
239
+ "Ġwait": 146,
240
+ "Ġagree": 147,
241
+ "Ġaround": 148,
242
+ "Ġbathroom": 149,
243
+ "Ġbreak": 150,
244
+ "Ġby": 151,
245
+ "Ġcheck": 152,
246
+ "Ġcoming": 153,
247
+ "Ġdoesn": 154,
248
+ "Ġdog": 155,
249
+ "Ġdoor": 156,
250
+ "Ġenjoy": 157,
251
+ "Ġeye": 158,
252
+ "Ġfire": 159,
253
+ "Ġhas": 160,
254
+ "Ġmakes": 161,
255
+ "Ġmean": 162,
256
+ "Ġmiss": 163,
257
+ "Ġput": 164,
258
+ "Ġquestion": 165,
259
+ "Ġsay": 166,
260
+ "Ġsend": 167,
261
+ "Ġstore": 168,
262
+ "Ġtell": 169,
263
+ "Ġus": 170,
264
+ "Ġany": 171,
265
+ "Ġanything": 172,
266
+ "Ġball": 173,
267
+ "Ġbed": 174,
268
+ "Ġbirthday": 175,
269
+ "Ġcold": 176,
270
+ "Ġcut": 177,
271
+ "Ġdown": 178,
272
+ "Ġeasy": 179,
273
+ "Ġevery": 180,
274
+ "Ġfall": 181,
275
+ "Ġfind": 182,
276
+ "Ġhi": 183,
277
+ "Ġits": 184,
278
+ "Ġkeys": 185,
279
+ "Ġlife": 186,
280
+ "Ġlight": 187,
281
+ "Ġlive": 188,
282
+ "Ġluck": 189,
283
+ "Ġman": 190,
284
+ "Ġmany": 191,
285
+ "Ġmay": 192,
286
+ "Ġmind": 193,
287
+ "Ġmoment": 194,
288
+ "Ġmoney": 195,
289
+ "Ġnext": 196,
290
+ "Ġnight": 197,
291
+ "Ġold": 198,
292
+ "Ġorder": 199,
293
+ "Ġphone": 200,
294
+ "Ġquick": 201,
295
+ "Ġsame": 202,
296
+ "Ġshould": 203,
297
+ "Ġsleep": 204,
298
+ "Ġspeak": 205,
299
+ "Ġtouch": 206,
300
+ "Ġvery": 207,
301
+ "Ġweather": 208,
302
+ "Ġworking": 209,
303
+ "Ġworth": 210,
304
+ "'d": 211,
305
+ "Ġagain": 212,
306
+ "Ġalways": 213,
307
+ "Ġanother": 214,
308
+ "Ġapologize": 215,
309
+ "Ġapple": 216,
310
+ "Ġask": 217,
311
+ "Ġattached": 218,
312
+ "Ġbad": 219,
313
+ "Ġbeautiful": 220,
314
+ "Ġbelieve": 221,
315
+ "Ġblind": 222,
316
+ "Ġburn": 223,
317
+ "Ġbusy": 224,
318
+ "Ġbut": 225,
319
+ "Ġcare": 226,
320
+ "Ġcat": 227,
321
+ "Ġcourse": 228,
322
+ "Ġcup": 229,
323
+ "Ġdevil": 230,
324
+ "Ġdinner": 231,
325
+ "Ġdoctor": 232,
326
+ "Ġemail": 233,
327
+ "Ġend": 234,
328
+ "Ġends": 235,
329
+ "Ġeveryone": 236,
330
+ "Ġexcuse": 237,
331
+ "Ġfair": 238,
332
+ "Ġfamily": 239,
333
+ "Ġfar": 240,
334
+ "Ġfeeling": 241,
335
+ "Ġfollow": 242,
336
+ "Ġforget": 243,
337
+ "Ġglad": 244,
338
+ "Ġglass": 245,
339
+ "Ġgold": 246,
340
+ "Ġhand": 247,
341
+ "Ġhard": 248,
342
+ "Ġhead": 249,
343
+ "Ġhear": 250,
344
+ "Ġhearing": 251,
345
+ "Ġheart": 252,
346
+ "Ġhello": 253,
347
+ "Ġhis": 254,
348
+ "Ġhit": 255,
349
+ "Ġhold": 256,
350
+ "Ġhorse": 257,
351
+ "Ġinconvenience": 258,
352
+ "Ġkill": 259,
353
+ "Ġkind": 260,
354
+ "Ġlost": 261,
355
+ "Ġmaybe": 262,
356
+ "Ġmile": 263,
357
+ "Ġminute": 264,
358
+ "Ġmorning": 265,
359
+ "Ġmouth": 266,
360
+ "Ġnews": 267,
361
+ "Ġnothing": 268,
362
+ "Ġopen": 269,
363
+ "Ġoutside": 270,
364
+ "Ġpatience": 271,
365
+ "Ġpay": 272,
366
+ "Ġpenny": 273,
367
+ "Ġpleasure": 274,
368
+ "Ġpossible": 275,
369
+ "Ġquestions": 276,
370
+ "Ġregards": 277,
371
+ "Ġrome": 278,
372
+ "Ġsafe": 279,
373
+ "Ġsaid": 280,
374
+ "Ġsense": 281,
375
+ "Ġshame": 282,
376
+ "Ġside": 283,
377
+ "Ġsomething": 284,
378
+ "Ġstation": 285,
379
+ "Ġtable": 286,
380
+ "Ġthings": 287,
381
+ "Ġtired": 288,
382
+ "Ġtogether": 289,
383
+ "Ġtonight": 290,
384
+ "Ġtrain": 291,
385
+ "Ġtwice": 292,
386
+ "Ġunderstand": 293,
387
+ "Ġupdate": 294,
388
+ "Ġwho": 295,
389
+ "Ġwhy": 296,
390
+ "Ġwithout": 297,
391
+ "Ġworry": 298,
392
+ "Ġwrong": 299,
393
+ "Ġyet": 300,
394
+ "Ġyourself": 301,
395
+ "!": 302,
396
+ "Ġahead": 303,
397
+ "Ġairport": 304,
398
+ "Ġamazing": 305,
399
+ "Ġasking": 306,
400
+ "Ġavailable": 307,
401
+ "Ġbark": 308,
402
+ "Ġbetween": 309,
403
+ "Ġbill": 310,
404
+ "Ġbird": 311,
405
+ "Ġbirds": 312,
406
+ "Ġbless": 313,
407
+ "Ġblue": 314,
408
+ "Ġboat": 315,
409
+ "Ġbook": 316,
410
+ "Ġboth": 317,
411
+ "Ġbridge": 318,
412
+ "Ġbucket": 319,
413
+ "Ġbush": 320,
414
+ "Ġbye": 321,
415
+ "Ġbygones": 322,
416
+ "Ġcake": 323,
417
+ "Ġcalling": 324,
418
+ "Ġcalm": 325,
419
+ "Ġcanceled": 326,
420
+ "Ġcats": 327,
421
+ "Ġcaught": 328,
422
+ "Ġchange": 329,
423
+ "Ġchase": 330,
424
+ "Ġcheers": 331,
425
+ "Ġchip": 332,
426
+ "Ġchristmas": 333,
427
+ "Ġcomes": 334,
428
+ "Ġcontact": 335,
429
+ "Ġcorner": 336,
430
+ "Ġcost": 337,
431
+ "Ġcry": 338,
432
+ "Ġdeal": 339,
433
+ "Ġdelay": 340,
434
+ "Ġdelicious": 341,
435
+ "Ġdeserve": 342,
436
+ "Ġdetails": 343,
437
+ "Ġdidn": 344,
438
+ "Ġdocument": 345,
439
+ "Ġdogs": 346,
440
+ "Ġdone": 347,
441
+ "Ġdreams": 348,
442
+ "Ġeggs": 349,
443
+ "Ġelse": 350,
444
+ "Ġenglish": 351,
445
+ "Ġenough": 352,
446
+ "Ġexcited": 353,
447
+ "Ġface": 354,
448
+ "Ġfault": 355,
449
+ "Ġfeather": 356,
450
+ "Ġfile": 357,
451
+ "Ġfirst": 358,
452
+ "Ġfly": 359,
453
+ "Ġfool": 360,
454
+ "Ġfox": 361,
455
+ "Ġfriend": 362,
456
+ "Ġfun": 363,
457
+ "Ġfunny": 364,
458
+ "Ġgets": 365,
459
+ "Ġgift": 366,
460
+ "Ġgod": 367,
461
+ "Ġgoes": 368,
462
+ "Ġgot": 369,
463
+ "Ġgrab": 370,
464
+ "Ġgrain": 371,
465
+ "Ġgrow": 372,
466
+ "Ġhang": 373,
467
+ "Ġhay": 374,
468
+ "Ġhey": 375,
469
+ "Ġhospital": 376,
470
+ "Ġhot": 377,
471
+ "Ġice": 378,
472
+ "Ġinto": 379,
473
+ "Ġisn": 380,
474
+ "Ġjoin": 381,
475
+ "Ġjump": 382,
476
+ "Ġkidding": 383,
477
+ "Ġlast": 384,
478
+ "Ġlearn": 385,
479
+ "Ġleft": 386,
480
+ "Ġleg": 387,
481
+ "Ġletting": 388,
482
+ "Ġlights": 389,
483
+ "Ġlot": 390,
484
+ "Ġlunch": 391,
485
+ "Ġmeans": 392,
486
+ "Ġmedicine": 393,
487
+ "Ġmerry": 394,
488
+ "Ġmonkey": 395,
489
+ "Ġmoon": 396,
490
+ "Ġmother": 397,
491
+ "Ġmove": 398,
492
+ "Ġmovie": 399,
493
+ "Ġmusic": 400,
494
+ "Ġname": 401,
495
+ "Ġnear": 402,
496
+ "Ġnine": 403,
497
+ "Ġoh": 404,
498
+ "Ġowe": 405,
499
+ "Ġown": 406,
500
+ "Ġpass": 407,
501
+ "Ġpen": 408,
502
+ "Ġpick": 409,
503
+ "Ġpie": 410,
504
+ "Ġplace": 411,
505
+ "Ġplay": 412,
506
+ "Ġpoint": 413,
507
+ "Ġpot": 414,
508
+ "Ġproblem": 415,
509
+ "Ġproud": 416,
510
+ "Ġpull": 417,
511
+ "Ġquiet": 418,
512
+ "Ġraining": 419,
513
+ "Ġreach": 420,
514
+ "Ġreaching": 421,
515
+ "Ġred": 422,
516
+ "Ġreservation": 423,
517
+ "Ġreview": 424,
518
+ "Ġrock": 425,
519
+ "Ġrolling": 426,
520
+ "Ġrule": 427,
521
+ "Ġsalt": 428,
522
+ "Ġschedule": 429,
523
+ "Ġscratch": 430,
524
+ "Ġseat": 431,
525
+ "Ġshort": 432,
526
+ "Ġshoulder": 433,
527
+ "Ġsight": 434,
528
+ "Ġsit": 435,
529
+ "Ġskin": 436,
530
+ "Ġsmoke": 437,
531
+ "Ġsomeone": 438,
532
+ "Ġspade": 439,
533
+ "Ġspoil": 440,
534
+ "Ġstone": 441,
535
+ "Ġstraight": 442,
536
+ "Ġsurprise": 443,
537
+ "Ġsweet": 444,
538
+ "Ġtakes": 445,
539
+ "Ġteam": 446,
540
+ "Ġtext": 447,
541
+ "Ġthey": 448,
542
+ "Ġthin": 449,
543
+ "Ġthing": 450,
544
+ "Ġthought": 451,
545
+ "Ġtough": 452,
546
+ "Ġtree": 453,
547
+ "Ġtrip": 454,
548
+ "Ġtrue": 455,
549
+ "Ġtry": 456,
550
+ "Ġunder": 457,
551
+ "Ġuse": 458,
552
+ "Ġwanted": 459,
553
+ "Ġwaste": 460,
554
+ "Ġwatch": 461,
555
+ "Ġwear": 462,
556
+ "Ġweek": 463,
557
+ "Ġwhile": 464,
558
+ "Ġwhole": 465,
559
+ "Ġwords": 466,
560
+ "Ġworks": 467,
561
+ "Ġworld": 468,
562
+ "Ġyear": 469,
563
+ "'ve": 470,
564
+ "-": 471,
565
+ "handed": 472,
566
+ "Ġabsence": 473,
567
+ "Ġabsolutely": 474,
568
+ "Ġaccept": 475,
569
+ "Ġactions": 476,
570
+ "Ġadd": 477,
571
+ "Ġadvance": 478,
572
+ "Ġadvocate": 479,
573
+ "Ġafraid": 480,
574
+ "Ġafter": 481,
575
+ "Ġafternoon": 482,
576
+ "Ġagainst": 483,
577
+ "Ġain": 484,
578
+ "Ġair": 485,
579
+ "Ġalike": 486,
580
+ "Ġalmost": 487,
581
+ "Ġam": 488,
582
+ "Ġambulance": 489,
583
+ "Ġamerican": 490,
584
+ "Ġanniversary": 491,
585
+ "Ġanymore": 492,
586
+ "Ġanyone": 493,
587
+ "Ġanytime": 494,
588
+ "Ġanyway": 495,
589
+ "Ġappetit": 496,
590
+ "Ġappointment": 497,
591
+ "Ġarm": 498,
592
+ "Ġattendance": 499,
593
+ "Ġavailability": 500,
594
+ "Ġaway": 501,
595
+ "Ġawesome": 502,
596
+ "Ġbaby": 503,
597
+ "Ġbackwards": 504,
598
+ "Ġbag": 505,
599
+ "Ġbandwagon": 506,
600
+ "Ġbank": 507,
601
+ "Ġbarking": 508,
602
+ "Ġbase": 509,
603
+ "Ġbasket": 510,
604
+ "Ġbat": 511,
605
+ "Ġbeans": 512,
606
+ "Ġbeat": 513,
607
+ "Ġbeaten": 514,
608
+ "Ġbeauty": 515,
609
+ "Ġbeaver": 516,
610
+ "Ġbee": 517,
611
+ "Ġbeggars": 518,
612
+ "Ġbegins": 519,
613
+ "Ġbeholder": 520,
614
+ "Ġbeing": 521,
615
+ "Ġbell": 522,
616
+ "Ġbend": 523,
617
+ "Ġbenefit": 524,
618
+ "Ġbeyond": 525,
619
+ "Ġbig": 526,
620
+ "Ġbitten": 527,
621
+ "Ġblack": 528,
622
+ "Ġblessing": 529,
623
+ "Ġbliss": 530,
624
+ "Ġblock": 531,
625
+ "Ġblood": 532,
626
+ "Ġblow": 533,
627
+ "Ġboard": 534,
628
+ "Ġboils": 535,
629
+ "Ġbold": 536,
630
+ "Ġbon": 537,
631
+ "Ġborrow": 538,
632
+ "Ġbother": 539,
633
+ "Ġbreakfast": 540,
634
+ "Ġbreaking": 541,
635
+ "Ġbreeds": 542,
636
+ "Ġbridges": 543,
637
+ "Ġbroke": 544,
638
+ "Ġbroth": 545,
639
+ "Ġbrown": 546,
640
+ "Ġbuck": 547,
641
+ "Ġbuilt": 548,
642
+ "Ġbull": 549,
643
+ "Ġbullet": 550,
644
+ "Ġbury": 551,
645
+ "Ġbus": 552,
646
+ "Ġbusiness": 553,
647
+ "Ġbutton": 554,
648
+ "Ġbuy": 555,
649
+ "Ġcaboodle": 556,
650
+ "Ġcandle": 557,
651
+ "Ġcard": 558,
652
+ "Ġcards": 559,
653
+ "Ġcareful": 560,
654
+ "Ġcart": 561,
655
+ "Ġcash": 562,
656
+ "Ġcatch": 563,
657
+ "Ġcatches": 564,
658
+ "Ġcause": 565,
659
+ "Ġcharity": 566,
660
+ "Ġchecking": 567,
661
+ "Ġcheek": 568,
662
+ "Ġcheer": 569,
663
+ "Ġchew": 570,
664
+ "Ġchicken": 571,
665
+ "Ġchickens": 572,
666
+ "Ġchild": 573,
667
+ "Ġchin": 574,
668
+ "Ġchips": 575,
669
+ "Ġchoosers": 576,
670
+ "Ġcigar": 577,
671
+ "Ġcircle": 578,
672
+ "Ġclam": 579,
673
+ "Ġclean": 580,
674
+ "Ġcleanliness": 581,
675
+ "Ġclear": 582,
676
+ "Ġclosed": 583,
677
+ "Ġcloser": 584,
678
+ "Ġcloses": 585,
679
+ "Ġcloset": 586,
680
+ "Ġclosing": 587,
681
+ "Ġcloud": 588,
682
+ "Ġclouds": 589,
683
+ "Ġcompany": 590,
684
+ "Ġcompletely": 591,
685
+ "Ġconfirm": 592,
686
+ "Ġconfusion": 593,
687
+ "Ġcontacting": 594,
688
+ "Ġcontempt": 595,
689
+ "Ġconvenience": 596,
690
+ "Ġconversation": 597,
691
+ "Ġcooks": 598,
692
+ "Ġcool": 599,
693
+ "Ġcorners": 600,
694
+ "Ġcouldn": 601,
695
+ "Ġcount": 602,
696
+ "Ġcounts": 603,
697
+ "Ġcourt": 604,
698
+ "Ġcover": 605,
699
+ "Ġcredit": 606,
700
+ "Ġcross": 607,
701
+ "Ġcrossed": 608,
702
+ "Ġcucumber": 609,
703
+ "Ġcure": 610,
704
+ "Ġcuriosity": 611,
705
+ "Ġcustomer": 612,
706
+ "Ġcute": 613,
707
+ "Ġdark": 614,
708
+ "Ġdate": 615,
709
+ "Ġdead": 616,
710
+ "Ġdeadline": 617,
711
+ "Ġdeep": 618,
712
+ "Ġdefinitely": 619,
713
+ "Ġdeserves": 620,
714
+ "Ġdiamond": 621,
715
+ "Ġdime": 622,
716
+ "Ġdisagree": 623,
717
+ "Ġdiscuss": 624,
718
+ "Ġdisguise": 625,
719
+ "Ġdivided": 626,
720
+ "Ġdoornail": 627,
721
+ "Ġdoubt": 628,
722
+ "Ġdozen": 629,
723
+ "Ġdraw": 630,
724
+ "Ġdrawing": 631,
725
+ "Ġdrink": 632,
726
+ "Ġdrive": 633,
727
+ "Ġdrop": 634,
728
+ "Ġdumps": 635,
729
+ "Ġdust": 636,
730
+ "Ġeach": 637,
731
+ "Ġeager": 638,
732
+ "Ġear": 639,
733
+ "Ġearliest": 640,
734
+ "Ġearly": 641,
735
+ "Ġearned": 642,
736
+ "Ġeasier": 643,
737
+ "Ġeaster": 644,
738
+ "Ġeaten": 645,
739
+ "Ġelephant": 646,
740
+ "Ġenemies": 647,
741
+ "Ġeven": 648,
742
+ "Ġevening": 649,
743
+ "Ġevil": 650,
744
+ "Ġexactly": 651,
745
+ "Ġexam": 652,
746
+ "Ġexception": 653,
747
+ "Ġexit": 654,
748
+ "Ġexpensive": 655,
749
+ "Ġextra": 656,
750
+ "Ġeyes": 657,
751
+ "Ġfamiliarity": 658,
752
+ "Ġfather": 659,
753
+ "Ġfavor": 660,
754
+ "Ġfavors": 661,
755
+ "Ġfeeds": 662,
756
+ "Ġfence": 663,
757
+ "Ġfiddle": 664,
758
+ "Ġfight": 665,
759
+ "Ġfinds": 666,
760
+ "Ġfingers": 667,
761
+ "Ġfinish": 668,
762
+ "Ġfish": 669,
763
+ "Ġfit": 670,
764
+ "Ġfits": 671,
765
+ "Ġfive": 672,
766
+ "Ġfix": 673,
767
+ "Ġflies": 674,
768
+ "Ġflight": 675,
769
+ "Ġflock": 676,
770
+ "Ġflow": 677,
771
+ "Ġfonder": 678,
772
+ "Ġfood": 679,
773
+ "Ġfoot": 680,
774
+ "Ġforgot": 681,
775
+ "Ġfortune": 682,
776
+ "Ġfriends": 683,
777
+ "Ġfrying": 684,
778
+ "Ġgain": 685,
779
+ "Ġgained": 686,
780
+ "Ġgathers": 687,
781
+ "Ġgentlemen": 688,
782
+ "Ġgetting": 689,
783
+ "Ġghost": 690,
784
+ "Ġgives": 691,
785
+ "Ġglitters": 692,
786
+ "Ġgodliness": 693,
787
+ "Ġgolden": 694,
788
+ "Ġgoodbye": 695,
789
+ "Ġgoose": 696,
790
+ "Ġgosh": 697,
791
+ "Ġgraduation": 698,
792
+ "Ġgrasping": 699,
793
+ "Ġgrass": 700,
794
+ "Ġgrateful": 701,
795
+ "Ġgrease": 702,
796
+ "Ġgreener": 703,
797
+ "Ġgun": 704,
798
+ "Ġgym": 705,
799
+ "Ġhad": 706,
800
+ "Ġhands": 707,
801
+ "Ġhappened": 708,
802
+ "Ġhappiness": 709,
803
+ "Ġhaste": 710,
804
+ "Ġhatch": 711,
805
+ "Ġhatchet": 712,
806
+ "Ġhatter": 713,
807
+ "Ġhaving": 714,
808
+ "Ġhaystack": 715,
809
+ "Ġhe": 716,
810
+ "Ġheadache": 717,
811
+ "Ġheads": 718,
812
+ "Ġheals": 719,
813
+ "Ġhealth": 720,
814
+ "Ġheat": 721,
815
+ "Ġheels": 722,
816
+ "Ġhell": 723,
817
+ "Ġhelping": 724,
818
+ "Ġherding": 725,
819
+ "Ġherring": 726,
820
+ "Ġhesitate": 727,
821
+ "Ġhilarious": 728,
822
+ "Ġhills": 729,
823
+ "Ġhim": 730,
824
+ "Ġhimself": 731,
825
+ "Ġhistory": 732,
826
+ "Ġholidays": 733,
827
+ "Ġhonesty": 734,
828
+ "Ġhook": 735,
829
+ "Ġhorns": 736,
830
+ "Ġhorses": 737,
831
+ "Ġhungry": 738,
832
+ "Ġhurry": 739,
833
+ "Ġhurts": 740,
834
+ "Ġiceberg": 741,
835
+ "Ġignorance": 742,
836
+ "Ġimportant": 743,
837
+ "Ġinch": 744,
838
+ "Ġincluded": 745,
839
+ "Ġindeed": 746,
840
+ "Ġinformation": 747,
841
+ "Ġinjury": 748,
842
+ "Ġinsult": 749,
843
+ "Ġintentions": 750,
844
+ "Ġinvention": 751,
845
+ "Ġinvitation": 752,
846
+ "Ġiron": 753,
847
+ "Ġisland": 754,
848
+ "Ġitself": 755,
849
+ "Ġjack": 756,
850
+ "Ġjudge": 757,
851
+ "Ġjumps": 758,
852
+ "Ġjustifies": 759,
853
+ "Ġkeeps": 760,
854
+ "Ġkettle": 761,
855
+ "Ġkick": 762,
856
+ "Ġkilled": 763,
857
+ "Ġkit": 764,
858
+ "Ġknocks": 765,
859
+ "Ġknot": 766,
860
+ "Ġknowledge": 767,
861
+ "Ġladies": 768,
862
+ "Ġlaughter": 769,
863
+ "Ġlazy": 770,
864
+ "Ġlead": 771,
865
+ "Ġleaf": 772,
866
+ "Ġleap": 773,
867
+ "Ġleave": 774,
868
+ "Ġleaving": 775,
869
+ "Ġlemonade": 776,
870
+ "Ġlemons": 777,
871
+ "Ġleopard": 778,
872
+ "Ġlie": 779,
873
+ "Ġliked": 780,
874
+ "Ġline": 781,
875
+ "Ġlines": 782,
876
+ "Ġlining": 783,
877
+ "Ġlistening": 784,
878
+ "Ġliving": 785,
879
+ "Ġlock": 786,
880
+ "Ġlol": 787,
881
+ "Ġlooks": 788,
882
+ "Ġlose": 789,
883
+ "Ġloss": 790,
884
+ "Ġlosses": 791,
885
+ "Ġlouder": 792,
886
+ "Ġloves": 793,
887
+ "Ġmad": 794,
888
+ "Ġmadness": 795,
889
+ "Ġmatter": 796,
890
+ "Ġmeal": 797,
891
+ "Ġmeasure": 798,
892
+ "Ġmention": 799,
893
+ "Ġmenu": 800,
894
+ "Ġmessage": 801,
895
+ "Ġmessenger": 802,
896
+ "Ġmethod": 803,
897
+ "Ġmidnight": 804,
898
+ "Ġmightier": 805,
899
+ "Ġmilk": 806,
900
+ "Ġminds": 807,
901
+ "Ġminutes": 808,
902
+ "Ġmisery": 809,
903
+ "Ġmolehill": 810,
904
+ "Ġmoss": 811,
905
+ "Ġmountain": 812,
906
+ "Ġmouse": 813,
907
+ "Ġmud": 814,
908
+ "Ġmule": 815,
909
+ "Ġmust": 816,
910
+ "Ġnail": 817,
911
+ "Ġnearest": 818,
912
+ "Ġnecessity": 819,
913
+ "Ġneedle": 820,
914
+ "Ġneither": 821,
915
+ "Ġnick": 822,
916
+ "Ġnoon": 823,
917
+ "Ġnose": 824,
918
+ "Ġnutshell": 825,
919
+ "Ġoffice": 826,
920
+ "Ġoil": 827,
921
+ "Ġomelet": 828,
922
+ "Ġopens": 829,
923
+ "Ġopportunity": 830,
924
+ "Ġor": 831,
925
+ "Ġother": 832,
926
+ "Ġounce": 833,
927
+ "Ġour": 834,
928
+ "Ġox": 835,
929
+ "Ġpage": 836,
930
+ "Ġpain": 837,
931
+ "Ġpale": 838,
932
+ "Ġpan": 839,
933
+ "Ġpar": 840,
934
+ "Ġparade": 841,
935
+ "Ġpardon": 842,
936
+ "Ġpassword": 843,
937
+ "Ġpath": 844,
938
+ "Ġpaved": 845,
939
+ "Ġpeace": 846,
940
+ "Ġpeople": 847,
941
+ "Ġperfect": 848,
942
+ "Ġpharmacy": 849,
943
+ "Ġpicture": 850,
944
+ "Ġpiece": 851,
945
+ "Ġpigs": 852,
946
+ "Ġpill": 853,
947
+ "Ġpizza": 854,
948
+ "Ġplain": 855,
949
+ "Ġplan": 856,
950
+ "Ġpleased": 857,
951
+ "Ġpolicy": 858,
952
+ "Ġposted": 859,
953
+ "Ġpound": 860,
954
+ "Ġpours": 861,
955
+ "Ġpower": 862,
956
+ "Ġpractice": 863,
957
+ "Ġprepare": 864,
958
+ "Ġpresent": 865,
959
+ "Ġpretty": 866,
960
+ "Ġprevention": 867,
961
+ "Ġpride": 868,
962
+ "Ġprobably": 869,
963
+ "Ġproof": 870,
964
+ "Ġproves": 871,
965
+ "Ġpudding": 872,
966
+ "Ġrace": 873,
967
+ "Ġrains": 874,
968
+ "Ġread": 875,
969
+ "Ġready": 876,
970
+ "Ġreap": 877,
971
+ "Ġreceive": 878,
972
+ "Ġrecommend": 879,
973
+ "Ġrelaxing": 880,
974
+ "Ġreminder": 881,
975
+ "Ġrepeat": 882,
976
+ "Ġrepeats": 883,
977
+ "Ġreply": 884,
978
+ "Ġreport": 885,
979
+ "Ġreschedule": 886,
980
+ "Ġresponse": 887,
981
+ "Ġrest": 888,
982
+ "Ġrestroom": 889,
983
+ "Ġreturns": 890,
984
+ "Ġride": 891,
985
+ "Ġrings": 892,
986
+ "Ġroad": 893,
987
+ "Ġroads": 894,
988
+ "Ġrod": 895,
989
+ "Ġromans": 896,
990
+ "Ġroom": 897,
991
+ "Ġroot": 898,
992
+ "Ġropes": 899,
993
+ "Ġrose": 900,
994
+ "Ġrough": 901,
995
+ "Ġrun": 902,
996
+ "Ġrunning": 903,
997
+ "Ġsack": 904,
998
+ "Ġsad": 905,
999
+ "Ġsafely": 906,
1000
+ "Ġsaved": 907,
1001
+ "Ġsaves": 908,
1002
+ "Ġsaying": 909,
1003
+ "Ġsecond": 910,
1004
+ "Ġseeing": 911,
1005
+ "Ġseen": 912,
1006
+ "Ġserious": 913,
1007
+ "Ġserved": 914,
1008
+ "Ġshine": 915,
1009
+ "Ġshines": 916,
1010
+ "Ġshipped": 917,
1011
+ "Ġshoe": 918,
1012
+ "Ġshoes": 919,
1013
+ "Ġshop": 920,
1014
+ "Ġshopping": 921,
1015
+ "Ġshot": 922,
1016
+ "Ġshow": 923,
1017
+ "Ġshy": 924,
1018
+ "Ġsick": 925,
1019
+ "Ġsilence": 926,
1020
+ "Ġsilly": 927,
1021
+ "Ġsilver": 928,
1022
+ "Ġsincerely": 929,
1023
+ "Ġskeletons": 930,
1024
+ "Ġslate": 931,
1025
+ "Ġsleeping": 932,
1026
+ "Ġsleeve": 933,
1027
+ "Ġslow": 934,
1028
+ "Ġslowly": 935,
1029
+ "Ġsly": 936,
1030
+ "Ġsmall": 937,
1031
+ "Ġsnowing": 938,
1032
+ "Ġsometime": 939,
1033
+ "Ġsore": 940,
1034
+ "Ġsow": 941,
1035
+ "Ġspare": 942,
1036
+ "Ġspell": 943,
1037
+ "Ġspice": 944,
1038
+ "Ġspill": 945,
1039
+ "Ġspilled": 946,
1040
+ "Ġspots": 947,
1041
+ "Ġsquare": 948,
1042
+ "Ġsqueaky": 949,
1043
+ "Ġstand": 950,
1044
+ "Ġstart": 951,
1045
+ "Ġstarted": 952,
1046
+ "Ġsteady": 953,
1047
+ "Ġsteal": 954,
1048
+ "Ġsteam": 955,
1049
+ "Ġstill": 956,
1050
+ "Ġstitch": 957,
1051
+ "Ġstop": 958,
1052
+ "Ġstorm": 959,
1053
+ "Ġstory": 960,
1054
+ "Ġstraw": 961,
1055
+ "Ġstraws": 962,
1056
+ "Ġstrike": 963,
1057
+ "Ġstrong": 964,
1058
+ "Ġstronger": 965,
1059
+ "Ġstubborn": 966,
1060
+ "Ġsuch": 967,
1061
+ "Ġsun": 968,
1062
+ "Ġswallow": 969,
1063
+ "Ġsword": 970,
1064
+ "Ġtaken": 971,
1065
+ "Ġtalking": 972,
1066
+ "Ġtango": 973,
1067
+ "Ġtaste": 974,
1068
+ "Ġtea": 975,
1069
+ "Ġteach": 976,
1070
+ "Ġteeth": 977,
1071
+ "Ġthanksgiving": 978,
1072
+ "Ġthem": 979,
1073
+ "Ġthen": 980,
1074
+ "Ġthick": 981,
1075
+ "Ġthicker": 982,
1076
+ "Ġthirsty": 983,
1077
+ "Ġthorn": 984,
1078
+ "Ġthose": 985,
1079
+ "Ġthoughts": 986,
1080
+ "Ġthousand": 987,
1081
+ "Ġthrough": 988,
1082
+ "Ġthrow": 989,
1083
+ "Ġthumb": 990,
1084
+ "Ġthumbs": 991,
1085
+ "Ġthunder": 992,
1086
+ "Ġtie": 993,
1087
+ "Ġtight": 994,
1088
+ "Ġtip": 995,
1089
+ "Ġtongue": 996,
1090
+ "Ġtotally": 997,
1091
+ "Ġtowel": 998,
1092
+ "Ġtrades": 999,
1093
+ "Ġtrash": 1000,
1094
+ "Ġtreasure": 1001,
1095
+ "Ġtrees": 1002,
1096
+ "Ġtricks": 1003,
1097
+ "Ġtrust": 1004,
1098
+ "Ġtruth": 1005,
1099
+ "Ġtunnel": 1006,
1100
+ "Ġumbrella": 1007,
1101
+ "Ġunderstanding": 1008,
1102
+ "Ġunited": 1009,
1103
+ "Ġuntil": 1010,
1104
+ "Ġupdated": 1011,
1105
+ "Ġupon": 1012,
1106
+ "Ġvacation": 1013,
1107
+ "Ġvalentine": 1014,
1108
+ "Ġvariety": 1015,
1109
+ "Ġventured": 1016,
1110
+ "Ġvirtue": 1017,
1111
+ "Ġwalk": 1018,
1112
+ "Ġwall": 1019,
1113
+ "Ġwar": 1020,
1114
+ "Ġwarm": 1021,
1115
+ "Ġwasn": 1022,
1116
+ "Ġwatched": 1023,
1117
+ "Ġwaters": 1024,
1118
+ "Ġwealth": 1025,
1119
+ "Ġwedding": 1026,
1120
+ "Ġweight": 1027,
1121
+ "Ġwheel": 1028,
1122
+ "Ġwhite": 1029,
1123
+ "Ġwicked": 1030,
1124
+ "Ġwild": 1031,
1125
+ "Ġwin": 1032,
1126
+ "Ġwindow": 1033,
1127
+ "Ġwins": 1034,
1128
+ "Ġwishes": 1035,
1129
+ "Ġwishing": 1036,
1130
+ "Ġwolf": 1037,
1131
+ "Ġwonderful": 1038,
1132
+ "Ġword": 1039,
1133
+ "Ġworlds": 1040,
1134
+ "Ġworm": 1041,
1135
+ "Ġworried": 1042,
1136
+ "Ġworries": 1043,
1137
+ "Ġworse": 1044,
1138
+ "Ġworst": 1045,
1139
+ "Ġwounds": 1046,
1140
+ "Ġwow": 1047,
1141
+ "Ġwriting": 1048,
1142
+ "Ġwrongs": 1049,
1143
+ "Ġyards": 1050,
1144
+ "Ġyes": 1051,
1145
+ "Ġyours": 1052
1146
+ },
1147
+ "unk_token": "<unk>"
1148
+ }
1149
+ }
tokenizer_config.json ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "backend": "tokenizers",
3
+ "bos_token": "<|endoftext|>",
4
+ "clean_up_tokenization_spaces": false,
5
+ "eos_token": "<|endoftext|>",
6
+ "model_max_length": 128,
7
+ "pad_token": "<|endoftext|>",
8
+ "tokenizer_class": "PreTrainedTokenizerFast",
9
+ "unk_token": "<unk>",
10
+ "add_prefix_space": true,
11
+ "model_input_names": [
12
+ "input_ids",
13
+ "attention_mask"
14
+ ]
15
+ }
ui.html ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>tiny-autocomplete</title>
7
+ <style>
8
+ :root { --bg: #fafaf9; --fg: #1c1917; --muted: #78716c; --line: #e7e5e4; --accent: #2563eb; --ghost: #a8a29e; --card: #fff; }
9
+ @media (prefers-color-scheme: dark) {
10
+ :root { --bg: #0c0a09; --fg: #f5f5f4; --muted: #a8a29e; --line: #292524; --accent: #60a5fa; --ghost: #57534e; --card: #1c1917; }
11
+ }
12
+ * { box-sizing: border-box; }
13
+ body { margin: 0; font: 16px/1.5 system-ui, -apple-system, sans-serif; background: var(--bg); color: var(--fg); }
14
+ main { max-width: 720px; margin: 0 auto; padding: 48px 20px; }
15
+ h1 { font-size: 24px; margin: 0 0 4px; }
16
+ .sub { color: var(--muted); margin: 0 0 28px; font-size: 14px; }
17
+ .editor { position: relative; background: var(--card); border: 1px solid var(--line); border-radius: 12px; }
18
+ .editor:focus-within { border-color: var(--accent); }
19
+ .layer { font: 20px/1.5 system-ui, -apple-system, sans-serif; padding: 16px 18px; margin: 0; white-space: pre-wrap; word-wrap: break-word; }
20
+ #ghost { position: absolute; inset: 0; pointer-events: none; color: transparent; }
21
+ #ghost span { color: var(--ghost); }
22
+ #prompt { position: relative; width: 100%; min-height: 64px; border: 0; outline: 0; resize: none; background: transparent; color: var(--fg); overflow: hidden; }
23
+ .hint { display: flex; justify-content: space-between; color: var(--muted); font-size: 13px; margin: 8px 2px 24px; }
24
+ kbd { border: 1px solid var(--line); border-radius: 4px; padding: 0 5px; font-size: 12px; background: var(--card); }
25
+ h2 { font-size: 13px; text-transform: uppercase; letter-spacing: .06em; color: var(--muted); margin: 0 0 10px; font-weight: 600; }
26
+ #next { display: grid; gap: 6px; margin-bottom: 28px; min-height: 40px; }
27
+ .cand { display: grid; grid-template-columns: 140px 1fr 56px; align-items: center; gap: 12px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 8px; background: var(--card); cursor: pointer; text-align: left; font: inherit; color: inherit; }
28
+ .cand:hover { border-color: var(--accent); }
29
+ .bar { height: 6px; border-radius: 3px; background: var(--line); overflow: hidden; }
30
+ .bar i { display: block; height: 100%; background: var(--accent); }
31
+ .pct { text-align: right; color: var(--muted); font-variant-numeric: tabular-nums; font-size: 14px; }
32
+ .tok { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; white-space: pre; overflow: hidden; text-overflow: ellipsis; }
33
+ .examples { display: flex; flex-wrap: wrap; gap: 8px; }
34
+ .ex { border: 1px solid var(--line); background: var(--card); color: inherit; border-radius: 999px; padding: 4px 12px; font: inherit; font-size: 14px; cursor: pointer; }
35
+ .ex:hover { border-color: var(--accent); }
36
+ .empty { color: var(--muted); font-size: 14px; }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <main>
41
+ <h1>tiny-autocomplete</h1>
42
+ <p class="sub" id="info">A compiled (not trained) Llama-format model, running in plain TypeScript.</p>
43
+
44
+ <div class="editor">
45
+ <pre class="layer" id="ghost" aria-hidden="true"></pre>
46
+ <textarea class="layer" id="prompt" rows="1" placeholder="Start typing, e.g. Once upon a" autofocus spellcheck="false"></textarea>
47
+ </div>
48
+ <div class="hint">
49
+ <span><kbd>Tab</kbd> accept completion &nbsp; <kbd>Enter</kbd> accept next word</span>
50
+ <span id="status"></span>
51
+ </div>
52
+
53
+ <h2>Next word</h2>
54
+ <div id="next"><div class="empty">Type a prompt to see predictions.</div></div>
55
+
56
+ <h2>Try</h2>
57
+ <div class="examples" id="examples"></div>
58
+ </main>
59
+ <script>
60
+ var EXAMPLES = ["Once upon a", "The quick brown", "Thank you for", "I hope this email", "Can I have the",
61
+ "Actions speak", "How are", "Looking forward to", "Where is the", "Don't worry"];
62
+ var promptEl = document.getElementById("prompt");
63
+ var ghostEl = document.getElementById("ghost");
64
+ var nextEl = document.getElementById("next");
65
+ var statusEl = document.getElementById("status");
66
+ var latest = { completion: "", top: [] };
67
+ var seq = 0, timer = null;
68
+
69
+ function esc(s) { return s.replace(/[&<>]/g, function (c) { return { "&": "&amp;", "<": "&lt;", ">": "&gt;" }[c]; }); }
70
+
71
+ function render() {
72
+ var text = promptEl.value;
73
+ ghostEl.innerHTML = esc(text) + "<span>" + esc(text ? latest.completion : "") + "</span>";
74
+ promptEl.style.height = "auto";
75
+ promptEl.style.height = Math.max(promptEl.scrollHeight, ghostEl.scrollHeight) + "px";
76
+ if (!text.trim() || !latest.top.length) {
77
+ nextEl.innerHTML = '<div class="empty">Type a prompt to see predictions.</div>';
78
+ return;
79
+ }
80
+ nextEl.innerHTML = "";
81
+ latest.top.forEach(function (t) {
82
+ var p = Math.exp(t.logprob) * 100;
83
+ var b = document.createElement("button");
84
+ b.className = "cand";
85
+ b.innerHTML = '<span class="tok"></span><span class="bar"><i style="width:' + p.toFixed(1) + '%"></i></span><span class="pct">' + p.toFixed(1) + "%</span>";
86
+ b.querySelector(".tok").textContent = t.id === 0 ? "<end>" : JSON.stringify(t.token);
87
+ b.onclick = function () { if (t.id !== 0) append(t.token); promptEl.focus(); };
88
+ nextEl.appendChild(b);
89
+ });
90
+ }
91
+
92
+ function append(s) { promptEl.value += s; latest = { completion: "", top: latest.top }; render(); schedule(0); }
93
+
94
+ function schedule(delay) {
95
+ clearTimeout(timer);
96
+ timer = setTimeout(run, delay);
97
+ }
98
+
99
+ function run() {
100
+ var id = ++seq, text = promptEl.value;
101
+ if (!text.trim()) { latest = { completion: "", top: [] }; statusEl.textContent = ""; render(); return; }
102
+ statusEl.textContent = "thinking...";
103
+ fetch("/api/complete", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ prompt: text, topK: 5 }) })
104
+ .then(function (r) { return r.json(); })
105
+ .then(function (r) {
106
+ if (id !== seq || promptEl.value !== text) return; // stale
107
+ if (r.error) { statusEl.textContent = r.error; return; }
108
+ latest = r;
109
+ statusEl.textContent = r.ms + " ms";
110
+ render();
111
+ })
112
+ .catch(function (e) { statusEl.textContent = String(e); });
113
+ }
114
+
115
+ promptEl.addEventListener("input", function () { latest = { completion: "", top: [] }; render(); schedule(200); });
116
+ promptEl.addEventListener("keydown", function (e) {
117
+ if (e.key === "Tab" && latest.completion) { e.preventDefault(); append(latest.completion); }
118
+ else if (e.key === "Enter") {
119
+ e.preventDefault();
120
+ var t = latest.top[0];
121
+ if (t && t.id !== 0) append(t.token);
122
+ }
123
+ });
124
+
125
+ EXAMPLES.forEach(function (ex) {
126
+ var b = document.createElement("button");
127
+ b.className = "ex"; b.textContent = ex;
128
+ b.onclick = function () { promptEl.value = ex; latest = { completion: "", top: [] }; render(); schedule(0); promptEl.focus(); };
129
+ document.getElementById("examples").appendChild(b);
130
+ });
131
+
132
+ fetch("/api/info").then(function (r) { return r.json(); }).then(function (i) {
133
+ document.getElementById("info").textContent = "A compiled (not trained) Llama-format model: " + i.params.toLocaleString() +
134
+ " parameters, " + i.vocab.toLocaleString() + " words, " + i.neurons.toLocaleString() + " n-gram neurons. Running in plain TypeScript.";
135
+ });
136
+ render();
137
+ </script>
138
+ </body>
139
+ </html>
ui.ts ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Browser UI for trying prompts (zero dependencies).
2
+ //
3
+ // bun ui.ts then open http://localhost:3000
4
+ // PORT=8080 node ui.ts
5
+ //
6
+ // Serves ui.html and a JSON endpoint backed by the plain-TypeScript model in model.ts.
7
+
8
+ import { readFileSync } from "node:fs";
9
+ import { createServer } from "node:http";
10
+ import { join } from "node:path";
11
+ import { Autocompleter } from "./model.ts";
12
+
13
+ const DIR = import.meta.dirname ?? new URL(".", import.meta.url).pathname;
14
+ const PORT = Number(process.env.PORT ?? 3000);
15
+
16
+ console.log("Loading model...");
17
+ const ac = new Autocompleter(DIR);
18
+ const html = readFileSync(join(DIR, "ui.html"));
19
+ const cfg = ac.model.cfg;
20
+ const info = {
21
+ params: ac.model.numParams,
22
+ vocab: cfg.vocab_size,
23
+ layers: cfg.num_hidden_layers,
24
+ hidden: cfg.hidden_size,
25
+ neurons: cfg.intermediate_size,
26
+ };
27
+
28
+ function json(res: import("node:http").ServerResponse, status: number, body: unknown): void {
29
+ res.writeHead(status, { "content-type": "application/json" });
30
+ res.end(JSON.stringify(body));
31
+ }
32
+
33
+ createServer((req, res) => {
34
+ if (req.method === "GET" && (req.url === "/" || req.url === "/index.html")) {
35
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
36
+ res.end(html);
37
+ } else if (req.method === "GET" && req.url === "/api/info") {
38
+ json(res, 200, info);
39
+ } else if (req.method === "POST" && req.url === "/api/complete") {
40
+ let body = "";
41
+ req.on("data", (chunk) => (body += chunk));
42
+ req.on("end", () => {
43
+ try {
44
+ const { prompt, topK } = JSON.parse(body || "{}");
45
+ if (typeof prompt !== "string" || prompt.length > 1000) return json(res, 400, { error: "prompt must be a string under 1000 chars" });
46
+ const t0 = performance.now();
47
+ const result = prompt.trim() ? ac.complete(prompt, Math.min(Number(topK) || 5, 20)) : { prompt, completion: "", top: [] };
48
+ json(res, 200, { ...result, ms: Math.round(performance.now() - t0) });
49
+ } catch (e) {
50
+ json(res, 400, { error: String(e) });
51
+ }
52
+ });
53
+ } else {
54
+ res.writeHead(404).end();
55
+ }
56
+ }).listen(PORT, "127.0.0.1", () => console.log(`tiny-autocomplete UI: http://localhost:${PORT}`));