
CodeLlama tokenizer strips one leading space on encode→decode round-trip (regression vs v4)
快速结论:该问题在 transformers v5 迁移后出现,原因是解码器中增加了一个无条件的 decoders.Strip(content=" ", left=1) 步骤,导致所有以空格开头的文本在 encode→decode 往返后丢失一个前导空格。优先排查 tokenizer 的 _decode 方法是否已应用修复补丁,或回退到 transformers v4 系列。
问题场景
用户在使用 transformers v5 加载所有 CodeLlama 系列模型(codellama/CodeLlama-7b-hf、codellama/CodeLlama-7b-Instruct-hf、codellama/CodeLlama-7b-Python-hf、codellama/CodeLlama-13b-hf、codellama/CodeLlama-34b-hf、codellama/CodeLlama-70b-hf)时,执行 encode → decode 往返操作,发现前导空格(leading space)被无声地剥离一个。该问题在 transformers v4 中不存在。
报错原文
BUG: ' leading spaces' -> ' leading spaces'
BUG: ' indented_line' -> ' indented_line'
原因分析
该问题的根本原因是 tokenization_code_llama.py 中解码器序列(第163-164行)包含了一个无条件的 decoders.Strip(content=" ", left=1) 步骤:
self._tokenizer.decoder = decoders.Sequence(
[decoders.Replace("▁", " "), decoders.ByteFallback(), decoders.Fuse(), decoders.Strip(content=" ", left=1)]
)
这个 Strip 步骤原本是为了移除 SentencePiece 规范化器引入的合成 ▁ 前缀(类似于 Prepend ▁ → Replace " " → ▁ 的流程)。但是,在解码管道中,合成前缀和用户提供的前导空格在 ▁ → " " 替换后变得无法区分,导致每次解码时无条件剥离一个前导空格。在 v4 的 CodeLlamaTokenizerFast 中,这种区分是存在的,但 v5 迁移移除了 Fast 类,合并了新的解码逻辑。
进一步分析发现:对于恰好一个前导空格的情况(如 " hello"),即使没有 Strip 也无法正确还原,因为 "hello" 和 " hello" 编码后会得到完全相同的 token ID 列表(均为 [22172, 3186]),这是算法固有的歧义。实际需要修复的是多前导空格场景(如两个以上空格),此时会产生纯 ▁ token。
环境排查
- transformers 版本:v5.x(问题在 v5.10.2 上复现);v4.x 无此问题
- tokenizers 版本:0.22.2
- Python 版本:3.11.2(其他版本可能也受影响)
- 模型路径:所有 CodeLlama 系列 checkpoints(
CodeLlama-7b-hf、CodeLlama-13b-hf、CodeLlama-34b-hf、CodeLlama-70b-hf等均受影响) - PyTorch:不影响(仅 tokenizer 操作)
解决步骤
- 确认问题表现:使用以下代码验证本地环境是否受影响:
- 方案一(推荐,可优先尝试):升级
transformers到包含修复版本(打开 Issue #46491 的修复 PR #46574)。修复方式为:
– 移除decoders.Strip(content=" ", left=1)
– 在CodeLlamaTokenizer类中复写_decode方法,实现条件性 strip:仅当首个真实 token(非特殊 token)的文本内容完全由▁字符组成时,才移除前导空格 - 方案二:如无法升级,临时回退到
transformersv4 系列(如 v4.55.4)。注意回退可能需要调整其他依赖。 - 方案三(自行修补):手动编辑本地
tokenization_code_llama.py文件,移除Strip并添加条件判断。具体实现可参考 PR #46574 中的改动。
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("codellama/CodeLlama-7b-hf")
for s in [" leading spaces", " indented_line"]:
decoded = tok.decode(tok.encode(s, add_special_tokens=False))
print(f"{'OK' if decoded == s else 'BUG'}: {s!r} -> {decoded!r}")
验证方法
修复后运行上述验证脚本,应输出:
OK: ' leading spaces' -> ' leading spaces'
OK: ' indented_line' -> ' indented_line'
同时验证单前导空格场景的合理行为:decode(encode(" hello")) 返回 "hello"(这是算法歧义下的合理选择,与 v4 行为一致)。



