Model or dataset
HarryR/z80ai avatar
HarryR/z80ai

Z80-μLM: a 2-bit language model that fits in a CP/M .COM file

Z80-μLM is a 2-bit quantized language model small enough to run on an 8-bit Z80 processor. Train conversational models in Python, export them as CP/M .COM binaries, and chat with your vintage computer.

1,118 stars49 forksPythonLicense varies

At a glance

What is it?
HarryR/z80ai trains small conversational models in Python and exports them as CP/M or ZX Spectrum binaries. The inference path is a 2-bit multiply-accumulate loop, and the whole thing is designed around the 64KB address space of a 1976 CPU.
Who is it for?
Z80-μLM is for people who want a conversational binary on real Z80 hardware or in an emulator, and who accept that responses are one or two words chosen from a fixed charset. It is not for anyone expecting multi-turn context or generated sentences.
Can I use it commercially?
Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
Is it still maintained?
Yes. The repository last received commits 139 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What a 40KB .COM file is actually for

The README frames the project around a question: how small can a conversational model get while still having personality, and can it be trained or fine-tuned easily? The stated answer is a 40KB .COM binary containing inference, weights and a chat-style UI, running on a 4MHz processor. That number matters because CP/M loads programs into the Transient Program Area, and the README lists fitting inside the TPA as a feature rather than an afterthought. So the target user is someone with a Z80 machine, an emulator, or an interest in retrocomputing who wants a program that reacts to typed input without a host machine doing the thinking. The README is direct about the ceiling: it says the model will not pass the Turing test. Two examples ship with the repository. tinychat answers casual Q&A pairs with terse replies, and guess implements a 20 Questions game where the model holds a secret topic and answers YES, NO or MAYBE. Both are classification tasks dressed as conversation, and that is the honest description of what this thing does.

Trigram hashing: the input layer is a tag cloud, not a sentence

Input text is hashed into 128 buckets using trigram encoding. The README describes the result as an abstract tag cloud, and gives a worked example: "hello there" and "there hello" produce the same bucket counts, while "helo ther" produces similar but not identical counts. That makes the encoding word-order invariant and typo tolerant by construction, not by training. The architecture section adds a second set of 128 context buckets alongside the 128 query buckets, so the first layer sees 256 inputs. Hidden layers are configurable in depth and width, with 256, 192 and 128 given as an example shape, ReLU between hidden layers, and one output neuron per character in the charset. The README is unusually candid about where this breaks. Longer or order-dependent sentences blur together because concepts compete for the same buckets, and it offers a concrete failure: "Open the door and turn on the lights" will likely be too close to distinguish from "turn on the door and open the lights." That is not a bug to be filed. It is the direct consequence of hashing word order away, and it bounds the kind of input the model can separate.

How 2-bit weights survive a 16-bit accumulator

Each weight is one of four values: -2, -1, 0 or +1. Four weights pack into a byte, and the Z80 unpacking code in the README shows the whole trick: load the packed byte, mask the bottom two bits with and 03h, subtract 2 to map 0,1,2,3 onto -2,-1,0,+1, then rotate the packed byte twice with rrca to expose the next weight. The multiply-accumulate routine then branches on the weight. A zero weight skips the addition entirely, which is a real saving on a CPU with no multiplier. A +1 adds the activation to a 16-bit accumulator held in HL. Negative weights use sbc hl, de, once for -1 and twice for -2. After each layer the accumulator is arithmetic right-shifted by two, preserving sign, which divides by four and keeps the next layer from overflowing. The README states that the 16-bit accumulator gives numerical stability when summing 256 inputs, and that the model's expressiveness is still bottlenecked by the 2-bit weights. It also warns that naive training may overflow or behave oddly without quantization-aware training. The README estimates roughly 100,000 multiply-accumulate iterations per generated character. There is no floating point anywhere in the pipeline.

Running a pre-built binary in under five minutes

The quickstart is short. Download the pre-built binaries from GitHub Releases, install an emulator, and run. For CP/M the recommended emulator is iz-cpm on Linux, Windows and macOS, and the command is iz-cpm CHAT.COM. For ZX Spectrum, the README suggests fuse-emulator-gtk via apt on Linux, Fuse on Windows, or brew install fuse-emulator on macOS, followed by fuse --tape CHAT.TAP, then LOAD "" CODE and RANDOMIZE USR 32768 inside the emulator. Running CHAT with no arguments enters interactive chat mode. Building from source or training a model is a separate path documented in TRAINING.md, and the ZX Spectrum specifics live in ZX-SPECTRUM.md, with example directories providing run-zx.sh. The repository layout implied by the README includes buildz80com.py for CP/M output and buildz80tap.py for ZX Spectrum output. The guess example also ships tools for generating training data through Ollama or the Claude API, plus scripts for balancing class distributions. Everything past the emulator step is documented in files I have not read, so treat the training workflow as unverified here.

The response vocabulary is the interface

The README argues that a one or two word reply can carry nuance, and lists examples: OK as neutral acknowledgement, WHY? as questioning the premise, R U? as existential doubt, MAYBE as uncertainty, AM I? as reflecting the question back. It calls this a different mode of interaction rather than a limitation, and suggests probing with direct yes/no questions such as "are you a bot" or "are you human" to see whether the memorized answers stay logically consistent. I think that framing is mostly fair, with one caveat the README does not draw out. Terse replies only read as personality when the training set was curated for it. The guess example makes this explicit by shipping data-generation and class-balancing tools, which is an admission that output distribution is something you tune by hand. If your training pairs are noisy, the same 2-bit weights will produce a model that answers MAYBE to everything. The README's own list of what the project is not is worth taking literally: not a chatbot that generates novel sentences, not something that tracks multi-turn context deeply, not a grammar parser.

Where a hand-written state machine beats this

If your goal is a Z80 program that recognizes a fixed set of commands and replies, a hand-written state machine or a lookup table will be smaller, faster, and predictable. It will also be debuggable in a way a quantized network is not. The difference in approach is the point. Z80-μLM buys typo tolerance and word-order invariance through trigram hashing, so "helo ther" still lands near "hello there" without you enumerating either string. A table of exact matches gives you none of that, and a table of fuzzy matches grows with every variant you anticipate. The trade is that the model's behaviour is statistical, so a wrong answer is not traceable to a line of code. For a 20 Questions game or a greeting responder, the fuzzy matching is probably worth it. For a terminal that must parse a fixed command set correctly every time, it is the wrong tool, and the README's own description of the hashing limit explains why: order-dependent input collapses into the same buckets.

Licence, maintenance and what to verify first

The README ends with a single line: "License: MIT or Apache-2." The repository metadata supplied here lists the licence as unknown, and there is no LICENSE file named in the material. That discrepancy is worth resolving before you ship a binary built from this code, and I am not going to guess which of the two licences applies. On maintenance, the evidence is thin but not discouraging: v0.2 was released on 2026-01-18 and the last push to main was 2026-04-29, so the repository was active in the months before this writing. The project is not archived. Beyond that I cannot say how often it is updated, and the material does not describe a changelog or a support channel. The practical maintenance cost for a user is low if you only run pre-built binaries, because there is nothing to keep in sync. It rises sharply if you train your own models, since you inherit the Python training pipeline, the data generation tools that call out to Ollama or the Claude API, and the quantization-aware training step the README says is needed to avoid overflow or odd behaviour. Verify the licence first, then run the released CHAT.COM under iz-cpm, then read TRAINING.md before committing to a custom model.

Editorial conclusion

Z80-μLM is for people who want a conversational binary on real Z80 hardware or in an emulator, and who accept that responses are one or two words chosen from a fixed charset. It is not for anyone expecting multi-turn context or generated sentences. Before adopting it, check the repository's licence file (the README states MIT or Apache-2, but the metadata shows no licence), then run the pre-built CHAT.COM from the v0.2 release under iz-cpm, and only then decide whether to train your own model with TRAINING.md.

Official sources

  1. HarryR/z80ai on GitHub
  2. Issues
  3. README
  4. Releases
Community notes

Community notes