---
title: miniLLM_tokenizer
canonical_url: "https://www.modelscope.cn/models/kayson2026/miniLLM_tokenizer"
md_url: "https://www.modelscope.cn/models/kayson2026/miniLLM_tokenizer.md"
repository: kayson2026/miniLLM_tokenizer
last_updated: 2026-09-09
pipeline_tag: word-segmentation
tasks:
  - word-segmentation
downloads: 0
stars: 0
---

# miniLLM_tokenizer

> miniLLM_tokenizer - kayson2026 在 ModelScope 开源的模型。miniLLM Tokenizer：8K ByteLevel-BPE 分词器

- **Repository**: kayson2026/miniLLM_tokenizer
- **Tasks**: word-segmentation
- **Downloads**: 0
- **Stars**: 0
- **Last updated**: 2026-09-09

Source: https://www.modelscope.cn/models/kayson2026/miniLLM_tokenizer

---

# miniLLM Tokenizer：8K ByteLevel-BPE 分词器

这是 miniLLM 项目从语料中训练得到的 **8,192 词表 ByteLevel-BPE 分词器**，以中文为主要设计方向，兼顾英文、代码和混合文本。分享本分词器，希望为学习“如何从原始语料构建自己的 Tokenizer”提供一个可运行、可检查的实例。

分词器负责把文本转换成 token ID，并将 ID 序列还原为文本。它可以独立加载和使用，无需语言模型权重或 GPU；生成回答、推理和工具调用能力则需要后续语言模型训练。

## 1. 基本信息

| 项目 | 本次产物 |
| --- | --- |
| 名称 | `miniLLM-tokenizer` |
| 算法 | ByteLevel-BPE（字节级字节对编码） |
| 词表大小 | 8,192，ID 范围为 0～8,191 |
| 词表组成 | 256 个基础字节符号 + 7,911 个合并项 + 25 个预置标记 |
| 实现 | Hugging Face Tokenizers，封装为 `PreTrainedTokenizerFast` |
| 主要语料 | 中文网页、中文百科、英文网页与代码 |
| 文本归一化 | 未配置 Normalizer，不进行大小写转换或 Unicode 归一化 |
| 预分词 | `ByteLevel(add_prefix_space=False, use_regex=True)` |
| 解码 | ByteLevel Decoder；关闭空白清理 |
| 对话格式 | `<\|im_start\|>role\ncontent<\|im_end\|>` |
| 训练元数据日期 | 2026-09-01 |

`tokenizer_config.json` 中的 `model_max_length=32768` 是接口配置值，实际模型的上下文范围取决于模型结构和训练方案。它也不会让所有编码调用自动截断到这个长度。

## 2. 原理与设计思路

### 从字节到子词

```text
原始文本
   ↓ ByteLevel：按规则预切分，将 UTF-8 字节映射为字节符号
基础符号序列
   ↓ BPE：按训练得到的合并规则组合相邻符号
子词 token
   ↓ 查询词表
整数 ID 序列
```

训练时，BPE 反复统计并合并高频相邻符号对；使用时，则按已经保存的合并规则切分新文本。这样既能用较长 token 表示常见片段，也能把没见过的词拆成更小的片段。算法与组件说明可参阅 [Hugging Face Tokenizers 官方文档](https://huggingface.co/docs/tokenizers/components)。

本分词器保留了完整的 256 个基础字节符号，因此有效 UTF-8 文本中的生僻字、emoji 和其他符号都有基础表示。低频字符可能被拆成多个 token，**可编码并不等于切分紧凑**。这里的覆盖来自完整字节字母表，产物中的 `byte_fallback=false` 不代表缺少字节覆盖。

### 为什么使用 8,192 词表

对小型语言模型，词表大小会影响输入嵌入和输出层的规模，也会影响同一段文本占用的 token 数。以隐藏维度 768 为例，`8192 × 768` 的嵌入矩阵有 6,291,456 个参数。

本项目选择 8K，是为了在模型参数成本与中英文、代码的切分长度之间取得适合入门实验的平衡。它属于本次设计选择，尚未通过相同语料下的多词表对比证明其最优性。

### 为什么保留空白和原始字符

代码缩进、换行、Tab，以及英文大小写，都可能影响文本含义。本分词器不主动做大小写转换、Unicode 归一化或解码空白清理，并且不自动在输入开头补空格。对完整的有效文本进行编解码时，可以保留这些信息。

直接查看 `vocab.json` 或 `convert_ids_to_tokens()` 时，部分片段会显示为不直观的字节映射字符。可读文本应通过 `decode()` 获得；单独解码只包含某个汉字部分字节的 token，可能得到替换字符，应以完整序列的解码结果为准。

## 3. 训练语料与方法

### 语料设计

项目采样方案以约 3 GB JSONL 为目标，中文占主要部分，英文和代码补充不同的词形、标点与格式。下表是**采样目标**，来源依据项目采样脚本和训练元数据中的输入文件记录。

| 类别 | 数据来源 | 目标大小 | 目标比例 |
| --- | --- | ---: | ---: |
| 中文网页 | [FineWeb2](https://huggingface.co/datasets/HuggingFaceFW/fineweb-2)，`cmn_Hani` | 1.50 GB | 50% |
| 中文百科 | [Wikipedia](https://huggingface.co/datasets/wikimedia/wikipedia)，`20231101.zh` | 600 MB | 20% |
| 中文高质量网页 | [CCI3-HQ](https://huggingface.co/datasets/BAAI/CCI3-HQ) | 450 MB | 15% |
| 英文网页 | [FineWeb](https://huggingface.co/datasets/HuggingFaceFW/fineweb)，`sample-10BT` | 300 MB | 10% |
| 代码 | [The Stack Smol](https://huggingface.co/datasets/bigcode/the-stack-smol) | 150 MB | 5% |

这些比例按采样文件大小规划，不能直接理解为文档数、字符数或训练后 token 数的比例。分词器元数据没有逐来源实际字节数，实际进入训练的总量如下。

### 实际训练记录

| 项目 | 记录值 |
| --- | ---: |
| 输入文件数 | 5 |
| 读取文档数 | 949,239 |
| 使用文档数 | 949,239 |
| 送入训练的文本块数 | 956,177 |
| 送入训练的字符数 | 1,375,851,167 |
| 分词器训练读取阶段跳过的短文本、无效 JSON 和不支持记录 | 均为 0 |

“字符数”按 Python 字符串长度累计，不是 UTF-8 字节数，也不是训练 token 数。这里的跳过数量只反映分词器训练时的读取阶段；上游采样脚本可以事先进行过滤。

### 训练流水线

1. 按文件流式读取 JSONL/TXT，提取正文或对话中的文本。
2. 过滤去除首尾空白后不足 20 字符的文档，将长文档按 20,000 字符切块。
3. 使用完整字节字母表和预置标记初始化 BPE，学习合并规则，目标词表为 8,192，最低合并频次为 2。
4. 保存词表、合并规则和完整分词流水线，再封装为 Transformers 可加载的分词器。
5. 检查词表大小、核心 ID、结构标记原子性，以及文本编解码的一致性。

本次 `max_documents=0`，即不在训练读取阶段设置文档数量上限。Tokenizer 训练使用 CPU 和系统内存；流式读取减少了 Python 端保存全部语料的开销，BPE 的词频与相邻对统计仍会消耗内存。

## 4. 核心 ID 与结构标记

25 个预置标记已经包含在 8,192 词表内。其中 3 个为核心标记、6 个为思考/工具结构标记，另外预留了 16 个槽位。

| ID | 标记 | 用途 |
| --- | --- | --- |
| 0 | `<\|endoftext\|>` | `pad_token` 与 `unk_token` 的配置值 |
| 1 | `<\|im_start\|>` | `bos_token`，消息开始 |
| 2 | `<\|im_end\|>` | `eos_token`，消息结束 |
| 3、4 | `<think>`、`</think>` | 思考内容边界 |
| 5、6 | `<tool_call>`、`</tool_call>` | 工具调用内容边界 |
| 7、8 | `<tool_response>`、`</tool_response>` | 工具返回内容边界 |
| 9～24 | `<\|reserved_0\|>`～`<\|reserved_15\|>` | 为后续扩展预留的 ID |

思考与工具标记各自编码为一个 token，但在底层 `tokenizer.json` 中设为 `special=false`，因此 `decode(..., skip_special_tokens=True)` 会保留它们。核心和预留标记在底层设为特殊标记，使用同一选项会被跳过。

核心标记的名字与实际配置需要对应理解：本项目用 ID 2 的 `<|im_end|>` 作为 EOS；ID 0 的 `<|endoftext|>` 用于 PAD/UNK。直接把原文中的字面量 `<|endoftext|>` 编码为 0，也不意味着遇到了无法表示的普通字符。

开始训练语言模型后，需要保持 token 到 ID 的映射稳定。已有模型的嵌入矩阵逐行对应这些 ID，词表大小相同并不足以保证两个分词器可互换；模板、特殊 token 语义和合并规则也应与模型训练时匹配。

## 5. 快速使用

### 安装分词所需依赖

使用 Python 3.10 或更高版本，分词和模板渲染只需以下依赖：

```bash
python -m pip install "transformers>=4.51,<4.56" "tokenizers>=0.20,<1.0" "jinja2>=3.1,<4.0"
```

本次文档示例在 `transformers==4.55.4`、`tokenizers==0.21.4` 下验证。下载本目录后，将下面的 `TOKENIZER_DIR` 设置为实际保存位置；示例默认从 miniLLM 项目根目录执行。

### 编码与解码

```python
from transformers import AutoTokenizer

TOKENIZER_DIR = "model/tokenizer"
tokenizer = AutoTokenizer.from_pretrained(
    TOKENIZER_DIR,
    use_fast=True,
    local_files_only=True,
)

text = "你好，世界！miniLLM 从零开始训练。"
ids = tokenizer.encode(text, add_special_tokens=False)
decoded = tokenizer.decode(
    ids,
    skip_special_tokens=False,
    clean_up_tokenization_spaces=False,
)

print("词表大小：", len(tokenizer))  # 8192
print("Token 数：", len(ids))        # 15
print("ID：", ids)
print("还原文本：", decoded)
assert decoded == text
assert tokenizer.encode("你好", add_special_tokens=False) == [588, 540]
```

当前流水线没有自动添加 BOS/EOS 的后处理规则，普通 `encode()` 即使指定 `add_special_tokens=True`，也不会自动包上消息边界。对话数据应使用配套模板构造。

### 渲染对话模板

```python
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained(
    "model/tokenizer", use_fast=True, local_files_only=True
)
messages = [{"role": "user", "content": "你好"}]

prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
)
print(prompt)

# 直接得到用于后续模型输入的 ID 列表。
input_ids = tokenizer.apply_chat_template(
    messages,
    tokenize=True,
    add_generation_prompt=True,
)
```

当前模板默认生成以下前缀，结束位置在空思考块之后：

```text
<|im_start|>user
你好<|im_end|>
<|im_start|>assistant
<think>

</think>

```

模板还支持 `reasoning_content`、`tool_calls`、`role="tool"` 消息和 `tools` 定义。设置 `open_thinking=True` 时，生成前缀会停在未闭合的 `<think>` 后面。这些选项只控制输入文本的组织方式，使用时应与下游模型实际接受的训练格式一致。

普通消息的 `content` 应为字符串；当前模板不会将图片、音频或分段列表转换为多模态输入。若先渲染模板再单独编码，使用 `add_special_tokens=False`，明确由模板负责消息边界。

## 6. 已有验证结果

训练元数据记录的 6 条基础样例，在本次文档整理时重新验证，token 数保持一致，完整编码后解码均精确还原原文：

| 样例 | Token 数 | 完整往返一致 |
| --- | ---: | --- |
| `你好，世界！miniLLM 从零开始训练。` | 15 | 是 |
| `Byte-level BPE keeps English, numbers 12.5%, and punctuation.` | 30 | 是 |
| `Python 示例：\ndef add(a, b):\n    return a + b` | 21 | 是 |
| `中英混合：Transformer 使用 attention 处理上下文。` | 17 | 是 |
| `生僻字与 emoji：𠮷野家 🙂🚀` | 25 | 是 |
| `空白测试：第一行\n    第二行\tTab` | 13 | 是 |

表中的 `\n`、`\t` 表示原始样例中的真实换行和 Tab。

另外，本次运行项目 `eval/eval_tokenizer.py` 的 **14 条内置样例**，覆盖中文、英文、混合文本、代码和边界情况：

| 检查项 | 结果 |
| --- | --- |
| 词表大小、核心 ID | 通过 |
| 思考/工具标记单 token 编码及可见性 | 通过 |
| 对话模板 | 通过 |
| 14 条样例编解码不一致数 | 0 |
| 样例中 UNK token 数 | 0 |
| 样例总 token 数 | 313 |
| 样例平均字符数 / token | 1.897764 |

这些结果用于检查实现是否正常，不构成大规模语料上的压缩率或下游模型效果评测。完整字节覆盖可以保证基础可表示性，但不同语言、专业术语和代码的切分效率仍需用独立语料评估。

## 7. 学习与复现实验

使用完整 miniLLM 项目代码时，可以准备自己的 JSONL 语料，例如每行一条 `{"text":"文档内容"}`，再按同一算法配置训练一个新分词器：

```bash
python trainer/train_tokenizer.py \
  --input dataset/tokenizer_corpus \
  --output model/tokenizer_experiment \
  --vocab-size 8192 \
  --min-frequency 2 \
  --min-chars 20 \
  --chunk-chars 20000 \
  --max-documents 0
```

评估本次分享的分词器：

```bash
python eval/eval_tokenizer.py \
  --tokenizer model/tokenizer \
  --show-examples 5 \
  --report eval/tokenizer_public_eval.json
```

训练命令演示的是同一方法和主要参数。精确重建同一词表还需要相同的语料快照及兼容的库版本；当前训练元数据未记录输入语料哈希和完整运行环境，不能据此保证逐字节复现。

适合进一步比较的问题包括：固定语料时，8K、16K、32K 词表对各语言 token 数的影响；增加代码占比后，代码与自然语言的切分如何变化；保持相同词表预算时，生僻字和领域术语会被拆成多少片段。

## 8. 文件与版本说明

| 文件 | 作用 |
| --- | --- |
| `tokenizer.json` | 完整的分词流水线，包括词表、合并规则、标记和编解码配置 |
| `tokenizer_config.json` | Transformers 加载配置、PAD/BOS/EOS/UNK 设置等 |
| `chat_template.jinja` | 当前聊天模板，含思考及工具调用格式 |
| `vocab.json` | token 字符串到 ID 的映射，便于检查词表 |
| `merges.txt` | 按优先级保存的 BPE 合并规则，便于学习合并过程 |
| `tokenizer_metadata.json` | 初始训练参数、输入文件记录、语料统计、基础检查和当时的文件哈希 |
| `README.md` | 本分词器说明 |

使用 `AutoTokenizer` 时，至少保留 `tokenizer.json`、`tokenizer_config.json` 和 `chat_template.jinja`；公开分享时可完整保留本目录，方便学习与追溯。默认加载只读取本地文件，不要求原始训练语料仍位于元数据中的历史路径。

## 9. 来源与许可

本分词器由 miniLLM 项目的 `trainer/train_tokenizer.py` 训练，基于 Hugging Face Tokenizers 与 Transformers 实现。语料来源见上方链接，原始数据的使用条件分别以对应数据集及源内容说明为准。

当前目录尚未声明本分词器产物的独立许可证。本 README 记录其技术实现与实验结果，供学习交流和复现实验参考。
