数据截至 (上游 commit d5827816baed)
04 · Python 绑定与生态对接
这一章讲什么: Rust 核心怎么变成
pip install tokenizers之后那个Tokenizer——PyO3 层的结构、GIL 的释放策略、批编码/异步编码/训练三条入口线,以及它作为 transformers「fast tokenizer」地基的生态位置。读完你会知道为什么 fast 比 slow 快一个数量级,以及 free-threaded Python 下这层绑定做了什么额外功课。
1. 它要解决的小问题
Rust 核心写得再快,生态在 Python。绑定层要同时满足四个互相拉扯的要求:
- 快:不能把时间花在跨语言拷贝上;批处理要真能并行(Python 侧 GIL 不放开,Rust 线程池就是摆设)。
- 稳:Python 用户会
multiprocessingfork、会多线程共享 tokenizer、会随时tokenizer.pre_tokenizer = ...改配置——不能 race、不能死锁。 - 全:Rust 侧每个 trait 的每个实现都要在 Python 侧可构造、可组合、可序列化。
- 可训练:训练数据在 Python 侧(list、generator、numpy),喂数据时不能一直被 GIL 卡死。
2. 直觉:薄壳 + 大锁 + 放 GIL
三句话概括绑定层的全部策略:
- 每个 Python 对象就是 Rust 对象的智能指针——
PyTokenizer内部是Arc<RwLock<Tokenizer>>,方法调用 = 拿锁、调 Rust、转错误; - 耗时操作先
py.detach(放开 GIL)再进 Rust——Rust 内部的 Rayon 并行才真正生效; - 跨边界的数据早拷贝、一次成型——Python 字符串进 Rust 就转成 owned,Rust 结果出 Python 前才装箱。
3. 图示:一次 tokenizer.encode_batch 的旅程
Python: tokenizer.encode_batch(["a", "b", ...])
│
▼
绑定层 bindings/python/src/tokenizer.rs:1312
① Vec<PyAny> 逐个 extract 成 tk::EncodeInput(持 GIL,纯拷贝)
│
▼
② py.detach(|| { ... }) ← GIL 在此释放
│
▼
Rust 核 encode_batch_char_offsets(tokenizers/src/tokenizer/mod.rs:1360)
③ maybe_par_iter: Rayon 多线程,每条输入独立走第 1 章的管线
④ pad_encodings 统一长度
│
▼
⑤ Vec<Encoding> 逐个转 PyEncoding(重新持 GIL)
│
▼
Python: list[Encoding]
4. 原理演示(示意代码)
# 示意,非源码:绑定方法的通用形态
def encode_batch(self, inputs):
owned = [to_rust_encode_input(i) for i in inputs] # 持 GIL:拷贝
with gil_released(): # py.detach
results = self.inner.encode_batch(owned) # Rust + Rayon
return [PyEncoding(r) for r in results] # 持 GIL:装箱
5. 真实实现
5.1 模块根部:gil_used = false 与 fork 保命
#[pymodule(gil_used = false)](bindings/python/src/lib.rs:50)声明本扩展在 free-threaded Python(无 GIL 构建)下安全使用——底气来自所有共享状态都在 Rust 侧的 Arc<RwLock> 里,而不是依赖 GIL 保护。
同文件 36-46 行注册了一个 pthread_atfork 回调:
// 摘自 bindings/python/src/lib.rs:39-46
extern "C" fn child_after_fork() {
use tk::parallelism::*;
if has_parallelism_been_used() && !is_parallelism_configured() {
set_parallelism(false);
}
}
原因写在注释里:Python 用户 fork 进程很常见,而 fork 后 Rayon 全局线程池的状态是坏的,子进程一用就死锁——所以子进程里直接把并行关掉。这是典型的「被用户 issue 教育过」的防御代码。