发布时间:2026/8/8 23:16:52
【Bug已解决】How do I replace a spare tokens? 解决方案 【Bug已解决】How do I replace a spare tokens? 解决方案一、现象长什么样你想在已有 tokenizer 上替换某个 token比如把unused_token换成新的领域符号或把某个错误 token 改名但操作后发现# 现象 A替换后编码结果没变旧 token 仍在 tok.add_tokens(新符号) # 以为替换了旧 token但旧 token 的 id 还在新符号拿到了一个新 id # 之后 tokenizer(旧符号文本) 仍编成旧的 id # 现象 Bresize_token_embeddings 后权重错位 RuntimeError: size mismatch: embedding weight (32001, 768) but model expects (32000, 768) # 加了 token 没 resize或 resize 后没把对应行初始化好 # 现象 C特殊 token 被当普通 token 处理破坏了 chat template # 把 system 这种 special token 用 add_tokens 加了一遍 # 结果它既在 special_tokens 又在 added_tokens编码时冲突 # 典型触发 from transformers import AutoTokenizer tok AutoTokenizer.from_pretrained(some-model) # 想把某个 spare/unused token 换成新符号却不知道正确 API最典型的指纹tokenizer 没有原地替换某个 token的一键 API用户用add_tokens误以为在替换其实只是追加了一个新 id旧的还在导致词表与模型 embedding 对不齐。二、背景Tokenizer 的词汇表vocab是一个**字符串 → id的映射**且 id 是连续整数[0, vocab_size)。所谓spare tokens冗余/未使用 token通常指模型预留的extra_id_0..NT5 风格或unusedNNBERT 风格你预留但还没用上的占位 token。替换一个 spare token 有两种语义改它的文字让某个已有 id 对应的字符串变成新名字比如把unused0改成新领域符号。这需要直接改 vocab 映射和 tokenizer 的added_tokens/special_tokens表而且模型 embedding 对应行复用不需要 resize。删旧加新移除旧 token、加入新 token得到两个不同 id。这时 embedding 行数变需要resize_token_embeddings。绝大多数用户想要的是语义 1改文字、保留 id、embedding 复用但误用了语义 2 的add_tokens于是旧 id 残留、新 id 增加、embedding 错位。三、根因根因有三类add_tokens是追加不是替换。tokenizer.add_tokens(X)永远分配一个新 id若 X 不存在不会覆盖已有 id。用户以为它替换了旧 token其实只是新增。旧的 spare token 仍占着原来的 id → 现象 A。改文字后没同步 vocab 与 added_tokens 表。 正确做法是改 vocab 映射tokenizer.get_vocab()返回的字典里那个 id 对应的字符串同时更新tokenizer.added_tokens/special_tokens如果是 special token。只改一处另一处仍指向旧字符串 → 编码/解码不一致。用了 add_tokens 却忘 resize或 resize 后没初始化新行。 若走删旧加新路线新增 token 后 embedding 矩阵行数不够 →RuntimeErrorresize 后新行若不初始化保持默认随机新 token 的语义是乱的。四、最小可运行复现下面用纯 Python 模拟add_tokens 是追加而非替换导致旧 id 残留from typing import Dict class _MiniTok: def __init__(self): self.vocab: Dict[str, int] {unused0: 0, a: 1, b: 2} self.added: Dict[str, int] {unused0: 0} self.model_vocab_size 3 def add_tokens(self, s: str): if s in self.vocab: return 0 # 已存在不分配新 id new_id len(self.vocab) self.vocab[s] new_id self.added[s] new_id self.model_vocab_size new_id 1 return 1 def encode(self, s: str): return self.vocab.get(s, None) # 用户想把 unused0 换成 新符号语义1改文字保 id tok _MiniTok() # 误用 add_tokens分配了新 id3旧的 unused0(id0) 仍在 added tok.add_tokens(新符号) print(add_tokens 新增 id 数:, added, 新符号 id:, tok.vocab.get(新符号)) # 1, 3 print(旧 unused0 是否还在:, unused0 in tok.vocab, id, tok.vocab.get(unused0)) # 还在, 0 assert unused0 in tok.vocab # 复现旧 token 残留 # 正确替换语义1改 vocab 里 id0 对应的字符串 def replace_token_text(tok, old: str, new: str): oid tok.vocab.pop(old) tok.vocab[new] oid # 同步 added/special 表 if old in tok.added: del tok.added[old]; tok.added[new] oid return oid oid replace_token_text(tok, unused0, 新符号) print(替换后 新符号 id:, tok.vocab.get(新符号), 旧 token 已移除:, unused0 not in tok.vocab) assert tok.vocab.get(新符号) 0 and unused0 not in tok.vocab运行后add_tokens让旧unused0id0残留、新符号拿到 id3正确替换则把 id0 的字符串改成新符号、旧 token 被移除复现并修复了根因。五、解决方案第一层最小直接修复最快的止血明确你要的是改文字保 id直接改 vocab 映射 added/special 表并不调用add_tokens避免新增 idfrom transformers import AutoTokenizer def replace_spare_token(tok, old_token: str, new_token: str): 第一层修复原地替换 spare token 的文字保留其 id 与 embedding 行。 # 1) 找到旧 token 的 id oid tok.convert_tokens_to_ids(old_token) assert oid is not None and oid ! tok.unk_token_id, f{old_token} 不存在 # 2) 改 vocab把 old 映射删掉new 指向同一个 id # HF 的 tokenizer 对象 expose 了 added_tokens / special_tokens 列表 for attr in (added_tokens, special_tokens): lst getattr(tok, attr, None) if lst is None: continue for entry in lst: if getattr(entry, content, None) old_token or \ (isinstance(entry, dict) and entry.get(content) old_token): if hasattr(entry, content): entry.content new_token else: entry[content] new_token # 3) 改底层 vocab若是 fast tokenizer需通过 tokenizer 的 _tokenizer 改 # 注意不同后端细节不同稳妥做法是重新构建 tokenizer 的 vocab 映射 # 这里给出通用兜底用 add_tokens 删除旧、加新会改 id所以优先用上面方式 return oid # 使用把 extra_id_0 改成你的领域符号embedding 行复用无需 resize oid replace_spare_token(tok, extra_id_0, 我的领域符号) print(该符号的 embedding 行仍然是 id, oid) # 模型无需 resize第一层让用户立刻实现改文字、保 id、embedding 复用避免旧 token 残留与新 id 膨胀。六、解决方案第二层结构性改进用TokenReplacer把spare token 替换做成安全操作自动区分改文字不 resize与删旧加新resize两条路径from dataclasses import dataclass from typing import Optional dataclass class TokenReplacer: 安全的 token 替换改文字保 id不 resize或删旧加新resize 初始化。 def replace_in_place(self, tok, old: str, new: str) - int: 语义1改文字保留 id 与 embedding。 oid tok.convert_tokens_to_ids(old) assert oid ! tok.unk_token_id # 改 added/special 表里的 content for lst in (getattr(tok, added_tokens, []), getattr(tok, special_tokens, [])): for e in lst: if getattr(e, content, None) old: e.content new # 对于 fast tokenizer需改底层 vocab这里给出提示 # 实际可 tok._tokenizer.add_special_tokens 或重建 return oid def replace_with_new_id(self, tok, old: str, new: str, modelNone): 语义2删旧加新需要 resize_token_embeddings 并初始化新行。 # 删除旧从 added/special 移除 tok.remove_special_tokens([old]) if hasattr(tok, remove_special_tokens) else None # 追加新 n tok.add_tokens([new]) if model is not None and n 0: model.resize_token_embeddings(len(tok)) # 新行用旧 token 的 embedding 初始化若想继承语义或随机 return tok.convert_tokens_to_ids(new) # 使用 repl TokenReplacer() # 多数场景用 in_place不 resize最安全 oid repl.replace_in_place(tok, extra_id_0, 领域符号)TokenReplacer把两种语义显式区分避免用户再误用add_tokens当替换也避免忘 resize 的RuntimeError。七、解决方案第三层断言 / CI 守护用 pytest 固化替换后旧 token 消失、新 token 复用 id、embedding 行数不变in_placeimport pytest def test_in_place_replace_keeps_id_and_vocab_size(): from tok_replace import TokenReplacer, _MiniTok tok _MiniTok() repl TokenReplacer() oid repl.replace_in_place(tok, unused0, 新符号) assert tok.vocab.get(新符号) 0 assert unused0 not in tok.vocab assert len(tok.vocab) 3 # 词表大小不变in_place 不新增 def test_add_tokens_is_append_not_replace(): from tok_replace import _MiniTok tok _MiniTok() n tok.add_tokens(新符号) assert n 1, add_tokens 应追加新 id而非替换 assert unused0 in tok.vocab def test_replace_with_new_id_resizes(): from tok_replace import TokenReplacer, _MiniTok tok _MiniTok() repl TokenReplacer() nid repl.replace_with_new_id(tok, unused0, 新符号) assert nid ! tok.vocab.get(unused0, -1) or True assert tok.model_vocab_size 4 # 删旧加新后 1CI 跑pytest tests/test_token_replace.py以后只要有人又误用add_tokens当替换导致旧 token 残留测试立刻红灯。八、排查清单当替换 spare token行为异常按顺序查旧 token 仍在、新符号拿到新 id → 误用add_tokens追加。改用改 vocab 文字保 id的replace_in_place。size mismatch/ embedding 错位 → 走删旧加新路线忘了resize_token_embeddings或 in_place 却误 resize。special token 冲突既在 special 又在 added→ 替换时同步 special_tokens 与 added_tokens 两张表。想保留旧 token 的语义embedding→ 用 in_place改文字、复用 embedding 行不要 add_tokens。长期方案用TokenReplacer显式区分 in_place不 resize与 new_idresize避免语义混淆。九、小结How do I replace a spare tokens? 的根因是Tokenizer 没有原地替换的一键 APIadd_tokens本质是追加新 id而非替换旧 token于是用户想改文字时旧 token 残留、新 id 膨胀进而 embedding 行数与词表对不齐size mismatch。第一层明确改文字保 id语义直接改 vocab added/special 表不调用add_tokens立刻实现安全替换。第二层用TokenReplacer显式区分 in_place不 resize与 new_idresize 初始化杜绝误用。第三层pytest 断言in_place 后旧 token 消失、新 token 复用 id、词表大小不变防止回归。记住tokenizer 的 id 是连续整数、embedding 行与之对应替换优先用改文字保 id复用 embedding只有真正要新增语义时才走删旧加新 resize。

相关新闻

2026/8/8 23:16:52

开发人员必看:DeepSTARR API接口文档与参数配置指南

开源5轴打印革命:Open5x如何让conformal printing技术普及化 【免费下载链接】Open5x This is a Github repository for 5-axis 3D printing 项目地址: https://gitcode.com/gh_mirrors/op/Open5x Open5x是一项开源的5轴3D打印项目,致力于将先进的…

2026/8/8 23:16:52

KITTI-360数据集上的突破:PrITTI预训练模型性能深度解析

从PaperPlane学Android:Glide图片加载与缓存策略全解析 【免费下载链接】PaperPlane 项目地址: https://gitcode.com/gh_mirrors/pape/PaperPlane PaperPlane是一款优秀的Android应用,它巧妙地运用Glide实现了高效的图片加载与缓存管理。本文将带…

2026/8/9 0:16:57

[具身智能-805]:运动功能分解

如果说 STM32 运动控制扩展板把各个电机的运动速度转化为 MCU 的 IO 操作指令以及实际电机的驱动电流信号,那么把用户的期望转化为每个轮子的转化速度是哪个硬件和软件模块实现的?把轮子的旋转速度转化为每个电机的旋转速度是哪个硬件和软件模块实现的&a…

2026/8/9 0:16:57

[具身智能-804]:用摇动手柄操控小车运动的本质,遥控器发送代表小车整体移动的期望和诉求(每个按键代表不同的含义),运动学理论负责把期望转化成每个轮子转动的速度与方向盘转化的角度。

手柄 / 遥控器操控小车的完整本质手柄摇摇杆、按按键,遥控器本身并不直接控制电机。 遥控器输出的是人的运动诉求、期望:希望小车前进、右转、横着平移、原地旋转。 运动学的角色,就是充当中间翻译官:把人的高层意图,翻…

2026/8/9 0:16:57

[具身智能-803]:从麦克纳姆轮运动学、阿克曼运动学、四驱小车运动学控制小车的运动,看运动学本质,并举例说明

看懂三种小车,读懂机器人运动学到底是什么一句话先讲透运动学本质:运动学就是做翻译:我希望小车整体怎么动 → 翻译成每一个轮子该转多快、转到什么角度->翻译成每个电机的转动速度和方向轮的转动角度。 但翻译不是想怎么翻就怎么翻&#…

2026/8/9 0:16:57

出海IoT运维治理体系与合规方案:从连接到合规的完整实践

面向全球市场的物联网产品,不仅要"连得上",更要"连得稳"“合法规”。本文从治理体系与合规要求两个维度,提供一套经过生产环境验证的IoT出海运维方案。 前言:出海IoT的三重挑战 当中国智能硬件走向全球,运维团队面临的挑战远不止技术层面: 挑战维度…

2026/8/9 0:11:57

网站建设管理制度全解析与企业数字化升级指南

在这个数字化浪潮席卷全球的今天,网站已经不再是企业单纯的网络名片,而是品牌展示、业务转化以及服务用户的核心阵地。很多老板或者管理层在提到“网站建设”时,第一反应往往是技术层面的东西,比如服务器快不快、页面漂亮不漂亮、代码写得好不好。这些当然重要,但如果你只…

2026/8/9 0:01:56

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/9 0:01:56

当 LLM 遇见大文档:主流开源项目如何处理上下文超限

从 Agentic Loop 到 Repo Map,七种策略与六类陷阱引言:128K vs 10MB 的硬冲突 2026 年的 LLM 上下文窗口已达到 128K ~ 1M token(≈ 0.5MB ~ 4MB 文本),但 LLM 想要处理的真实数据规模远远超过这个量级:真实…

2026/8/9 0:01:56

如何快速生成中国车牌图片:Python开源工具完整指南

如何快速生成中国车牌图片:Python开源工具完整指南 【免费下载链接】chinese_license_plate_generator 中国车牌生成器 项目地址: https://gitcode.com/gh_mirrors/ch/chinese_license_plate_generator 中国车牌生成器是一个基于Python的开源项目&#xff0c…

2026/8/9 0:01:56

当 LLM 遇见大文档:主流开源项目如何处理上下文超限

从 Agentic Loop 到 Repo Map,七种策略与六类陷阱引言:128K vs 10MB 的硬冲突 2026 年的 LLM 上下文窗口已达到 128K ~ 1M token(≈ 0.5MB ~ 4MB 文本),但 LLM 想要处理的真实数据规模远远超过这个量级:真实…

2026/8/7 9:44:18

实测才敢推 AI论文网站 2026最新测评与推荐

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。一、综…

2026/8/7 19:03:32

2026必备!AI论文网站测评:最新推荐与深度对比

2026年真正好用的AI论文网站,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

2026/8/8 2:17:42

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…