English | 中文
中英翻译模型,自建分词器,纯 PyTorch 实现。
本项目的使用流程,是从一个换过词表的 1.5B 底座训出中英互译模型,流水线 SFT → CPO → GRPO。
包含从下载语料到发布上 Hugging Face 的完整流程:数据构建、三阶段训练、评测、
导出发布。模型与训练代码(src/)只依赖 torch —— Qwen3 的 forward、LoRA、
Adafactor、GRPO 的策略梯度、safetensors 读写都是自己实现的。
这个仓库同时是一组 A/B 对照。 主线用自训的 Piece 分词器(81903),
Qwen/ 用 Qwen3 原生 BBPE(151643),两边共享同一批数据、同一套超参与奖励,
只差底座和分词器。要回答的问题是:换掉词表之后,走完流水线能不能追上?
import torch
from transformers import AutoModelForCausalLM
from tokenizer import PieceTokenizerWrapper # 发布包自带
tok = PieceTokenizerWrapper(".")
model = AutoModelForCausalLM.from_pretrained(".", torch_dtype=torch.bfloat16).cuda().eval()人工智能正在深刻改变我们的生活方式。
→ Artificial intelligence is profoundly changing our way of life.
Machine translation has improved dramatically in recent years.
→ 机器翻译在近几年有了显著的进步。
| Hugging Face | 分词器 | WMT23 zh→en | WMT23 en→zh |
|---|---|---|---|
Interpreter-Qwen3-1.7B |
Qwen3 BBPE | 20.31 / 0.8053 | 33.69 / 0.8540 |
Interpreter-Qwen3-1.7B-ReTok |
Piece(自训) | 19.29 / 0.7986 | 32.46 / 0.8512 |
BLEU / COMET,COMET 是 Unbabel/wmt22-comet-da。前者的 zh→en COMET 与 1.8B 的
HY-MT1.5 打平(0.8053 vs 0.8052)。
两个底座不微调、直接 5-shot 的成绩(同协议,WMT21 demos):
| 分词器 | WMT23 zh→en | WMT23 en→zh | |
|---|---|---|---|
Qwen3-1.7B-Base |
Qwen3 BBPE | 21.83 / 0.7950 | 41.15 / 0.8490 |
ReTok 底座(phase2_ckpt_v18_tie) |
Piece(自训) | 17.44 / 0.7582 | 38.10 / 0.8255 |
不微调的 Qwen 底座 BLEU 比流水线产物还高 —— en→zh 41.15 → 33.69,掉 7.5 分, 换来 COMET +0.005。整条 SFT→CPO→GRPO 是拿词汇重合度换 COMET 的充分性,而 COMET 又正是 GRPO 的奖励。报提升要对着这两行报,不能只对着 SFT 报 —— 否则会把「补回自己造成的损失」说成「提升」。唯一四项全面超过 base 5-shot 的 是调过参的 SFT→GRPO(22.85 / 0.8003 · 41.97 / 0.8540)。
底座这两行有两套测量,差在 demo 选取和停止条件:上表是仓内可复现的那次, Summer 侧另测过 22.17 / 0.7958 · 42.85 / 0.8490(Qwen)和 19.60 / 0.7834 · 40.99 / 0.8377(ReTok)。COMET 几乎相同而 BLEU 差 0.3~2 分,仓内比较一律用 上表,别混。
huggingface-cli download Ismantic/Interpreter-Qwen3-1.7B-ReTok --local-dir ReTok
pip install git+https://github.com/Ismantic/PieceTokenizer
cd ReTok && python example_load.pyPiece 那个不是即插即用的 HF 模型 —— 分词器是编译的 C++ 扩展,
AutoTokenizer 加载不了,发布包里带了 tokenizer.py 和 example_load.py。
相对 Qwen 基线的 COMET 差(两个方向):
| 阶段 | zh→en | en→zh |
|---|---|---|
| SFT | −0.0162 | −0.0164 |
| CPO | −0.0076 | −0.0027 |
| GRPO | −0.0036 | −0.0029 |
换分词器在 SFT 阶段损失最大(约 −0.016),CPO/GRPO 把大部分差距补回来, 到 GRPO 时收窄到 −0.003 ~ −0.004。 这是这个项目的主要发现。
BLEU 不同步收窄,而且 GRPO 那行的大差距是路径差不是分词器差 ——
要纯比分词器效应看 CPO 那一行。详见 docs/WHY.md。
data/ 下载 + 构建三阶段数据。source.py 是数据源注册表,唯一的真相来源
prepare/ 编排:装依赖、分词、预编码、调训练、采样打分、评测
src/ 模型 + 训练。只依赖 torch
save/ 导出 HF 发布包 + 上传 + 与线上核对
外加 Qwen/(A/B 对照,自带 prepare/ src/ save/,共享根 data/)、
docs/、test/。make help 看命令,每层还有自己的 make help。
两条不能破坏的分层约束:
src/只依赖 torch。 Qwen3 forward、LoRA、Adafactor、GRPO 的更新都是 自己实现的(替掉 transformers / peft / trl)。加依赖前先想能不能放prepare/。src/不碰文本。 分词、对话模板、截断全在prepare/encode.py,src/只读预编码好的 id。所以 PieceTokenizer 不是src/的依赖。
Qwen/ 不受这两条约束 —— 那一层的价值是「别人用标准工具链能复现的基线」,
忠实比可读重要。
make deps # 从 GitHub clone + 编译 PieceTokenizer
make -C data required # 下载语料与模型资产
bash prepare/pipeline.sh # SFT → CPO → GRPO
bash prepare/pipeline.sh sft_grpo # SFT → GRPO(两步,跳过 CPO)
make test # 回归防线两步和三步的 COMET 是打平的,而两步的 BLEU 高 2.5 / 8.3 分。 三步多花一次 CPO 训练,换来的是 en→zh BLEU 掉 8 分。两条都留着是因为 CPO 阶段本身是本项目 重要发现的来源。要复现最优模型走两步,要理解流水线走三步。
语料、底座、评分模型、C++ 扩展全部可从公网重建,不允许指向本机既有目录。
加新数据源必须在 data/source.py 登记公开出处;找不到出处的宁可不用。
| Summer | 产出本仓库的底座:给 Qwen3-1.7B-Base 换词表(ReTok) |
| PieceTokenizer | Piece 分词器的 C++ 实现与词表 |
怎么跑看 make help,部署看 HF 上的 model card(发布包自带
example_load.py,cd 进去就能跑)。
Apache-2.0。训练数据源自 ALMA / X-ALMA 平行语料与 WMT 测试集, 底座源自 Qwen3-1.7B-Base,请一并遵守上游许可。