Count tokens and explore chat templates using HuggingFace tokenizers or OpenAI tiktoken
Project description
llmtokens
Count tokens and explore chat templates using HuggingFace tokenizers or OpenAI tiktoken.
Installation
Run directly without installation:
uvx llmtokens --help
Or install as a persistent tool and run with just the tool name:
uv tool install llmtokens
llmtokens --help
Quick Start
echo "hello world" | llmtokens
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
---------------------------------------------------------------
Qwen/Qwen2.5-0.5B raw 1 2 12 12 3
Qwen/Qwen2.5-0.5B template 6 11 120 120 22
By default, llmtokens shows both raw token counts and templated counts (with the model's chat template applied).
Token Statistics
The tool prints token counts alongside standard text statistics (lines, words, characters, bytes). This makes it useful for comparing tokenizer efficiency across models and languages.
Example: Comparing tokenizers across languages
English (Pride and Prejudice, Jane Austen):
echo "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife. However little known the feelings or views of such a man may be on his first entering a neighbourhood, this truth is so well fixed in the minds of the surrounding families, that he is considered the rightful property of some one or other of their daughters." | llmtokens --mode raw -m Qwen/Qwen2.5-0.5B -m meta-llama/Llama-3.3-70B-Instruct -m mistralai/Mistral-7B-Instruct-v0.3
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
----------------------------------------------------------------------------
Qwen/Qwen2.5-0.5B raw 0 70 375 375 76
meta-llama/Llama-3.3-70B-Instruct raw 0 70 375 375 77
mistralai/Mistral-7B-Instruct-v0.3 raw 0 70 375 375 80
Russian (Anna Karenina, Leo Tolstoy):
echo "Все счастливые семьи похожи друг на друга, каждая несчастливая семья несчастлива по-своему. Все смешалось в доме Облонских. Жена узнала, что муж был в связи с бывшею в их доме француженкою-гувернанткой, и объявила мужу, что не может жить с ним в одном доме. Три дня после этого Степан Аркадьич Облонский — Стива, как его звали в свете, — не выходил из дома." | llmtokens --mode raw -m Qwen/Qwen2.5-0.5B -m meta-llama/Llama-3.3-70B-Instruct -m mistralai/Mistral-7B-Instruct-v0.3
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
----------------------------------------------------------------------------
Qwen/Qwen2.5-0.5B raw 0 61 357 644 138
meta-llama/Llama-3.3-70B-Instruct raw 0 61 357 644 123
mistralai/Mistral-7B-Instruct-v0.3 raw 0 61 357 644 150
Chinese (Dream of the Red Chamber, Cao Xueqin, 红楼梦):
echo "甄士隐梦幻识通灵,贾雨村风尘怀闺秀。此开卷第一回也。作者自云:因曾历过一番梦幻之后,故将真事隐去,而借"通灵"之说,撰此《石头记》一书也。故曰"甄士隐"云云。但书中所记何事何人?自又云:今风尘碌碌,一事无成,忽念及当日所有之女子,一一细考较去,觉其行止见识,皆出于我之上。何我堂堂须眉,诚不若彼裙钗哉?实愧则有余,悔又无益之大无可如何之日也!" | llmtokens --mode raw -m Qwen/Qwen2.5-0.5B -m meta-llama/Llama-3.3-70B-Instruct -m mistralai/Mistral-7B-Instruct-v0.3
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
----------------------------------------------------------------------------
Qwen/Qwen2.5-0.5B raw 0 1 171 505 153
meta-llama/Llama-3.3-70B-Instruct raw 0 1 171 505 172
mistralai/Mistral-7B-Instruct-v0.3 raw 0 1 171 505 217
Chat Templates
The tool can access chat templates provided with HuggingFace models. Chat templates define how models expect input to be formatted, including special tokens, role markers, and system prompts.
Example: viewing template details
echo "What is 2+2?" | llmtokens --show-original --show-template --show-rendered
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
---------------------------------------------------------------
Qwen/Qwen2.5-0.5B raw 1 3 13 13 7
Qwen/Qwen2.5-0.5B template 6 12 121 121 26
============================================================
ORIGINAL TEXT
============================================================
What is 2+2?
============================================================
CHAT TEMPLATE: Qwen/Qwen2.5-0.5B
============================================================
{%- if tools %}
{{- '<|im_start|>system\n' }}
{%- if messages[0]['role'] == 'system' %}
{{- messages[0]['content'] }}
{%- else %}
{{- 'You are a helpful assistant.' }}
{%- endif %}
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
{%- for tool in tools %}
{{- "\n" }}
{{- tool | tojson }}
{%- endfor %}
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
{%- else %}
{%- if messages[0]['role'] == 'system' %}
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
{%- else %}
{{- '<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- for message in messages %}
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
{%- elif message.role == "assistant" %}
{{- '<|im_start|>' + message.role }}
{%- if message.content %}
{{- '\n' + message.content }}
{%- endif %}
{%- for tool_call in message.tool_calls %}
{%- if tool_call.function is defined %}
{%- set tool_call = tool_call.function %}
{%- endif %}
{{- '\n<tool_call>\n{"name": "' }}
{{- tool_call.name }}
{{- '", "arguments": ' }}
{{- tool_call.arguments | tojson }}
{{- '}\n</tool_call>' }}
{%- endfor %}
{{- '<|im_end|>\n' }}
{%- elif message.role == "tool" %}
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %}
{{- '<|im_start|>user' }}
{%- endif %}
{{- '\n<tool_response>\n' }}
{{- message.content }}
{{- '\n</tool_response>' }}
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
{{- '<|im_end|>\n' }}
{%- endif %}
{%- endif %}
{%- endfor %}
{%- if add_generation_prompt %}
{{- '<|im_start|>assistant\n' }}
{%- endif %}
============================================================
RENDERED TEXT: Qwen/Qwen2.5-0.5B
============================================================
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
What is 2+2?
<|im_end|>
<|im_start|>assistant
The example shows the original input, the model's Jinja2 chat template, and the rendered text with special tokens and formatting applied.
Raw vs template mode
Use --mode to control whether chat templates are applied:
# Raw mode: count tokens in the text as-is
echo "What is 2+2?" | llmtokens --mode raw
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
-----------------------------------------------------------
Qwen/Qwen2.5-0.5B raw 1 3 13 13 7
# Template mode: apply chat template and count tokens
echo "What is 2+2?" | llmtokens --mode template
Output:
============================================================
STATS
============================================================
model mode lines words chars bytes tokens
---------------------------------------------------------------
Qwen/Qwen2.5-0.5B template 6 12 121 121 26
The default mode is both-auto, which shows both raw and templated counts when a template is available.
JSON inputs
You can provide structured conversation inputs as JSON arrays:
echo '[{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "What is 2+2?"}]' | llmtokens --wrap-input no
Control input wrapping with --wrap-input:
--wrap-input auto(default): Try to parse as JSON; if that fails, wrap the text as a user message--wrap-input yes: Always wrap the input as a single user message--wrap-input no: Expect JSON array format; fail if input is not valid JSON
Available Models
The tool uses the HuggingFace library and can access any model on HuggingFace Hub. For models in HuggingFace format, it downloads only the tokenizer files (small, typically a few MB), not the full model weights. Downloaded tokenizers are cached by HuggingFace and reused across runs.
Specify models with -m:
echo "test" | llmtokens -m gpt2 -m meta-llama/Llama-3.3-70B-Instruct
Default model: Qwen/Qwen2.5-0.5B
All Options
Model selection
-m MODEL- Specify model (default:Qwen/Qwen2.5-0.5B)- Multiple
-mflags to compare models side-by-side
Modes
--mode raw- Count raw text only (no chat template)--mode template- Apply chat template (warn if missing)--mode either- Use template if available, otherwise raw--mode both- Show both raw and templated (warn if template missing)--mode both-auto- Show both if template available (default)
Input wrapping
--wrap-input auto- Parse as JSON messages if possible, otherwise wrap as user message (default)--wrap-input yes- Always wrap input as a user message--wrap-input no- Parse input as JSON messages array
Display options
--show-original- Show the original input text--show-template- Show the Jinja2 chat template--show-rendered- Show rendered text after applying template--show-all- Show all of the above
Other
-v, --verbose- Verbose outputfile- Read from file instead of stdin
Help
llmtokens --help
Gated Models
Some models (e.g., Llama) require HuggingFace authentication. Either set the HF_TOKEN environment variable or run:
huggingface-cli login
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 llmtokens-0.1.1.tar.gz.
File metadata
- Download URL: llmtokens-0.1.1.tar.gz
- Upload date:
- Size: 11.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
412aae681258b413dd41729604972bbdba056333eb7f445ff19a0c45a92cdb97
|
|
| MD5 |
36af1232d3170409b69d6e41d0d9989e
|
|
| BLAKE2b-256 |
6a4742d6537512ebe3a49c02f36f9b5f57adf58fe7e61a69aae8e44342051e24
|
File details
Details for the file llmtokens-0.1.1-py3-none-any.whl.
File metadata
- Download URL: llmtokens-0.1.1-py3-none-any.whl
- Upload date:
- Size: 9.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.28 {"installer":{"name":"uv","version":"0.9.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7f64822c900453e691c7c66b12a946d00b1b08e65f9370b9a5988953ef3611a5
|
|
| MD5 |
28e6e25ac965bed14862d971f3d6353a
|
|
| BLAKE2b-256 |
6323845e5f2ad5e03d347be390bd68ae3f2c9ec3052e3d33be1b986549f9fb90
|