TONER-1
State of the art among language models that run entirely inside a print job.
TONER-1 runs on hardware already in your copy room. It uses 0 host FLOPs, ships as a single file, and delivers every output fused to paper. It does not take prompts. It has a context window of 8.5 × 11 inches. It writes about printers.
Everything below is literal, except the section marked PARODY.
What it is
TONER-1 is Andrej Karpathy's llama2.c stories260K (260,032 parameters), fine-tuned by us on 8,757
short children's stories about printers, then exported to int8 with one scale per row, plus a forward
pass written in PostScript. The whole model is one print job,
TONER-1_print-job.ps (595,372 bytes of plain text): the weights, the
512-piece vocabulary, about 100 lines of inference code, and the page layout. Sent raw to a PostScript
printer, it makes the printer's own CPU write a children's story of about 200 tokens, one token at a
time, and then typeset it on a US Letter page. The computer that sends the file only sends bytes.
The float32 weights are here too, as model.safetensors, for use in Python.
It is a working print job and a parody of frontier-model launches. The stories are real, unedited model output. The benchmarks are not real evaluations.
Its bigger sibling, TONER-1 Pro, is our own 12.2M model with our own tokenizer.
Status
Printed on an HP Color LaserJet Pro MFP M281fdw on 2026-11-05: 200 tokens in 3:37 by the printer's own clock (0.92 tok/s), 3:39 from send to finished job.
A language model that ran on an actual printer: one print job, zero host FLOPs.
What the printer itself sent back over the network while the job ran: its own output and its own timer. These are the first and last lines; the story in between is left out here.
TONER-1 | 260032 params | int8 | PostScript-native inference
200 tokens in 217.235 s = 0.920662 tok/s [STEPS limit]
The combined TONER-1-V job printed the same day: the story in 3:38 and the doodle in 25 s by the printer's own clock, 4:04 from send to finished job. See TONER-1-V.
Full logs, with every byte the printer sent back and when it arrived: docs/printer-run-2026-11-05 in the GitHub repo.
Sample output
A Ghostscript 10.07.1 render of samples/story_seed20_t0.8.pdf
(seed 20, temperature 0.8), not a photo of a printed page. The page is the print job's own output: the
story, all 200 sampled tokens, and a run card with the interpreter's name and its timing (82.3 tok/s).
The story begins: "Once upon a time, there was a little girl named Lucy. She lived in a small house."
A few sentences later: "Then, a little boy named Tim came to the printer."
How to run it
Preview in Ghostscript (no printer needed)
gs -q -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dSEED=7 -dTEMP=0.9 -o story.pdf TONER-1_print-job.ps
This takes a few seconds on an Apple M4 Mac and writes a one-page PDF. The story text and the timing also go to stdout. Optional parameters:
| Name | Default | Meaning |
|---|---|---|
SEED |
42 | seed for PostScript's rand |
TEMP |
0.8 | sampling temperature; 0 is greedy |
STEPS |
200 | maximum number of tokens |
Print it
Only on a PostScript printer you own or have permission to use. Send the file raw, so that the printer's PostScript interpreter runs it instead of a driver printing it as text:
nc <printer-ip> 9100 < TONER-1_print-job.ps # raw TCP port 9100
lpr -P <queue> -o raw TONER-1_print-job.ps # or through a raw CUPS queue
A printer uses the defaults (seed 42, temperature 0.8, 200 tokens). To change the seed, edit the 42
in /SEED 42 def, which appears once in the file.
Before you send it:
- It needs a PostScript interpreter, Level 2 or later. The job uses only Level 1 operators plus
rectstroke(Level 2), and the standard Helvetica, Times and Courier fonts. A PCL- or GDI-only printer may print the 595 KB file as pages of text. - It is slow. On an HP Color LaserJet Pro MFP M281fdw it wrote 200 tokens in 3:37 by the printer's own clock (0.92 tok/s), and took 3:39 from send to finished job. No paper comes out until the story is finished. A long job can hit a printer's job timeout or run out of PostScript memory (VM). Ghostscript uses about 3.3 MB of VM writing a PDF and 7.5 MB rendering to PNG.
- PostScript's
randis implementation-defined, so another interpreter may tell a different story from the same seed. At seed 42, the HP's story was byte-identical to Ghostscript's, apart from the printer's CRLF line endings.
Use the weights in Python
model.safetensors holds the fine-tuned float32 weights, before int8
quantization, and config.json holds the architecture. They load into llama2.c's
model.py. The vocabulary is Karpathy's
tok512.model, from
karpathy/tinyllamas:
import json, torch
from safetensors.torch import load_file
import sentencepiece as spm
from model import ModelArgs, Transformer # model.py from github.com/karpathy/llama2.c
cfg = json.load(open("config.json"))
m = Transformer(ModelArgs(**cfg["model_args"]))
missing, unexpected = m.load_state_dict(load_file("model.safetensors"), strict=False)
assert missing == ["output.weight"] and not unexpected # output.weight is tied to tok_embeddings.weight
m.eval()
with torch.no_grad():
out = m.generate(torch.tensor([[1]]), max_new_tokens=200, temperature=0.8)[0, 1:].tolist() # 1 = BOS
ids = out[:next((i for i, t in enumerate(out) if t in (1, 2)), len(out))] # stop at BOS or EOS
print(spm.SentencePieceProcessor(model_file="tok512.model").decode(ids))
This samples with PyTorch's random numbers, not PostScript's, so it tells different stories from the print job.
Rebuild the job from its parts
python3 src/l2c_safetensors.py to-bin . toner1.bin # model.safetensors -> llama2.c .bin
python3 src/export.py toner1.bin tok512.bin src/weights.ps # regenerates src/weights.ps
sh src/build.sh > rebuilt.ps && cmp rebuilt.ps TONER-1_print-job.ps # byte-identical
tok512.bin is in
karpathy/tinyllamas. These
commands reproduce the shipped files byte for byte. l2c_safetensors.py uses only the Python
standard library.
Specs
| Base model | stories260K from Karpathy's llama2.c (published in tinyllamas), trained by him on TinyStories |
| Fine-tune | ours: 3,000 steps (about 11 minutes on an Apple M4), lr 3e-4, on 8,757 printer stories mixed with TinyStories replay (TONER-1-printer-stories) |
| Parameters | 260,032 |
| Architecture | Llama-2-style decoder: 5 layers, dim 64, 8 query heads sharing 4 key/value heads, head size 8, SwiGLU feed-forward (hidden 172), RoPE, RMSNorm; the embedding is shared with the output layer |
| Vocabulary | 512-piece SentencePiece (tok512), Karpathy's, unchanged |
| Context | 512 tokens; the job generates up to 200 |
| Quantization | int8 weights, one scale per row (scale = max abs weight / 127); activations are PostScript reals |
| Input | None. Every story starts from the beginning-of-sequence token. |
| Output | One US Letter page: the story, the token stream, and a run card |
| Print job | 595,372 bytes, 455 lines of plain text, one page |
| PostScript | Level 1 operators plus rectstroke: needs Level 2 or later |
Did the fine-tune work?
Sampled from the int8 weights the way the print job samples (from BOS, temperature 0.8, up to 200 tokens):
Original stories260K |
TONER-1 | |
|---|---|---|
| Stories with any printer word (n = 300) | 3.7% | 87.3% |
| Stories that say "printer" (n = 300) | 0% | 71.7% |
| Loss on 461 held-out printer stories | 1.83 | 1.23 |
| Loss on TinyStories validation | 1.341 | 1.393 |
Blind story test. One AI rater scored 50 shuffled Ghostscript transcripts blind, on 1-to-5 scales. The original scored 1.90 coherence, 3.65 language and 1.90 fun; TONER-1 scored 1.70, 3.30 and 2.25, and 9 of its 20 stories were printer-themed (0 of 20 for the original). So the fine-tune trades a little coherence (not significant, p = 0.37) for printer content and fun (p = 0.04). This is one AI rater on 40 stories from these two models, not a human study.
Does the PostScript compute the right thing? Teacher-forced on 96 tokens of a printer story and of a TinyStories story, the logits in Ghostscript are within 7.0e-5 of an int8 float64 reference, and the most likely token agrees at all 96 positions.
Speed
| Where | Speed | Status |
|---|---|---|
| Ghostscript 10.07.1, Apple M4 Mac | about 80 tok/s, under 4 s per page (the sample page logged 82.3) | measured |
| HP Color LaserJet Pro MFP M281fdw, 2026-11-05 | 0.92 tok/s: 200 tokens in 3:37 by the printer's own clock; 3:39 from send to finished job | measured, one run (seed 42) |
Before that run, the printer speed was an estimate, 0.3 to 5 tok/s; 0.92 falls inside it. The printer's
logs are in the GitHub repo under docs/printer-run-2026-11-05/.
Files
| File | What it is |
|---|---|
TONER-1_print-job.ps |
The model: one print job (595,372 bytes) |
model.safetensors |
The fine-tuned float32 weights (llama2.c model.py names; output.weight is not stored, it is tok_embeddings.weight) |
config.json |
Architecture and token ids |
src/engine2.ps |
The forward pass, sampler and page layout, in PostScript |
src/weights.ps |
The int8 weights and vocabulary as written by export.py. The print job already embeds them. |
src/export.py |
Converts a llama2.c .bin and tok512.bin into weights.ps |
src/l2c_safetensors.py |
Converts between llama2.c .bin and model.safetensors (standard library only) |
src/build.sh |
DSC header + weights.ps + engine2.ps = the print job |
docs/TONER-1_model-card-and-benchmarks.pdf |
The two-page model card and parody benchmarks, as a PDF. The handout is itself a PostScript program. |
docs/TONER-1_explainer.pdf |
A two-page technical explainer: how a print job runs a language model |
samples/story_seed20_t0.8.pdf |
One story page, produced by the print job in Ghostscript |
samples/story_seed20_t0.8_ghostscript-render.png |
A 110 dpi Ghostscript render of that page |
Limitations
- It has 260K parameters. The stories are short and simple, and often stop making sense ("She loved to bring paper to a avocado"). It mostly writes about Lily, Tim and the printer.
- There is no prompt input. The model chooses the plot.
- English only, in the style of TinyStories.
- Printer speed comes from one run on one printer. Other printers have not been tried.
- PostScript's
randis implementation-defined, so seeds are not guaranteed to be portable between interpreters (the HP matched Ghostscript at seed 42). - PCL- and GDI-only printers cannot run it. Long jobs can hit a printer's job timeout or VM limit.
- Outputs are fused to paper and cannot be edited.
Benchmarks (PARODY)
PARODY. Not real evaluations. Frontier models from major labs were not run on these benchmarks and are not named. "N/A" means only that they cannot execute inside a print job.
| Benchmark (parody) | What it pretends to measure | TONER-1 | Frontier models from major labs |
|---|---|---|---|
| TPS (Tokens Per Sheet), higher is better | Output tokens per US Letter sheet, simplex. Not tokens per second. | 200 | N/A: cannot execute inside a print job |
| HelloPaperJam, higher is better | Copy-room commonsense, 4-way multiple choice (chance 25%); parody of HellaSwag | 27.9% | N/A: cannot execute inside a print job |
| PC LOAD LETTER, higher is better | "Truthfulness": share of outputs printed on the paper size the job asked for; parody of TruthfulQA | 100.0% | N/A: cannot execute inside a print job |
| Jam Rate, lower is better | "Hallucination": invented objects per 100 tokens | 1.5 | N/A: cannot execute inside a print job |
| TonerBench, lower is better | Dollars per 1M output tokens, in toner: 5,000 sheets of 200 tokens at about 2.5 cents each. Paper extra. | $125 | N/A: cannot execute inside a print job |
The charts are on page 2 of
docs/TONER-1_model-card-and-benchmarks.pdf.
Prior art
- EXO Labs ran
stories260Kwith llama98.c on a Windows 98 Pentium II at 39.31 tok/s: https://blog.exolabs.net/day-4/ - Nicolas Seriot's PSChess is a chess engine in PostScript that plays on printers: https://github.com/nst/PSChess
- int-llm-postscript (August 2026) runs a 14K-parameter microGPT in PostScript, under Ghostscript: https://github.com/nmicic/int-llm-postscript
- llama.ttf is a font file that contains and runs a small language model: https://github.com/fuglede/llama.ttf
Credits
- Base model, tokenizer, architecture and inference reference: Andrej Karpathy's
llama2.c and
tinyllamas (
stories260K,tok512), MIT License. - TinyStories, by Ronen Eldan and Yuanzhi Li (2023), arXiv:2305.07759, CDLA-Sharing-1.0: the base model's training data, and replay data in the fine-tune.
- The printer stories: written by Xing4.0-29B-A4B (China Telecom's TeleAI, Apache-2.0) through OpenRouter's free tier, and filtered by us. They are published as TONER-1-printer-stories (Apache-2.0).
License
MIT; see LICENSE. The weights are our fine-tune of Andrej Karpathy's stories260K (MIT).
No Llama 2 materials are in this repository.
Links
- GitHub: https://github.com/jacobalansussman/toner-1
- TONER-1 Pro, our own 12.2M model in a 24.9 MB print job: https://huggingface.co/jacobalansussman/TONER-1-Pro
- TONER-1-V, the doodle model that illustrates TONER-1's stories in the same print job: https://huggingface.co/jacobalansussman/TONER-1-V
- The printer stories: https://huggingface.co/datasets/jacobalansussman/TONER-1-printer-stories
Not affiliated with or endorsed by Adobe, HP, Artifex, Google, or any AI lab.
We're proudly pushing forward the PostScript Pareto Frontier.
- Downloads last month
- 333
Model tree for jacobalansussman/TONER-1
Base model
karpathy/tinyllamas