2026/2/23 21:42:47
网站建设
项目流程
在家有电脑怎么做网站,芜湖高端网站建设,有一个专门做lol同人的网站,dnf交易网站建设本文详细介绍了如何从零构建一个小型LLM模型#xff0c;通过逐步实现Tokenizer、Embedding、Attention机制和Transformer结构等核心组件#xff0c;帮助开发者理解大模型底层原理。作者用朴素的代码实现了类似GPT-2的QDogBaby模型#xff0c;包括多头注意力、前馈网络、残差…本文详细介绍了如何从零构建一个小型LLM模型通过逐步实现Tokenizer、Embedding、Attention机制和Transformer结构等核心组件帮助开发者理解大模型底层原理。作者用朴素的代码实现了类似GPT-2的QDogBaby模型包括多头注意力、前馈网络、残差连接等关键部分并介绍了模型训练与推理方法。文章旨在打破大模型神秘感让开发者真正理解而非仅调用API。RoadMap1. 引言1.1 背景在部门浓厚的AI技术探索氛围驱动下为了将晦涩的理论转化为直观的工程触感我们开展一次略带“整活”性质的实践。希望通过从零“手搓”的方式在满足工程师好奇心的同时帮助大家打破对大模型的神秘感真正理解其底层的运行机制与实现原理。1.2 内容说明简单但完整的模型结构结构主要参考GPT2从零构建。介绍模型结构以及代码实现尽量规避数学与复杂的算法。个人非科班的业余爱好者靠各种碎片信息修炼的邪修可能会对一些术语不了解请见谅。适应人群: 对于智商超群的算法同学来说可能有些幼稚但对终日加班目光涣散的开发苦哥们来说刚刚好。主要参考资料: 《Build a Large Language Model (From Scratch)》 和 CS336: Language Modeling from Scratch 更多资料详见文末。1.3 模型结构概览QDogBaby模型结构现在看起来一头雾水没关系接下来我们来逐步拆解模型瞧瞧这些到底是怎么个事。1.4 HelloWorld首先给我们的模型起个名字然后做一个简单的逻辑我们的第一个LLM就诞生了。开发嘛实用优先(又不是不能用)plaintextclass QdogBaby整活(): def chat(self, text): if text.endswith(‘吗’): return text.replace(‘吗’, ‘!’) return textmodel QdogBaby整活()print(model.chat(‘会说话吗’))print(model.chat(‘是人工智能吗’))会说话! 是人工智能! --这个“人工”智能最大的问题在于它不懂语义只是在做字符替换。它无法理解“今天天气不错”和“风和日丽”之间的联系更无法生成它没见过的句子。真正的 LLM 是一个概率模型。它不是在查找答案而是在根据上文预测下一个字最可能是什么。为了实现从“逻辑处理”到“概率预测”的转变我们首先要解决第一个问题如何把人类的语言翻译成模型能理解的语言2. 编码Tokenizer2.1 引言正如我们第一部分所说LLM 是一个概率模型通过数学计算来进行下一个字的预测。然而计算机只认识数字0和1并不认识今天、天气或者Today这些单词。因此想要让计算机能够理解人类的语言需要通过特定的编码机制将自然语言翻译成模型能运算的数字序列。分词器Tokenizer是实现这一功能的核心组件它能够将连续文本拆解为离散语义单元即Token并转换为唯一索引Token ID用于模型输入。所有 LLM 的输入都依赖 Tokenizer 的预处理结果。如果分词策略不合理如拆分后丢失语义或 Token 粒度过细会直接影响模型对文本的理解能力。因此一个设计优良的 Tokenizer 是提升 LLM 性能的基础前提。2.2 Tokenizer分类根据切分粒度的不同Tokenizer可分为三大类字符级TokenizerCharacter-level、词级TokenizerWord-level以及子词级 TokenizerSubword-level。我们以句子 “Today is sunday.” 为例看看它们是如何工作的。字符级 TokenizerCharacter-level核心以单个字符作为最小语义单元进行拆分不依赖词典或复杂规则。分词结果[T, o, d, a, y, i, s, s,u, n, d, a, y, .]优点词汇表规模小任何文本均能拆分适配性强缺点Token序列长信息密度低碎片化严重解码效率偏低词级 TokenizerWord-level核心以单个词语为最小语义单元进行拆分通常使用空格或者标点符号进行分隔。分词结果[Today, is, sunday, .]优点单元语义完整序列长度较短信息密度高模型无需学习字符组合关系。缺点词表规模庞大训练成本升高。同时若单词未出现在语料或词典中会出现OOV(out-of-vocabulary)的现象。子词级 TokenizerSubword-level核心介于字符级与词级之间常见的词保留完整不常见的词拆分成有意义的片段词根、词缀。分词结果[To, day, is, sun, day, .]优点词汇表规模可控序列长度介于两者之间能学习词缀、词根之间的关联平衡了信息密度与计算效率同时有效缓解了OOV问题。缺点分词逻辑较上述两者复杂对低频子词的拆分可能不够合理需通过优化算法进行改善。目前子词级 Tokenizer 是 LLM 的主流选择。2.3 BPE算法原理目前最流行的子词分词算法是 BPE (Byte Pair Encoding)。它的核心逻辑与人类学习语言的过程相似先认字再组词。从最小的字符单元出发反复合并语料中频率最高的相邻单元对逐步扩展词汇表直至达到预设词汇表大小或下一个高频出现的字符对频率为1时停止。具体示例初始语料[apple, apple, banana, banana, grape, grape, grapes]步骤1预处理为原始语料中的每个单词添加后缀/w作为分隔符并将所有单词拆分为字符作为初始分词单元。[a,p,p,l,e,/w](2次)[b,a,n,a,n,a,/w](2次)[g,r,a,p,e,/w](2次)[g,r,a,p,e,s,/w](1次)步骤2初始化词汇表此时词汇表仅包含独立字符与单词后缀。V {a, b, e, g, l, n, p, r, s, /w}步骤3进行第一次合并。步骤3.1统计所有相邻单元对及其频率需要叠加单词重复次数。[a,p,p,l,e,/w](2次)相邻对为(a,p)、(p,p)、(p,l)、(l,e)、(e,/w)各2次[b,a,n,a,n,a,/w](2次)相邻对为(b,a)、(a,n)、(n,a)、(a,n)、(n,a)、(a,/w)各2次[g,r,a,p,e,/w](2次)相邻对为(g,r)、(r,a)、(a,p)、(p,e)、(e,/w)各2次[g,r,a,p,e,s,/w](1次)相邻对为(g,r)、(r,a)、(a,p)、(p,e)、(e,s)、(s,/w)各1次各单元对出现的频率依次为5次(a,p)、4次(a,n)、4次(n,a)、4次(e,/w)等。步骤3.2选择频率最高的相邻对进行合并当前(a,p)出现次数最多因此把ap作为一个新元素加入词汇表并进行词汇表的更新。新词汇表V {ap, a, b, e, g, l, n, p, r, s, /w}步骤3.3用合并后的词汇表重新进行单词的拆分。[ap, p, l, e, /w](2次)[b, a, n, a, n, a, /w](2次)[g, r, ap, e, /w](2次)[g, r, ap, e, s, /w](1次)步骤4依次类推重新统计各元素出现的频率重复步骤3进行合并直至达到预设词汇表大小或下一个高频出现的字符对频率为1时停止。最终分词逻辑经过上述步骤得到词汇表后按照Token长度进行排序优先匹配最长的Token如果匹配上则进行替换直到单词的所有字符都被替换为词表中的Token为止。2.4 编码分词仅仅是第一步计算机仍然不认识分词结果。我们需要将其转化为数字索引。在模型训练前我们会生成一个巨大的映射表给每一个 Token 分配一个唯一的ID。如TokenID……apple1023to1024is1025……grape2045day2046sun2047……banana8786……随后我们可以对文本进行最后一步转换了。输入文本“today is sunday”我们将其分为[to, day, is, sun, day]查表映射输出结果为[1024, 2046, 1025, 2047, 2046]。至此我们解决了人机交流的第一道障碍文本已经变成了整齐的数字序列。接下来我们需要把这些数字喂给模型让它开始真正的计算。3. 模型 LLM Model3.1 引导: 单字处理的窘境将字转化为token_id之后我们终于可以对文字做计算了于是乎我搞了一个翻译模型这个模型找到了汉字与单词之间的对应关系会将给定的英文token转化为中文token比如:model QdogBabyTranslate()print(model.translate(I like penguin))print(model.translate(We all like penguin)) plaintext 我喜欢小企鹅 我们都喜欢小企鹅我觉得很开心于是我拿去用发现:print(model.translate(How are you))print(model.translate(How old are you)) plaintext 怎么是你 怎么老是你感觉哪里怪怪的它把 ‘old’ 翻译成了 ‘老’完全没看 ‘How’ 和 ‘are’ 的脸色也就是说我们现在的每个字只关注了他自己本身却忽略了对话上下文我们希望当前的文字可以注意到其他的文字。3.2 embedding在建立字与字的关系之前还需要解决一个问题: 数字本身是没有语义的。我们将“我喜欢小企鹅”转换成了 [397, 2320, 3169, 2665, 1270]。但对模型来说只是五个整数它并不知道 397我和 2320喜欢之间有什么关系。如果我们直接用这些整数去计算模型会“懵圈”。所以我们要给每个 Token ID 做一次升维把一个干巴巴的整数变成一个高维向量。首先需要准备一个巨大的表格比如词表大小是 6400维度是 512那就是一个 6400 x 512 的数字矩阵然后我们就可以通过最简单的查表的方式将 Token ID 映射成一个高维向量。此外还有一个关键点Transformer 是“脸盲”的。它并行处理所有输入如果不加特殊处理它无法区分“我喜欢小企鹅”和“小企鹅喜欢我”的区别词都一样只是顺序不同。因此我们还需要一个位置映射表Positional Embedding把“位置信息”也加到向量里去。from transformers import AutoTokenizerimport torchtokenizer AutoTokenizer.from_pretrained(./tokenizer)input tokenizer.encode(我喜欢小企鹅, return_tensorspt)print(input)tok_emb torch.nn.Embedding(num_embeddingstokenizer.vocab_size, embedding_dim512)# 这里由于字在不同的位置含义是不一样的所以需要一个位置嵌入来区分不同的位置pos_emb torch.nn.Embedding(num_embeddings512, embedding_dim512)print(tok_emb(input)pos_emb(torch.arange(input.shape[1]))) plaintext tensor([[ 397, 2320, 3169, 2665, 1270]])tensor([[[-0.5469, -2.2388, 0.8119, ..., 0.3477, 0.0979, -1.2581], [-0.3531, -0.4719, -0.0993, ..., -2.6921, -1.4172, 0.4816], [-1.1212, -0.8914, -0.0145, ..., -1.3678, -1.0177, -0.8076], [-0.1734, 1.6962, -2.1702, ..., 0.1568, 1.6497, -0.1667], [-0.3965, -0.2542, -0.1799, ..., 0.3170, 0.5109, -1.2761]]], grad_fnAddBackward0)你可以将这个操作理解为将token在空间中展开这样我们能更明显的感知到token之间的关系另外在token向量化后我们发现可以形象的对token进行计算了某种意义上可以完成这样的操作:妈妈哥哥舅舅国王-男女王后巴黎-法国意大利罗马3.3 Attentioncoding start: 这部分会涉及到一些编码可能会比较繁琐无聊但我认为还是有必要的其他部分代码均有删减但这部分保留了。3.3.1 朴素的Attention如何找到token与token之间的关系回到我们刚刚embedding后的空间如果我们在空间中建立一个坐标系会发现当两个向量含义相近时他们的点积会比较大而两个向量没什么关系时它们的点积会是0甚至负数.我们可以利用这个特性将句子中的每个 Token 与其他所有 Token 进行点积运算(请注意这里的token是一个多维度的向量)以此来计算这两个词的关系好不好。向量点积的本质是空间投影但对被需求淹死了的开发哥们来说存在理解难度因此我们拿出初中知识简单理解为离得近了(方向)点积大离得远了(方向)点积小图中为序列 Your journey starts with one step中第二个tokenjourney的计算过程 (这里使用了原书中的例子感兴趣的同学可以去阅读一下原书讲解的十分详细)plaintextinputs torch.tensor( [[0.43, 0.15, 0.89], # Your (x^1) - 第一个token的嵌入向量 [0.55, 0.87, 0.66], # journey (x^2) - 第二个token的嵌入向量 [0.57, 0.85, 0.64], # starts (x^3) - 第三个token的嵌入向量 [0.22, 0.58, 0.33], # with (x^4) - 第四个token的嵌入向量 [0.77, 0.25, 0.10], # one (x^5) - 第五个token的嵌入向量 [0.05, 0.80, 0.55]] # step (x^6) - 第六个token的嵌入向量)dim inputs.shape[1]attn_scores torch.empty(6, 6)# 计算注意力分数矩阵遍历所有token对计算它们之间的点积相似度# 外层循环遍历每个token作为查询(query)for i, x_i in enumerate(inputs): # 内层循环遍历每个token作为键(key) for j, x_j in enumerate(inputs): # 注意这里的token是一个多维度的向量所以要展开计算 dot_product 0.0 # 遍历向量的每个维度进行计算 for k in range(len(x_i)): dot_product x_i[k] * x_j[k] attn_scores[i, j] dot_product上面那一坨坨代码可以写成下面这种矩阵相乘的形式一行搞定。除了代码简化外矩阵的计算还可以充分利用显卡的并行计算能力 (感兴趣的小伙伴可以改一下这两段代码测一下性能差距) plaintext # inputs 形状: [seq_len, dim]# inputs.T 形状: [dim, seq_len]# 结果 attn_scores 形状: [seq_len, seq_len] - 得到了一个 N*N 的关系矩阵attn_scores inputs inputs.T # query keyattn_scores plaintext tensor([[0.9995, 0.9544, 0.9422, 0.4753, 0.4576, 0.6310], [0.9544, 1.4950, 1.4754, 0.8434, 0.7070, 1.0865], [0.9422, 1.4754, 1.4570, 0.8296, 0.7154, 1.0605], [0.4753, 0.8434, 0.8296, 0.4937, 0.3474, 0.6565], [0.4576, 0.7070, 0.7154, 0.3474, 0.6654, 0.2935], [0.6310, 1.0865, 1.0605, 0.6565, 0.2935, 0.9450]])经过这样的计算后我们得到了一个代表了词与词之间关系的矩阵接下来还需要做两步操作:Softmax 归一化和加权求和。由于原始的数据点积数值范围非常大直接使用会使我们的计算结果不稳定Softmax 的作用就是把这些乱七八糟的分数变成“百分比”概率分布让它们加起来等于 1。这样我们就得到了每个词的权重而加权求和则是真正的按这些权重将我们需要的信息融合到一起。plaintext1. Softmax: 将分数转化为概率权重相当于分配注意力比例# 比如[0.1, 0.8, 0.1] 表示主要关注中间那个词attn_weight torch.softmax(attn_scores, dim-1)# 2. MatMul: 根据权重提取并融合信息# 权重大的向量会被更多地“吸取”进来权重小的则被忽略context_vec attn_weight inputs##### 3.3.2 增加权重  我们用一个小例子来通俗的理解一下attention计算的整个过程: 我们现在有一个会议室然后来了几个token来开会每个token在会议室门口根据会议室的规则(W\_q, W\_k, W\_v)领到了三个角色: Query: 提问者当轮到他发言时他会说出他需要的信息比如关于‘广告推荐架构’这个问题谁有经验。 Key: 回答者, 当他被提问时他会说出他对外的标签比如运维、后端、算法、美术。 Value: 信息本身如果他被选中了他会将自己的信息贡献出来。 会议开始后首先Query和Key关于当前的问题进行沟通得到需要信息的比例比如 运维0.2、后端0.4、算法0.4、美术0对齐之后按照信息比例把需要的Value获取到并融合起来。会议结束时每个人都通过这种方式从其他人那里吸收了自己需要的信息完成了信息的融合。 而用于讨论的会议室(W\_q, W\_k, W\_v)不是天然就存在的需要我们使用我们训练的语料一点一点的盖起来。 plaintext dim inputs.shape[1] # 词向量维度# 定义三个可训练的参数矩阵# 在实际 PyTorch 源码中通常使用 nn.Linear 实现W_query torch.nn.Parameter(torch.rand(dim, dim))W_key torch.nn.Parameter(torch.rand(dim, dim))W_value torch.nn.Parameter(torch.rand(dim, dim))# 1. 线性变换将输入映射为 Q, K, Vquerys inputs W_querykeys inputs W_keyvalues inputs W_value# 2. 计算注意力分数 (Scaled Dot-Product Attention)# [seq_len, dim] [dim, seq_len] - [seq_len, seq_len]attn_scores querys keys.T # 3. 缩放 (Scaling) 与 归一化 (Softmax)# 除以根号 dim防止数值过大scale_factor keys.shape[-1] ** 0.5attn_weights torch.softmax(attn_scores / scale_factor, dim-1)# 4. 加权求和得到上下文向量# [seq_len, seq_len] [seq_len, dim] - [seq_len, dim]context_vec attn_weights valuescoding end: 到这里就可以愉快的继续看图了3.4 mask 和 dropout我们的模型还存在着一个明显的问题就是看答案。对于一些文本处理类任务比如相关性计算、实体提取会提前给定全部的语句这种使用全部的token计算attention权重是没有问题的但我们的模型是要说话的在说出下一个字之前没人知道后面要说什么。因此在计算权重时我们要给当前词之后的词都遮起来。另外还有一种手段会在计算过程中随机丢弃一些权重可以防止模型过拟合通俗的说就是我们希望模型可以学到知识而不是背课文。PS: 早期 GPT-2 和 Qwen-14B 采用了这个方式但最新的 qwen3 kimi-2 等模型均将此参数设置为03.5 MultiHeadAttention这里为什么最后要加一层linear将矩阵转成原来的形状因为我们要开始叠层了~3.6 Transformer我们参考2017年的著名论文《Attention Is All You Need》https://arxiv.org/abs/1706.03762来设计我们的transformerblock相对于论文原始结构我们将Norm层提前这是目前 LLaMA、Qwen 等主流大模型普遍采用的结构比原始 Transformer 的 Post-Norm 训练更稳定。3.6.1 Feed Forwardplaintextclass FeedForward(nn.Module): definit(self, emb_dim, dropout): super().init() self.linear1 nn.Linear(emb_dim, emb_dim * 4) self.linear2 nn.Linear(emb_dim * 4, emb_dim) def forward(self, x): x self.linear1(x) x nn.GELU()(x) x self.linear2(x) return x这里的FeedForward主要负责两件事情: 1. 增加了非线性的激活函数 GELU() 关于激活函数不做过多展开可以先简单理解为不加非线性函数的话多层数学上就等价于单层层数就叠不起来。 2. 通过Linear将维度升为 4 \* dim假设我们的模型维度为512那么这里的维度就是2048。(这里的4倍有说法的感兴趣的同学可以研究一下) 这里的主要目的还是提高模型参数增加模型的拟合能力。 我们可以通俗的理解为: 我们前面加上参数的attention都是向量运算而向量运算本质上是一个向量空间向另一个向量空间的转化所以这个操作本质上可以认为是将词向量从原始的语义空间内拉扯到我们当前这句话的语义空间内相当于让模型学了我们这里的方言。但学完了之后模型还需要思考(或者搜索)。因此在这里给模型更多的参数让他充分的理解这块的方言。 毕竟学而不思则罔思而不学则殆嘛说明孔子在2500年前已经领悟到LLM的真谛。 以上为邪修猜想当个乐子看。 ##### 3.6.2 Residual 我们训练的主要过程就是求梯度、求导如果无脑叠层很容易就给梯度整没了所以这里给原始的input和我们计算过后的output连起来保证梯度不消失毕竟做模型嘛不能忘本。  在解决了这些问题之后我们的模型就可以开始叠层了  #### 3.7 LLM模型 至此我们主要的模型结构就完成了我们结合标注来再次复习一下:  QDogBaby模型结构 plaintext import torchfrom torch import nn# # 1. 配置清单 (相当于装修前的设计图纸)# class QdogBabyLearnConfig(): def __init__(self): self.model_name qdogbabylearn self.version 1.0.0 self.num_hidden_layers 16# 盖多少层楼 (Transformer Block的数量) self.num_heads 8 # 雇多少个专家同时看 (多头注意力的头数) self.emb_dim 512 # 词向量的宽度 (每个词包含多少信息量) self.dropout 0.0 # 随机扔掉多少神经元 (防止死记硬背) self.context_size 512 # 一次能看多长的文章 (上下文窗口大小) self.vocab_size 6400 # 字典里有多少个字 (词表大小)# # 2. 模型主体 (QdogBabyLearn 机器人本体)# class QdogBabyLearnLLM(nn.Module): def __init__(self, config: QdogBabyLearnConfig): super().__init__() self.config config # ★ 查表升维把离散的字变成向量 self.tok_emb nn.Embedding(config.vocab_size, config.emb_dim) # ★ 加入位置信息让模型知道“我爱你”和“你爱我”顺序不同 self.pos_emb nn.Embedding(config.context_size, config.emb_dim) self.dropout nn.Dropout(config.dropout) # ★ 叠罗汉堆叠 16 层 Transformer Block self.transformer_blocks nn.Sequential( *[TransformerBlock(config.emb_dim, config.num_heads, config.dropout, config.context_size) for _ in range(config.num_hidden_layers)] ) # 最后输出前的标准化 self.norm LayerNorm(config.emb_dim) # ★ 映射回字典把向量变回预测下一个字的概率分数 self.out nn.Linear(config.emb_dim, config.vocab_size) def forward(self, x): # x 的形状: [batch_size, seq_len] (比如: [[1, 2, 3]]) # 1. 查词表 tok_embeds self.tok_emb(x) # 2. 查位置表 pos_embeds self.pos_emb(torch.arange(x.shape[1], devicex.device)) # 3. 信息融合 (词义 位置) x tok_embeds pos_embeds x self.dropout(x) # 4. 经过 16 层“思考” x self.transformer_blocks(x) # 5. 最终整理 x self.norm(x) x self.out(x) # 输出形状: [batch_size, seq_len, vocab_size] return x # # 3. Transformer Block (模型的大脑皮层)# class TransformerBlock(nn.Module): def __init__(self, emb_dim, num_heads, dropout, context_size): super().__init__() self.multi_head_attn MultiHeadAttention(emb_dim, num_heads, dropout, context_size) self.layer_norm1 LayerNorm(emb_dim) self.feed_forward FeedForward(emb_dim, dropout) self.layer_norm2 LayerNorm(emb_dim) def forward(self, x): # --- 第一步注意力机制 (词与词找关系) --- residual x # ★ 做模型不能忘本先记住原始输入 x self.layer_norm1(x) # ★ 拉齐起跑线 x residual self.multi_head_attn(x) # 原始信息 新学到的关系 # --- 第二步前馈网络 (思考/记忆) --- residual x # ★ 再次记住当前状态 x self.layer_norm2(x) # ★ 拉齐起跑线 x residual self.feed_forward(x) # 原始信息 思考后的结果 return x# # 4. 多头注意力 (核心我要啥、你有啥、我是啥)# class MultiHeadAttention(nn.Module): def __init__(self, emb_dim, num_heads, dropout, context_size): super().__init__() self.emb_dim emb_dim self.num_heads num_heads self.head_dim emb_dim // num_heads # 每个头负责的维度 (512/8 64) # 定义 Q, K, V 的映射层 self.W_querys nn.Linear(emb_dim, emb_dim) # 我要啥 self.W_keys nn.Linear(emb_dim, emb_dim) # 你有啥 self.W_values nn.Linear(emb_dim, emb_dim) # 我是啥 self.out nn.Linear(emb_dim, emb_dim) # 信息汇总 self.dropout nn.Dropout(dropout) # ★ Mask (不让看答案)创建一个下三角矩阵 # 1 0 0 # 1 1 0 # 1 1 1 self.register_buffer(mask, torch.tril(torch.ones(context_size, context_size))) def forward(self, x): batch_size, num_tokens, emb_dim x.shape # 1. 线性变换生成 Q, K, V querys self.W_querys(x) keys self.W_keys(x) values self.W_values(x) # 2. 切分成多头 (8个人分工合作) # 形状变换: [B, T, 512] - [B, T, 8, 64] querys querys.view(batch_size, num_tokens, self.num_heads, self.head_dim) keys keys.view(batch_size, num_tokens, self.num_heads, self.head_dim) values values.view(batch_size, num_tokens, self.num_heads, self.head_dim) # 3. 转置把头(Head)放到前面方便并行计算 # 形状变换: [B, 8, T, 64] querys querys.transpose(1, 2) keys keys.transpose(1, 2) values values.transpose(1, 2) # 4. 计算相似度 (我俩好不好) # Q K^T attn_score querys keys.transpose(-1, -2) # 5. ★ Mask 操作 (不让偷看后面的词) # 把 mask 为 0 的位置填成负无穷大 (-inf) mask_bool self.mask.bool()[:num_tokens, :num_tokens] attn_score attn_score.masked_fill(mask_bool 0, float(-inf)) # 6. 归一化 (概率化) # 除以根号dk是为了防止梯度消失 attn_weight torch.softmax(attn_score / (self.head_dim ** 0.5), dim-1) attn_weight self.dropout(attn_weight) # 7. 加权求和 (有你的我) # Weight V context_vec attn_weight values # 8. 还原形状 (把8个头的结果拼回去) # [B, 8, T, 64] - [B, T, 8, 64] - [B, T, 512] context_vec context_vec.transpose(1, 2).contiguous().view(batch_size, num_tokens, emb_dim) # 9. 最终线性输出 context_vec self.out(context_vec) return context_vec# # 5. 前馈网络 (发散思维 - 收敛总结)# class FeedForward(nn.Module): def __init__(self, emb_dim, dropout): super().__init__() # ★ 发散思维维度放大 4 倍 (512 - 2048) self.linear1 nn.Linear(emb_dim, emb_dim * 4) # ★ 收敛总结维度变回原样 (2048 - 512) self.linear2 nn.Linear(emb_dim * 4, emb_dim) def forward(self, x): x self.linear1(x) x nn.GELU()(x) # 非线性激活 (增加脑回路的复杂度) x self.linear2(x) return x # # 6. 层归一化 (拉齐起跑线)# class LayerNorm(nn.Module): def __init__(self, emb_dim, eps1e-5): super().__init__() self.eps eps # 可学习的参数缩放(scale) 和 平移(shift) self.scale nn.Parameter(torch.ones(emb_dim)) self.shift nn.Parameter(torch.zeros(emb_dim)) def forward(self, x): # 计算平均值 mean x.mean(dim-1, keepdimTrue) # 计算方差 var x.var(dim-1, keepdimTrue, unbiasedFalse) # 归一化公式(x - 均值) / 根号(方差 极小值) norm (x - mean) / torch.sqrt(var self.eps) return norm * self.scale self.shift4. 训练与生成数据预处理和训练是一件十分庞大和耗时的工程受限于篇幅这里只对训练方式和推理做简单介绍不做展开。4.1 模型的推理方式在完成模型结构之后我们先来尝试一下看看模型到底会输出什么内容# 首先将输入文本进行编码# tokenizer.encode()方法将文本字符串转换为模型可理解的数字序列token IDsinputs tokenizer.encode(QQ浏览器广告后台开发)print(编码后的token IDs:, inputs)# 将编码结果转换为PyTorch张量并添加批次维度# torch.tensor([inputs])创建了一个形状为[1, sequence_length]的张量batch torch.tensor([inputs])# 将批次数据输入模型进行推理# model(batch)返回模型的输出通常是logits或概率分布output model(batch)print(模型输出:, output)print(输出形状:, output.shape)# 使用argmax获取每个位置概率最大的token索引# dim-1表示在最后一个维度通常是词汇表维度上取最大值predictions torch.argmax(output, dim-1)print(预测结果token索引:, predictions)# 将预测的token索引解码回文本# tokenizer.decode()将数字序列转换回可读的文本字符串print(解码后的文本:, tokenizer.decode(predictions[0])) plaintext 编码后的token IDs: [51, 51, 586, 240, 6262, 1179, 5046, 799, 2507, 3158]模型输出: tensor([[[ 0.1939, -0.1545, -0.8574, ..., 0.1755, 0.4264, -0.2223], [ 0.4136, -0.0892, -0.4605, ..., 0.2929, 0.6576, 0.2146], [ 0.1227, 0.5149, -0.7861, ..., -0.2095, 0.1483, -0.0575], ..., [-0.1255, 0.3100, -0.4338, ..., -0.4569, 0.1680, -0.2783], [ 0.0123, -0.2317, 0.1034, ..., -0.6657, 0.4466, -0.1969], [-0.0439, -0.2680, -0.8711, ..., -0.8857, 0.7761, 0.2247]]], grad_fnViewBackward0)输出形状: torch.Size([1, 10, 6400])预测结果token索引: tensor([[1536, 1536, 738, 5155, 4507, 5155, 2857, 4672, 3506, 3283]])解码后的文本: 什什 R安排 similar安排软伴 diagnosonom可以看到输入 9 个 Token 后模型对应输出了 9 个向量每个向量的长度都等于词表大小。这些数值可以理解为模型对词表中每个词的‘打分’或者说概率这里省略了softmax处理。如果我们选取每个向量中数值最大的那个即模型认为概率最高的词并将其还原为文字就得到了模型最终的预测结果。目前模型返回的数据还是毫无逻辑的token因为我们还没进行训练接下来我们就让他变聪明些。4.2 训练4.2.1 数据构造我们先要知道我们期望模型返回的数据格式对于输入的 “QQ浏览器广告后台开发”, 我们期望模型整体前推一个字即输出 “Q浏览器广告后台开发组”这里的Q浏览器广告后台开发与我们的input相同我们希望通过这种方式让模型学习到语言模式而第n个字组即是我们想要的字。# 原始语料数据 (假设这是分词后的 ID 序列)# 对应文本: Q Q 浏 览 器 广 告 后 台 开 发 组data [51, 51, 586, 240, 6262, 1179, 5046, 799, 2507, 3158, 1335]data_tensor torch.tensor(data)# 1. 构造输入 (Input): 取序列的前 N-1 个字# 也就是: QQ浏览器广告后台开发x data_tensor[:-1] # x [51, 51, 586, ..., 3158]# 2. 构造目标 (Target): 取序列的后 N-1 个字 (即每个位置对应的下一个字)# 也就是: Q浏览器广告后台开发组y data_tensor[1:] # y [51, 586, 240, ..., 1335]print(f输入长度: {len(x)}, 目标长度: {len(y)})# 输入长度: 11, 目标长度: 11 (一一对应)# 3. 扔进模型计算logits model(x.unsqueeze(0)) # 增加 batch 维度# 4. 计算 Loss (交叉熵损失)# 这一步是在问模型# 当输入第1个Q时你预测是第2个Q的概率大吗# 当输入浏时你预测是览的概率大吗# ...# 当输入发时你预测是组的概率大吗loss nn.cross_entropy(logits.view(-1, vocab_size), y.view(-1))loss.backward() # 反向传播修正参数这里有个反直觉的点虽然我们在逻辑上是“预测下一个字”但在训练时模型是并行看到所有输入的。它不需要等“浏”字生成了再算“览”字而是一次性计算所有位置的预测误差(由于 3.4节 causal mask 的存在模型不会发生看答案的情况)。通过这种方式模型学会了看到 “QQ浏览”大概率要接 “器”看到 “后台开”大概率要接 “发”看到 “开发”大概率要接 “组” (或者 “者”、“工程师”)这就是大模型“学会说话”的根本原因。4.2.2 预训练 与 SFT这里补充说明一下SFT时的数据构造情况我们通常会定义一些特殊的 Token如 |im_start|来告诉模型哪里是人说的哪里是它该说的因此我们构造的对话数据一般为这种格式:# SFT 数据构造示例sft_template |im_start|user请介绍一下QQ浏览器。|im_end||im_start|assistantQQ浏览器是腾讯开发的一款...|im_end|在 SFT 阶段我们只计算 assistant 回答部分的 Loss。这就好比考试评分老师只给你的“答案”打分而不会因为你把“题目”抄写得好不好而扣分。 这样做的目的是让模型专注于学习“如何根据问题给出正确回答”而不是去死记硬背问题本身。4.3 采样策略在模型生成文本时我们通常不希望每次都选择概率最高的词这样会让输出变得过于确定和单调。温度和topk就是用来控制生成多样性的两个重要参数。温度Temperature就像是给模型的创造力调个温低温如0.1模型更保守总是选择最可能的词输出稳定但可能单调高温如0.5-1.0模型更放飞自我会尝试更多可能性输出更有创意但可能不够准确Top-k采样则是另一种控制方式只从概率最高的k个候选词中选择避免选择那些概率极低的词比如k4就只从前4个最可能的词中随机选择这两种方法可以单独使用也可以结合使用让模型在靠谱和有趣之间找到平衡。AI时代未来的就业机会在哪里答案就藏在大模型的浪潮里。从ChatGPT、DeepSeek等日常工具到自然语言处理、计算机视觉、多模态等核心领域技术普惠化、应用垂直化与生态开源化正催生Prompt工程师、自然语言处理、计算机视觉工程师、大模型算法工程师、AI应用产品经理等AI岗位。掌握大模型技能就是把握高薪未来。那么普通人如何抓住大模型风口AI技术的普及对个人能力提出了新的要求在AI时代持续学习和适应新技术变得尤为重要。无论是企业还是个人都需要不断更新知识体系提升与AI协作的能力以适应不断变化的工作环境。因此这里给大家整理了一份《2026最新大模型全套学习资源》包括2026最新大模型学习路线、大模型书籍、视频教程、项目实战、最新行业报告、面试题、AI产品经理入门到精通等带你从零基础入门到精通快速掌握大模型技术由于篇幅有限有需要的小伙伴可以扫码获取1. 成长路线图学习规划要学习一门新的技术作为新手一定要先学习成长路线图方向不对努力白费。这里我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。2. 大模型经典PDF书籍书籍和学习文档资料是学习大模型过程中必不可少的我们精选了一系列深入探讨大模型技术的书籍和学习文档它们由领域内的顶尖专家撰写内容全面、深入、详尽为你学习大模型提供坚实的理论基础。书籍含电子版PDF3. 大模型视频教程对于很多自学或者没有基础的同学来说书籍这些纯文字类的学习教材会觉得比较晦涩难以理解因此我们提供了丰富的大模型视频教程以动态、形象的方式展示技术概念帮助你更快、更轻松地掌握核心知识。4. 大模型项目实战学以致用当你的理论知识积累到一定程度就需要通过项目实战在实际操作中检验和巩固你所学到的知识同时为你找工作和职业发展打下坚实的基础。5. 大模型行业报告行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估以了解哪些行业更适合引入大模型的技术和应用以及在哪些方面可以发挥大模型的优势。6. 大模型面试题面试不仅是技术的较量更需要充分的准备。在你已经掌握了大模型技术之后就需要开始准备面试我们将提供精心整理的大模型面试题库涵盖当前面试中可能遇到的各种技术问题让你在面试中游刃有余。为什么大家都在学AI大模型随着AI技术的发展企业对人才的需求从“单一技术”转向 “AI行业”双背景。企业对人才的需求从“单一技术”转向 “AI行业”双背景。金融AI、制造AI、医疗AI等跨界岗位薪资涨幅达30%-50%。同时很多人面临优化裁员近期科技巨头英特尔裁员2万人传统岗位不断缩减因此转行AI势在必行这些资料有用吗这份资料由我们和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理现任上海殷泊信息科技CEO其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证服务航天科工、国家电网等1000企业以第一作者在IEEE Transactions发表论文50篇获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。资料内容涵盖了从入门到进阶的各类视频教程和实战项目无论你是小白还是有些技术基础的技术人员这份资料都绝对能帮助你提升薪资待遇转行大模型岗位。大模型全套学习资料已整理打包有需要的小伙伴可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】