Hard shell. No floats. Same taco on every machine.
Taco is a lossless compressor that uses a language model (GPT-2). The model predicts the next piece of the data, and a range coder stores only what the model did not predict. The engine of Taco, HardShell, runs the model with integer arithmetic only. Thus every CPU and GPU calculates the same predictions, bit for bit. A file that one machine compresses ("folds") decompresses ("unfolds") on every other machine.
taco fold notes.txt # makes notes.txt.taco
taco unfold notes.txt.taco # restores notes.txt, byte for byte

Real ./try output of the 0.1.0 release on macOS (CPU), Linux (CUDA) and Windows (CUDA). Long lines
are shortened; the full output is in docs/img/try-output/.
Warning
Alpha: a proof of concept (version 0.1.0, 2026-10-19). Taco is an experiment. It is slow, and it is not a replacement for zstd or xz. Keep a second copy of data that you fold. Read Limits before you use it.
A note from me
Hi, I made Taco. I work in ML and backend. I don't know Rust, CUDA or Vulkan.
LLM compressors get great ratios, but they have one ugly problem: the model runs on floats, and floats round a little differently on different CPUs and GPUs. Compress on one machine, decompress on another, and one tiny difference can break the whole file. So moving a compressed file to different hardware is risky.
My idea was simple: take floats out of the model completely. Convert GPT-2 to integers once, run every step in integer math, and every machine has to get the same answer. The design is mine. Claude (with Claude Code) wrote almost all of the code, the tests and the docs, because I can't write the GPU stuff myself. That's also why the docs below read like Claude. So don't trust the code because I say so, trust the tests: conformance hashes, the cross-machine check, fuzzing, and every fast path checked against the slow reference.
It's slow and it's an alpha. The one thing I want you to try: run ./try from the
release, and check that you get the same hash
as everyone else. If your machine gives a different hash, that's a bug, and I really want to
know about it. Open an issue.
Contents
- Proof: the same bytes on every machine
- Try it
- What Taco is, and what it is not
- Results
- What is guaranteed
- Hardware
- Limits
- How it works
- Build from source
- Usage
- Roadmap
- Development
- Licence
Proof: the same bytes on every machine
This is the point of Taco. Speed and ratio come second.
The release program of version 0.1.0 folded the demo sample (demo/samples/sample.txt, 3,672
bytes, --profile text) and unfolded it again on 7 backends: 3 operating systems, 2 CPU
architectures and 4 kinds of processors. Each fold ran on the backend in the row, and each
unfold restored the sample byte for byte (measured, 2026-10-19):
| Machine | System | Backend | SHA-256 of the .taco file |
|---|---|---|---|
| Apple M2 | macOS (ARM) | CPU (NEON) | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
| Apple M2 | macOS (ARM) | GPU (Metal) | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
| AMD Ryzen 7 5800H | Linux x86-64 | CPU (AVX2) | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
| NVIDIA RTX 3060 Laptop | Linux x86-64 | CUDA | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
| AMD Ryzen 7 5800H | Windows x86-64 | CPU (AVX2) | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
| NVIDIA RTX 3060 Laptop | Windows x86-64 | CUDA | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
| NVIDIA RTX 3060 Laptop | Windows x86-64 | GPU (Vulkan) | 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e |
Thus a file that any of these machines folds unfolds on all of the others. To add a row, run
./try on your machine (Try it) and compare the hash. More checks of the same
property, with more inputs, are in Same bits on different machines.
Try it
There are two ways. Both give the same .taco bytes.
A. Download the program (nothing to build)
- Open the Releases page and download
taco-demo-0.1.0.zip(about 120 MB). It has the programs for macOS (Apple Silicon), Linux (x86-64) and Windows (x86-64), the model filegpt2.hsm, and a one-command demo. If you want only the program for one system, downloadtaco-0.1.0-<system>.tar.gz(or.zipfor Windows); it does not include the model file. - Unpack the zip and open a terminal in the unpacked folder (
taco-demo-0.1.0). - Run
./try(macOS, Linux) ortry.cmd(Windows).
The demo folds and unfolds a short English text (3,672 bytes), checks it, and prints the SHA-256
of the .taco file. It takes about 10 s to 1 minute. The end of the output:
PASS: the file came back byte for byte.
Original: 3672 bytes. Folded: 648 bytes.
SHA-256 of the .taco file: 2fab1ef7a1c70cd4e39ddfd88a8d66389ae07a403111db131326ae425460652e
This hash is the same on every machine and every backend. Run the demo on a second
computer and compare. To fold your own files, use ./taco fold notes.txt and
./taco unfold notes.txt.taco in the same folder (Windows: taco.cmd). Details and
troubleshooting: demo/README.md.
B. Build the same program yourself
You need: rustup (it installs Rust 1.99.0 from
rust-toolchain.toml), uv for the Python tools, a C compiler (for
zstd), about 1.5 GB of disk and 8 GB of memory. A GPU is optional.
git clone https://github.com/tac0mania/taco.git && cd taco
uv run --project tools python -m baseline.fetch # GPT-2 weights; the tool checks each SHA-256
uv run --project tools python -m convert.gpt2 # makes tools/data/out/gpt2.hsm (about 1 minute)
shasum -a 256 tools/data/out/gpt2.hsm # must be aaa03eb96e9ad795...b51001ba5c2
cargo build --release --locked -p taco-cli
export TACO_MODEL="$PWD/tools/data/out/gpt2.hsm"
./target/release/taco fold demo/samples/sample.txt --profile text -o /tmp/sample.taco
shasum -a 256 /tmp/sample.taco # must be the hash in section A
./target/release/taco unfold /tmp/sample.taco -o /tmp/sample.txt
cmp demo/samples/sample.txt /tmp/sample.txt && echo "restored byte for byte"
- The converter calculates with exact fractions, so your
gpt2.hsmis the same file, byte for byte, as the one in the release. If its hash is different, do not use it. - Your
.tacofile must have the hash of section A. Then your build and the release program are compatible: each one unfolds the files of the other. scripts/release/build.sh <target>makes the release archive itself. It builds with fixed paths and times, so two builds of one commit on one machine gave the same binary, byte for byte (measured on Linux).BUILDINFOin each archive records the commit and the SHA-256 of the program.
Caution
Taco is slow. With a GPU, it folds text at about 85 to 129 KB/s and unfolds at about 26 to 31 KB/s. With the CPU only, it folds at about 0.3 to 2 KB/s and unfolds at about 0.1 to 0.5 KB/s (measured). Start with a file of a few KB.
What Taco is, and what it is not
Taco is a compressor whose output does not depend on the machine. Its parts are:
- an open specification of integer-only GPT-2 inference (
docs/adr/0004-hardshell-numerics.md); - an exact converter: it reads each float32 weight as
mantissa x 2^exponentand calculates with exact fractions, so every machine makes the same model file (ADR 0014); - implementations for the CPU (NEON, AVX2), NVIDIA GPUs (CUDA) and other GPUs (wgpu: Vulkan, Metal, DX12), which tests show give the same bits;
- conformance vectors that connect the model to the bytes of real
.tacofiles.
Taco is not the first or the best compressor with a language model:
- ts_zip (Fabrice Bellard) makes smaller files and is faster. Its page also states that its result does not depend on the GPU or the CPU.
- The ideas under Taco have earlier work: language models with entropy coders (LLMZip), integer networks for compression that decode the same on every platform (Ballé et al., 2019), and integer-only Transformers (I-BERT).
What Taco adds is the combination: an inspectable integer specification, a repeatable model conversion, several independent implementations, and tests that show they agree.
Results
All numbers are measured unless they are labelled "published" or "est.". Details are in
docs/research/. The sizes do not include the model file (127 MB), which both machines must
have.
Size
| File | Original | Taco | xz -9e | zstd -19 |
|---|---|---|---|---|
| English book (32 KiB) | 32,768 | 8,432 | 12,400 | 12,463 |
| HTML page | 24,603 | 4,759 | 7,652 | 7,713 |
| English (4 KiB) | 4,096 | 1,237 | 1,684 | 1,599 |
| Server log (16 KiB) | 16,384 | 3,129 | 3,164 | 3,139 |
| Excel file (binary) | 8,192 | 8,285 (stored raw) | 1,672 | 2,097 |
Against another compressor with a language model, on the whole alice29.txt (152,089 bytes):
| Compressor | Bytes | Bits per byte |
|---|---|---|
| xz -9e | 48,492 (published) | 2.55 |
| Taco | 38,939 | 2.05 |
| ts_zip | 21,713 (published) | 1.14 |
ts_zip is 44% smaller. It uses a larger and newer model (RWKV, 169M parameters; GPT-2 small
has 124M), and Taco's profile text restarts the model context at each 16 KiB block.
Logs (profile logs: GPT-2, a match model and a mixer)
The 16 Loghub log files (4,458,875 bytes in total), the defaults of the profile logs (blocks
of 64 KiB, context overlap 512), whole files for the other compressors
(docs/research/2026-10-19-overlap-cli.md, docs/research/2026-10-19-logs-profile.md):
| Compressor | Bits per byte |
|---|---|
| zstd -19 --long=27 | 0.597 |
| xz -9e | 0.554 |
Taco, profile logs |
0.494 (10.8% smaller than xz) |
| paq8px -8 (a slow context-mixing compressor) | smaller than Taco: 0.348 against 0.600 (est.) on 6 other whole logs |
Speed of the profile logs with the GPU mixer (RTX 3060 at 95 W, CUDA, commit 1493af3):
| File | Fold | Unfold |
|---|---|---|
Apache_2k.log, 171 KB |
4.5 s (was 208.8 s on the CPU) | 53.8 s (was 90.1 s) |
apache_access.log, 2.37 MB |
70 to 98 s | 255 to 280 s |
What can make logs smaller (not in this version): the skip rule, 4.8 to 7.3% smaller (est., simulation, ADR 0017); and a model fine-tuned on the logs of known systems, 53.2% smaller on those systems but 4.6% larger on other systems (measured, frozen; Experiments).
Integer model against the float model
| Model | Bits per byte (16 text files, 42,256 bytes) |
|---|---|
| GPT-2 small, float | 1.867 |
GPT-2 small, integer (gpt2.hsm) |
1.871 (+0.2%) |
The +0.2% is a total. Single files change from −9.0% to +6.6%. The conversion saturates large
activations, which changes the predictions; against a float model with the same saturation,
the integer model is 2.3% larger (docs/research/2026-10-18-integer-gpt2.md §6).
Speed (RTX 3060 Laptop GPU, 95 W)
| What | Speed |
|---|---|
| Engine, decode over a full 1,024-token window | 20,472 tokens/s |
taco fold, English text |
85 to 129 KB/s |
taco unfold, English text |
26 to 31 KB/s |
taco fold, CPU only |
about 0.3 to 2 KB/s |
taco unfold, CPU only |
about 0.1 to 0.5 KB/s |
For comparison, zstd -3 compresses at hundreds of MB/s and xz -9e at about 0.7 MB/s.
ts_zip states "up to 1 MB/s" on an RTX 4090 (published, a different GPU).
Large files (streaming): a memory test, not a speed test
This test checks that the memory does not grow with the file (ADR 0018). It used 200,000,000 bytes of random words and the small test model, on the devbox CPU.
| Run | Largest memory (RSS) |
|---|---|
| fold, file to file | 254 MiB |
| fold, stdin to stdout | 276 MiB |
| unfold, file to file | 38 MiB |
| unfold, stdin to stdout | 35 MiB |
Both unfolds restored the input byte for byte. Its times say nothing about Taco's speed: the test model has random weights, so every block was stored with zstd or raw, and unfold ran no model step. The real speed is in the table above. The largest files folded with GPT-2 so far are 2.4 to 2.7 MB (measured).
Same bits on different machines
| Check | Result |
|---|---|
Mac (ARM) folds, Linux (x86) unfolds, and the reverse (scripts/xcheck, 12 inputs) |
12 of 12 restored each way; 12 of 12 .taco files identical |
Native Windows x86 against the Mac (scripts/wcheck) |
9 of 9 .taco files identical, 9 of 9 restored |
| CPU, CUDA and wgpu (Metal, GL) | the same bytes, 14 of 14 GPU cases |
| CI on each push: Linux x86-64 and Linux ARM64 | the same 50 conformance hashes |
| Fuzzing: damaged containers, coder streams and model files | millions of cases, 0 failures (docs/security.md) |
What is guaranteed
-
No floating-point arithmetic in fold or unfold. The engine, the coder, the mixer and the GPU kernels use integers only.
scripts/check-no-floats.shchecks this on each push. -
The same result on every machine, when each operation is fully specified. Integers alone are not sufficient. Taco makes these conditions rules (
CLAUDE.md§1):- every sum has an accumulator that is wide enough, with the bound written in the code, or it wraps on overflow in a defined way;
- every rounding and every clamp has one written rule (ADR 0004);
- signed division uses floor-division helpers.
Then a compiler, a CPU type or a GPU driver has no freedom to change a result. Tests check these conditions. Tests cannot prove that the code obeys them everywhere.
-
The model file is the same on every machine. The Mac and Linux make the same
gpt2.hsm, byte for byte. Its SHA-256 isaaa03eb96e9ad7957432168c8ac6b4a99ed55d57e637e4786a664b51001ba5c2. -
A wrong result is never silent. Each
.tacofile stores the SHA-256 of the original data, andtaco unfoldchecks it.
Important
The checksum finds an error. It cannot repair one. If
taco unfoldfails, try again with--backend cpuor on a different machine. If the error happened duringtaco fold(a bug or a hardware fault on that machine), no decoder can restore the file. Keep the original until you have unfolded the.tacofile one time.
These problems can still occur, as in every program: a bug in our code, a bug in a GPU driver (Taco runs a self-test before it uses a GPU, and uses the next backend if the test fails), or a hardware fault. In each case the SHA-256 check finds the error.
Hardware
| Hardware | Backend | Result |
|---|---|---|
| Apple M2 (macOS) | CPU (NEON), GPU (wgpu on Metal) | bit-exact |
| AMD Ryzen 7 5800H (Linux, native Windows) | CPU (AVX2) | bit-exact |
| NVIDIA RTX 3060 Laptop | CUDA | bit-exact |
| NVIDIA RTX 3060 Laptop | wgpu on OpenGL (Linux), DX12 with DXC (Windows) | bit-exact |
| AMD Radeon iGPU (Ryzen 7 5800H), native Windows | wgpu on DX12 with DXC | bit-exact |
| NVIDIA RTX 3060, AMD Radeon iGPU, native Windows | wgpu on Vulkan | bit-exact (4 op tests; the full model tests did not run yet) |
| GitHub Actions: Linux x86-64, Linux ARM64 | CPU | the same 50 conformance hashes |
Not tested yet: Intel GPUs; AMD GPUs on Linux; AMD discrete GPUs; macOS on Intel; big-endian and 32-bit CPUs; phones and browsers.
GPU notes:
- AMD and Intel GPUs need no special code. They use wgpu and the normal GPU driver.
- The CUDA backend needs compute capability 8.0 or more (RTX 30 series, A100 and later). Older NVIDIA GPUs use wgpu or the CPU. The bits are the same; only the speed changes.
- On Windows, DX12 with the old FXC shader compiler cannot compile 7 of the 14 kernels. The self-test then fails, and Taco uses the next backend. Vulkan, Metal and DX12 with DXC work.
Limits
| Limit | Details |
|---|---|
| Slow | See Speed. A GPU is strongly recommended. |
| Small files unfold slowly | One block decodes one token after the other: about 10 to 13 s for a 16 KiB block on the RTX 3060 (est. from measured times). |
| English only | GPT-2 is an English model. On German and French, Taco is larger than PPMd; on Chinese, larger than gzip (measured). |
| Binary data | Photos, video, archives and other binary files are stored with zstd or raw. Use zstd for them. |
| Logs | The profile logs is smaller than xz, but paq8px is smaller still (measured). |
| The model file | 127 MB. Both machines must have the same file. The release does not download it yet. |
| Untrusted model files | A model file can state a very large KV cache (finding F6, open). Load only the published gpt2.hsm. |
| Wide models | Model-file version 2 runs on the CPU and with CUDA, not on wgpu yet. The default gpt2.hsm is version 1 and runs everywhere. |
| Format | Container version 2 is frozen: later versions of Taco read the files of this version. |
| Release 0.1.0-alpha.1 | Its programs fold the default profile auto on the CPU (use --profile text for the GPU), and a crafted .taco file can use about 11 GB in GPU unfold (F2; use --backend cpu for untrusted files). Both are fixed on main; the GPU logs speeds above are from main. |
How it works
A symbol that the model expects with probability p costs about −log₂(p) bits. For each position, the model gives a probability to each of the 50,257 GPT-2 tokens. The range coder divides a number range in proportion to these probabilities. The decoder runs the same model, divides the range in the same way, and finds the token.
The decoder must calculate exactly the same probabilities as the encoder. If one frequency
is different by one unit, the decoder can select a wrong token, and all output after it is
wrong. Floating-point results can change with the order of additions, the chip, the math
library and the batch size. Specified integer results do not change. Thus HardShell uses i8
weights, i32 and i64 sums, one rounding rule, and tables for GELU and exp that are stored
in the model file.
flowchart LR
A[input bytes] --> B[blocks]
B --> C[tokenizer<br/>byte-level BPE]
C --> D[HardShell<br/>integer GPT-2]
D --> E[frequency table]
E --> F[range coder]
F --> G[.taco file<br/>model hash + SHA-256]
G -. unfold: same model,<br/>same table .-> H[original bytes<br/>checksum verified]
- Blocks are independent, so they run in parallel, and damage stays in one block.
- A block that does not become smaller is stored zstd or raw. The worst-case growth is the 104-byte header and 5 bytes for each block.
- The profile
logsadds a match model and a mixer for repeated lines. The profileauto(the default) selects the smaller result for each block. - The file format is in ADR 0006 and ADR 0011. The model file format is in ADR 0004.
The full explanation of every part, with the measurements and the reasons for each
decision, is in [docs/core-architecture.md
