Transformers 5.19.0 首发源码研读:Tokenizer.call() 核心机制与实战验证

以下是基于 transformers 5.19.0 源码编写

python 复制代码
from transformers import AutoTokenizer, TokenizersBackend


# MODEL_PATH = r"E:\LLM-model\Qwen3.5-4B"
# MODEL_PATH = r'E:\LLM-model\Qwen2.5-1.5B-Instruct'
# MODEL_PATH = r'E:\LLM-model\Qwen2.5-3B-Instruct'
MODEL_PATH = r'E:\LLM-model\Qwen3.5-2B'

print('加载分词器中...')
tokenizer = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
tokenizer: TokenizersBackend  # 写上类型,便于写代码

一、__call__() 是什么?有什么用?

在 Python 中,__call__ 是一个魔法方法,它允许一个类的实例像函数一样被直接调用。也就是说,当你写 tokenizer("你好") 时,底层实际执行的就是 tokenizer.__call__("你好")。

它有什么用? 它是整个 Tokenizer 最核心、最全能、抽象层级最高 的入口方法。 前面提到的 tokenize、encode 都只做了流水线上的"一部分"工作,而 __call__() 是一条完整的全自动流水线。它把以下所有步骤打包在一起,一步到位:

  1. 分词(Tokenize)
  2. 查表转 ID(Convert to IDs)
  3. 添加特殊 Token(Add special tokens,如 BOS/EOS/CLS/SEP)
  4. 截断(Truncation,超长切断)
  5. 填充(Padding,不够长补齐)
  6. 生成辅助张量(如 Attention Mask、Token Type IDs)
  7. 格式转换(转为 PyTorch/TensorFlow/NumPy 的 Tensor)

一句话总结 :在日常 99% 的推理和训练场景中,你只需要调用 __call__(),把原始文本扔进去,它直接吐出模型能"吃"的 Tensor 字典。


二、完整的函数签名

以下是 PreTrainedTokenizerBase 中 __call__ 的完整签名(:

python 复制代码
def __call__(
    self,
    text: Union[str, List[str], List[List[str]]] = None,                  # [必填] 主文本或文本批次 (注: 虽默认None, 但实际调用时必须提供)
    text_pair: Optional[Union[str, List[str], List[List[str]]]] = None,   # [可选] 句对任务的第二句话
    text_target: Union[str, List[str], List[List[str]]] = None,           # [可选] 序列到序列任务的目标文本(标签)
    text_pair_target: Optional[Union[str, List[str], List[List[str]]]] = None, # [可选] 目标文本的句对
    add_special_tokens: bool = True,                                      # [可选] 是否添加特殊token
    padding: Union[bool, str, PaddingStrategy] = False,                   # [可选] 填充策略
    truncation: Union[bool, str, TruncationStrategy] = False,             # [可选] 截断策略 (注: 源码默认为 False, 而非 None)
    max_length: Optional[int] = None,                                     # [可选] 最大长度
    stride: int = 0,                                                      # [可选] 滑动窗口步长
    is_split_into_words: bool = False,                                    # [可选] 输入是否已经是预分词列表
    pad_to_multiple_of: Optional[int] = None,                             # [可选] 填充到指定倍数
    return_tensors: Optional[Union[str, TensorType]] = None,              # [可选] 返回的张量类型
    return_token_type_ids: Optional[bool] = None,                         # [可选] 是否返回 token_type_ids
    return_attention_mask: Optional[bool] = None,                         # [可选] 是否返回 attention_mask
    return_overflowing_tokens: bool = False,                              # [可选] 是否返回被截断溢出的token
    return_special_tokens_mask: bool = False,                             # [可选] 是否返回特殊token掩码
    return_offsets_mapping: bool = False,                                 # [可选] 是否返回字符级偏移映射
    return_length: bool = False,                                          # [可选] 是否返回序列长度
    verbose: bool = True,                                                 # [可选] 是否打印警告信息
    **kwargs,                                                             # [可选] 其他向后兼容的参数
) -> BatchEncoding:

三、逐参数详解

  1. 核心输入参数
  • text [必填]
    • 解释 :你要处理的主文本。它的类型非常灵活,可以是单条字符串("你好")、字符串列表(["你好", "世界"],代表一个 Batch),甚至是列表的列表([["你", "好"], ["世", "界"]],配合 is_split_into_words=True 使用)。
    • 注意 :虽然签名中默认值为 None,但如果不传且未传 text_target,方法会抛出 ValueError。
  • text_pair [可选]
    • 解释 :用于"句对"任务(如自然语言推断 NLI、问答 QA、语义相似度)。如果你传了 text="前提句子", text_pair="假设句子",Tokenizer 会在中间自动插入分隔符(如 [SEP]),并生成对应的 token_type_ids 来区分这两句话。
  • text_target / text_pair_target [可选]
    • 解释 :专门用于 Seq2Seq 任务(如翻译、摘要,比如 T5/BART 模型)。当你需要同时编码输入(encoder)和标签(decoder)时,把标签传给 text_target,它会返回一个包含 input_ids 和 labels 的字典。
  1. 预处理控制参数
  • add_special_tokens [可选, 默认 True]
    • 解释 :是否在序列首尾添加模型需要的特殊 Token(如 Qwen 的 <|im_start|>、BERT 的 [CLS] 和 [SEP])。强烈建议保持 True,除非你在做非常底层的自定义拼接。
  • padding [可选, 默认 False]
    • 解释 :控制如何把长短不一的句子补齐到相同长度。
      • False 或 "do_not_pad":不填充。
      • True 或 "longest":填充到当前 Batch 中最长句子的长度。
      • "max_length":填充到 max_length 参数指定的长度。
  • truncation [可选, 默认 False]
    • 解释 :当文本超过 max_length 时如何截断。
      • False 或 'do_not_truncate':不截断(如果超长,底层 Rust 可能会报错或警告)。
      • True 或 'longest_first':截断到 max_length。如果是句对,优先截断最长的那一句。
      • 'only_first':只截断第一句话。
      • 'only_second':只截断第二句话。
    • 补充 :如果设为 True 但未指定 max_length,则会自动回退使用模型的 model_max_length。
  • max_length [可选, 默认 None]
    • 解释 :截断和填充的最大长度阈值。如果不传,默认使用模型配置中的 model_max_length(比如 Qwen 通常是 32768 或更大)。建议显式指定(如 512、2048),以节省显存。
  • stride [可选, 默认 0]
    • 解释 :滑动窗口的重叠步长。配合 truncation 和 return_overflowing_tokens=True 使用。当文本被截断时,stride 决定了截断窗口之间的重叠词元数。注意:只有当 return_overflowing_tokens=True 时此参数才生效。常用于长文本的滑动窗口分类任务。
    • 例如 max_length=512, stride=128,则每次滑动 384 个 token,保留 128 个 token 的重叠上下文(常用于长文档问答)。
  • is_split_into_words [可选, 默认 False]
    • 解释 :如果你传入的 text 已经是分好词的列表(比如 ["Hello", "world"] 而不是 "Hello world"),必须设为 True,否则 Tokenizer 会把它当成一个包含多个句子的 Batch,即 Tokenizer 会把每个字符串当成一个完整句子,而不是一个词。
  • pad_to_multiple_of [可选, 默认 None]
    • 解释 :把序列长度填充到指定数字的倍数(如 8 或 64)。这在某些硬件(如 GPU 的 Tensor Core)上能加速矩阵乘法。注意:仅在 padding 不为 False 时生效。
  1. 输出控制参数(Return 系列)
  • return_tensors [可选, 默认 None]
    • 解释 :极其重要 。决定输出的数据类型。
      • "pt":返回 PyTorch 的 torch.Tensor。
      • "tf":返回 TensorFlow 的 tf.Tensor。
      • "np":返回 NumPy 的 ndarray。
      • None:返回普通的 Python list。
  • return_token_type_ids [可选, 默认 None]
    • 解释 :是否返回 token_type_ids(用于区分句对中的第一句和第二句)。默认 None 表示"如果模型配置需要就返回"。对于不需要句对区分的模型(如 GPT/Qwen),通常不返回。
    • 用于区分句 A 和句 B(0 表示句 A,1 表示句 B)。现代 Decoder-only 模型(如 Llama, Qwen)不需要这个,BERT 等 Encoder 模型需要。
  • return_attention_mask [可选, 默认 None]
    • 解释 :是否返回 attention_mask(1 表示真实 token,0 表示 padding)。默认 None 表示"如果模型配置需要就返回"。绝大多数现代模型都需要它。
  • return_overflowing_tokens [可选, 默认 False]
    • 解释:如果文本被截断了,是否把被截掉的那些"溢出"的 token 也返回。常用于长文档处理。
  • return_special_tokens_mask [可选, 默认 False]
    • 解释:返回一个掩码,1 表示该位置是特殊 token(如 BOS/EOS/PAD),0 表示普通文本 token。常用于 MLM(掩码语言模型)训练时避免 mask 掉特殊 token。
  • return_offsets_mapping [可选, 默认 False]
    • 解释 :只有 Fast Tokenizer 支持 。返回每个 token 在原始字符串中的字符级起止索引(如 (0, 5))。在命名实体识别(NER)和问答(QA)任务中,用于将 token 级别的预测映射回原始文本。Slow Tokenizer 调用此参数会抛出异常。
  • return_length [可选, 默认 False]
    • 解释 :是否在返回字典中额外包含一个 length 字段,表示每个序列的实际长度(不含 padding)。
  • verbose [可选, 默认 True]
    • 解释 :如果文本超长且你没有设置 truncation,是否打印警告日志。生产环境中建议设为 False 以避免日志污染。

四、输入与输出到底是什么?

📥 输入 (Input)

  • 类型 :str、List[str]、List[List[str]],或者它们的组合(配合 text_pair)。
  • 本质 :人类可读的原始自然语言文本(或者已经预分词的列表)。

📤 输出 (Output)

  • 类型 :BatchEncoding(继承自 Python 的 dict)。
  • 本质 :它是一个字典,但被封装成了一个强大的对象。
  • 包含的键(Keys) :
    • input_ids:Token ID 列表(模型最核心的输入)。
    • attention_mask:注意力掩码(告诉模型哪些是 padding,不要关注)。
    • token_type_ids:(可选)句子类型 ID。
    • 其他你通过 return_xxx=True 请求的字段。
  • BatchEncoding 的核心能力 :
    1. 它像字典一样支持 inputs["input_ids"]。
    2. 它支持属性访问 inputs.input_ids。
    3. 它可以直接解包传给模型 :model(**inputs),因为它实现了字典解包协议。
    4. 它自带 .to("cuda") 方法,可以一键把所有内部的 Tensor 转移到 GPU 上。
    5. 它自带 .char_to_token() 和 .word_to_tokens() 方法 :当开启了 return_offsets_mapping 或使用 is_split_into_words 时,可以通过这两个方法直接将原始字符索引或单词索引映射到 token 索引,这在 NER/QA 后处理中比手动解析 offset_mapping 更安全便捷。
    6. 它自带 .tokens() 方法:可以快速查看某条样本编码后的 token 字符串列表,方便调试。

五、常用操作与代码示例

  1. 最基础的单句编码(日常推理)
python 复制代码
# 返回 PyTorch Tensor,自动填充,自动截断到 512
inputs = tokenizer(
    "你好,世界!", 
    return_tensors="pt", 
    padding=True, 
    truncation=True, 
    max_length=512
)
# inputs 包含: {'input_ids': tensor(), 'attention_mask': tensor()}

# 直接喂给模型
outputs = model(**inputs) 
  1. 批量编码(Batching)
python 复制代码
sentences = ["第一句话很短", "第二句话稍微长一点点,需要被填充"]
inputs = tokenizer(
    sentences, 
    return_tensors="pt", 
    padding=True,      # 短句子会被填充到和长句子一样长
    truncation=True, 
    max_length=512
)
  1. 句对编码(用于相似度、分类任务)
python 复制代码
# 比如 BERT 的 NLI 任务
inputs = tokenizer(
    text="一个男人在踢足球", 
    text_pair="一个男人在做运动", 
    return_tensors="pt",
    truncation=True
)
# 内部会自动拼接为: [CLS] 一个男人在踢足球 [SEP] 一个男人在做运动 [SEP]
# 并生成 token_type_ids: [0,0,0,0,0,0,0, 1,1,1,1,1,1, 1]
  1. 获取字符级偏移(用于 NER / 抽取式 QA)
python 复制代码
# 必须是 Fast Tokenizer 才支持 return_offsets_mapping
text = "Apple 是一家科技公司"
inputs = tokenizer(
    text, 
    return_tensors="pt", 
    return_offsets_mapping=True
)

# 方式一:手动解析 offset_mapping
offset = inputs["offset_mapping"][0][1].tolist() # 跳过 BOS
print(text[offset[0]:offset[1]]) # 输出: "Apple"

# 方式二【推荐】:使用 BatchEncoding 内置方法
token_idx = inputs.char_to_token(0, char_index=0) # 获取字符' A'对应的token索引
print(inputs.tokens()[token_idx]) # 输出: "Apple"
  1. 滑动窗口处理超长文本
python 复制代码
# 当文本有 2000 个 token,但模型 max_length 只有 512
inputs = tokenizer(
    very_long_text,
    return_tensors="pt",
    truncation=True,
    max_length=512,
    stride=128,  # 每次滑动 128 个 token
    return_overflowing_tokens=True # 返回被截断的剩余部分
)
# inputs["input_ids"] 会变成一个包含多个 512 长度片段的 Batch
# inputs["overflow_to_sample_mapping"] 会告诉你这些片段属于原始的第几个句子

六、补充:__call__ 与 encode 的核心区别

很多初学者会混淆 tokenizer(text) 和 tokenizer.encode(text),这里做一个明确对比:

特性 __call__() (即 tokenizer(text)) encode() (即 tokenizer.encode(text))
返回值 BatchEncoding (字典,含多个字段) list[int] (纯粹的 ID 列表)
Padding ✅ 支持 ❌ 不支持
Return Tensors ✅ 支持 (直接转 pt/tf) ❌ 不支持 (只能返回 Python list)
Attention Mask ✅ 自动生成 ❌ 不生成
Batch 处理 ✅ 完美支持 ⚠️ 仅支持单条(需手写循环)
使用场景 直接喂给模型(99% 的场景) 只需要 ID(如计算 token 数量、自定义拼接)

终极建议 :只要你的下一步操作是把数据喂给 model(),永远使用 __call__()。

相关推荐
云卷云舒___________5 天前
Qwen本地新模型!Gemini4 Carbon订档本月!Anthropic Fable5.5将至!GPT-6.1Terra现身! | 10月20日 AI日报
openai·qwen·anthropic·ai日报·gemini4carbon·fable55·gpt61terra
小易老师AI实战6 天前
VLLM部署Qwen大模型性能压测实践:自研Python压测脚本+指标解读
大模型·私有化部署·qwen·vllm·llm推理·性能压测
余槐i8 天前
transformers 升级后加载旧权重报错:三处不兼容如何逐一验证
python·大模型·transformers·模型量化·模型推理
死磕音视频11 天前
让小模型做选择题:限定候选项并读取 logits 概率的入门实践(附 Python 代码)
开发语言·python·大模型·文本分类·transformers
虎鲸不是鱼12 天前
【RTX Pro 4500 Blackwell】Qwen3.8 27B NVFP4 MTP在dsh的真实表现
大模型·llm·qwen·lm studio·harness
Jarming13 天前
infinite-creation本地AI短剧带货项目推荐-本地运行无费用0负担
agent·qwen·skill·minimax·deepseek·skills·agent-skills·minimax-h3
天意__14 天前
Linux部署Qwen轻量模型
linux·服务器·qwen
mont evergreen16 天前
RTMP 里的那根“连接线”:读一份 NetConnection 源码
插件·rtmp·源码解读
北风催呀催17 天前
HuggingFace下载镜像加速技巧
linux·huggingface