# tokenizers-moonbit

MoonBit 版 HuggingFace `tokenizers`。直接加载标准 `tokenizer.json`，在
`wasm`、`wasm-gc`、`js`、`native` 后端运行同一套 encode/decode 实现，无 native
依赖。

项目定位：面向 LLM、边缘端和浏览器场景，在 Rust `tokenizers` 不便直接分发或
集成成本较高时，提供纯 MoonBit 的 tokenizer 运行时。

英文主页：[`README.md`](./README.md)

## 目标

- **跨后端一致。** 同一份 MoonBit 源码编译到 wasm/wasm-gc/js/native，不依赖 FFI
  或平台二进制。
- **对齐 HuggingFace。** 使用 Python `tokenizers` 生成期望结果，对真实模型做
  token id 级对拍。
- **直接复用 `tokenizer.json`。** 不需要转换格式，可复用 Python/Transformers
  流水线中的 tokenizer 文件。
- **可选 Hub 下载。** 核心 loader 保持全后端/离线可用；`hub` 包可在 native/js
  后端在线下载 `tokenizer.json`，并写入 HuggingFace 风格 cache。

## 支持范围

- **模型：** BPE、byte-level BPE（含 `byte_fallback` / `fuse_unk` /
  `ignore_merges`）、WordPiece、Unigram、WordLevel。
- **流水线：** Normalizer → Pre-tokenizer → Model → Post-processor → Decoder；
  另支持 AddedVocabulary，用于识别文本中的 special/added token。
- **API：** `encode`、`encode_pair`、`encode_batch`、`decode`、truncation、
  padding、`token_to_id`、`id_to_token`、`get_vocab_size`。

在可选 fixture 存在时，对拍覆盖 39 个真实模型：gpt2、roberta、llama、
bert/bert-cased、distilbert、t5、albert、xlm-roberta、Qwen/DeepSeek/Phi/Mistral/
Falcon/StarCoder/GPT-NeoX/CLIP/GLM/Granite 家族、ModernBERT/GTE-ModernBERT、
SmolLM2，以及 BGE、E5、MiniLM、Jina、Nomic、MixedBread 等 embedding tokenizer。

正则兼容边界是显式的：主流 HuggingFace tokenizer 使用的常见 `Split` / `Replace`
regex family 已用跨后端 deterministic scanner 实现；任意复杂正则，例如
look-around、backreference、完整通用 Unicode regex 语义，仍不在当前支持范围内。
迁移自定义 tokenizer 前请先查看 [`docs/components.md`](./docs/components.md#limitations)。

组件状态见 [`docs/zh/components.md`](./docs/zh/components.md)，路线图见
[`PROGRESS.md`](./PROGRESS.md)。

## 文档

- [使用指南](./docs/zh/usage.md)
- [API 参考](./docs/zh/api.md)
- [组件与限制](./docs/zh/components.md)
- [从 HuggingFace 迁移](./docs/zh/migration-from-hf.md)
- [Benchmark](./docs/zh/benchmarks.md)

## 快速开始

Mooncakes 发布/依赖模块名：`howtomakeaname/tokenizers-moonbit`。

```bash
moon new my-app && cd my-app
moon add howtomakeaname/tokenizers-moonbit
```

`moon add` 只会写入 `moon.mod`，还需要在使用方包的 `moon.pkg` 里声明子包导入：

```text
import {
  "howtomakeaname/tokenizers-moonbit/tokenizer",
}
```

完整可运行程序（`from_file`/`encode` 在输入非法时会 raise，需用 `try`/`catch`
包裹；`fn main` 函数体不能直接调用会 raise 的函数）：

```moonbit
fn main {
  try {
    // 从字符串加载（跨后端，无文件 IO）：
    // let tok = @tokenizer.Tokenizer::from_str(json_text)
    // 或从文件加载（使用 moonbitlang/x/fs，全后端可用）：
    let tok = @tokenizer.from_file("tokenizer.json")

    let enc = tok.encode("Hello world")
    println(enc.ids)
    println(enc.tokens)

    let pair = tok.encode_pair("question", "context")
    let text = tok.decode(enc.ids, skip_special_tokens=true)
    println(text)
  } catch {
    e => println("failed: \{e.message()}")
  }
}
```

运行：`moon run cmd/main`。

native/js 可选：通过 `hub` 包在线下载。其入口是 `async` 函数，需要导入
`moonbitlang/async`（与本库声明的版本一致）、声明 `supported_targets =
"+js+native"` 并使用 `async fn main`：

```moonbit
async fn main {
  try {
    let tok = @hub.from_pretrained("bert-base-uncased")
    // 需要镜像时：
    let tok2 = @hub.from_pretrained(
      "bert-base-uncased",
      options=@hub.HubDownloadOptions::new(endpoint="https://hf-mirror.com"),
    )
    println(tok2.get_vocab_size())
  } catch {
    e => println("failed: \{e.message()}")
  }
}
```

`encode(text, add_special_tokens=false)` 会跳过 post-processor 模板；文本中已有的
special token 仍会被识别。完整细节见
[`docs/zh/usage.md`](./docs/zh/usage.md)。

## 测试

```bash
export PATH="$HOME/.moon/bin:$PATH"
moon test
moon test --target native
```

完整模型对拍需要下载大文件 fixture：

```bash
python3 scripts/fetch_models.py
pip install tokenizers
python3 scripts/gen_parity.py
moon test --target native
```

fixture 缺失时，对拍测试自动跳过。

## License

Apache-2.0。实现参考 HuggingFace `tokenizers` 的算法与文件格式。
