首页 > 其他分享 >深入解析 Transformers 框架(三):Qwen2.5 大模型的 AutoTokenizer 技术细节

深入解析 Transformers 框架(三):Qwen2.5 大模型的 AutoTokenizer 技术细节

时间:2024-11-01 20:20:52浏览次数:4  
标签:AutoTokenizer Transformers tokenizer 技术细节 Qwen2.5 模型 词表 tokens Token

前面 2 篇文章,我们通过查看 Transformers 包代码,学习了 Transformer 包模块 API 设计、模型初始化和加载流程:

本文是 Transformers 推理 LLM 大语言模型技术细节的第 3 篇,我们将基于 Qwen2.5 大模型,通过走读 Transformers 源代码的方式,来学习AutoTokenizer技术细节:

  1. 环境准备:配置虚拟环境,下载 Qwen2.5 模型文件
  2. AutoTokenizer分词器介绍、初始化和存储代码流程的技术细节
  3. Qwen2.5使用的分词算法介绍,和一些常用的 Token 操作用法

环境准备:配置虚拟环境和下载模型文件

【配置虚拟环境】 我们可以继续使用在上一篇中我们已经配置好的虚拟环境:

# Python虚拟环境名:Qwen2.5,版本号:3.10
conda create -n Qwen2.5 python=3.10 -y

# 激活虚拟环境
conda activate Qwen2.5

# 安装必要的Python依赖包
pip install torch
pip install "transformers>=4.43.1"
pip install "accelerate>=0.26.0"

【下载 Qwen2.5 模型文件】 我们也可以继续使用在上一篇中下载好的模型文件:

# Git大文件系统
git lfs install

# 下载模型文件
git clone https://www.modelscope.cn/qwen/Qwen2.5-1.5B-Instruct.git Qwen2.5-1.5B-Instruct

# 若下载过程中异常中断,可以通过`git lfs install`命令继续下载:
# 切换到Git目录
cd Qwen2.5-1.5B-Instruct

# 中断继续下载
git lfs install
git lfs pull

AutoTokenizer 初始化和存储流程

在大模型中,分词就是把模型的输入内容(如:文本序列)转换为Token(也称:词元)序列,Token 是最小的语义单元,且每个 Token 都有相对完整的语义。

如下代码示例,我们可以通过AutoTokenizer.from_pretrained方法初始化分词器:

import os

from transformers import AutoTokenizer

# 初始化分词器,从本地文件加载模型
model_dir = os.path.join('D:', os.path.sep, 'ModelSpace', 'Qwen2.5', 'Qwen2.5-1.5B-Instruct')
tokenizer = AutoTokenizer.from_pretrained(
    model_dir,
    local_files_only=True,
)

根据第 1 篇Transformers 包模块设计,我们可以找到AutoTokenizer类定义在./models/auto/tokenization_auto.py模块中,我们可以走读from_pretrained方法执行流程:

第 1 步AutoTokenizer.from_pretrained解析tokenizer_config.json配置文件,获取tokenizer_class配置项,Qwen2.5 的配置文件中的值为Qwen2Tokenizer

AutoTokenizer根据配置获取分词器类

第 2 步:默认情况下,Transformers 优先使用带有Fast结尾的、性能更好的分词器实现。因此会先把Qwen2Tokenizer类型转为Qwen2TokenizerFast类,并调用tokenizer_class_from_name()方法加载Qwen2TokenizerFast类:

加载Qwen2TokenizerFast类

最终,成功加载Qwen2TokenizerFast类后,调用Qwen2TokenizerFast.from_pretrained进一步完成初始化。

其中,tokenizer_class_from_name()是一个重要的方法,它的定义如下,我们可以看到它的实现和我们第 1 篇中动态模块加载非常类似:

动态加载Qwen2TokenizerFast类

执行逻辑是从TOKENIZER_MAPPING_NAMES常量中,循环匹配到Qwen2TokenizerFast类型,并且得到qwen2模块名称,为动态加载提供完整的模块路径:

# ...省略...
(
    "qwen2",
    (
        "Qwen2Tokenizer",
        "Qwen2TokenizerFast" if is_tokenizers_available() else None,
    ),
),
# 说明:is_tokenizers_available() 方法定义在 transformers.utils.import_utils.py 模块中,其值为 True
# ...省略...

从动态加载代码可以看出,Qwen2TokenizerFast类定义在transformers.models.qwen2.tokenization_qwen2_fast.py模块中:

第 3 步:分词器执行Qwen2TokenizerFast.from_pretrained方法,由于Qwen2TokenizerFast -> PreTrainedTokenizerFast -> PreTrainedTokenizerBase类继承链,from_pretrained方法实际在PreTrainedTokenizerBase类定义。

PreTrainedTokenizerBase.from_pretrained方法中,主要是在收集配置参数文件列表,最终执行Qwen2TokenizerFast._from_pretrained方法,实际还是PreTrainedTokenizerBase._from_pretrained方法:

收集配置文件列表

最终收集到的配置文件不一定都存在,其中 vocab_file/merges_file/tokenizer_file/tokenizer_config_file 存在对应的文件,而 added_tokens_file/special_tokens_map_file 文件却并不存在。文件不存在其实不影响接下来的处理逻辑,因为收集文件的目的是为了解析且内,只要内容存在就可以了,接下来我们将会看到。

第 4 步:解析tokenizer_config.json配置文件,收集初始化参数(init_kwargs变量):

解析并收集配置参数

其实在第 1 步的时候,为了获取tokenizer_class配置项,这个配置文件就解析过一次。然而在这里再次解析了一次,并且再次获取了一次该配置项!

第 5 步:继续收集参数,包括 3 个配置文件路径,和tokenizer_config.json配置文件中的added_tokens_decoder配置项字典元素内容:

解析并收集配置参数

收集的 3 个文件为 vocab_file/merges_file/tokenizer_file,而added_tokens_decoder配置项内容为特殊 Token ID 和映射。

第 6 步:规整化收集到的特殊 Token 参数,最后进行Qwen2TokenizerFast类实例化:

Qwen2TokenizerFast类实例化

至此,AutoTokenizer.from_pretrained初始化完成,其实我们也可以看到,其实我们直接使用Qwen2TokenizerFast.from_pretrained方法结果一样,并且还可以直接跳过第 1 步第 2 步解析Qwen2TokenizerFast的处理过程,因此代码执行效率会更高一些。

最后,老牛同学用一张图对上面步骤进行简单总结:

AutoTokenizer初始化流程

接下来,我们可以通过XXXTokenizer.save_pretrained方法存储分词器:

# 存储分词器
save_dir = os.path.join('D:', os.path.sep, 'ModelSpace', 'Qwen2.5', 'Qwen2.5-1.5B-Instruct-COPY')
tokenizer.save_pretrained(save_dir)

执行完成,我们可以看到Qwen2.5-1.5B-Instruct-COPY目录中 6 个文件,而这 6 个文件,正是初始化过程中收集的那 6 个文件:

Tokenizer存储文件列表

分词器存储的文件列表中,vocab.json 就是我们的词表文件,文件内容是一个大字典,字典键为 Token,字典值就是对应的 Token ID(从 0 开始)值。

Qwen2.5 字节对编码(Byte Pair Encoding, BPE)分词算法

接着,我们尝试用编辑器打开vocab.json词表文件可以看到:第 1 个 Token 是!,接着是一些数字、字母、标点符号等 Token,这些还好理解;接下来是一些如ortass等英语短语,它们不是完整的单词,当然也能看到一些如atCheckinner等完整单词;在接下来感觉就开始是乱码了,应该不是完整的汉字。最大的 Token ID 为151642代表了词表的大小。

根据上面看到的内容初步判断:Qwen2.5 并不是按照单词或者汉字粒度进行分词,我们也可以从Qwen2TokenizerFast类源代码注释也可以佐证(Based on byte-level Byte-Pair-Encoding.)。

首先有个疑惑:大模型为什么不能按照单个完整的单词或者汉字的粒度进行分词,这样分词的方法不是更加便于理解、同时分词结果也更加直观吗?

老牛同学认为有 2 个主要的考虑因素:

  • 能有效控制 Token 总数量,不至于随着单词或者汉字等词汇的增长而膨胀,可以有效地节省内存和计算资源;同时,当有新造词出现时,无需更新模型的词表。
  • 能有效处理预训练时未遇见或罕见词汇,因为分词算法将这些词汇分解为已知的 Token 单元。

字节对编码(Byte Pair Encoding, BPE)是一种流行的分词算法,它的主要思想是通过迭代合并最常见的字符对来生成词汇表。主要步骤:

  1. 初始化词汇表:从字符级别开始,词汇表包含所有出现的字符。
  2. 统计字符对频率:统计文本中所有字符对的出现频率。
  3. 合并最常见的字符对:将出现频率最高的字符对合并为一个新的 Token,并更新词汇表。
  4. 重复步骤 2 和 3:重复上述过程,直到达到预定的词汇表大小或满足预设的停止条件。

举一个简单例子:假设我们的语料库就一句话Hello World.,我们首先统计单词出现频率(“Hello”:1 次,“ ”:1 次,“World”:1 次, “.”:1 次)

  • 第 1 步,初始化词汇表:
  • 第 2 步,统计字符对频率:
  • 第 3 步,合并最常见的字符对:l 和 o 频次最高,组合的 Token 为llo
  • 继续第 2 步,此时词汇表:
  • 继续第 3 步,假设合并 H 和 e 组成新 Token 为He
  • 继续第 2 步,此时词汇表:

假设预设停止合并条件为:词汇表大小不超过 9 个词汇,则此时即完成了词汇表的生成过程。

以上是英文构建词汇表,对于中文来说类是,比如中文语料库:台风又双叒叕来了!

  • 第 1 步,初始化词汇表:'台', '风', '又', '双', '叒', '叕', '来', '了', '!'
  • 第 2 步,统计所有相邻字符对的频率:('台', '风'): 1,('风', '又'): 1,('又', '双'): 1,('双', '叒'): 1,('叒', '叕'): 1,('叕', '来'): 1,('来', '了'): 1,('了', '!'): 1
  • 第 3 步,合并最常见的字符对:由于字符对的频率相同,因此可选择任意一个进行合并,比如合并('台', '风')为:'台风', '又', '双', '叒', '叕', '来', '了', '!'
  • 继续第 2 步,统计频率;然后第 3 步合并字符对,直到达到终止条件

Token 常用操作:分词、编码和解码、添加 Token 等

有了分词器和词表,我们就可以对输入的文件进行分词、映射 ID、根据 Token ID 解码成文本、往词表中添加 Token 等操作。

老牛同学下面展示的代码片段,多次使用 tokenizer 实例,建议使用 Jupyter Lab 编辑器:大模型应用研发基础环境配置(Miniconda、Python、Jupyter Lab、Ollama 等)

text = 'Transformers分词:台风又双叒叕来了!'
tokens = tokenizer.tokenize(text)

print(tokens)

# 输出:['Transform', 'ers', 'åĪĨ', 'è¯į', 'ï¼ļ', 'åı°é£İ', 'åıĪ', 'åıĮ', 'åı', 'Ĵ', 'åıķ', 'æĿ¥äºĨ', 'ï¼ģ']

从上述输出可以看出:Transformers单词被分成了Transformers两个 Token。我们可以把上面的 Token 映射其 Token ID:

ids = tokenizer.convert_tokens_to_ids(tokens)

print(ids)

# 输出:[8963, 388, 17177, 99689, 5122, 108118, 99518, 99493, 5758, 240, 122378, 101161, 6313]

以上 2 步操作的结果,其实可以通过编码方法一步完成:

# 编码
token_ids = tokenizer.encode(text)

print(token_ids)

# 输出:[8963, 388, 17177, 99689, 5122, 108118, 99518, 99493, 5758, 240, 122378, 101161, 6313]

可以看出:tokenizer.encode编码操作,其实是上面tokenizer.tokenize分词和tokenizer.convert_tokens_to_ids映射 2 个操作的组合。

Token ID 是计算机识别的,我们可以通过词表和分词器把 Token ID解码成文本内容:

# 解码
token_text = tokenizer.decode(token_ids)

print(token_text)

# 输出:Transformers分词:台风又双叒叕来了!

接下来,我们来看看如何往词表中增加 Token:添加普通 Token 和添加特殊 Token。

# 添加普通Token,词表中已存在的Token会被忽略
new_tokens = ["老牛同学", "imxulin"]
new_tokens = set(new_tokens) - set(tokenizer.vocab.keys())

num_add_tokens = tokenizer.add_tokens(list(new_tokens))

print(f'新增加 {num_add_tokens}个普通Token到词表。')

# 输出:新增加 2个普通Token到词表。

添加特殊 Token 的方法是:add_special_tokens,入参是字典,键只能从bos_token, eos_token, unk_token, sep_token, pad_token, cls_token, mask_token, additional_special_tokens中选择:

# 添加特殊Token,词表存在则忽略
mew_special_tokens = {'cls_token': '[LNTX]'}

num_add_spec_tokens = tokenizer.add_special_tokens(mew_special_tokens)

print(f'新增加 {num_add_spec_tokens}个特殊Token到词表。')
print(f'特殊Token值:{tokenizer.cls_token}')

# 输出:
# 新增加 1个特殊Token到词表。
# 特殊Token值:[LNTX]

解下来,我们可以验证以下我们添加的 Token 了:

text = '[LNTX]大家[LNTX]好,我是老牛同学,他是一位[LNTX]大模型[LNTX]爱好者!'
tokens = tokenizer.tokenize(text)

print(tokens)

# 输出:['大家', '[LNTX]', '好', 'ï¼Į', 'æĪijæĺ¯', '老牛同学', 'ï¼Į', 'ä»ĸ', 'æĺ¯ä¸Ģä½į', '大', '模åŀĭ', '[LNTX]', 'çĪ±å¥½èĢħ', 'ï¼ģ']

根据输出我们可以看到:新添加的[LNTX]特殊 Token,和老牛同学普通 Token,在分词结果中都直接作为了一个完整的 Token,没有被进一步的切分。

最后,当我们更新了词表后,为了能让大模型推理过程能正常进行,我们还需要调整模型的 embedding 矩阵大小:

print(f'调整前:{model.model.embed_tokens.weight.size()}')

model.resize_token_embeddings(len(tokenizer))

print(f'调整后:{model.model.embed_tokens.weight.size()}')

# 输出:
# 调整前:torch.Size([151936, 1536])
# 调整后:torch.Size([151668, 1536])

分词器的其他用法,如编码和解码多段文本、Token ID 张量填充对齐、超长截断等,请大家阅读官网,有中文版:https://hf-mirror.com/docs/transformers/v4.46.0/zh/index


往期推荐文章:

基于 Qwen2.5-Coder 模型和 CrewAI 多智能体框架,实现智能编程系统的实战教程

vLLM CPU 和 GPU 模式署和推理 Qwen2 等大语言模型详细教程

基于 Qwen2/Lllama3 等大模型,部署团队私有化 RAG 知识库系统的详细教程(Docker+AnythingLLM)

使用 Llama3/Qwen2 等开源大模型,部署团队私有化 Code Copilot 和使用教程

基于 Qwen2 大模型微调技术详细教程(LoRA 参数高效微调和 SwanLab 可视化监控)

ChatTTS 长音频合成和本地部署 2 种方式,让你的“儿童绘本”发声的实战教程

transformers 推理 Qwen2.5 等大模型技术细节详解(一)transformers 包和对象加载

transformers 推理 Qwen2.5 等大模型技术细节详解(二)AutoModel 初始化和模型加载

微信公众号:老牛同学

标签:AutoTokenizer,Transformers,tokenizer,技术细节,Qwen2.5,模型,词表,tokens,Token
From: https://www.cnblogs.com/obullxl/p/18521203/NTopic2024103101

相关文章

  • transformers 推理 Qwen2.5 等大模型技术细节详解(二)AutoModel 初始化和模型加载(免费
    接上文:transformers推理Qwen2.5等大模型技术细节详解(一)transformers包和对象加载老牛同学和大家通过Transformers框架的一行最常见代码fromtransformersimportAutoModelForCausalLM,走读了transformers包初始化代码的整个流程。从中体会到了dummy对象、LazyModule延迟......
  • 融云IM干货丨️ 实施MFA有哪些技术细节需要注意?
    实施多因素认证(MFA)时,需要注意以下几个技术细节:确定安全需求:首先,需要识别和理解机构或系统的安全需求,包括评估数据保护的重要性、可能面临的风险类型,以及现有安全措施的强度与弱点。这有助于确定实施MFA的目标和优先级。选择合适的验证因素:MFA通常涉及三种基本的验证因素:知......
  • transformers 推理 Qwen2.5 等大模型技术细节详解(一)transformers 初始化和对象加载(
    上周收到一位网友的私信,希望老牛同学写一篇有关使用transformers框架推理大模型的技术细节的文章。老牛同学刚开始以为这类的文章网上应该会有很多,于是想着百度几篇质量稍高一点的回复这位网友。结果,老牛同学搜索后发现,类似文章确实不少,但是总觉得不太满意,要么细节深度不够,要么......
  • spacy-transformers: 在spaCy中使用预训练Transformer模型
    spacy-transformersspacy-transformers简介spacy-transformers是一个强大的库,它为spaCy提供了使用预训练Transformer模型的能力。这个库允许用户在spaCy管道中无缝集成像BERT、RoBERTa、XLNet和GPT-2这样的先进Transformer模型。通过spacy-transformers,我们可以轻松地将最先进......
  • Transformers: 引领自然语言处理的革命性工具
    transformers引言:Transformers的崛起在人工智能和自然语言处理(NLP)领域,Transformers模型的出现无疑是一场革命。而HuggingFace公司开发的Transformers库,更是将这场革命推向了一个新的高度。作为一个开源项目,Transformers为研究人员和开发者提供了一个强大而灵活的工具,使他......
  • transformers和bert实现微博情感分类模型提升
    关于深度实战社区我们是一个深度学习领域的独立工作室。团队成员有:中科大硕士、纽约大学硕士、浙江大学硕士、华东理工博士等,曾在腾讯、百度、德勤等担任算法工程师/产品经理。全网20多万+粉丝,拥有2篇国家级人工智能发明专利。社区特色:深度实战算法创新获取全部完整项目......
  • huggingface的transformers与datatsets的安装与使用
    目录1.安装 2.分词2.1tokenizer.encode() 2.2tokenizer.encode_plus ()2.3tokenizer.batch_encode_plus() 3.添加新词或特殊字符 3.1tokenizer.add_tokens()3.2 tokenizer.add_special_tokens() 4.datasets的使用4.1加载datasets 4.2从dataset中取数据  4.3对datas......
  • transformers中的generate函数解读
    转载:https://zhuanlan.zhihu.com/p/654878538这里仅当学习记录,请看原文,排版更丰富转载补充:https://www.likecs.com/show-308663700.html 这个非常的清晰明了,也建议前往学习今天社群中的小伙伴面试遇到了一个问题,如何保证生成式语言模型在同样的输入情况下可以保证同样的输出......
  • 编译流程背后的一些技术细节
    在c#语言中,中间代码是如何被转换为特定平台的机器码的在C#语言中,中间代码是通过.NET框架中的**公共语言运行时(CommonLanguageRuntime,CLR)**转换为特定平台的机器码的。这个过程涉及几个关键步骤,主要包括编译、JIT编译和执行。以下是详细的解释:1.源代码编译成中间语言(I......
  • Hugging Face NLP课程学习记录 - 2. 使用 Hugging Face Transformers
    HuggingFaceNLP课程学习记录-2.使用HuggingFaceTransformers说明:首次发表日期:2024-09-19官网:https://huggingface.co/learn/nlp-course/zh-CN/chapter2关于:阅读并记录一下,只保留重点部分,大多从原文摘录,润色一下原文2.使用HuggingFaceTransformers管道的内部......