[大模型实战 03] 拆解 Transformers:从原理图解到 HuggingFace Transformers实战
[大模型实战 03] 拆解 Transformers:从原理图解到 HuggingFace Transformers 实战
核心摘要 (TL;DR)
- 原理:图解 Transformer 是如何通过“注意力机制”和“位置编码”来理解人类语言的。
- 实战:在 Kaggle (双 T4 GPU) 环境下,拆解 HuggingFace 代码的“铁三角”(Config, Tokenizer, Model)。
- 技巧:掌握
Temperature和Top_p,学会控制 AI 的“创造力”。
前言
各位友人们,大家好,这里是阿尔。在上一节的“炼丹”环境搭建中,咱们成功地将Qwen2.5模型运行了起来,跑通了。 但是相信大家对运行的时候的那些参数都代表着什么,都还是懵的。 这篇博客就是为此准备的,我们打算先快速大概地了解一下当前的大模型的底层原理,再结合在一起介绍通用的transformers库, 去看看代码和如何对应这些理论的。
1. Transformer 极简原理:大模型是怎么思考的?
大模型和普通的机器学习模型一样,本质也是一个函数,不同的是,传统机器学习可能输入的是一些整理好的数据,比如房子的尺寸,地段,购买时长,和市中心的距离等等数据,输出一个预测的放假,而大模型输入的是我们的问题,拿到的是大模型给出的回答。但,咱们究其根本,它们都可以看作是一个函数,我们输入一些东西,经过运算之后,输出给我们一些东西。
对于大模型,它的本质就是一个下一个字符预测器,我们输入一些文字,它只负责根据它所训练的海量数据,输出最符合,最有可能的下一个字符,就像一个文字接龙机器,其核心任务只有一个:根据上文,猜下一个字是什么。
为了实现这个目标,Transformer 架构经历了一个精密的流水线:

下面,我们会稍微详细一点地介绍一下各个步骤。
1.1 第一步:Token 化 (Tokenization) —— 查字典
大模型既然是一个函数,那么肯定是针对数字进行处理的,所以,我们就需要一个法子,去将我们的文字字符(甚至是图片)变成大模型认识的数字(或者说张量,向量)。 这就是Token化,去做这一步操作的函数,或者模块就是分词器(Tokenizer)。
那么Tokenizer具体是如何工作的?它实际上就是将一个个的字符,对应成一个个数字,或者说ID,就像ASCII码一样,不过它做得更高级。
把每一个字都找一个数字对应,有点奢侈,特别是对于英语,act, acting,action,actor其实都有相同的词根,主要的语义来源于其词根act,所以分词器是按照词元(token)去拆分的,能把有些词拆成词根、前缀和后缀等等,当然具体如何拆,取决于字典如何定义,字典有多大。
这里我们给一个简单的例子
- 输入:“我爱 AI”
- 动作:切分 ->
["我", "爱", "AI"]-> 查表 ->[2301, 452, 1083]
1.2 第二步:Embedding & 位置编码 —— 赋予含义与顺序
有了token之后,我们想知道词和词的关系,我们想要通过一个可以量化的量去判断两个词是否是有关系的,关系多大。这就引入了我们的下一个模块,词嵌入模块(Embedding)。
- Embedding (词向量):把每个 ID 变成一个长长的向量(比如 4096 维的数组)。这个向量在模型训练之前是随机的,其后随着海量的训练数据洗礼,越相关的词向量越靠近,越不相关的词越远离。这个向量代表了词的含义。比如“猫”和“狗”的向量在空间里距离很近,“苹果”和“手机”在某种语境下也更近。
光知道词和词的关系还不够,“我爱你”和“你爱我”的每一个词都是相同的,但是它们确实可以不相关的两个句子,“小狗咬了我”和“我咬了小狗”,也会被人视作“可以正常理解”和“这人好像不对劲”两种完全不同的理解。显而易见,词语在句中的位置是一个非常重要的信息,我们不能弄丢它,也需要将这一部分信息传递给模型训练时候去学习。
- Positional Encoding (位置编码):Transformer 是并行计算的(它一眼看完所有词),这导致它不知道“我爱你”和“你爱我”的区别。所以,我们需要给每个词贴上一个“座位号”,告诉模型谁在前面,谁在后面。

1.3 第三步:Self-Attention (自注意力) —— 寻找关系
接下来就是大模型的灵魂,我们想知道一句话里的每一个词元和其他词元的关联度,其实就是上下文的联系。当模型处理“苹果”这个词时,如果上下文里有“手机”、“发布会”,注意力机制会告诉模型:“嘿,这里的‘苹果’指的是科技公司,不是水果!”,自注意力机制,让模型能理解上下文。

1.4 第四步:MLP (前馈神经网络) —— 消化吸收
如果说注意力机制是“看”,那么 MLP 就是“想”。它包含多层神经元,负责对提取到的信息进行复杂的非线性变换和逻辑推理,也就是传统的深度学习。

1.5 第五步:Decoder & Softmax —— 输出概率
经过层层计算,模型最终会输出一个包含了所有词汇(比如 15 万个词)的概率列表。
AI(80%)吃(10%)睡(5%)
…
最后,我们根据这个概率列表,选择下一个词。
这就是大模型词语接龙的原理了。
2. 拆解模型文件夹:下载下来的到底是什么?
理论讲完了,我们来看看在 Kaggle 的文件系统里,这些理论变成了什么文件。
当你下载一个模型(以 Qwen2.5-7B 为例)时,文件夹结构如下:

2.1 核心架构:config.json
config.json: 是大模型的身份证,也可以说是体检表,它其中就是真正的我们的模型,具体由哪些层构成,是什么架构类型,隐藏层有多深,注意力头的数量有几个,词表的大小是多少。对于大模型而言,它就是模型本身,也是骨架,因为大模型重要的是训练完的参数,模型本身是很小的。

2.2 行为预设:generation_config.json
generation_config.json:是模型的出厂默认设置。
2.3 大脑本身:*.safetensors和*.index.json
有了config.json中的躯体,我们再从*.safetensors和*.index.json载入灵魂,这才是我们能说会道的大模型。
model-xxxxx.safetensors:这里面存的是实打实的张量(Tensor)数据,即数十亿个参数的浮点数。为了方便存储和加载,通常会被切分成多个 2GB-5GB 的小文件(Shard)。model.safetensors.index.json:这是一张藏宝图。因为权重被切分了,模型需要知道“第 5 层的权重”到底藏在哪个文件里。- 内部长这样:
1
2
3
4
5
6
7{
"metadata": { "total_size": 15423653888 },
"weight_map": {
"model.layers.0.self_attn.q_proj.weight": "model-00001-of-00004.safetensors",
"model.layers.20.mlp.gate_proj.weight": "model-00003-of-00004.safetensors"
}
}
- 内部长这样:
数字和文字的翻译官:tokenizer相关文件
vocab.json/merges.txt:这是最原始的生词表。记录了所有字、词根对应的 ID。tokenizer.json:这是一个编译后的高效字典文件,包含了分词的所有逻辑(Pre-tokenization, Normalization 等),加载速度比读原始文本快得多。tokenizer_config.json(至关重要):这是分词器的配置文件。- 它定义了特殊符号(Special Tokens):比如哪个 ID 代表“开始”,哪个代表“结束”。
- 它包含了 Chat Template (聊天模板):这是一段 Jinja2 代码,决定了
apply_chat_template如何工作。 - 内部长这样:
1
2
3
4
5{
"chat_template": "{% for message in messages %}...<|im_start|>...",
"eos_token": "<|im_end|>",
"pad_token": "<|endoftext|>"
}
3. Transformers库实战:代码中的“铁三角”
在Transforms库中,我们永远绕不开三个核心类。
环境准备:
在 Kaggle 右侧设置中,确保 Internet: On 且 Accelerator: GPU T4 x2。
1 | !pip install -U transformers accelerate bitsandbytes |
3.1 AutoTokenizer (翻译官)
对应理论中的 Token 化 步骤,和模型文件中的tokenizer相关文件。
1 | from transformers import AutoTokenizer |
得到的结果将会是这样
1 | 原文: Transformer is amazing |
对话格式:Chat Template
大模型需要特定的对话格式(Prompt)。来将模型的回答,和用户的问题做区分, 我们一般都可以通过载入语言模型对应模板(不同家的模型,可能模板会有不同),甚至去拼装历史记录。
1 | messages = [ |
运行结果如下
1 | 模型实际看到的输入: |
看到这里,我们也能更好地理解,为什么一个本质是词语接龙的模型,能够区分问题,然后做出回答。因为在训练的过程中,我们会让他知道<|im_start>表示一个message的开始,其中会告诉模型,这句话是谁说的,这句话在什么位置结束,它接龙的时候也会带上开始和结束符号,在推理模型中,甚至会带上思考的标签。
3.2 AutoModel (大脑本体)
对应理论中的 Embedding -> Attention -> MLP 计算过程,通过从config.json中加载模型躯体,在加载上模型的safetensor灵魂数据以及generation_config.json的默认初始化,
在 Kaggle 上,我们拥有 双 T4 (15GB x 2),一定要利用 device_map="auto" 让库自动分配显存。
1 | from transformers import AutoModelForCausalLM |
输出结果为
1 | 模型加载成功!显存分布: {'model.embed_tokens': 0, 'model.layers.0': 0, 'model.layers.1': 0, 'model.layers.2': 0, 'model.layers.3': 0, 'model.layers.4': 0, 'model.layers.5': 0, 'model.layers.6': 0, 'model.layers.7': 0, 'model.layers.8': 0, 'model.layers.9': 0, 'model.layers.10': 0, 'model.layers.11': 0, 'model.layers.12': 1, 'model.layers.13': 1, 'model.layers.14': 1, 'model.layers.15': 1, 'model.layers.16': 1, 'model.layers.17': 1, 'model.layers.18': 1, 'model.layers.19': 1, 'model.layers.20': 1, 'model.layers.21': 1, 'model.layers.22': 1, 'model.layers.23': 1, 'model.layers.24': 1, 'model.layers.25': 1, 'model.layers.26': 1, 'model.layers.27': 1, 'model.norm': 1, 'model.rotary_emb': 1, 'lm_head': 1} |
4. 掌控生成的“调节旋钮”
在模型输出的过程中,我们有一些参数可以对输出结果进行调节,对应于我们讲理论部分的中的 第五步 (Softmax 概率输出), 我们有一堆下一个词元的概率分布了,但是我们应该如何去选择呢?
4.1 Temperature (温度)
我们可以设定的Temperature参数,我们在generation_config.json中也看见过它
值越大,更热,更具创造性,更容易输出各种天马行空的词, 会缩小所有词元的差距,雨露均沾,以达到创造性,让低概率的词,也有机会被选中,当然,也更容易胡说八道,出现幻觉.
值越小,更冷,更严肃,在温度为0的时候甚至会固定输出最高的那个词,它会拉大高概率和低概率的差距,赢家通吃,让模型的回答更稳定,严谨。
4.2 Top_p (核采样)
和温度不同,我们还有另一种方式,这种方式更类似于拉网,我们只要可能性前80%的词,在那些词里进行挑选,这个可能性就是P,这个选词(采样)方法又叫top_p(核采样).
简而言之:其只在累积概率达到 P (e.g., 0.9) 的前几个词里选。直接切掉尾部那些极低概率的离谱词。
4.3 实验一下
我们这里先定义一个函数,以温度和top_p为参数去测试不同的参数对回答的影响
1 | # 定义一个测试函数 |
温度实验
然后再其下新建code block去测试,看看效果
1 | prompt = "请用这三个词写一个微故事:量子、失恋、炒饭。" |
这是我的运行结果
1 | ======== 设置: Temperature=0.1, Top_p=0.9 ======== |
top_p实验
接下来是top_p
1 | prompt_2 = "请给一种不存在的颜色起个名字,并描述它的样子。" |
输出结果为
1 | ======== 设置: Temperature=0.8, Top_p=0.01 ======== |
5. 完整 Transformers 代码实战
把所有积木搭在一起,这就是一段标准的模型推理代码:
1 | import torch |
这是输出结果
1 | Loading checkpoint shards: 100% |
Hint:
所有的代码,都可以在 这个笔记本中直接获取运行哦
6. 常见问题 (Q&A)
Q: 在 Kaggle 上 device_map="auto" 是必须的吗?
A: 如果你使用单卡 T4 (15GB) 跑 7B 模型(约 14GB),勉强能塞进一张卡。但如果你开启了 Kaggle 的 T4 x2,为了利用全部 30GB 显存,必须加这个参数,否则模型只会塞进第一张卡,导致第一张爆满,第二张围观。
Q: 为什么生成的每一句话都不一样?
A: 因为我们开启了 do_sample=True 并且设置了 temperature > 0。模型在选择下一个词时是按概率随机抽取的。如果你想让结果每次都一样(比如做数学题),请设置 do_sample=False(此时温度失效,变为贪婪解码)。
Q: 什么是 Logits?
A: 在代码深处,模型输出的那个“概率表”在变成百分比之前,叫 Logits(未归一化的数值)。Softmax 函数的作用就是把 Logits 变成概率。你可以把 Logits 理解为模型对每个词的“原始打分”。
Q: Token 和字是一一对应的吗?
A: 不一定。
- 英文:通常一个单词是一个 Token,长单词可能被切分。
- 中文:通常一个汉字是一个 Token,但常见词(如“你好”)可能会合并为一个 Token。
- 平均来说,0.75 个英文单词 ≈ 1 Token,1 个汉字 ≈ 1.5 - 2 Token(取决于分词器效率,Qwen 的中文压缩率很高)。