toklen 0.1.0

A single-threaded, lightweight, and fast token counter.
Documentation
# toklen

A single-threaded, lightweight, and efficient token counter for BPE tokenizers.

**toklen** is a focused Rust library that counts tokens in a text—without allocating
a token list, without decoding, and without generating token IDs. So it's faster.

It loads the standard **tokenizer.json** format used by HuggingFace
**[tokenizers](https://github.com/huggingface/tokenizers)**.
Same config, zero conversion—drop it in and start counting.

It's single-threaded by choice — no Async, no Rayon, no thread pools. Despite this, it is thread-safe:
all internal state is thread-local, so you can safely share the tokenizer across threads.

## Usage

Add to your `Cargo.toml`:

```toml
[dependencies]
toklen = "0.1"
```

Rust code:

```rust
use toklen::Tokenizer;

// Load a standard tokenizer.json (e.g. from a HuggingFace model).
let json = std::fs::read_to_string("tokenizer.json").unwrap();
let tokenizer = Tokenizer::from_json(&json).unwrap();

// Count tokens.
let count = tokenizer.encode_len("Hello, world!").unwrap();
println!("Token count: {count}");

// On failure, encode_len returns Err(estimate) —— input.len() / 4
// as a rough fallback that is always safe for progress reporting.
```

For CLI tools or WASM builds, embed the JSON at compile time:

```rust
const TOKENIZER_JSON: &[u8] = include_bytes!("tokenizer.json");

fn main() {
    let tokenizer = toklen::Tokenizer::from_json(TOKENIZER_JSON).unwrap();
    let count = tokenizer.encode_len("Some text to count").unwrap();
    println!("{count} tokens");
}
```

## API

| Method | Description |
|---|---|
| `Tokenizer::from_json(json)` | Build a tokenizer from `tokenizer.json` bytes. |
| `tokenizer.encode_len(text)` | Count tokens in `text`. Returns `Ok(count)` or `Err(estimate)`. |

The `encode_len` method never panics on valid UTF-8 input. Errors bubble up
from underlying regex or normalization steps; the fallback estimate
(`text.len() / 4`) lets callers degrade gracefully.

## Performance

Its single-threaded throughput is on par with multi-threaded [fastokens](https://docs.rs/crate/fastokens/latest/features)
and roughly 5× faster than [tokenizers](https://docs.rs/tokenizers/latest/tokenizers) on equivalent hardware.

See [`/bench`](bench/) for the benchmark suite and raw numbers.

**Note:**

- `toklen` is thread-safe and designed to be stored as a global static resource (e.g., with `LazyLock`), 
  allowing the tokenizer configuration to be reused across calls without re-initialization overhead.
- The first call to `encode_len()` incurs cold-start overhead (lazy initialization of internal caches).
  Subsequent calls are stable and fast—this behavior is consistent with other tokenizer implementations.

## How it works

```text
input text
  → added-token split (Aho–Corasick)
  → normalization (NFC, sequence)
  → pre-tokenization (ByteLevel, Split, regex)
  → BPE merge counting (priority-queue merge loop)
  → token count
```

## Supported `tokenizer.json` subset

| Section | Status |
|---|---|
| `added_tokens` | Full support (Aho–Corasick matching with memchr prefilter) |
| `normalizer` | NFC + Sequence. Other normalizers return an error. |
| `pre_tokenizer` | ByteLevel, Split (PCRE2 JIT + fancy_regex fallback), Sequence |
| `model` | **BPE only** — other model types error immediately |
| `decoder` | Not used (this is a counter, not a full tokenizer) |

## License

Licensed under either of [Apache License 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT), at your option.