Published on
22 min readmentions

Token-Native Storage

Read and Write in Your Agent's Language

Looking for TL;DR? Check key takeaways

Every vector search engine stores two things for each point: the vector and the payload. The AI community has gone all in to compress the vectors with quantization and MRL. However, the text payload gets raw UTF-8, or LZ4 at best, even though the systems that actually consume it (embedders, rerankers, LLMs) operate on tokens, not UTF-8 text. I realised that using OpenAI r50k token IDs over raw text already achieves 2.27× compression, surpassing every popular byte-level compression algorithm, and an entropy-based compressor on top of the token IDs reaches 3.26× (surpassing even a corpus-trained zstd). Effectively, tokenization gives you free compression!

Also, if agents are going to be the biggest users of your system, then your infra should speak their language (tokens), so you're not paying a translation cost on every read/write. This post argues for token-native storage (read/write text as BPE token IDs, not UTF-8 bytes) anywhere models are the primary readers and writers: search/db engines, LLM agent thinking traces / chat history, and maybe even local files. I'll show my point with experiments, talk about limitations, and how the AI/DB community and the AI labs can help get rid of them.

How Token-Native Storage Works

Same bytes, read two ways (byte codec vs. tokens)
t 0.00 · 0.0s / 13.3s · copy
UTF-8 BYTE COMPRESSION (LZ4)TOKEN ID COMPRESSION (r50k)
r50k (GPT-2 BPE, 50,257 tokens)final ANS: 3.26× vs LZ4: 1.15×

The top path is what databases do today: store UTF-8 bytes, compress with a generic codec. The bottom path is token-native storage: tokenize first, then compress (here entropy-code) the token IDs.

Step 1: Generate Token IDs

BPE (Byte Pair Encoding) tokenization starts with raw bytes and repeatedly merges the most frequent adjacent pair into a new token, building up a vocabulary of common subwords. OpenAI has three popular BPE tokenizers:

  1. r50k: used by GPT-2/GPT-3, 50,257 tokens and fits in uint16 (= ~65k)
  2. cl100k: used by GPT-3.5/GPT-4, 100,277 tokens and fits in 17 bits (131k) but only 24 bits (3 bytes) is practical due to overhead
  3. o200k: used by GPT-4o/GPT-4o mini/o1/o3, 200,019 tokens, same 24-bit (3-byte) practical packing as cl100k.

BPE merges frequent byte sequences into single tokens. "storage" is one token. "Token-native" is three (Token + - + native). On average one BPE token covers 3/4 of a word. An average English word is ~5 characters plus a space, so:

napkin
Using UTF-8 text: 6 bytes/word (1 char = 1 byte) × 3/4 word/token = 4.5 bytes/token (raw text)
Using Tokens: 2 bytes/token (r50k uint16 token IDs)
ratio: ~2.25x

This napkin math is what gave me the motivation to run the experiment, and my experiments below prove it in practice.

Another fun fact is that a every word in a sentence comes with a " ", so using uint16 token IDs performs better for any word longer than 1 character. Example: " cat" is 4 raw bytes (space + 3 letters) but just 1 token, 2 bytes (uint16), an easy win :D

Step 2 (Extra): Token ID Compression

Theoretically, step 1 alone already gets you to ~2.25× compression, just raw uint16 packing with no compression algorithm running at all. This step is for squeezing further: gzip and LZ4 see opaque bytes and can't know that [0x01, 0x7F] is token 383. I'm sharing two algorithms for token ID compression but there can be more in future:

  1. ANS: An entropy coder working directly on token IDs can give frequent tokens short codes: "the" is 40x more common than "embeddings", so it gets a much shorter code. I used ANS (Asymmetric Numeral Systems) via the constriction library. Here's a great explanation video from 3Blue1Brown (Thanks to my friend Dev for sharing this video)
  2. Fix token IDs: ANS gives great compression but comes at some latency cost. While running experiments, I realised a simple but major problem in BPE tokenizer algorithm: BPE assigns token IDs in merge-discovery order, not by frequency. So a common token can have ID 40000 while a rare one gets ID 12. Re-assigning IDs by frequency means the most used tokens get the smallest IDs, so variable-length integer compression algorithms can exploit it better. I used streamvbyte and it almost reaches ANS's compression ratio with ~15-20x faster decode! Also, here's my long term proposal for the AI labs.

Both algorithms, ~30 lines of Python each. Expand to see.

ANS:

python
import constriction, numpy as np, tiktoken

enc   = tiktoken.get_encoding("r50k_base")
VOCAB = 50_257

# ── one-time setup: train on corpus ──────────────────────────────────────────
# The ANS frequency table is **trained once on a large corpus and then reused everywhere**.
# It is *not* generated separately for each document, which keeps things efficient.
# While generating a custom frequency table per document can compress even further (up to 5x in my tests),
# this approach requiresincluding that table alongside each payload for decoding. This
# would add roughly 900 bytes per 512-token chunk, negating most of the compression gains.
# That's why we re-use it

corpus_ids = enc.encode(corpus_text)
counts = np.bincount(corpus_ids, minlength=VOCAB).astype(np.float64) + 1  # Laplace
probs  = counts / counts.sum()
model  = constriction.stream.model.Categorical(probs, perfect=False)

# ── per-document encode (store this) ─────────────────────────────────────────
def compress(text):
    ids   = enc.encode(text)
    coder = constriction.stream.stack.AnsCoder()
    coder.encode_reverse(np.array(ids, dtype=np.int32), model)
    return len(ids).to_bytes(4, 'big') + coder.get_compressed().tobytes()

# ── per-document decode ───────────────────────────────────────────────────────
def decompress(data):
    n   = int.from_bytes(data[:4], 'big')
    buf = np.frombuffer(data[4:], dtype=np.uint32).copy()
    ids = constriction.stream.stack.AnsCoder(buf).decode(model, n).tolist()
    return enc.decode(ids)

Frequency-sorted + streamvbyte:

python
import numpy as np, tiktoken, pyfastpfor

enc   = tiktoken.get_encoding("r50k_base")
VOCAB = 50_257
codec = pyfastpfor.getCodec("streamvbyte")

# ── one-time setup: rank tokens by frequency in the corpus ───────────────────
# BPE hands out IDs in merge-discovery order, not by how often a token is
# actually used, so a common token can sit at ID 40,000 while a rare one sits
# at ID 12. Re-ranking by frequency means the most-used tokens get the
# smallest IDs, which is exactly what a variable-length integer codec like
# streamvbyte rewards.
corpus_ids = np.array(enc.encode(corpus_text), dtype=np.int64)
counts = np.bincount(corpus_ids, minlength=VOCAB)
order  = np.argsort(-counts)                          # token id, most → least frequent
rank_of = np.empty(VOCAB, dtype=np.uint32)
rank_of[order] = np.arange(VOCAB, dtype=np.uint32)    # token id  -> frequency rank
token_of_rank  = order                                # frequency rank -> token id

# ── per-document encode (store this) ─────────────────────────────────────────
def compress(text):
    ids = np.array(enc.encode(text), dtype=np.int64)
    ranks = rank_of[ids]
    out = np.zeros(len(ranks) * 2 + 1024, dtype=np.uint32)
    n = codec.encodeArray(ranks, len(ranks), out, len(out))
    return len(ids).to_bytes(4, 'big') + out[:n].tobytes()

# ── per-document decode ───────────────────────────────────────────────────────
def decompress(data):
    n = int.from_bytes(data[:4], 'big')
    packed = np.frombuffer(data[4:], dtype=np.uint32)
    out = np.zeros(n + 1024, dtype=np.uint32)
    codec.decodeArray(packed, len(packed), out, n)
    return enc.decode(token_of_rank[out[:n]].tolist())

Benchmarks

Storage efficiency

I ran benchmarks on three real, held-out, non-overlapping corpora: English (C4), code (codeparrot-clean, Python), and Hindi (Wikipedia), at 512-token chunks (Standard chunk size). Whenever training was required, it happened only on each domain's train split, and every method below is lossless.

Observations:

  1. Raw Token IDs beat the popular byte codecs (LZ4, gzip, zstd) on human languages (English and Hindi) r50k raw hits 2.27x on English (as predicted by napkin math!), o200k reaches 2.55x on Hindi.
    • r50k & cl100k perform poorly on Hindi because they never learnt the Hindi/Devanagari vocabulary. All of them seem to perform poorly on code (maybe due to lots of . in python)
  2. tokenizer+ANS achieves the highest compression ratio and surpass almost every algorithm by a wide margin in all 3 datasets! It beats almost everything on code dataset where raw token IDs didn't do well.
  3. tokenizer+freq bars sits between raw and ANS for all 3 datasets. It satisfies my goal to buy latency for losing some compression and sorting token IDs by frequency turned out to be a clean way to do it.
  4. The fairest byte-codec competitor is zstd --train, since it also learns a vocab from the corpus and achieves similar and sometimes slightly better compression. However, unlike tokenizers, its vocabulary isn't standarized for reuse and encoding latency is much higher. brotli is another interesting algorithm that was trained by Google on a web corpus but is much slower, as we'll see in the next section

Does this hold for every tokenizer? How much is the entropy coder vs frequency sorting vs. the tokenizer?

This isn't an OpenAI-specific trick. Checked directly against real static frequency tables for every modern LLM tokenizer, on the same English (C4) corpus and 512-token chunks as the rest of this post:

TokenizerVocabuint32/16 raw3-byte raw+ static ANS
r50k50,2572.27x (uint16)3.26x
cl100k100,2771.17x1.56x3.27x
o200k200,0191.20x1.60x3.18x
Qwen2.5151,6431.16x1.55x3.18x
DeepSeek-V2100,0001.12x1.50x3.20x
Gemma-2256,0001.17x1.56x3.12x
mxbai WordPiece (embedding model)30,5222.35x (uint16)3.56x
  • All seven land within a tight 3.12-3.27x band (WordPiece a bit higher, more on that below), so the effect clearly isn't an OpenAI or vocab-size artifact.
  • Vocab size doesn't predict the winner either: Gemma has the biggest vocabulary of the bunch and comes out lowest, cl100k (100K) edges out o200k (200K).
  • Bigger vocab isn't a liability once the packing width is fixed: 3-byte packing covers up to 16M+ token IDs, so even Gemma's 256,000-token vocabulary only uses 18 of the 24 available bits. You can practically never exhaust it.

How much does each step contribute: entropy coder vs. tokenizer? Same English (C4) corpus, 512-token chunks:

  • ANS on raw UTF-8 bytes (no tokenization) gets 1.76x, barely better than gzip/zstd and worse than brotli, even though ANS is theoretically the tighter coder, gzip/zstd/brotli exploit repeated substrings via LZ77 too, a plain (order-0) byte-level model can't.
  • Token-level ANS (r50k) jumps to 3.26x. That jump's from the tokenizer, not the coder, subwords are just far more predictable symbols than bytes.
  • Full zstd (LZ77 + entropy) on packed r50k token-ID bytes alone gets 2.73x: beats byte-level zstd but falls short of token-level ANS, since BPE's already folded repeated words into single tokens, there's less left for zstd's substring matching to find.
  • Bottom line: most of the gain is the tokenizer, not the coder. Both ANS (1.76x → 3.26x) and zstd (raw text vs on token IDs) jump once tokens replace bytes as the input symbol, the coder's a smaller, secondary lever on top.

How much of +freq's ratio is the rank-remap, and how much is streamvbyte itself?

  • streamvbyte alone, no rank-remap (raw BPE merge-order token IDs): 2.13x

  • streamvbyte + rank-remap (the actual +freq method): 2.62x

  • streamvbyte already benefits from some token IDs being small (common short words, punctuation) even without remapping, since it's a variable-length integer codec. But roughly half of +freq's total gain over raw packing (2.27x → 2.62x) comes specifically from the rank-remap step, not from streamvbyte on its own — this is the BPE ID-assignment problem from earlier in concrete numbers.

  • What about WordPiece, the tokenizer embedding models actually use? The mixedbread embedding model (mxbai-embed-large-v1) uses BERT's WordPiece tokenizer with a 30,522-token vocabulary, in the table above, landing at the high end of the band (3.56x).

  • Raw uint16 packing is basically a wash against r50k BPE at the median: mxbai's smaller vocabulary barely matters before any entropy coding runs. The gap only opens up once ANS is applied, ~5% better compression at the median.

  • The catch: character error rate (CER). WordPiece decoding is lossy, 80.4% of WikiText-103 articles came back corrupted on decode. Punctuation-spacing errors are regex-fixable and minor, but the real damage is BERT's lowercasing ("Qdrant""qdrant", "Wikipedia""wikipedia"), which isn't recoverable after the fact.

  • mxbai isn't a viable lossless codec for real text, though if lowercase-everywhere is acceptable for your use case, the compression is genuinely better.

Latency

This is the fun part. Now our text compression algorithms will deliver different latency depending on whether the reader/writer is a human or an LLM/agent

Converting to token IDs is a relatively expensive operation. You spend 300-500μs (0.3-0.5ms) on tokenization while LZ4 compression encodes in 12μs. However, when your users are agents, the whole story flips because tokenization is a mandatory step for input/output to/from the model. The difference is only in whether you pay the tokenization/detokenization cost every time or just once:

Observations:

  1. When the writer is an LLM, token native storage is 2-20x faster. LLMs produce token IDs by default. So you can just store them raw or optionally apply token ID compression algorithms in 2-18μs (depending on additional token ID compression algo) while converting token IDs to bytes and then storing with even the fastest LZ4 algorithm will take 35μs!
  2. When the writer is a human, you can tokenize and store in 300-500μs which is heavy but gives great compression gains. In agentic systems like claude code or even chatgpt, agents do most of the talking/writing while humans give instructions in relatively fewer sentences.
  3. brotli, zstd, and zstd --train already have relatively high latency for humans. Now having a detokenize step on top makes even more slower. gzip stays cheap by comparison (56-131μs depending on domain) but its vocab isn't standardized and reusable for agents.

Read is where effect is more pronounced, since it recurs on every read instead of happening once:

Observations:

  1. When the readers is agent, tokenization is an expensive but mandatory step. Agents can only understand token IDs so even the fastest decompressing LZ4 algorithm will take 4μs but then the big tokenization step will consume another ~450μs on every single read. But if you store token IDs, it's free or a tiny 1-40μs token ID decompression (10-100x faster!)

  2. When reader is human, detokenization slower than LZ4 but comparable to gzip. Detokenization is relatively expensive and takes 25-65μs, but agents read hundreds of chunks before presenting anything to humans. So not having token IDs is a loss in each of those reads. And there will be thousands or millions of read queries on the same row/record from the agent over time. So it's much better to just detokenize once in the end before showing to humans (maybe the frontend should do it)

  3. ANS decode (41μs) is ~3-40x slower than reading raw token IDs (2-byte: 1μs, 3-byte: 13μs) which motivated me to propose my design for fixing token IDs which seems to be even faster (10.7μs) than decoding 3-byte token IDs! On English text that costs only 13-20% of ANS's compression ratio, though the gap is much wider on Code/Hindi (up to ~55% for r50k), since +freq's static rank table fits those distributions less tightly than ANS's full probability model does.

Let's do some napkin math to estimate the impact: We start with a ~500μs per read cost but each search query returns 10 chunks for the LLM to read so you lose 5ms of serial CPU time on each such query. Just 11 RPS leads to 30M requests / month each reading 10 chunks = 42 hours wasted / month. Overall, agents already read and write more than humans in agentic systems like claude code or chatgpt. This trend is going to go up.

Also note that every copy of your data enjoy the same 2.27-3.26x reduction, multiplying benefits beyond storage: snapshots, backups, write-ahead log, replicas, and network egress. It means less data moves over the network, more fits in RAM, and reads and indexing get faster.

Full results and latency tables, method glossary

Storage efficiency, min / median / max per method (English/C4, 512-token chunks, 40 test chunks):

Methodminmedianmax
LZ41.14x1.28x1.86x
gzip-91.68x1.95x2.62x
zstd-191.68x1.97x2.66x
brotli-q112.10x2.60x3.31x
zstd --train2.02x2.69x3.12x
r50k raw1.19x2.27x2.69x
cl100k raw0.73x1.56x1.84x
o200k raw0.74x1.60x1.84x
r50k +ANS1.54x3.26x3.76x
cl100k +ANS1.45x3.27x3.74x
o200k +ANS1.42x3.18x3.60x

Latency, median and p99 (same corpus and chunks):

Methodenc medianenc p99dec mediandec p99
LZ47.7µs10.7µs3.6µs4.9µs
gzip-989.0µs109.3µs20.2µs23.9µs
zstd-19686.2µs878.2µs10.2µs13.0µs
brotli-q118368.5µs12414.1µs21.0µs25.0µs
zstd --train973.8µs1478.3µs8.8µs10.4µs
r50k raw420.4µs460.2µs1.4µs2.3µs
r50k +ANS12.6µs14.5µs38.5µs42.9µs
cl100k raw492.5µs622.0µs15.6µs17.8µs
cl100k +ANS12.3µs13.7µs39.9µs42.0µs
o200k raw282.8µs377.6µs15.9µs18.0µs
o200k +ANS12.0µs13.9µs42.7µs49.2µs

(raw enc/dec here are tokenize-included: pack/unpack plus the tokenize/detokenize cost. +ANS enc/dec are the ANS coder alone, tokenize/detokenize not included — see the interactive charts above for the full Agent/Human/write/read breakdown across all three domains, since a single static table can't show that split.)

What the names mean:

  • LZ4: the fast, low-ratio default (used in Qdrant, Elasticsearch, and friends).
  • gzip-9: max compression level, classic deflate. The -9 is the level (1-9).
  • zstd-19: Zstandard at level 19, high compression without the "ultra" tier's compile-time-only levels.
  • brotli-q11: Google's codec at max quality. Best ratio among the untrained/off-the-shelf byte codecs (zstd --train can still edge it out since it gets to see your corpus), but slow, see the write chart.
  • zstd --train: zstd with a 112KB dictionary trained on that domain's own train split (a global dictionary shared across all documents in that domain, same design as the static ANS table), not a generic one.
  • r50k/cl100k/o200k raw: token IDs packed with no entropy coder at all. r50k fits in uint16 (2 bytes); cl100k and o200k need 3 bytes (they exceed uint16's 65,535 range but both fit in 24 bits, so 3-byte packing beats naive uint32 padding).
  • +freq: token IDs re-ranked by real-world frequency, then packed with streamvbyte, a variable-length integer codec. No entropy coder either, just a smarter container (see how it works above).
  • +ANS: token IDs entropy-coded with a static, corpus-trained ANS table (see how it works above).
Cost Savings at scale, and how the text payload compares to the vector next to it

English Wikipedia is ~7M articles averaging 708 words: 31.5 GB of raw UTF-8. At r50k's 3.26x ANS ratio that's down to 9.7 GB (r50k raw packing alone, 2.27x, already gets you to 13.9 GB). Most production vector DBs hold way more than 7M docs though, so scale this to 1 billion documents (same 708-word average): raw storage is 4.5 TB, down to 2.0 TB with r50k raw packing, or 1.4 TB with r50k + ANS. At $0.08/GB for SSD and $0.02/GB for S3-class object storage, for that 1B-document corpus:

Storage tier (1B docs)Raw UTF-8r50k raw (uint16)r50k + ANSSaved (vs raw)
SSD ($0.08/GB)$360/mo$159/mo$110/mo$250/mo
S3 ($0.02/GB)$90/mo$40/mo$28/mo$62/mo

Those savings only matter next to whatever else you're storing, so let's put a real vector next to a real text chunk. Take one sized to mxbai-embed-large-v1's own max sequence length of 512 tokens: at this post's ~4.5 bytes/token, that's 512 × 4.5 ≈ 2,304 bytes of raw UTF-8. mxbai-embed-large-v1 outputs 1024-dim vectors, so float32 (4 bytes per dimension) puts the embedding at 1024 × 4 = 4,096 bytes:

StageSize
Text chunk, raw UTF-82,304 B
Text chunk, r50k uint16 (raw)1,015 B
Text chunk, r50k + ANS707 B
Embedding, 1024-dim float324,096 B
Embedding, 512-dim (MRL) float322,048 B
Embedding, 256-dim (MRL) float321,024 B
Embedding, 1024-dim int8 quantized1,024 B
Embedding, 1024-dim binary quantized128 B

Unquantized, the embedding (4,096 B) dominates the text either way, most collections with big uncompressed embeddings look like this. MRL brings it closer: 512 or 256 dims lands at 2,048 B or 1,024 B, same ballpark as the text instead of dwarfing it. int8 quantization lands in the same place, 1,024 B. Binary quantization is the real outlier: 128 B, ~5.5x smaller than the compressed text chunk (707 B), though check your recall numbers before leaning on it that hard.

How can the AI community and the AI labs help?

I have two request from the AI community/labs:

  1. Standarization and open sourcing of the tokenizers:

    • Token native storage has great advantages and is production ready today. Most LLMs families except Anthropic Claude and Google Gemini (However Gemma did it) have open-sourced their tokenizers. I request these two major players to do the same.
    • Overall we need a UTF-8 / ASCII like standard for tokenizers. This also avoids any kind of vendor or model version lock in. Agents will soon be the biggest users of web, apis, and all infra. We need to prepare new standards that are optimized for them.
    • Closed source LLM APIs don't accept or return token IDs today. There's also need for standardizing token first input/output LLM APIs, even when your storage tokenizer matches the model's exactly, this only works end to end if you own the inference stack, since hosted chat APIs take text prompts and return text, not raw token arrays. See this follow-up proposing a token-ID-native LLM API.
    • For the organizations training embedding/reranker modelsCohere, BGE, Mixedbread, Jina AI, ZeroEntropy, etc, I request you to please re-use the same tokenizers as LLM BPEs so we can unify search and generation systems to work more efficiently together. (Yes, I know it's not easy to get rid of the WordPiece legacy from base models). This has already started: OpenAI's text-embedding-3 family already uses cl100k directly. Jina's embeddings-v4 moved to a BPE tokenizer too, inheriting from its Qwen2.5-VL backbone.
  2. Improving the BPE tokenization algorithm:

    • Request to Anthropic, OpenAI, Google, xAI, Alibaba (Qwen), DeepSeek, and anyone else training the next BPE vocabulary: please re-assign token IDs by frequency rank after building the vocabulary. You can do this on the tokenizer/training corpus or general web. It costs almost nothing but it gives every downstream application some extra compression without any extra effort.
    • Please note that as an user of (or a db/search engine integrating) token native storage, you don't have to wait for this. If you build the remapping on your own corpus, you'll get better token ID compression than a vendor's assigned token IDs anyway :)

Interface

I'm proposing an interface for token-native storage so it's clear what it would look like. First is one-time schema setup: register a field's tokenizer at the collection level.

js
PUT /collections/my_collection/index
{ "schema": { "text": { "type": "token", "tokenizer": "r50k" } } }

Inserts can take token IDs or plain string (the engine tokenizes it)

js
PUT /collections/my_collection/points
{
  "points": [{
    "id": 123,
    "vector": [0.12, -0.34, ...],
    "payload": { "text": [1858, 6427, 20272, 318, 257, 6997, ...] } // or a plain string
  }]
}

The engine can decide to store raw token IDs or compress those using ANS or +freq approach I described above based on config. The token ID compression must happen on write and reverse on read, invisibly.

Reads can just specify the field name and if they need field's data as tokens or text.

js
POST /collections/my_collection/points/search
{ "vector": [...], "limit": 10, "with_payload": { "text": "tokens" } }
// Response:
// {"results": [{"id": 1, "text": [1858, 6427, 20272, 318, 257, 6997, ...]}]}

Default stays "text", so existing clients see no change. An LLM-facing pipeline asks for "tokens" and skips detokenization; anything human-facing asks for "text". This only works end to end if you own the inference stack, since hosted LLM APIs take text prompts, not token arrays (yet).

Limitations

  • No shared vocabulary across models yet. Embedding/reranker models use WordPiece or SentencePiece, LLMs use BPE variants (r50k, cl100k, o200k), and different LLM families/providers don't share the vocab either. A token-native payload only pays off end to end for a consumer using the same tokenizer it was stored with, see how the ecosystem could fix this.
  • Raw token ID compression efficiency can suffer on unknown languages. For a language/script a tokenizer doesn't have a vocab for, token IDs can lose against raw bytes. r50k never learned to merge Devanagari (Hindi) bytes, so the Hindi word भारत (,,,, each a 3-byte UTF-8, 12 bytes total) falls back to 7 token IDs. Storing them as uint16 makes it slightly worse (14 bytes) so you need token ID compression algorithms to recover the ratio. Raw token IDs are still useful for latency either way, if an LLM is going to read/write them.
  • Frequency remapping and ANS are corpus-specific, not free. The remapping itself is cheap but not free to compute, and if there's a big mismatch between the frequency table and your actual corpus, the compression ratio suffers. The core argument, storing raw token IDs instead of UTF-8 bytes, holds regardless of whatever compression you apply on top. See how labs could fix the remap for free, though you don't have to wait for that either.

Key Takeaways

  • Compression: Token IDs surpass every popular byte compression algorithm before you even compress them: raw r50k/o200k hit 1.5-2.5x, entropy-coding on top (ANS) gets to 3.26x! And they generalize and perform well across every tokenizer and dataset I tested.
  • Latency: When the writer or reader is an LLM, tokenization/detokenization is a mandatory steps. Storing token IDs instead of UTF-8 bytes turns a mandatory ~300-500μs cost into a 2-230x faster read/write, since agents produce and consume tokens natively.
  • Token-native storage is production-ready to be adopted in DB/Search engines. But the ecosystem needs to evolve and accept it across the stack for amplifying the impact: models don't share a vocabulary yet, and hosted LLM APIs still only take text input/output for now.

Acknowledgements

The benchmarks use tiktoken, zstandard, lz4, constriction, streamvbyte. Thanks to the concept of napkin math for making me realise this gap.

All benchmark scripts behind this post are on GitHub.

Citation

If you find this useful, please cite:

bibtex
@misc{kumar2026tokenstorage,
  author       = {Kumar Shivendu},
  title        = {Token-Native Storage: Read and Write in Your Agent's Language},
  year         = {2026},
  url          = {https://www.kshivendu.dev/blog/token-storage},
  note         = {Blog post}
}
✓ link copied
← Back to the blog