Text Generation
Transformers
Safetensors
English
llama
hand-crafted
compiled
autocomplete
no-training
n-gram
code
browser
text-generation-inference
Instructions to use tmickleydoyle/tiny-autocomplete with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use tmickleydoyle/tiny-autocomplete with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="tmickleydoyle/tiny-autocomplete")# pip install -U transformers accelerate # Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("tmickleydoyle/tiny-autocomplete") model = AutoModelForCausalLM.from_pretrained("tmickleydoyle/tiny-autocomplete", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use tmickleydoyle/tiny-autocomplete with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "tmickleydoyle/tiny-autocomplete" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "tmickleydoyle/tiny-autocomplete", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/tmickleydoyle/tiny-autocomplete
- SGLang
How to use tmickleydoyle/tiny-autocomplete with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "tmickleydoyle/tiny-autocomplete" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "tmickleydoyle/tiny-autocomplete", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "tmickleydoyle/tiny-autocomplete" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "tmickleydoyle/tiny-autocomplete", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use tmickleydoyle/tiny-autocomplete with Docker Model Runner:
docker model run hf.co/tmickleydoyle/tiny-autocomplete
Add performance benchmarks: 6.2 s CPU compile, speed, held-out accuracy
Browse files- README.md +57 -0
- docs/benchmarks.json +47 -0
- scripts/benchmark.py +193 -0
README.md
CHANGED
|
@@ -49,6 +49,61 @@ Full construction details are in [docs/how-it-works.md](docs/how-it-works.md).
|
|
| 49 |
The last rows show its main weakness. It sees only 4 tokens of context, so longer
|
| 50 |
completions can drift between topics.
|
| 51 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
## Use it with transformers
|
| 53 |
|
| 54 |
```python
|
|
@@ -161,6 +216,7 @@ pip install -r requirements.txt
|
|
| 161 |
python scripts/fetch_coding_prompts.py # optional: re-download data/coding_prompts.txt
|
| 162 |
python build_weights.py # writes config, weights and tokenizer
|
| 163 |
python test_model.py # HF loading, n-gram exactness, pipeline, TypeScript parity
|
|
|
|
| 164 |
```
|
| 165 |
|
| 166 |
Tested with transformers 4.52.4 and 5.22.1, Bun 1.3 and Node 24.
|
|
@@ -180,6 +236,7 @@ excerpts from OpenAssistant oasst2 (Apache-2.0), MBPP (CC-BY-4.0) and CoNaLa (MI
|
|
| 180 |
| `build_weights.py` | the compiler that writes every file above |
|
| 181 |
| `phrases/*.txt`, `data/coding_prompts.txt` | source text |
|
| 182 |
| `scripts/fetch_coding_prompts.py`, `data/SOURCES.md` | data download and attribution |
|
|
|
|
| 183 |
| `test_model.py` | verification suite |
|
| 184 |
| `model.ts`, `autocomplete.ts`, `ui.ts`, `ui.html` | zero-dependency TypeScript runtime, CLI and web UI |
|
| 185 |
| `docs/how-it-works.md` | full construction details |
|
|
|
|
| 49 |
The last rows show its main weakness. It sees only 4 tokens of context, so longer
|
| 50 |
completions can drift between topics.
|
| 51 |
|
| 52 |
+
## Performance: why compile a model instead of training one
|
| 53 |
+
|
| 54 |
+
Measured with `python scripts/benchmark.py` on a 10-core Apple Silicon laptop (32 GB RAM).
|
| 55 |
+
CPU only, no GPU. Raw numbers are in [`docs/benchmarks.json`](docs/benchmarks.json).
|
| 56 |
+
|
| 57 |
+
**Building it**
|
| 58 |
+
|
| 59 |
+
| | |
|
| 60 |
+
|---|---|
|
| 61 |
+
| Compile all 380,290,320 weights from text | **6.2 s** (tokenize 0.5 s, build tensors 2.7 s, write 1.5 GB) |
|
| 62 |
+
| Text in, model out, from scratch | about 10 s to download the data + 6 s to compile |
|
| 63 |
+
| Peak memory while compiling | 2.3 GB |
|
| 64 |
+
| GPU hours / compute cost | **0 / $0** |
|
| 65 |
+
| Reproducible | two rebuilds give a byte-identical `model.safetensors` (sha256 `93c23040…`) |
|
| 66 |
+
| Exact | matches its reference n-gram to within 2.5e-5 nats |
|
| 67 |
+
|
| 68 |
+
For scale: the sibling experiment [tiny-research](https://tiny-research.vercel.app/)
|
| 69 |
+
trained a 34M model with a 5-minute budget on an RTX 4090. Compiling this 380M model takes
|
| 70 |
+
6 seconds on a laptop CPU.
|
| 71 |
+
|
| 72 |
+
**Using it**
|
| 73 |
+
|
| 74 |
+
| | |
|
| 75 |
+
|---|---|
|
| 76 |
+
| PyTorch (CPU, 8 threads) | 70 ms per next word; 16 tokens/s greedy generation; 0.3 s to load |
|
| 77 |
+
| Plain TypeScript (Bun, no ML library) | 0.44 s per token; 0.4 s to load |
|
| 78 |
+
|
| 79 |
+
**How good is it on text it has never seen?** The benchmark compiles the model without a
|
| 80 |
+
fixed 10% of the coding sentences (2,040 lines, 35,039 tokens), then scores each next token:
|
| 81 |
+
|
| 82 |
+
| | |
|
| 83 |
+
|---|---|
|
| 84 |
+
| Top-1 next-token accuracy | **25.8%** |
|
| 85 |
+
| Top-3 next-token accuracy | **39.6%** |
|
| 86 |
+
| Next token outside the 3,000-word vocabulary | 8.8% (always counted as a miss) |
|
| 87 |
+
|
| 88 |
+
On the source phrases themselves it completes almost every line exactly.
|
| 89 |
+
|
| 90 |
+
**Why build a model like this**
|
| 91 |
+
|
| 92 |
+
- **Iteration in seconds:** edit a `.txt` file, rebuild, and test in under 10 s. There is no
|
| 93 |
+
training run, no hyperparameter tuning, and no GPU.
|
| 94 |
+
- **Auditable:** every MLP neuron is one context from the source text, so you can trace any
|
| 95 |
+
prediction back to the phrases that caused it.
|
| 96 |
+
- **Editable:** to remove a behavior, delete the line and recompile. Nothing has to be
|
| 97 |
+
fine-tuned away.
|
| 98 |
+
- **Deterministic and exact:** the same text always gives the same bytes, and the network
|
| 99 |
+
computes a known formula.
|
| 100 |
+
- **Standard format:** it's a plain `LlamaForCausalLM`, so it works with `transformers`,
|
| 101 |
+
`pipeline`, and any runtime that loads Llama safetensors.
|
| 102 |
+
|
| 103 |
+
**What it costs you:** it doesn't generalize like a trained model, and only 7% of the weights
|
| 104 |
+
are non-zero (26.8M of 380M), so the dense file is mostly empty space. A sparse runtime could
|
| 105 |
+
be 10× smaller and faster.
|
| 106 |
+
|
| 107 |
## Use it with transformers
|
| 108 |
|
| 109 |
```python
|
|
|
|
| 216 |
python scripts/fetch_coding_prompts.py # optional: re-download data/coding_prompts.txt
|
| 217 |
python build_weights.py # writes config, weights and tokenizer
|
| 218 |
python test_model.py # HF loading, n-gram exactness, pipeline, TypeScript parity
|
| 219 |
+
python scripts/benchmark.py # compile time, speed, held-out accuracy -> docs/benchmarks.json
|
| 220 |
```
|
| 221 |
|
| 222 |
Tested with transformers 4.52.4 and 5.22.1, Bun 1.3 and Node 24.
|
|
|
|
| 236 |
| `build_weights.py` | the compiler that writes every file above |
|
| 237 |
| `phrases/*.txt`, `data/coding_prompts.txt` | source text |
|
| 238 |
| `scripts/fetch_coding_prompts.py`, `data/SOURCES.md` | data download and attribution |
|
| 239 |
+
| `scripts/benchmark.py`, `docs/benchmarks.json` | performance measurements |
|
| 240 |
| `test_model.py` | verification suite |
|
| 241 |
| `model.ts`, `autocomplete.ts`, `ui.ts`, `ui.html` | zero-dependency TypeScript runtime, CLI and web UI |
|
| 242 |
| `docs/how-it-works.md` | full construction details |
|
docs/benchmarks.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"hardware": {
|
| 3 |
+
"cpu": "Darwin arm64",
|
| 4 |
+
"cores": 10,
|
| 5 |
+
"ram_gb": 32,
|
| 6 |
+
"gpu": "none used",
|
| 7 |
+
"python": "3.13.4",
|
| 8 |
+
"torch": "2.15.0"
|
| 9 |
+
},
|
| 10 |
+
"compile": {
|
| 11 |
+
"compile_seconds": 6.2,
|
| 12 |
+
"compile_runs": [
|
| 13 |
+
6.4,
|
| 14 |
+
6.2
|
| 15 |
+
],
|
| 16 |
+
"peak_memory_gb": 2.3,
|
| 17 |
+
"byte_identical_rebuild": true,
|
| 18 |
+
"model_sha256": "93c23040ccf6393710b0ee1d2fff98d57dc4193a27c4911cac7374e2c28f50f6",
|
| 19 |
+
"file_gb": 1.52,
|
| 20 |
+
"tokenize_seconds": 0.49,
|
| 21 |
+
"build_tensors_seconds": 2.74,
|
| 22 |
+
"source_lines": 21106,
|
| 23 |
+
"params": 380290320,
|
| 24 |
+
"nonzero_params": 26792230,
|
| 25 |
+
"contexts_seen": 434284,
|
| 26 |
+
"neurons": 24774
|
| 27 |
+
},
|
| 28 |
+
"pytorch": {
|
| 29 |
+
"load_seconds": 0.3,
|
| 30 |
+
"next_word_ms": 70.3,
|
| 31 |
+
"generate_tokens_per_second": 16.4,
|
| 32 |
+
"threads": 8
|
| 33 |
+
},
|
| 34 |
+
"typescript": {
|
| 35 |
+
"runtime": "bun, plain TypeScript, no ML library",
|
| 36 |
+
"load_seconds": 0.4,
|
| 37 |
+
"seconds_per_token": 0.44
|
| 38 |
+
},
|
| 39 |
+
"max_logprob_error_vs_ngram": 2.4948840973593178e-05,
|
| 40 |
+
"heldout": {
|
| 41 |
+
"heldout_lines": 2040,
|
| 42 |
+
"heldout_tokens": 35039,
|
| 43 |
+
"next_token_top1": 0.258,
|
| 44 |
+
"next_token_top3": 0.396,
|
| 45 |
+
"oov_rate": 0.088
|
| 46 |
+
}
|
| 47 |
+
}
|
scripts/benchmark.py
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Measure compile cost, inference speed, exactness and held-out accuracy.
|
| 2 |
+
|
| 3 |
+
Writes docs/benchmarks.json. Run from the repo root after build_weights.py:
|
| 4 |
+
python scripts/benchmark.py
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import hashlib
|
| 8 |
+
import json
|
| 9 |
+
import os
|
| 10 |
+
import platform
|
| 11 |
+
import random
|
| 12 |
+
import resource
|
| 13 |
+
import shutil
|
| 14 |
+
import subprocess
|
| 15 |
+
import sys
|
| 16 |
+
import time
|
| 17 |
+
import zlib
|
| 18 |
+
from pathlib import Path
|
| 19 |
+
|
| 20 |
+
import torch
|
| 21 |
+
|
| 22 |
+
ROOT = Path(__file__).resolve().parent.parent
|
| 23 |
+
sys.path.insert(0, str(ROOT))
|
| 24 |
+
import build_weights as bw # noqa: E402
|
| 25 |
+
|
| 26 |
+
PROMPTS = [
|
| 27 |
+
"Once upon a", "I hope this email", "The quick brown", "Thank you for", "Looking forward to",
|
| 28 |
+
"In Python,", "How to convert a", "Write a python function to", "You can use the", "Here is an example of",
|
| 29 |
+
]
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def sha256(path: Path) -> str:
|
| 33 |
+
h = hashlib.sha256()
|
| 34 |
+
with open(path, "rb") as f:
|
| 35 |
+
for chunk in iter(lambda: f.read(1 << 24), b""):
|
| 36 |
+
h.update(chunk)
|
| 37 |
+
return h.hexdigest()
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def hardware() -> dict:
|
| 41 |
+
cpu = ""
|
| 42 |
+
if sys.platform == "darwin":
|
| 43 |
+
cpu = subprocess.run(["sysctl", "-n", "machdep.cpu.brand_string"], capture_output=True, text=True).stdout.strip()
|
| 44 |
+
cpu = cpu or f"{platform.system()} {platform.machine()}"
|
| 45 |
+
try:
|
| 46 |
+
ram = os.sysconf("SC_PAGE_SIZE") * os.sysconf("SC_PHYS_PAGES")
|
| 47 |
+
except (ValueError, OSError):
|
| 48 |
+
ram = 0
|
| 49 |
+
return {"cpu": cpu, "cores": os.cpu_count(), "ram_gb": round(ram / 2**30), "gpu": "none used",
|
| 50 |
+
"python": platform.python_version(), "torch": torch.__version__}
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def compile_runs(n: int = 2) -> dict:
|
| 54 |
+
"""Full `python build_weights.py` runs: wall time, peak memory, determinism."""
|
| 55 |
+
times, hashes = [], []
|
| 56 |
+
for _ in range(n):
|
| 57 |
+
t0 = time.perf_counter()
|
| 58 |
+
subprocess.run([sys.executable, "build_weights.py"], cwd=ROOT, check=True, capture_output=True)
|
| 59 |
+
times.append(time.perf_counter() - t0)
|
| 60 |
+
hashes.append(sha256(ROOT / "model.safetensors"))
|
| 61 |
+
peak = resource.getrusage(resource.RUSAGE_CHILDREN).ru_maxrss
|
| 62 |
+
peak_gb = peak / 2**30 if sys.platform == "darwin" else peak / 2**20
|
| 63 |
+
return {
|
| 64 |
+
"compile_seconds": round(min(times), 1),
|
| 65 |
+
"compile_runs": [round(t, 1) for t in times],
|
| 66 |
+
"peak_memory_gb": round(peak_gb, 1),
|
| 67 |
+
"byte_identical_rebuild": len(set(hashes)) == 1,
|
| 68 |
+
"model_sha256": hashes[0],
|
| 69 |
+
"file_gb": round((ROOT / "model.safetensors").stat().st_size / 1e9, 2),
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def compile_stages() -> dict:
|
| 74 |
+
t0 = time.perf_counter()
|
| 75 |
+
phrases, data = bw.load_phrases(), bw.load_data()
|
| 76 |
+
t1 = time.perf_counter()
|
| 77 |
+
_, ngram, state, _, info = bw.build(phrases, data)
|
| 78 |
+
t2 = time.perf_counter()
|
| 79 |
+
nonzero = sum(int((t != 0).sum()) for t in state.values())
|
| 80 |
+
return {
|
| 81 |
+
"tokenize_seconds": round(t1 - t0, 2),
|
| 82 |
+
"build_tensors_seconds": round(t2 - t1, 2),
|
| 83 |
+
"source_lines": len(phrases) + len(data),
|
| 84 |
+
"params": info["params"],
|
| 85 |
+
"nonzero_params": nonzero,
|
| 86 |
+
"contexts_seen": info["contexts_seen"],
|
| 87 |
+
"neurons": info["neurons"],
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def torch_inference() -> dict:
|
| 92 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 93 |
+
|
| 94 |
+
t0 = time.perf_counter()
|
| 95 |
+
tok = AutoTokenizer.from_pretrained(ROOT)
|
| 96 |
+
model = AutoModelForCausalLM.from_pretrained(ROOT).eval()
|
| 97 |
+
load = time.perf_counter() - t0
|
| 98 |
+
|
| 99 |
+
with torch.no_grad():
|
| 100 |
+
ids = tok("Write a python function to", return_tensors="pt")
|
| 101 |
+
model(**ids) # warm-up
|
| 102 |
+
t0 = time.perf_counter()
|
| 103 |
+
for _ in range(20):
|
| 104 |
+
model(**ids)
|
| 105 |
+
forward_ms = (time.perf_counter() - t0) / 20 * 1000
|
| 106 |
+
|
| 107 |
+
new_tokens, t0 = 0, time.perf_counter()
|
| 108 |
+
for p in PROMPTS:
|
| 109 |
+
ids = tok(p, return_tensors="pt")
|
| 110 |
+
out = model.generate(**ids, max_new_tokens=16)
|
| 111 |
+
new_tokens += out.shape[1] - ids["input_ids"].shape[1]
|
| 112 |
+
gen = time.perf_counter() - t0
|
| 113 |
+
return {
|
| 114 |
+
"load_seconds": round(load, 1),
|
| 115 |
+
"next_word_ms": round(forward_ms, 1),
|
| 116 |
+
"generate_tokens_per_second": round(new_tokens / gen, 1),
|
| 117 |
+
"threads": torch.get_num_threads(),
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def ts_inference() -> dict | None:
|
| 122 |
+
bun = shutil.which("bun")
|
| 123 |
+
if not bun:
|
| 124 |
+
return None
|
| 125 |
+
t0 = time.perf_counter()
|
| 126 |
+
subprocess.run([bun, "autocomplete.ts", "--json"], cwd=ROOT, check=True, capture_output=True)
|
| 127 |
+
load = time.perf_counter() - t0
|
| 128 |
+
t0 = time.perf_counter()
|
| 129 |
+
out = subprocess.run([bun, "autocomplete.ts", "--json", *PROMPTS], cwd=ROOT, check=True, capture_output=True, text=True)
|
| 130 |
+
total = time.perf_counter() - t0 - load
|
| 131 |
+
words = sum(len(bw.pieces(r["completion"])) + 1 for r in json.loads(out.stdout)) # +1: the final EOS step
|
| 132 |
+
return {"runtime": "bun, plain TypeScript, no ML library", "load_seconds": round(load, 1), "seconds_per_token": round(total / words, 2)}
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def exactness() -> float:
|
| 136 |
+
from transformers import AutoModelForCausalLM
|
| 137 |
+
|
| 138 |
+
_, vocab, ngram, _, _, _ = bw.compile_model()
|
| 139 |
+
model = AutoModelForCausalLM.from_pretrained(ROOT).eval()
|
| 140 |
+
rng, worst = random.Random(0), 0.0
|
| 141 |
+
with torch.no_grad():
|
| 142 |
+
for _ in range(50):
|
| 143 |
+
ids = [0] + [rng.randrange(2, len(vocab)) for _ in range(rng.randint(1, 80))]
|
| 144 |
+
got = model(torch.tensor([ids])).logits[0, -1].double().log_softmax(-1)
|
| 145 |
+
worst = max(worst, (got - ngram.reference_logits(ids).log_softmax(-1)).abs().max().item())
|
| 146 |
+
return worst
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
def heldout(frac: float = 0.1) -> dict:
|
| 150 |
+
"""Compile without 10% of the data lines, then score next-word prediction on them."""
|
| 151 |
+
phrases, data = bw.load_phrases(), bw.load_data()
|
| 152 |
+
test = [t for t in data if zlib.crc32(" ".join(t).encode()) % 100 < frac * 100]
|
| 153 |
+
train = [t for t in data if zlib.crc32(" ".join(t).encode()) % 100 >= frac * 100]
|
| 154 |
+
vocab, ngram, _, _, _ = bw.build(phrases, train)
|
| 155 |
+
index = {p: i for i, p in enumerate(vocab)}
|
| 156 |
+
top1 = top3 = total = oov = 0
|
| 157 |
+
for toks in test:
|
| 158 |
+
ids = [0] + [index.get(p, 1) for p in toks] + [0]
|
| 159 |
+
for t in range(len(ids) - 1):
|
| 160 |
+
target = ids[t + 1]
|
| 161 |
+
total += 1
|
| 162 |
+
if target == 1:
|
| 163 |
+
oov += 1
|
| 164 |
+
continue
|
| 165 |
+
ranked = ngram.reference_logits(ids[: t + 1]).topk(3).indices.tolist()
|
| 166 |
+
top1 += ranked[0] == target
|
| 167 |
+
top3 += target in ranked
|
| 168 |
+
return {
|
| 169 |
+
"heldout_lines": len(test),
|
| 170 |
+
"heldout_tokens": total,
|
| 171 |
+
"next_token_top1": round(top1 / total, 3),
|
| 172 |
+
"next_token_top3": round(top3 / total, 3),
|
| 173 |
+
"oov_rate": round(oov / total, 3),
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
|
| 177 |
+
def main() -> None:
|
| 178 |
+
results = {"hardware": hardware()}
|
| 179 |
+
print("compiling (2 full runs)...", flush=True)
|
| 180 |
+
results["compile"] = {**compile_runs(), **compile_stages()}
|
| 181 |
+
print("inference...", flush=True)
|
| 182 |
+
results["pytorch"] = torch_inference()
|
| 183 |
+
results["typescript"] = ts_inference()
|
| 184 |
+
results["max_logprob_error_vs_ngram"] = exactness()
|
| 185 |
+
print("held-out accuracy...", flush=True)
|
| 186 |
+
results["heldout"] = heldout()
|
| 187 |
+
out = ROOT / "docs" / "benchmarks.json"
|
| 188 |
+
out.write_text(json.dumps(results, indent=2) + "\n")
|
| 189 |
+
print(json.dumps(results, indent=2))
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
if __name__ == "__main__":
|
| 193 |
+
main()
|