|
| 1 | +<div align="center"> |
| 2 | + <img src="assets/tiktoken_ex.svg" width="400" alt="TiktokenEx Logo" /> |
| 3 | +</div> |
| 4 | + |
| 5 | +# TiktokenEx |
| 6 | + |
| 7 | +**Pure Elixir TikToken-style byte-level BPE tokenizer (Kimi K2 compatible).** |
| 8 | + |
| 9 | +[](https://github.com/North-Shore-AI/tiktoken_ex/actions/workflows/ci.yml) |
| 10 | +[](https://hex.pm/packages/tiktoken_ex) |
| 11 | +[](https://hexdocs.pm/tiktoken_ex) |
| 12 | +[](LICENSE) |
| 13 | + |
| 14 | +TiktokenEx is a small, dependency-light implementation of the core TikToken |
| 15 | +idea: |
| 16 | + |
| 17 | +- Split text with a Unicode-aware regex (`pat_str`) |
| 18 | +- Encode pieces with byte-pair encoding (BPE) using `mergeable_ranks` |
| 19 | +- Optionally recognize special tokens (e.g. `<|im_end|>`) |
| 20 | + |
| 21 | +It’s focused on matching the behavior of MoonshotAI’s **Kimi K2** tokenizers |
| 22 | +that ship a `tiktoken.model` file and a TikToken-compatible `pat_str`. |
| 23 | + |
| 24 | +## Installation |
| 25 | + |
| 26 | +Add `tiktoken_ex` to your dependencies: |
| 27 | + |
| 28 | +```elixir |
| 29 | +def deps do |
| 30 | + [ |
| 31 | + {:tiktoken_ex, "~> 0.1.0"} |
| 32 | + ] |
| 33 | +end |
| 34 | +``` |
| 35 | + |
| 36 | +## Usage |
| 37 | + |
| 38 | +### Build an encoding directly |
| 39 | + |
| 40 | +```elixir |
| 41 | +alias TiktokenEx.Encoding |
| 42 | + |
| 43 | +mergeable_ranks = %{ |
| 44 | + "He" => 0, |
| 45 | + "ll" => 1, |
| 46 | + "llo" => 2, |
| 47 | + "H" => 10, |
| 48 | + "e" => 11, |
| 49 | + "l" => 12, |
| 50 | + "o" => 13 |
| 51 | +} |
| 52 | + |
| 53 | +{:ok, enc} = Encoding.new(pat_str: ".+", mergeable_ranks: mergeable_ranks) |
| 54 | + |
| 55 | +{:ok, ids} = Encoding.encode(enc, "Hello") |
| 56 | +{:ok, text} = Encoding.decode(enc, ids) |
| 57 | +``` |
| 58 | + |
| 59 | +### Load a Kimi K2 encoding from local HuggingFace artifacts |
| 60 | + |
| 61 | +Kimi provides: |
| 62 | + |
| 63 | +- `tiktoken.model` (mergeable ranks) |
| 64 | +- `tokenizer_config.json` (special tokens, etc) |
| 65 | + |
| 66 | +```elixir |
| 67 | +alias TiktokenEx.{Encoding, Kimi} |
| 68 | + |
| 69 | +{:ok, enc} = |
| 70 | + Kimi.from_hf_files( |
| 71 | + tiktoken_model_path: "/path/to/tiktoken.model", |
| 72 | + tokenizer_config_path: "/path/to/tokenizer_config.json" |
| 73 | + ) |
| 74 | + |
| 75 | +{:ok, ids} = Encoding.encode(enc, "Say hi") |
| 76 | +{:ok, decoded} = Encoding.decode(enc, ids) |
| 77 | +``` |
| 78 | + |
| 79 | +### Special tokens |
| 80 | + |
| 81 | +Special tokens are recognized by default. To treat them as plain text: |
| 82 | + |
| 83 | +```elixir |
| 84 | +{:ok, ids} = TiktokenEx.Encoding.encode(enc, "<|im_end|>", allow_special_tokens: false) |
| 85 | +``` |
| 86 | + |
| 87 | +### Regex compatibility note |
| 88 | + |
| 89 | +Kimi’s upstream `pat_str` uses character-class intersections (`&&`), which are |
| 90 | +not supported by Erlang’s PCRE engine. `TiktokenEx.Kimi.pat_str/0` provides a |
| 91 | +PCRE-compatible translation. |
| 92 | + |
| 93 | +## Development |
| 94 | + |
| 95 | +- Run tests: `mix test` |
| 96 | +- Run oracle parity tests (downloads HF artifacts): `mix test --include oracle` |
| 97 | +- Run dialyzer: `mix dialyzer` |
| 98 | + |
| 99 | +## License |
| 100 | + |
| 101 | +MIT © 2025 North-Shore-AI |
0 commit comments