快速结论:该报错发生在多线程并发调用同一 tokenizer 实例时,尤其是每次调用的 padding/truncation 设置不同导致每次都会触发内部状态重配的场景。优先排查是否为同一 tokenizer 实例被多线程共享,并检查每次调用的 padding/truncation 参数是否一致。
适用环境:transformers 5.5.4 和 4.57.6、Python 3.12.3、tokenizers 0.22.2(Rust 后端绑定)。
最快修复方案:使用共享的 threading.Lock 包装所有 tokenizer 调用,可消除并发错误(已验证 0/4000 错误);或确保每次调用使用完全相同的 padding/truncation 参数(已验证 0/4000 错误)。
注意事项:上述两种方案是已验证的可行方法,但并非官方修复;多线程性能会因加锁而下降。相同调用参数方案仅在当前设置匹配时跳过内部重配,并非语言级线程安全保证。
问题场景
用户在多线程环境下(如 ThreadPoolExecutor)共享同一个 AutoTokenizer 实例进行编码,且每次调用的 padding/truncation 设置不同(例如一个调用启用 padding 和 truncation,另一个调用不启用),触发并发错误。
报错原文
RuntimeError: Already borrowed
原因分析
可能原因:Rust 后端的 tokenizer 是一个共享的 PyO3 #[pyclass] 对象,内部使用类似 RefCell 的借用检查。当线程 A 执行 encode_batch 时,会释放 GIL 但持有对象的共享借用(&self);此时线程 B 执行 set_truncation_and_padding,需要获取可变借用(&mut self),与线程 A 的共享借用冲突,触发 PyO3 的运行时借用错误。
该问题并非 transformers v5 新增——在 4.57.6 上同样重现(3519/4000 错误)。由于每次调用使用不同 padding/truncation 设置,set_truncation_and_padding 的守卫判断 current != target 恒为真,导致每次调用都触发 &mut 重配,与并发编码的 &self 借用持续冲突。
环境排查
- 确认 Python 版本(Issue 验证:3.12.3)
- 确认 transformers 版本(Issue 验证:5.5.4 和 4.57.6 均复现)
- 确认 tokenizers 版本(Issue 验证:0.22.2)
- 确认是否在多线程中共享同一 tokenizer 实例
- 确认每次调用的 padding/truncation 参数是否一致
解决步骤
- 方案一(已验证):在共享 tokenizer 的所有调用处加入
threading.Lock,确保同一时刻只有一个线程访问 tokenizer。 - 方案二(已验证):确保线程池中所有调用使用完全相同的 padding/truncation 参数(包括 max_length、padding 策略、truncation 策略),使内部守卫跳过重配。
- 如果业务要求每次调用参数不同,可优先尝试方案一,或为不同参数组合创建独立的 tokenizer 实例。
验证方法
运行包含 4000 次并发调用的复现脚本,统计异常数量。预期结果从数千错误降至 0 错误(如 Issue 中验证的 errors=0/4000)。可添加异常类型捕获以确认错误类型为 RuntimeError: Already borrowed。
参考来源
huggingface/transformers #47085
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


