tiktoken in pure Python: byte-for-byte compatible BPE tokenizer with no Rust extension
Project description
puretiktoken
tiktoken in pure Python: byte-for-byte compatible BPE tokenizer for OpenAI models — no Rust extension, no binary wheel, zero dependencies.
OpenAI's tiktoken is a Rust extension. That's fast, but it means a compiled
wheel has to exist (and load) for your platform — which it doesn't on
Pyodide/WASM, often fails on fresh ARM or musl/Alpine boxes, bloats AWS Lambda
layers, and won't build in locked-down sandboxes with no Rust toolchain.
puretiktoken is the same tokenizer written in plain Python, so it runs
anywhere Python runs and produces the exact same token ids.
pip install puretiktoken
import puretiktoken as tiktoken # drop-in: same names, same ids
enc = tiktoken.get_encoding("cl100k_base")
enc.encode("hello world") # -> [15339, 1917] (identical to tiktoken)
enc.decode([15339, 1917]) # -> "hello world"
tiktoken.encoding_for_model("gpt-4o").encode("tokens!") # o200k_base
len(tiktoken.get_encoding("o200k_base").encode(prompt)) # count before an API call
The API mirrors the slice of tiktoken people actually use —
get_encoding, encoding_for_model, encode, encode_ordinary, decode,
decode_bytes, n_vocab, plus allowed_special / disallowed_special
handling — so for token counting and offline tokenization it is a drop-in
replacement.
Why pure Python
tiktoken (Rust), Hugging Face tokenizers (Rust) and sentencepiece (C++)
are all native extensions — there was no pure-Python tokenizer that matches
tiktoken's output. The hard part isn't the BPE merge (that's ~30 lines); it's
tiktoken's pre-tokenizer, a fancy-regex pattern using \p{L}/\p{N} Unicode
classes, possessive quantifiers and a lookahead — features the stdlib
re can't even compile. puretiktoken reproduces that splitter exactly: it
translates each character to a single-byte Unicode class code (the only
Unicode-aware step, done with str.translate at C speed), then runs an
equivalent pattern — now free of \p{...} — in the C re engine, and feeds
the pieces to the same rank-based merge. The result is verified byte-for-byte
against the real tiktoken (see below).
Zero dependencies, zero binaries: the vocabularies ship gzipped inside the
wheel, so it works fully offline — no download of *.tiktoken files at
runtime, which is the other thing that breaks tiktoken in sandboxed or
air-gapped environments.
Speed, stated honestly
A Rust extension should win, but on real-world text the gap is small — and
sometimes the other way. puretiktoken translates each character to a one-byte
Unicode class and lets the C re engine do the splitting, then caches the ids
of each pre-token (real text repeats tokens heavily, by Zipf's law). On prose,
code, JSON or markdown it runs at ~3.5–5.5 M tokens/sec (CPython 3.12),
within ~1.2–2.6× of tiktoken — occasionally faster, because it stays
in-process while tiktoken pays Python↔Rust overhead on every call.
| corpus (CPython 3.12) | cl100k_base |
o200k_base |
|---|---|---|
| code / prose / markdown | ~1.2–1.5× tiktoken | ~2.0–2.6× tiktoken |
| heavily repetitive | ~1.0× (can beat tiktoken) | ~1.5× tiktoken |
| adversarial high-entropy¹ | ~10–11× tiktoken | ~13–14× tiktoken |
¹ Random unique strings where no token ever repeats, so the cache can't help and
it falls back to the pure-Python BPE merge. Real text never looks like this; run
python tools/bench.py to measure on your own.
The point was never to beat Rust — it's getting tiktoken's exact ids where tiktoken won't install or run. That it's now also fast on real text is a bonus.
Verified against the real tiktoken
Conformance is differential, the same way purere2
checks itself against the real RE2 and purefzf
against the fzf binary: text is tokenized by both puretiktoken and tiktoken
and the token ids are compared. Across a curated multilingual/code/emoji corpus
plus tens of thousands of randomized inputs over every assigned Unicode
codepoint, the output is byte-for-byte identical for both cl100k_base
and o200k_base — including the tricky parts: contractions with Unicode case
folding ('ſ folds to 's), o200k's camelCase splitting (iPhone →
i+Phone), combining marks, and whitespace-run edges. Decoding round-trips
losslessly for arbitrary input. The test suite locks this, so any regression
fails CI.
The one place puretiktoken and tiktoken can differ is unassigned
codepoints. tiktoken's Rust regex bundles its own Unicode database; puretiktoken
uses your Python's unicodedata. When those versions disagree about whether a
brand-new codepoint is a letter, the split can differ — but only for text that
contains codepoints unassigned in your Python, which real-world text never does.
The conformance suite asserts this explicitly: every divergence must be
explained by an unassigned codepoint; a mismatch on assigned text is treated as
a bug.
Encodings
cl100k_base (GPT-4, GPT-3.5, text-embedding-3-*, ada-002) and o200k_base
(GPT-4o, GPT-4o-mini, o1, o3). encoding_for_model() maps model names to these,
matching tiktoken's mapping for the current model families.
License
MIT. The bundled vocabularies are OpenAI's, distributed under the MIT terms of tiktoken.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file puretiktoken-0.2.0.tar.gz.
File metadata
- Download URL: puretiktoken-0.2.0.tar.gz
- Upload date:
- Size: 2.5 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9c2901b5166bb10d9ef07c8244683cd8ae636f88f3836085eab1b6be12fd17f8
|
|
| MD5 |
7ffc061d997686637187683e8c00069d
|
|
| BLAKE2b-256 |
3ef0a7722f2172335318fd0b8a05180a2940f26141adb7bc41a6b9ab68e13e95
|
Provenance
The following attestation bundles were made for puretiktoken-0.2.0.tar.gz:
Publisher:
release.yml on adam2go/puretiktoken
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
puretiktoken-0.2.0.tar.gz -
Subject digest:
9c2901b5166bb10d9ef07c8244683cd8ae636f88f3836085eab1b6be12fd17f8 - Sigstore transparency entry: 1869523197
- Sigstore integration time:
-
Permalink:
adam2go/puretiktoken@f6e0256f54ce32fb9bfca48950e5b4f8468e2905 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/adam2go
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f6e0256f54ce32fb9bfca48950e5b4f8468e2905 -
Trigger Event:
release
-
Statement type:
File details
Details for the file puretiktoken-0.2.0-py3-none-any.whl.
File metadata
- Download URL: puretiktoken-0.2.0-py3-none-any.whl
- Upload date:
- Size: 2.5 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4d293ab9e55d48d41523568b5bedb63ce278a86039c5cff1fc133ba57d943c4e
|
|
| MD5 |
e043a118f25e9e41f5ba0c532033bd4a
|
|
| BLAKE2b-256 |
50442594276fdf626cd3804190087b50e6fa345b039f74c610b198191dfe0a0c
|
Provenance
The following attestation bundles were made for puretiktoken-0.2.0-py3-none-any.whl:
Publisher:
release.yml on adam2go/puretiktoken
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
puretiktoken-0.2.0-py3-none-any.whl -
Subject digest:
4d293ab9e55d48d41523568b5bedb63ce278a86039c5cff1fc133ba57d943c4e - Sigstore transparency entry: 1869523275
- Sigstore integration time:
-
Permalink:
adam2go/puretiktoken@f6e0256f54ce32fb9bfca48950e5b4f8468e2905 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/adam2go
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f6e0256f54ce32fb9bfca48950e5b4f8468e2905 -
Trigger Event:
release
-
Statement type: