0. 背景
- 本文主要介绍
hugging face
托管的LLM
如何下载,并用transformers repo
推理
1. 相关工具介绍
hugging face
是一个托管LLM
的网站,类似github / dockerhub
,可以上传下载大模型,也可以在线推理等transformers
是一个hugging face
维护的开源大模型代码库,包含约300
个开源大模型的网络结构,可以配合hugging face
下载的大模型,实现一键推理
2. 模型文件下载
2.1 下载工具
hugging face
有专用工具huggingface-cli
,作用类似于低配版git
,可以实现远程hugging face repo
的登录、登出、上传、下载等- 对于
Qwen2-0.5B-Instruct
模型,huggingface-cli
可直接免登录下载,而例如Llama3.2-1B
等模型需要向meta
提交申请并获得同意后下载
2.2 下载文件内容
hugging face
模型文件格式较为固定,一般为:config.json
:- 这个文件包含了模型的配置信息,比如模型的架构参数、词表大小、激活函数类型等
- 它通常用于在加载模型时,确保模型的配置与原始模型一致
generation_config.json
:- 这个文件包含了生成文本时的配置信息,比如最大长度、停止序列、解码相关配置(温度、
top-k
、top-p
、惩罚系数)等,以及依赖的transformers
版本 - 这些参数影响模型生成文本的行为
- 这个文件包含了生成文本时的配置信息,比如最大长度、停止序列、解码相关配置(温度、
LICENSE
:- 这个文件包含了模型的许可证信息,说明了你可以如何使用这个模型,以及使用时需要遵守的法律条款
merges.txt
:- 这个文件通常用于字节对编码(
Byte Pair Encoding, BPE
)分词器,它包含了词汇的合并规则 BPE
是一种用于创建词汇表和编码文本的算法
- 这个文件通常用于字节对编码(
model.safetensors
:- 这是一个保存模型权重的文件,使用了
SafeTensors
格式 - 这是一种高效的文件格式,用于存储和加载深度学习模型的权重
- 这是一个保存模型权重的文件,使用了
README.md
:- 这个文件包含了模型的说明文档,通常包括模型的简介、如何使用模型、训练细节、性能指标等信息
tokenizer_config.json
:- 这个文件包含了分词器的配置信息,比如分词器的类型、是否使用
BPE
等
- 这个文件包含了分词器的配置信息,比如分词器的类型、是否使用
tokenizer.json
:- 这个文件包含了分词器的预训练信息,比如词汇表、特殊标记等
- 它用于将文本转换为模型可以理解的数值输入
vocab.json
:- 这个文件包含了模型的词汇表,即模型在训练时使用的所有单词或标记的列表
- 分词器使用这个词汇表将文本分割成模型可以理解的输入
Qwen2-0.5B-Instruct
中的"Instruct"
是指此模型是经过指令遵循微调后的模型
2.3 模型参数解析
- 下载的文件中
model.safetensors
就是模型参数 safetensors
是由Hugging Face
开发的一种新型文件格式,专门用于安全地存储和加载机器学习模型的权重,这种格式特别关注模型的安全性、隐私保护和快速加载- 安全性:
safetensors
格式的设计目标之一是提高模型存储的安全性,它不允许执行代码,从而减少了模型文件被恶意篡改的风险 - 快速加载:与传统的
PyTorch
序列化格式(如.pth
或.bin
)相比,safetensors
可以更快地加载模型权重,尤其是在CPU
和GPU
上 - 跨语言和跨框架兼容性:
safetensors
支持在不同的编程语言和机器学习框架之间共享模型权重,例如可以在Python
中序列化并在C++ / Java / JavaScript
中加载 - 懒加载:支持懒加载,即可以选择只读取文件中的一部分张量,这对于分布式设置中的多节点或
GPU
非常有用
- 安全性:
- 如何解析一个
model.safetensors
文件?
1 | from safetensors import safe_open |
3. 构建模型并加载预训练参数
1 | from transformers import AutoModelForCausalLM |
hugging face
的transformers
库提供了一个AutoModelForCausalLM
类,可以根据模型路径,读取配置文件并自动加载模型
4. 输入构建
输入构建分成三个部分:
- 初始化
tokenizer
prompt
指令化text to token id
(tokenize
)
4.1 初始化 tokenizer
1 | from transformers import AutoTokenizer |
transformers
会自动读取tokenizer
相关配置文件,创建对应的tokenizer
对象
4.2 prompt
指令化
1 | prompt = "艾萨克牛顿是谁?" |
- 上面的
message
是Qwen2
指令微调后模型的输入格式,包含了role
和content
两个字段 - 具体来说需要两个步骤:
prompt to message
:将prompt
转换为message
格式message to chat
:在message
中插入特殊字符,形成chat
格式,特殊字符主要包括:<|im_start|>
:表示对话开始<|im_end|>
:表示对话结束\n
:间隔role
和content
- 最终生成的
text
实际内容为:
1 | <|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n<|im_start|>user\n艾萨克牛顿是谁?<|im_end|>\n<|im_start|>assistant |
- 一些额外知识:
prompt
输入模板信息在tokenizer_config.json
中被定义过,key
为chat_template
:1
{% for message in messages %}{% if loop.first and messages[0]['role'] != 'system' %}{{ '<|im_start|>system\nYou are a helpful assistant.<|im_end|>\n' }}{% endif %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}
- 模板用到的代码是
Jinja2
写的,Jinja2
是一种模板引擎,tokenizer
会根据这个模板生成chat
格式的text
- 代码中的
messages
和add_generation_prompt
是模板的输入参数
4.3 tokenize
(text to token id
)
1 | model_inputs = tokenizer([text], return_tensors="pt").to(model.device) |
- 这个过程就是
tokenize
,将text
转换为token id
- 首先根据
tokenizer.json
中声明的token encode
方法(例如BPE
),将text
分词 - 然后根据
vocab.json
中的词汇表,将分词后的词转换为token id
(查表) - 最后将
token id
转换为tensor
格式(tensor of uint64 dtype
)
- 首先根据
5. 模型前向传播
1 | generated_ids = model.generate( |
generate
方法是transformers
提供的一个高级封装方法,可以实现LLM
的推理max_new_tokens
参数表示最大生成的token
数量generate
方法会自动迭代调用模型的前向传播方法和解码方法,实现文本生成
- 本章节的重点关注
generate
的模型推理过程,可以分成:token id to token embedding
position emebedding
构造attention mask
构造decoder layer forward
5.1 token id to token embedding
nn.Embedding
本质就是查表,将token id
转换为token embedding
5.2 position emebedding
构造
Qwen2
模型使用的position embedding
方法是RoPE (rotary position embedding)
5.2.1 RoPE
介绍
RoPE
的核心思想是将 query embedding
和 key embedding
旋转一定角度,让模型感知到序列中的位置信息。
RoPE
的输入是:position id
(例如:[0, 1, 2, ..., seq_len - 1]
)query embedding (shape = [seq_len, head_dim])
key embedding (shape = [seq_len, head_dim])
RoPE
的输出是:- 经过
RoPE
处理后的query embedding
- 经过
RoPE
处理后的key embedding
- 经过
RoPE
的计算过程是:- 构造 和 两个矩阵
- 用两个矩阵去旋转
query embedding
和key embedding
RoPE
的实现代码如下:
1 | # 构造 RoPE 旋转矩阵 |
- 以上
RoPE
的公式解释:- 其中:
- 是
query embedding
或key embedding
的第 个位置的第 个维度的值 - , 是位置
id
- 是
head_dim
- 是
Qwen
模型中的RoPE
实现代码是RoPE
的一个简化版本,主要是为了提高计算效率,官方的RoPE
计算公式如下:- 区别在于:
- 官方的
RoPE
是同一个位置的相邻维度(2i 和 2i+1
)之间旋转 Qwen
的RoPE
是同一个位置的跳跃维度(i 和 i+d/2
)之间旋转
- 官方的
5.3 attention mask
构造
- 实现中,
attention mask
实际上并不需要构造,因为torch.nn.functional.scaled_dot_product_attention
的is_causal
参数可以自动实现attention mask
- 具体来说:
torch.nn.functional.scaled_dot_product_attention
的is_causal == True
时,会自动构造一个attention bias
,shape = [query_len, key_len]
,其中bias[i, j] = -inf if i > j else 0
- 在
query @ key.transpose(-2, -1)
得到attention score
后,会自动加上这个attention bias
- 然后再经过
softmax
,这样就实现了causal attention
- 而且
causal attention
在GPT-like LLM
中,只在prefill
阶段使用,generate
阶段不使用(或者说隐式使用)
5.4 decoder layer forward
LLM
的计算量几乎都体现在decoder layer forward
上- 从
layer norm
位置角度来看,Qwen2
模型属于pre-LM
,即: - 模型包含
24
层decoder layer
,每层包含2
个子层:multi-head self-attention
(MHA
)feed forward network
(FFN
)
- 为了减小计算量和参数量,
Qwen2
模型使用了GQA (grouped query attention)
方法:- 对于
query
来讲,每层MHA
包含14
个头,每个头的head_dim = 64
- 对于
key
和value
来讲,每层MHA
包含2
个头,每个头的head_dim = 64
- 计算时,需要将
key
和value
的head
通过repeat
扩展到14
个头
- 对于
- 同时,
Qwen2
使用了KV cache
算法,即:- 在
prefill
阶段,key
和value
会被缓存下来 - 在
generate
阶段,之前的key
和value
会被直接使用不再计算,只计算最后一个token
的query / key / value
- 在
- 在
prefill
阶段:- 输入为
hidden state
,shape = [1, 36, 896]
,其中36
是seq_len
,896
是hidden_dim
- 输入逐层经过
decoder layer
,最后输出hidden state
,和输入shape
一样(shape = [1, 36, 896]
) - 在这一阶段,每一层的每个头的每个位置(
token position
)的key
和value
都会被缓存下来,shape = [2, 1, 24, 2, 36, 64]
,其中:2
是key / value
1
是batch_size
24
是layers
36
是seq_len
2
是key_value_head_num
64
是head_dim
- 每个
decoder layer
层的MHA
都需要causal attention
,即is_causal = True
- 输出的
hidden state
的 最后一个token position
的feature
(shape = [1, 896]
) 会被输入到fc
层,输出shape = [1, 151936]
,151936
是vocab_size
- 然后通过
decode method
得到最终的token id
- 输入为
- 在
generate
阶段:- 输入为 预填充阶段最终输出的
token id
对应的token embedding
,shape = [1, 1, 896]
- 输入逐层经过
decoder layer
,最后输出hidden state
,和输入shape
一样(shape = [1, 1, 896]
) - 在这一阶段,每一层的每个头的之前的位置(
token position
)的key
和value
都会被直接使用,不再计算;只计算最后一个token
的query / key / value
,并将计算得到的key / value
缓存下来,缓存的key / value
会拼接到之前的key / value
上,形成新的key / value
,shape = [2, 1, 24, 14, 36 + 1, 64]
- 每个
decoder layer
层的MHA
的输入shape
如下:query
:shape = [1, 14, 1, 64]
key / value
:shape = [1, 14, 37, 64]
- is_causal = False,因为
query
长度为1
,不需要causal attention
- 最后一层
decoder layer
的输出经过fc
和decode method
得到最终的token id
- 重复直到生成的
token id
数量达到max_new_tokens
或生成的token id
为eos
为止
- 输入为 预填充阶段最终输出的
6. decode method
decode method
实际上属于模型forward
过程中的一部分,但是为了方便理解,这里单独拎出来讲decode method
的作用是将vocab logits
映射到token id
- 最简答的
decode method
是argmax
方法,即取vocab logits
中最大的值对应的token id
,但是这种方法会导致生成的文本过于单一 - 实际上
Qwen2
使用了五个串联的decode method
,分别是:RepetitionPenaltyLogitsProcessor
TemperatureLogitsWarper
TopKLogitsWarper
TopPLogitsWarper
SoftmaxSampler
6.1 RepetitionPenaltyLogitsProcessor
RepetitionPenaltyLogitsProcessor
的作用是惩罚重复的 token id
,即:
- 在预测的
vocab logits
中,找到之前所有token id
对应的logits
- 如果
logits > 0
,则将logits
除以一个penalty
,penalty
的值取1.1
- 如果
logits < 0
,则将logits
乘以一个penalty
,penalty
的值取1.1
- 目的是让模型更倾向于生成不重复的
token id
6.2 TemperatureLogitsWarper
TemperatureLogitsWarper
的作用是调整 vocab logits
的温度,即:
- 将
vocab logits
除以一个temperature
,temperature
的值取0.7
- 目的是提高模型的生成多样性
6.3 TopKLogitsWarper
TopKLogitsWarper
的作用是截断 vocab logits
,即:
- 保留
vocab logits
中最大的k
个值,其他值置为-inf
,k
的值取20
- 目的是降低模型的生成多样性,提高生成的准确性
6.4 TopPLogitsWarper
TopPLogitsWarper
的作用是截断 vocab probs
,即:
- 将
vocab logit
通过softmax
变成vocab probs
- 将
vocab probs
从大到小排序,累加到p
大于top-p
为止,保留这些vocab logits
,其他值置为-inf
top-p
的值取0.8
,最终至少需要保留一个token id
- 目的是降低模型的生成多样性,提高生成的准确性
6.5 SoftmaxSampler
- 将
vocab logits
(此时只有少量元素不是-inf
) 通过softmax
变成vocab probs
- 根据
vocab probs
采样一个token id
,作为最终的输出
7. 总结
- 本文主要介绍了
hugging face
和transformers
的使用方法,以及Qwen2-0.5B-Instruct
模型的推理过程 GPT-like
模型的推理过程主要包括tokenize
、RoPE
、attention mask
、decoder layer forward
、decode method
等步骤Qwen2
模型使用了RoPE
、GQA
、KV cache
等技术,提高了模型的计算效率和参数量decode method
使用了RepetitionPenaltyLogitsProcessor
、TemperatureLogitsWarper
、TopKLogitsWarper
、TopPLogitsWarper
、SoftmaxSampler
等方法,提高了模型的生成多样性和准确性