一文看懂大模型到底怎么“做出来、用起来、跑起来”?(上)
在这篇文章中,我们将循序渐进地阐述如何从零开始,构建一个融合对话助手、代码代理和任务代理的统一大模型应用平台。阅读完毕后,您将对“大模型是什么、如何预训练、如何对齐成为ChatGPT、如何产品化部署、如何做检索增强生成(RAG)与智能Agent、如何实现代码助手,以及如何搭建整个平台并做好上线治理”形成深刻且可实践的整体理解。
简要
在这篇文章中,我们将循序渐进地阐述如何从零开始,构建一个融合对话助手、代码代理和任务代理的统一大模型应用平台。阅读完毕后,您将对“大模型是什么、如何预训练、如何对齐成为ChatGPT、如何产品化部署、如何做检索增强生成(RAG)与智能Agent、如何实现代码助手,以及如何搭建整个平台并做好上线治理”形成深刻且可实践的整体理解。

立即下载:我的密馆 (VaultLib)
全景路线图:
我们将按模块逐步展开,每个模块都是最终搭建完整平台的一块拼图:

- A. 基础篇:大模型到底是什么 – 理解大模型的基本概念,包括Token、概率生成、上下文窗口、幻觉现象和评测方法,为后续深入奠定心智模型。
- B. 预训练基座篇:从零到一实现预训练Base Model – 通过一个TinyGPT小实验线,介绍如何准备预训练数据、构建Tokenizer、搭建Transformer模型并训练一个小型GPT模型,理解训练曲线和稳定性挑战,并给出现实中扩展到GPT级模型的路线图和限制。
- C. Transformer机理篇:深入理解Transformer – 剖析Transformer的核心组件(Attention机制、Q/K/V、多头注意力、前馈网络、残差连接、LayerNorm、位置编码)以及长上下文的衰减问题,从工程视角理解模型结构。
- D. 指令微调与对齐篇:高效微调大模型 – 探讨如何通过SFT(有监督指令微调)提升模型指令遵循能力,以及通过偏好对齐(如RLHF、DPO、ORPO)让模型输出符合人类偏好,形成类似ChatGPT的友好对话能力。
- E. 参数高效微调篇:LoRA及进阶 – 介绍LoRA/QLoRA等低成本微调方法,以及如何管理多个LoRA适配模块,以实现一套基础模型在多领域、多任务下的定制和治理(融合、路由、版本管理)。
- F. 大模型推理部署篇:高效推理与服务化 – 深入大模型推理流程(prefill与decode)、KV缓存机制,探讨吞吐与延迟权衡、并发调度策略、模型量化技术,及如何将模型封装成稳定的服务、多模型路由等部署要点。
- G. 分布式训练篇:大模型训练的工程实践 – 学习在多卡多机环境下训练大模型的方法,包括数据并行、张量并行、流水线并行、ZeRO优化,了解通信瓶颈、检查点保存、实验可复现性与训练过程监控。
- H. 显存优化篇:显存占用与OOM问题解析 – 拆解训练和推理时显存的构成,提供排查OOM的方法和显存优化清单(梯度检查点、混合精度、梯度累积、量化、缓存策略等),帮助在有限GPU资源下跑大模型。
- I. 蒸馏篇:大模型压缩与传承 – 阐述知识蒸馏的重要性,介绍如何通过Logits蒸馏、指令蒸馏、偏好蒸馏等手段将大型模型的能力迁移给小模型,以及建立蒸馏效果的验收体系。
- J. 检索增强生成(RAG)篇:让大模型连接知识库 – 全流程讲解如何将向量检索融入大模型应用:文档切分、向量化、检索、混合检索与重排、答案生成与引用,确保答案与证据一致,并讨论RAG系统的评测方法。
- K. LangChain篇:大模型应用的工程化抽象 – 学习使用LangChain等框架,将提示(Prompt)、链(Chain)、工具(Tool)、检索器(Retriever)等进行模块化封装,实现复杂任务的链式调用、结构化输出、可观测性、回归测试和灰度发布。
- L. 智能Agent篇:类似Manus的任务代理 – 深入解析智能体的工作模式:如何让模型进行任务规划-执行-反馈-反思循环;如何设计安全可靠的工具使用机制(权限控制、幂等性、沙箱、安全审计回放);探索多智能体协作以及评测体系。
- M. 多模态篇:拓展模型输入输出到图像/语音/视频 – 介绍如何将图像、语音、视频接入LLM,包括多模态RAG方案和多模态Agent的工具链扩展,让平台具备处理多种模态的能力。
A. 基础篇:大模型是什么
1. 从人类语言到Token序列
大语言模型(LLM)处理的是文本,但它并不直接“理解”文字,而是将文字转换为数字来处理。这种数字化单元就称为Token。你可以把Token简单理解为“词片”或“子字词”。例如,英文里的hello可能是一个Token,而中文常用字也通常对应一个Token。之所以不是简单用单词或字作为单位,是因为采用一种压缩编码(如BPE或SentencePiece)能更高效地表示海量文本 。生活中的类似比喻是:假设你要教一个不懂中文的人使用中文字典,你可能会先教他偏旁部首(类似于Token),而不是一下子教所有完整汉字——因为熟悉了部首拼出汉字更有效率。
为什么要用Token? 因为计算机好处理数字序列,而Token化就是把文本变成数字序列的过程 。模型的输入是一串Token对应的数字ID。比如“你好,世界”在某个中文词表下可能被切成 [你好, ,, 世界] 这三个Token,然后分别映射到某个ID序列(如[3745, 812, 10376])。大模型训练和推理时,底层处理的就是这样的ID序列。通俗类比:Token就像乐高积木,把复杂的语言拆成小块积木,模型通过这些积木才能“搭建”出句子来。
Token的含义还涉及词表(Vocabulary)大小。词表包含模型所能识别的全部Token及其ID映射关系。词表大小通常在几万到十万级别,对应模型能处理的语言“粒度”。过大词表会增加模型参数、增大计算量;过小词表则会使文本切分得过碎(例如每个字母都单独成Token),导致序列变长、上下文难学。因此,构建合适的Tokenizer和词表是一门技巧活,我们在后续预训练章节会介绍如何从语料构建Tokenizer。
2. 概率驱动的文本生成
当我们跟ChatGPT对话时,它好像在“思考”接下来回答什么,其实底层原理相对直接:LLM在根据当前输入的Token序列,按概率分布选出下一个可能的Token,然后将这个Token拼接到序列末尾,不断重复这个过程生成文本。简言之,LLM是个条件概率分布近似器:给定前文,它会输出一个对下文各候选Token的概率估计,然后抽样或选取概率最高的Token作为输出 。
打个比方:你给模型前置提示:“从前有座山,山上有座庙,庙里有个和尚在讲故事:”。现在模型需要续写故事,它可能预测下一个Token最可能是“故事”(比如生成“故事”二字来重复),或者“他”(开始一个新句子:“他在讲什么呢…”),或者其他词,每种都有一定概率。最终模型可能采样出“他”这个Token,于是文本变成“从前有座山…和尚在讲故事:他”。接着模型继续根据新序列预测下一个Token,如“讲”或“说”等,直到你设定的停止条件(比如生成一定长度或遇到句号)。整个过程就像是逐词接龙,而且每一步选择都以概率论在起作用。
这种下一个词概率预测的训练方式,使LLM具备了“续写”任意文本的能力。这也解释了为什么大模型能够对各种开放式问答、写作任务给出结果:无论你的提问多么奇特,模型都视其为一段上下文,然后尝试预测一个合理的“回答”续在后面。
需要注意的是,模型给出的并非唯一确定的输出,而是一个概率分布。如果我们总是取最高概率的Token(即贪心策略),可能导致模型输出千篇一律的回答;所以实践中常引入采样策略(如温度、Top-p采样)来增加多样性,让生成结果更自然 。温度(decoding temperature)可以理解为随机性的调节阀,温度高则更随机、更有创造性,温度低则更稳健、更保守。这些在后续推理篇我们会详细讨论和演示。
3. 上下文窗口与模型记忆
LLM并不像人脑有长期记忆,它不能永久记住你说过的话。一切的输入输出都发生在一个有限长度的上下文窗口里。上下文窗口指模型在一次处理过程中,能够看的Token序列长度上限 。例如,GPT-3的上下文长度是2048个Token 。如果输入+输出超出了这个长度,超出部分模型就处理不了或遗忘掉。
你可以把上下文窗口想象成模型的短期记忆筒。筒有固定容量,新的内容装进来可能会把旧内容顶出去。一旦内容移出窗口范围,模型就“失忆”了,需要你重新提供相关信息。举个例子:如果聊天时你的第1条消息很长,到第50条对话时,那最初消息的Token可能已不在上下文中了——模型此刻未必记得起开头讲过什么。
这就是为什么实际应用中,当对话轮次增多时,我们常需要将重要信息放进系统提示或每次重复一部分摘要,以防止模型忘记前情。一些高级方法如检索记忆也是为了解决这个上下文局限问题(这将在RAG篇探讨)。另外,也有模型通过扩大小模型的上下文长度来改进,比如GPT-4提供了32K甚至更高Token的上下文窗口,让模型一次能读更多文档 。但需要注意,上下文越长模型效果未必线性提升:超长上下文下模型可能难以有效利用早期信息,这被称为长上下文退化问题,我们在Transformer篇会谈到可能的原因和改进思路。
4. 大模型的“幻觉”现象
当我们问ChatGPT一个知识性问题,有时它会给出看似自信但实际错误的回答,甚至编造事实和引文。这种现象被称为幻觉(Hallucination)。幻觉并不神秘,本质上是模型在概率驱动下的一种“自由发挥”:模型只学会了统计上的相关性,并不知道事实真伪。当提示要求超出其知识范围时,它仍会基于训练中学到的语料模式给出最可能的回应,哪怕内容是错的。
类比来说,大模型有点像一名爱面子的学生。当他确实不会回答时,也不想说“不知道”,于是就根据平时看过的课本和片段拼凑一个答案,表面看来有模有样。模型产生幻觉的原因很多,比如:训练数据中的事实性错误或偏见,模型缺乏实时知识,或提示方式引导它编造。而且由于模型没有真实的理解和推理能力(只是拟合训练数据模式),一旦问题超出分布,它就倾向于编造一个听起来合理的答案。
幻觉给大模型应用带来了挑战,尤其在需要精准事实的场景(如医疗咨询、法律)。解决幻觉常用方法包括:引入检索增强(模型先检索知识库再回答,以提供依据);对模型进行后期微调或强化学习,让其学会说“不知道”或引用来源 ;还有模型架构改进等。在后续RAG篇我们将实现带引用的问答来缓解幻觉,在对齐篇我们也会看到偏好对齐如何减少有害输出和胡编乱造倾向。
5. 如何评测大模型的表现
衡量一个LLM的能力不像衡量分类器那样有简单准确率,因为LLM是生成开放文本,评价具有主观性。然而我们可以用一些直觉的指标去评测:
- 流畅性与连贯性:输出的文本是否通顺、符合上下文逻辑?如果模型在写故事,中途有没有角色乱入或前后矛盾?这些由模型的语言质量决定。
- 有用性(Helpful):回答是否切题,解决了用户问题?是否给出详细、有启发的信息?这个需要人为判定是否“Helpful”。
- 真实性(Truthful/Factual):内容是否准确真实?比如回答常识或知识问答正确率如何。可以通过对照权威资料或引入知识检索检查。
- 安全性与偏见:模型是否避免了有害内容输出,比如歧视、色情、仇恨言论等。也观察它在被诱导时能否拒绝不当请求 。
- 遵从性:是否按照指令要求的格式和内容回答。例如要求列清单,模型就用列表输出;要求简洁,是否啰嗦等等。
除了人工主观评估,也有一些自动指标和基准测试。例如困惑度(Perplexity)衡量模型对已知文本的预测概率(越低表示模型预测更准确),常用于评估语言模型的纯建模能力 。还有诸如BLEU、ROUGE评估摘要或翻译质量,不过这些在开放聊天场景并不完全可靠 。最新的趋势是用GPT-4等强模型作为评审者打分,或构造对比来自动评测偏好,但这些方法也各有偏差。
B. 预训练基座篇:从零到一预训练一个Base模型
1. 准备预训练语料:从文本到训练样本
训练一个语言模型,需要大量的无标签文本数据。LLM的预训练任务通常是自监督学习,如语言模型目标:给定前文预测下一个Token(也称因果语言建模),或掩码语言模型(如BERT那样随机遮盖部分词预测)。为了简化,我们以因果语言模型为例,即模型读一串文本,试图预测每个位置的下一个词。
选择数据: 理想情况下,数据越多越好,而且要多样,比如涵盖百科、小说、论坛聊天、代码等等 。但在我们的TinyGPT实验中,为了可控,我们可以选择一个小规模语料,如维基百科的一小部分、开源小说,或者干脆用您手头的英文书或中文小说片段。重要的是文本要尽量干净,避免太多乱码或特殊格式,减少模型学习无用模式。
数据清洗: 工程上,预训练前通常需要做数据清洗和过滤,例如去除明显的广告、HTML标签、低质量内容等。对于个人小实验,可以不花太大精力,但要确保文本编码统一(UTF-8)且分段合理。比如将文本按段落切开,每段作为一条样本。大型项目里这会用到MapReduce/Spark等大数据工具处理 ,个人实验则写些Python脚本就足够。
样本格式: 对于因果语言模型,我们可以把所有文本直接拼成一个长串序列,让模型在这个序列上顺序训练。也可以逐条喂入句子或段落。常见做法是将多个文档合并,加上特殊分隔符作为一个巨大的Token流,然后按固定长度窗口切分为训练样本(例如每个样本包含128个Token) 。这样模型看不出文档边界,只需一视同仁预测下一个Token即可。我们在实现时可以设定一个block_size来决定每次输入序列长度。
构建Tokenizer并编码: 有了原始文本,需要用Tokenizer把它转为Token ID序列(这在基础篇我们已经概述)。你可以使用开源工具训练自己的Tokenizer,例如Hugging Face的tokenizers库可以根据语料训练BPE模型。出于简化,你也可以直接使用已有的预训练Tokenizer(例如GPT-2的Tokenizer)来对文本编码,这样词表和切分规则都现成的。但为了学习,这里建议尝试自己训练一个小词表,比如大小8000或10000的BPE词表,看能否覆盖你的语料。
实际操作中,Hugging Face的Tokenizer.train_new_from_iterator接口可以很方便地输入文本迭代器训练新词表。训练完毕后,将文本转成Token ID序列并保存。注意: 保存的数据量可能较大,建议以二进制格式保存Token ID(例如numpy数组或PyTorch的Tensor),否则纯文本存储Token可能占空间太大。
经过以上步骤,你应该得到Tokenized的语料(形如很多ID序列构成的文件)。接下来我们就可以定义模型结构并开始训练了。
2. 定义一个迷你GPT模型
现在进入正题:实现一个迷你的GPT模型。我们将使用Transformer解码器结构,因为GPT系列属于Decoder-only Transformer 。你不需要从零写所有层,可以利用PyTorch的高层API或参考开源实现。但这里我们提纲挈领地说明核心结构:
一个标准GPT-like模型包含以下组件:
- 嵌入层(Embedding):将每个Token的ID映射到一个向量表示 。比如词表大小5000,嵌入维度d_model=256,则嵌入层是一个5000×256的矩阵。输入一个Token ID,会输出其对应的256维向量。
- 位置编码(Positional Encoding):因为Transformer不自带位置信息,需要加上位置编码 。可以使用固定的正余弦位置编码 或可学习的位置嵌入。简易实现可以直接用一个长度=最大序列长的矩阵pos_emb[seq_index]。
- 多层Transformer解码块:每层包括多头自注意力(Multi-Head Self-Attention)、前馈网络(Feed-Forward Network),每个子层有Residual残差连接和Layer Normalization 。多头注意力让模型可以从多个子空间关注不同维度的信息 ;前馈网络则对每个位置的表示做进一步非线性变换。残差连接可以看作“旁路”,让原始特征可以直接传递,帮助训练深层网络 。LayerNorm则保证数值稳定,加快收敛。
- 输出线性层:最后一个Transformer层的输出仍是每个位置一个向量,需要通过线性映射回词表维度,然后Softmax得到对每个词的概率分布 。这个线性层的权重通常与Embedding层权重共享(embedding matrix的转置),以减少参数并提升表现。
对于TinyGPT,你可以选择一个极小的配置,比如:层数 n_layer=2,隐藏尺寸 d_model=128,多头数 n_head=4,前馈层隐层维度 d_ff=256。参数规模会非常小(几十万级别),适合CPU上训练尝试。如果有GPU,也可以适当增大一些以观察更有趣的行为。
实现途径:最快的方式是使用transformers库提供的GPT2模型类,指定小配置然后随机初始化权重。但为了学习,也可以用PyTorch手写一个简化Transformer。下面给出一个使用HuggingFace Transformers的示例代码来创建模型:
import torch
from transformers import GPT2Config, GPT2LMHeadModel
# 定义配置
config = GPT2Config(
vocab_size=5000, n_positions=128, n_embd=256,
n_layer=2, n_head=4
)
model = GPT2LMHeadModel(config)
# 打印模型参数量
param_count = sum(p.numel() for p in model.parameters())
print(f"Model parameters: {param_count}")
这段代码构建了一个词表5000、上下文长度128、2层、4头注意力、每层隐状态256维的GPT模型,并输出参数量。你会发现参数量在几百万以内,非常小。这种模型肯定无法完成复杂任务,但足以验证训练过程。
3. 训练过程:让模型学习“下一词预测”
有了模型和数据,我们进入训练环节。这部分和训练普通神经网络类似,但要注意大模型训练的效率和稳定性。
损失函数: 对于因果语言模型,我们使用交叉熵损失(Cross-Entropy)。模型输出每个时间步的词概率分布,我们计算预测分布与真实下一个Token的差异。具体实现上,一次前向传输会得到形如(batch_size, seq_len, vocab_size)的logits张量,我们会右移输入序列来对齐输出和目标。例如:输入序列是[Token1, Token2, Token3],模型会预测Token2,Token3和Token4(下一个词)的概率,我们将真实序列的Token2, Token3, Token4(其中Token4是实际下一个词)作为标签计算损失 。
优化器和学习率调度: 大模型通常使用自适应优化器如AdamW,并配合学习率预热和衰减策略。因为一开始随机初始化模型输出是噪声,如果学习率太大,损失可能爆炸;预热可以让学习率从小逐步增加以稳定初期训练。我们也通常使用梯度裁剪来防止梯度过大。对于TinyGPT,选择较小的初始学习率如1e-3,训练若干迭代后观察损失曲线调整。
Batch Size和迭代: 由于我们语料有限,可将所有Token序列随机打乱组成批次。比如每个batch取16个序列,每序列128个Token。每个epoch遍历全部数据。注意总的数据量可能不大,训练很快就过拟合,所以我们更关注让模型成功学会一些模式。即使过拟合,也没关系——毕竟我们只是验证能否拟合训练集。
训练监控: 每隔一定迭代(比如每100步)打印训练loss,观察下降趋势。如果loss不降反升,可能学习率过高或实现有bug。对于深层大模型,还需要监控显存占用、速度等,但TinyGPT不会有这些麻烦。你可以记录loss曲线,并在训练结束后保存模型参数(保存model.state_dict()或使用transformers的.save_pretrained方法)。
让我们假设你已经用上述配置训练TinyGPT了一段时间,接下来看看我们能得到什么。
4. 初步结果与模型现象
训练完成后,我们先关注损失曲线。理想情况下,训练损失会逐步下降,趋向一个较低值。由于我们的小模型容量有限,最终损失可能停在一个较高水平。如果loss在开始阶段就震荡或发散,要检查学习率设置和梯度是否稳定。
现在,用训练好的TinyGPT尝试生成文本(来个“Hello, LLM”示例)。我们给模型一个短prompt让它续写:
model.eval()
prompt = "Hello LLM,"
input_ids = tokenizer.encode(prompt, return_tensors='pt')
output_ids = model.generate(input_ids, max_length=20)
print(tokenizer.decode(output_ids[0]))
这段代码会打印TinyGPT模型生成的一段文本。由于我们的模型非常小、语料有限,这段输出大概率是驴唇不对马嘴的。例如,可能输出类似:“Hello LLM, I LLM LLM”之类的怪句子。这并不意外——未经过微调的大模型原型,只学会了统计上的拼字,没有任何对人类指令的特殊优化。因此,我们不会期待TinyGPT直接变成对话助手。但如果你的模型成功输出了一些训练语料中常见的模式(比如模仿了语料里的小说语气),那已经证明训练起作用了。
另一个现象:如果训练语料不大,模型可能过拟合得很严重——也就是它会背诵训练集句子。当你用训练中过出现过的一句话开头作为prompt,模型可能一字不差地把后续原文背出来。这体现了语言模型记忆训练数据的能力。这种过拟合在工业界是隐患(涉及版权和机密泄露),但在TinyGPT验证中属于正常现象。
稳定性问题:在训练过程中,你可能遇到例如NaN损失或梯度爆炸。这通常因为学习率太高或模型初始化不当。通过降低学习率、使用LayerNorm、残差等技巧通常可以缓解 。另外,为提高稳定性,可以在训练初期采用较高的dropout来正则化,但太小模型上不一定明显。
通过TinyGPT的尝试,你应该对预训练的耗费有了切身体会:即使一个几百万参数的小模型,要学会语言模式也需要相当多迭代。而真正的大模型(数十亿参数)需要海量的数据和算力,其训练难度远非个人PC可以承受。下一节我们会具体谈这个现实鸿沟。
5. 从TinyGPT到大型GPT:现实与路径
经过TinyGPT实验,你可能会问:我能否按同样步骤训练一个ChatGPT那样强大的模型? 理论上路径类似,但现实中个人难以从零训出GPT-3/GPT-4级别模型,主要因为:
- 数据规模要求:GPT-3使用了约45TB的文本数据(约上千亿Token)进行训练 。个人很难收集、存储如此规模的数据,更别提清洗和管理。数据质量和多样性也需要专业团队打磨。
- 计算资源:GPT-3有1750亿参数 ,训练一次估计花费数百万美元 。有测算称单GPU需要数百年才能跑完 。即使借云服务,没有上千张GPU同步运转几周也是不现实的 。OpenAI用的是超级计算集群,个人笔记本连它万分之一都不到。
- 工程复杂性:训练超大模型需要分布式并行(数据/张量/流水线并行组合)、容错恢复、速度优化。稍有不慎就会中途崩溃。还要定期评估指标决定是否继续训练、检查过拟合。在企业中往往有专门的MLOps基础设施支持。
- 调优技巧:大模型对超参数和训练策略很敏感,需要经验反复调试,如学习率方案、正则化、混合精度训练等。如果一轮训练要几周,试错成本极其高昂,需要前期大量小规模实验(比如先在10亿参数模型上调优)才能放大规模。
综上,个人或小团队难以承担从零预训练一个ChatGPT级模型的成本(这就像让一个业余焊工独自造一架客机)。但这并不意味着我们无法参与大模型研发,替代路径是充分利用开放的基座模型,在其上微调、对齐,或者训练较小领域模型:
- 重现小规模模型:正如我们做的TinyGPT,或者进一步训练一个比如1亿参数的模型(可以试用公开数据和8块GPU大约几天完成)。这些实验能帮助理解模型行为,为将来有机会参加大模型训练打基础。
- 使用开源大模型基座:现在社区开放了许多强大的预训练模型(如Meta的LLaMA系列、Bloom、Falcon等)。你可以下载一个适合GPU容量的模型(7B、13B等),然后针对你的任务做二次微调。这跳过了最耗资的预训练阶段,又能让你定制模型行为。
- 侧重后期微调和产品集成:许多创新和价值其实在预训练之后,例如RLHF对齐、人机反馈迭代、工具使用整合等。个人和小团队完全可以在这些方向下功夫,用开源模型做出有特色的应用。
- 企业场景下:如果你在公司/机构,有海量数据和GPU集群,可以尝试训练自己的大模型。这需要明确的工程模块:数据管道(分布式数据加载、高效存储)、训练框架(DeepSpeed/Fairseq或自研调度)、评估基准(定期跑下游任务看效果)、Checkpoint机制(以防中断和版本回溯)、监控报警(跟踪loss曲线和硬件状态)。同时组织上要有多团队协作:算法研究员设计模型和训练流程,数据工程团队准备清洗数据,平台工程师维护集群和加速,以及测试团队负责评估模型。这样的“多人远航”才能驾驭GPT级别的训练任务。
C. Transformer机理篇:Transformer内部原理详解
1. 自注意力机制与 Q/K/V 三要素
Transformer之所以强大,核心在于自注意力机制(Self-Attention)。相比RNN逐步处理序列,注意力机制允许模型全局地看待整个序列,灵活地从中挑选相关信息。
类比说明: 想象你在阅读一段文字,为了理解当前句子的一个词,你可能回头翻阅前文相关段落。自注意力就类似这样的行为:对于序列中的每个词(称为“查询Query”),模型会去“查看”序列中所有词(作为“键Key”)以找到与该Query相关的信息,并从这些词对应的表示(“值Value”)中汇总出一个加权结果 。简单说,就是“一对多匹配,再加权汇总”。
形式化一点,每个输入Token首先通过线性变换映射出三个向量:查询向量Q、键向量K、值向量V 。Q用于跟其他词的K计算相似度(点积),得到一组注意力权重,然后这些权重会对相应的V进行加权求和,得到当前Token的新表示 。
举例:句子“猫在吃鱼”。当模型处理“吃”这个Token时,它的Query会去跟句子中每个词的Key计算分数,也许跟“猫”匹配度高,跟“鱼”次之,跟“在”很低。那么模型就会更注重“猫”和“鱼”的Value来更新“吃”的表示。这意味着模型捕捉到了“谁在吃什么”的关联。这种机制可以横跨较远距离,把关联词的语义结合起来。
数学上,常用Softmax将Q·K点积转成0~1的概率权重,然后Attention输出 = ∑(权重 * V)。这就是Scaled Dot-Product Attention公式的直观解释 。注意还会除以√d_k来平衡尺度(d_k是K向量维度) 。总之,自注意力让模型具有非局部的依赖捕获能力,不像CNN只能卷积邻域,也不像RNN只能记忆最近状态。
2. 多头注意力:一心多用的奥秘
如果只有单一一套QKV投影,模型可能关注信息的角度有限。Transformer引入多头注意力(Multi-Head Attention) ,简单理解为:模型并行地执行多组Attention,每组称为一个“头”。每个头有自己的一套投影矩阵,产生独立的Q_i, K_i, V_i,然后计算Attention_i输出。最后把各头输出拼接再线性变换融合 。
这样做的效果是每个注意力头可以关注不同类型的关系。比如在翻译任务里,一头可能学对齐语法主语宾语,一头学对齐指代词。一心多用,丰富了模型表征能力。
打个比喻,多头注意力就像一个乐队里有不同乐手:吉他注意旋律,贝斯注意节奏,鼓注意拍子,合在一起才完整。每个头关注序列里的某种模式,最终叠加出全面的理解。
工程上,多头Attention会把隐藏维度分成h份,每份做注意力。比如隐藏维度512,8头的话每头内部运算按64维。计算量跟单头同维度相当,只是并行了,所以参数量略增,但性能提升显著。实践中Transformer经常用8或16个头。
注意Mask机制: 在自回归语言模型中,为了避免泄露未来信息,需要对Attention做Mask,只允许每个位置关注它之前的位置(包括自己)。实现上是在计算Q·K时,对那些非法的未来位设置负无穷权重,让Softmax后权重为0。这样模型生成时才能一步步来,不会偷看后面的词 。库实现通常提供attention_mask或causal_mask参数实现这一功能。
3. 前馈网络与非线性变换
经过Attention层后,Transformer还有一个前馈全连接网络(Feed-Forward Network, FFN)作用于每个序列位置 。典型FFN包含两层线性变换,中间接一个非线性激活(如ReLU或GELU)。比如输入/输出维度都是256,中间扩展到1024维然后再投射回来。
为什么需要FFN? 因为Attention本质是加权平均操作(线性),单靠它难以表达复杂变换。而串联一个非线性网络可以提升表示能力,让模型能拟合更复杂的函数关系。例如,Attention可能聚合了相关词的语义,FFN可以进一步混合和转换这些语义。有研究认为FFN层可以看作对Attention提取的信息做“特征检测”,比如有的神经元可能专门捕捉某种语法结构,然后反馈到表示中。
类比:如果Attention让每个词获取了上下文相关的信息,那么FFN就像在这信息基础上做思考,推导出新的抽象特征。在Transformer中,每个Attention层后几乎总会跟一个FFN,两者搭配构成模型主要深度结构 。
值得一提,Transformer的FFN层通常占据相当大比例的参数和计算量,因为扩展维度很高(通常前馈维度是隐藏维度的4倍)。例如GPT-3 175B里,FFN层参数就占大头。所以有时优化会针对FFN(如混合精度、张量并行分块计算)。
4. 残差连接和 Layer Normalization:稳定深层训练
Transformer每个子层都有残差连接(Add & Norm) 。具体做法是:子层(Attention或FFN)计算输出后,加回原输入再做LayerNorm 。这样设计有两大益处:
- 缓解梯度消失/爆炸:残差提供了一条恒等路径,使得即使子层学不到东西,至少原输入可以传递到下一层去。对于深层网络,这能稳住梯度流,使训练更稳定。
- 方便叠加层:残差相当于让每层学“增量”改变,而非完全重算特征。模型可以逐层细化,也更容易训练出接近恒等的变换从而把梯度传到底层。
Layer Normalization(LN)则在残差之后将数据归一化,使每层输出分布稳定 。这对训练深层Transformer也很关键。如果没有LN,不同层输出尺度变化大,会难以收敛。
值得注意,有两种常见Transformer结构:前置LN(pre-LN)和后置LN(post-LN)。GPT系列使用Pre-LN,即在Attention和FFN中先LN再计算,而原始论文Transformer是Post-LN(在残差加和之后LN)。实践表明Pre-LN训练更稳定,已经成为主流。
对我们工程师来说,残差+LN提供了一个调试信号:如果某层输出全是NaN或分布异常,可能是某处LN失效或残差爆炸,需要检查梯度或者缩小学习率。另外也要知道,训练时常见为了节省显存,会将部分LN推迟到后向再计算(这是DeepSpeed Zero-Offload等的trick),但原理上不变。
5. 位置编码:让模型知道次序
Transformer与传统RNN不同,它没有顺序读取机制,因此必须注入位置信息。位置编码(Positional Encoding)就是给每个位置增加一个表示,让模型区分 “第1个词”和 “第5个词” 。
两类位置编码:
- 固定位置编码:如原始论文用正弦/余弦函数生成。具体公式略去,但直观讲,它为每个序号生成一串在不同频率上变化的值 。这些值加到词嵌入上,使得不同位置的词向量在某种维度上有规律差异。固定编码无需训练,但它为模型提供绝对位置感知。
- 可学习位置嵌入:为每个位置索引设一个可训练向量,与词Embedding类似 。模型会学出一套适合任务的位置表示。这种方法简单有效,但如果序列超出训练时长度,模型就无法处理(因为没学过更大位置的embedding)。因此GPT-2等用了可学位置,但GPT-3又回到正余弦。近来一些改进如RoPE(旋转位置编码)兼具可学习和可推广性特点,被LLaMA等采用。
对长上下文的退化:当上下文非常长时,位置编码的分辨率和模型注意力机制可能不够用了。一方面,正弦位置编码远距处几乎正交,模型可能难以关联起相距很远的词。另一方面,注意力的计算开销是O(n^2)随长度增长,长序列下模型可能倾向于关注局部而忽略远处(因为远处的注意力权重可能被稀释)。这就是长上下文退化现象的来源。
为缓解这问题,新技术包括:稀疏注意力(不看所有位置,只看局部窗口+全局摘要等)、分块或检索增强(将长文拆块,引入检索机制找相关块交给模型),或者让模型显式地有分层记忆。在Transformer内部,也有如ALiBi(Attention Linear Bias)等方法,通过改变位置编码方式使得注意力分布更平稳地覆盖长距离。这些超前研究超出本文范围,但在应用中若需要让模型看长文档,这些都是需要考虑的工程手段。
6. 综合示例:Transformer一次前向流程
让我们通过一个小实例串联起Transformer的子机制:假设输入句子(Token序列)为:“机器 学习 很 有趣”。Transformer的处理流程:
- Embedding:每个Token(“机器”、“学习”、“很”、“有趣”)映射成向量 e1, e2, e3, e4。
- 加位置编码:为序列位置0,1,2,3添加位置向量 p0, p1, p2, p3。得到输入 x1=e1+p0, x2=e2+p1, …
- Attention层(假设只有一头):先对 x1,…x4 线性变换得到 q1…q4, k1…k4, v1…v4。对于第i个词,例如i=3(“很”),计算 q3与所有k的点积:[q3·k1, q3·k2, q3·k3, q3·k4],softmax得到注意力权重 α3 = [α31, α32, α33, α34]。这表示第3词对每个词的重视程度。可能 α32, α34较高(关注“学习”和“有趣”),α31, α33较低。然后输出 o3 = α31v1 + α32v2 + α33v3 + α34v4。这就是融合了全句信息的表示。
- 前馈层:将 o1…o4 各自通过一层两层的MLP,得到新的表示 f1…f4。
- 残差+LN:其实在3和4步之间、之后,都各有残差连接。例如3步其实计算的是 y3 = LN(x3 + o3) 作为Attention输出,4步也是 z3 = LN(y3 + FFN(y3)) 作为层输出。
- 重复多层:如果模型有多层,就将上一层输出 z1…z4 作为下一层输入,重复Attention+FFN过程。
- 输出层:最后得到每个位置的隐藏表示 h1…h4。对每个 hi,通过线性映射到词表维度并softmax,得到针对下一个词的概率分布。训练时我们会用第1词的h1去预测第2词“学习”、h2预测“很”、… h4预测句子后面终止符。
通过这个流程,可以看出Transformer层反复在“全局交流(Attention)”和“局部变换(FFN)”之间交替,将信息逐步融合提炼,从而对序列有深刻的表示。
更多推荐


所有评论(0)