本文围绕大模型的技术细节展开,涵盖了模型结构、注意力机制、Transformer 及其变体(如 Bert、T5、GPT 等)的分析,讨论了大模型的训练和优化方法,如微调策略、解码策略以及强化学习(RLHF)。关注了大模型在处理长文本、多模态输入以及应对幻觉问题时的优化策略。此外,文章还探讨了向量数据库在大模型中的应用以及 Agent 设计的挑战。这些内容或为从事大模型应用的人士提供技术参考。
{% quot 本文大量内容整理自网络,侵删。 %}
模型结构
本质上这类问题是考基础,现有模型都是在标准的 Transformer 结构上修补。
基础概念
- 梯度消失:反向传播中,若某些层的梯度小于 1,随着层数的增加,梯度会指数级降低,最终靠近输入层时变得极小,导致权重更新缓慢,难以学习和优化。(e.g. 选用 Sigmoid 函数作为损失函数)
- 梯度爆炸:反向传播中,网络梯度值逐层放大,最终靠近输入层时变得极大,导致网络前层权重更新幅度大,难以稳定训练,甚至无法收敛。(e.g. ReLU 函数且权重较大时)
- FLOPS:等同于 FLOP/s,表示 Floating Point Operations Per Second,即每秒执行的浮点数操作次数,用于衡量硬件计算性能。
- FLOPs:表示 Floating Point Operations,表示某个算法的总计算量(即总浮点运算次数),用于衡量一个算法的复杂度。
- 神经网络初始化权重不能为 0,否则更新权重时不同参数之间始终一致,失去特征学习能力。常见的有随机初始化(可能出现梯度爆炸/消失),Xavier(每层方差尽量一致)等。
- 标签平滑:目标类别的概率略低于 1,而非目标类别的概率略高于 0,防止过拟合(自信认为某些类别 100%),提高鲁棒性。
- L1 正则化由于梯度恒定,强制小权重归零从而实现稀疏性(顶点在坐标轴的菱形),而 L2 正则化的梯度随权重变小而减弱(圆形区域,没有尖锐顶点),倾向于让权重接近但不完全为 0,导致权重分布更加均匀。
注意力机制
- 为什么要有注意力机制
- RNN 中 token 是一个个喂给模型的,随着序列长度的增加,模型下一步计算的等待时间越长,无法实现并行计算,而远距离间信息缺失情况也越明显。
- 编码器中的自注意力机制:用于输入序列的全局信息交互和依赖关系建模,每个位置都可以与其它位置进行信息交换。
- 解码器中的掩码自注意力机制:用于生成序列中的当前和之前位置的信息交互,防止生成时泄露未来的信息。
- 解码器中的交叉注意力机制:结合编码器输出和解码器输入(Q 来自解码器当前层的输入,K 和 V 来自编码器输出),生成新的序列,使解码器能够利用编码器的全局上下文信息。
- 注意力机制的计算公式,一定要这样计算吗
- 一般是 ,查询向量代表想要寻找的特征,键向量代表输入中的特征,相当于每个位置的特征描述,值向量可以理解为每个位置实际携带的信息。点积相似度作为矩阵乘法,能够利用针对性优化。
- Transformer 为什么使用多头注意力机制,分析 时间复杂度
- 捕捉多样化的特征,在不同子空间中学习不同的特征,多角度建模增强表达能力。
- 增强模型的稳定性和泛化能力,避免单一的注意力头过拟合某些特定特征。
- 并行计算,提高计算效率,尤其是在 GPU 环境下。
- 提高上下文信息的捕捉能力,关注输入序列的不同部分。
- 时间复杂度大致和单头的相似,均为 ,在不同子空间中并行地处理输入序列,从而捕捉更丰富的特征,提高模型的表现力和稳定性。
- 为什么 Q 和 K 使用不同权重矩阵生成,为什么不能使用同一个值进行自身点乘
- 每个词的 query 和 key 具有不同的含义,它们需要不同的权重矩阵来捕捉不同的信息,也就是在不同的空间投影,可以让模型更加灵活地调整每个位置对其他位置的关注度,从而提高模型的表达能力。如果 Q 和 K 一样,乘积结果矩阵中,对角线的值会比较大,导致每个词会过分关注自身,从而降低模型的表达泛化能力。
- 计算注意力为什么选择点乘而不是加法,从计算复杂度和效果上面讲区别
- 二者复杂度都是 ,前者可以利用矩阵乘法的硬件优化,后者需要额外经过 softmax 函数处理,增加了额外的复杂度。并且点乘可以自然度量相似性,加法捕捉向量间关系的能力较差。
- 为什么要对注意力进行 scaled(),公式推导
- 之所以进行 scaling,是为了使得方差稳定(具体来说就是控制为 1),数据分布相对均匀,进而在 softmax 的过程中 ( 方差过大可能导致 softmax 后只有一个值为 1 的元素,其余都是 0),梯度下降得更加稳定,避免因为梯度过小而造成模型参数更新的停滞。
- 所以甚至也可以在 和 时就提前缩放,T5 采用的就是这样的方式。
- 在计算注意力时如何对 padding 做 mask 操作
- pad 位置的注意力分数一般使用极大负数填充,后续 softmax 应用后这些位置的权重趋于零。
- 为什么在进行多头注意力时需要对每个 head 降维
- 提高处理效率,避免过拟合(每个训练头都只表示一部分特征信息),最终输出时多头结果会被合并,仍旧保有原来的表达能力。
- transformer 是如何处理可变长数据的
- RNN 是通过 timestep 的方式处理可变长数据,Transformer 是通过计算长度相关的 self-attention 得分矩阵来处理可变长数据。
- Flash Attention(Fast and Memory Efficient Exact Attention with IO-Awareness)
- 出发点:减少访存开销,和其它的减少计算开销的方案不同。
- v1
- 时间上计算瓶颈不仅仅在计算能力,而主要在读写速度,利用分块计算和核函数融合来降低对显存的访问,尽可能打满 SRAM,减少从显存搬运到 SRAM 计算的总时间。
- 空间上节省显存,标准 attention 场景需要保存和读取 的注意力矩阵,Flash Attention 将存储压力降至 。
- 精准注意力计算,Flash Attention 确保了完全等同于标准 attention 的实现方式,比之前的近似方法更优。
- 计算限制与内存限制
- 判断方式:算法所需总运算量和总数据读取存储量的比值,与硬件算力上限和带宽的比值作比较,前者更大是计算限制主导,反之是内存限制。
- 计算限制:大矩阵乘法、通道数很大的卷积运算。读更快,算更慢。
- 内存限制:逐点运算操作。激活函数、dropout、mask、softmax、BN 和 LN 等。算更快,读更慢。
- 分块计算:将 切分,然后 和 也切分(二者需要相同块数),本质是分块是为了减少 attention 计算期间的读写量,主要是 和 两个部分,也因此可以将显存占用从 降到 ,计算量则为 。对于 自身,可以由三次循环(确定 max、确定分母、计算数值)优化为两次循环(用递推在定 max 的循环里同时完成分母的计算),同时计算 可以在第一次循环完成,计算注意力和 的乘积可以在第二次循环完成。但是实际上我们并不关心 和 得到的注意力得分等中间结果,因此可以进一步将第二次循环的式子带入第一次,并通过递推消除这些中间结果,最终实现一次循环(v1 版本原型)读写完成注意力机制最终输出的计算。其中需要注意分块导致的 局部最大、全局最大以及局部和、全局和的细节处理,最终输出结果也需要每遍历一块就不断用当前最新的 rowmax 和 rowsum 去更新,直到遍历完最后一块,确保和标准场景下结果完全一致。
- 标准 attention 的 IO 复杂度是 ,分块后的 IO 复杂度是 ,显著降低。
- 数据块越大,读写次数越小,runtime 整体下降,但当数据块大小超过 256 后,runtime 下降不明显,因为矩阵变大导致计算耗时更大,平衡了读写节省的时间。
- v2
- 交换内外循环,v1 使用 为外循环, 为内循环,但其实 也是在行维度的,所以固定 循环 更自然,计算输出合并时也更直接,节省了中间结果读取,实际上 v2 版本推出前有很多 flash attention 的实现已经修改了循环顺序。
- 新增
seq_len维度的并行,尽可能打满 SM(流式多处理器)利用率。主要在 上切分,除非 SM 实在打不满否则不在 上切分,以确保不同的 block 之间可以独立计算(切分后每一行来自不同的 block,为了得到全局 结果还需要汇总计算一次)。 - 尽可能使用矩阵乘法以最大化利用硬件优化,对于最终注意力输出不再在每轮迭代中都完成 中带来的除法计算,而是对计算得到的结果统一计算。
- 优化 block 内部 wrap 级别的工作模式,尽量减少 warp 间的通讯和读取 shared memory 的次数,例如之前需要反复读取 的数据,现在只用反复读取 的数据。
- 总的来说 v1 版本已经基本实现了单体的最优化,v2 版本则只能侧重在打补丁和并行度方面的优化。
- v3
- 适配 Hopper 架构 GPU,矩阵乘法和 softmax 重叠,以及支持 FP8 低精度处理。
- 采用“生产者 - 消费者”编程模型,将注意力计算划分为两个角色。生产者异步加载数据到片上共享内存(SMEM),消费者从 SMEM 读取数据并执行矩阵乘法等任务。
- 重新安排计算顺序,第一阶段,softmax 操作与下一迭代的 矩阵乘法重叠。第二阶段, 矩阵乘法与下一迭代的 softmax 操作重叠。通过异步计算,实现了矩阵乘法与 softmax 操作的并行处理。
- FlashAttention-3 采用分块量化和非相干处理技术。分块量化为每个块单独设置缩放因子,捕捉局部数值分布。非相干处理通过旋转输入数据,破坏不同块之间的相干性,减少量化误差传播。
- Multi Query Attention 和 Group Query Attention
- MQA 和 GQA 都是节省 KV Cache 显存(进而这部分显存就可以用于加大 batch size,提高利用率,另外也减少了读写的数据量),前者是所有注意力头都共享同样的 K、V,后者是在原始的 MHA 和共享的 MQA 之间取权衡。
- KV Cache:Attention 每层的计算仅需要当前 参与,而 K 和 V 全程参与计算,因此需要将每一步的 K 和 V 缓存起来。占用大小计算:,其中 是批大小, 是输入输出长度, 是注意力头数, 是层数,两个 分别是 KV 以及 Float16 占 2 个 byte。
- MoBA:稀疏注意力的一种统一实现方式,Q 需要和紧邻的 KV block 做一次关联,然后再和其它的 block 做相关度关联,找出相关的。也就是说如果全选,就是 MHA;如果一个也不选,就是滑动窗口注意力。
- NSA:MoBA同期DeepSeek发布的稀疏注意力方案,压缩、选择和滑动窗口。
Transformer
- Transformer 的两个缺点
- 内存占用大:Transformer 的内存占用量随上下文长度而变化。这使得在没有大量硬件资源的情况下运行长上下文窗口或大量并行批处理变得具有挑战性,从而限制了广泛的实验和部署。
- 随着上下文长度的增加,推理速度会变慢:Transformer 的注意力机制随序列长度呈二次方扩展,并且会降低吞吐量,因为每个 token 都依赖于它之前的整个序列,从而将长上下文用例置于高效生产的范围之外。
- 讲解一下 Encoder 的构造
- 输入嵌入、多头自注意力机制计算 (MHA) 和前馈神经网络(FFN)。
- 此外,每一层的自注意力机制和前馈网络后都跟有 残差连接(Residual Connection) 和 层归一化(Layer Normalization)。
- 为什么在获取词向量后需要对矩阵乘以 embedding size 的开方
- Embedding matrix 的初始化方式是 xavier init,这种方式的方差是 1/embedding size,因此乘以 embedding size 的开方使得 embedding matrix 的方差是 1,在这个 scale 下可能更有利于 embedding matrix 的收敛,也有利于匹配位置编码的数据范围。
- 位置编码
- Transformer 并行计算,每个 token 彼此独立,也就是输入是无序的,所以需要位置编码提供位置信息。
- 位置编码的实现
- 用整型标记:模型可能会遇到比训练序列更长的输入,不利于泛化,且长度越长编码值会很大。
- 用 标记:当序列长度不同时,token 间的相对距离不一样。
- 用二进制编码:编码出来的位置向量,处在一个离散的空间中,不同位置间的变化是不连续的,无法使用位置向量来表示浮点型。
- 需要有界又连续的函数:三角函数(sin)满足条件,通过频率控制 sin 函数的波长,越往右走(也就是越低位)波长越大,对变化越不敏感,也就是需要更高精度控制。但是仍然有一个问题,sin 是周期函数,因此从纵向(token 序列)来看,如果函数的频率偏大,引起波长偏短,则不同 t 下的位置向量可能出现重合的情况,所以在原论文中选用了一个非常小的值作为频率,尽可能拉长波长。
- 使用 sin 已经实现了位置向量唯一、有界且可泛化(周期),现在还需要不同位置的位置向量可以通过线性变换得到。因此改为每个 token 位置向量中两两一组,sin 和 cos 交替出现,线性变换就由旋转实现, 时刻相当于旋转对应的角度。
- 位置编码的性质
- 能够表示相对位置,两个位置编码的点积仅取决于偏移量 。
- 位置编码的点积具备无向性(对称性),即 。
- 综上两条,也就是位置编码的点积只能表示距离,无法表示方向。
- 同时位置编码的内积存在远程衰减,也就是相近的位置对当前位置影响更大,较远的影响更弱,反映局部关系。远程衰减对于保持语意连续性非常重要,但是在远端不能一直为 0,而是震荡为 0 以保证能够注意到相关 token。
- 长度外推问题
- 大模型在训练时和预测时的输入长度不一致,导致模型的泛化能力下降。例如,如果一个模型在训练时只使用了 512 个 token 的文本,那么在预测时如果输入超过 512 个 token,模型可能无法正确处理。这就限制了大模型在处理长文本或多轮对话等任务时的效果,而这正是热点问题之一。
- 旋转位置编码 RoPE
- 原始位置编码的问题:经过 attention 层后,位置变量真正起作用的变成了 ,相当于引入了线性变化,无法保持对称性、远距离衰减等良好性质。
- 思路:attention 层计算会破坏输入层位置编码的性质,那么如果在 attention 层中融入位置信息,就能保持性质不变。当 较小时,拉近 和 的距离;较大时则拉远距离。
- 实现:直接定义一个逆时针正交旋转矩阵,相当于只旋转 、 来保证绝对位置信息,同时维护其原始特征。对于多维,相邻维度两两一组应用旋转变换即可(恰好现在大模型的输入维度都是偶数),可以理解为不同运转速度的时钟。
- RoPE 的完整操作流程是:对于 token 序列中的每个词嵌入向量,首先计算其对应的 query 和 key 向量,然后对每个 token 位置都计算对应的旋转位置编码,接着对每个 token 位置的 query 和 key 向量的元素按照两两一组应用旋转变换,最后再计算 query 和 key 之间的内积得到 self-attention 的计算结果。相当于通过旋转将相对位置信息嵌入注意力计算的内积过程中。
- 需要注意,在 的选择上,若引入过大的基数 ,则可能导致旋转角度十分微弱,相近位置的向量几乎重合,位置信息带来的帮助被大幅稀释。
- 使用【小基数 + 短数据】的组合训练,那么旋转角度较大,并不是每一个点都能训练到,但是通常可以收敛(大多数两两组合都可以至少旋转一圈);使用【大基数 + 长文本】的组合做训练,旋转角度较小,训练覆盖粒度较好,但是大部分圆盘都很难旋转满一圈(收敛)。因此一般选择先用【小基数 + 短数据】训练,然后【大基数 + 长文本】微调。
- NTK-RoPE(NTK:神经正切核):对于靠前的组合(圆盘),更容易反映绝对位置的变化,因此尽可能让其学习到绝对位置信息,也就是突破 pretrain 看过的圆周部分(外推)。而对于靠后的组合(圆盘),对绝对位置变化不敏感,我们尽可能让其学习到相对位置信息,保持在 pretrain 看过的圆周部分,更加角度精细化训练(内插)。对于最后一个组合(圆盘),缩放因子 , 表示训练文本长度。
- RoPE 仍然存在外推问题,表示为推理超过训练长度的内容时模型崩坏,PPL 等指标显著上升,可以从 进制转换的角度 理解外推、内插与 NTK-aware Scaled RoPE。
- 位置线性内插(Position Interpolation,PI),将超出长度的样本线性缩放映射回原区间,每个位置都不再是整数,也即位置 。内插和外推一样也还是需要微调,因为模型没有在训练时处理过拥挤的位置关系,但是所需要的步数明显更少,因为很多场景下相对大小(序信息)更加重要,而且可以利用模型具备的泛化能力结合相对大小理解绝对大小信息,缺点是在处理相近的 token 时可能无法准确区分它们的顺序和位置,分布情况可能变得复杂不均,加大了模型的学习难度。
- 低频(长波长高维度)部分:指位置编码中使用的较低频率的成分。这些成分变化缓慢,对应着较大跨度的位置变化。低频部分主要用于捕捉远距离的位置关系。在序列中,低频成分可以帮助模型更好地理解长距离之间的依赖性。在外推的情况下无法处理这部分信息,影响模型的理解。远程衰减性质也基本上是由低频部分保持。
- 高频(短波长低维度)部分:指位置编码中使用的较高频率的成分。这些成分变化快,对应着序列中较短的距离。高频部分主要用于编码邻近位置之间的关系,即局部信息。这在短距离的关系中尤其有用,比如相邻的单词或符号之间的关联性。内插在此时由于拥挤,处理会随长度增长越来越困难。
- 直接外推会将外推压力集中在高位(m 较大)上,而位置内插则会将低位(m 较小)的表示变得更加稠密,所以 NTK-Aware Scaled RoPE 的思想就是高频外推,低频内插,这里选择的方案是在 引入 ,保持和内插一致,而 很大时有 ,相当于外推。这种方式下不微调就取得了不错的外推结果。
- NTK-by-Parts:定义波长代表 维 RoPE 嵌入执行完整旋转(2)所需的 token 长度。如果波长远小于上下文长度,不进行内插。如果波长等于或大于上下文长度(低频部分),只进行内插并避免外推(与 NTK-aware 不同,避免外推到越界值影响内插和比例因子)。如果是介于中间的长度,采用 NTK-aware 的方案。
- Dynamic NTK:每次前向传递中,位置嵌入都会更新比例因子 ,其中 表示当前序列长度。
- FIRE(Functional Interpolation for Relative Position Embeddings)
- 渐进式插值(查询和键的相对距离除以查询位置,实现归一化)与可学习的映射函数(也就是非线性内插)。
- YaRN(Yet another RoPE extensioN)
- 使用了 NTK-by-Parts。
- 在计算注意力时引入温度缩放能够带来改进,因此把两个旋转位置嵌入缩放 即可(内积后相当于在 softmax 操作前缩放到 。另外,原论文通过在 LLama 拟合最低困惑度实验,得到了相应的表达式来计算温度的缩放系数,即 ,其中 表示缩放因子,该式也表明温度常数和熵可能存在一定的通用关系。
- CoPE(Contextual Positional Encoding)
- 本质上和其它的位置编码方式不冲突,相当于一种新的位置信息思路。这种动态位置编码的方式更贴合人实际的处理逻辑,往往并不会单独一个个处理 token,而是相对模糊关注某一个词或者词组维度,且切割方式是不固定的,所以在某些场景下能够获得更优性能。
- 位置矩阵计算量过大,相当于 Attention 矩阵计算量,且由于是 token 定制,希望做到 Contextual Positon,所以很难借助预处理省略。想要避免两次 Attention 矩阵计算量,那么只能应用于位置和语义解耦的位置编码(ALiBi 等),而也需要一定的适配复杂度。
- 难以兼容 RoPE,因为 RoPE 会改变 和 ,而在位置矩阵确定前无法进行 RoPE,陷入死锁。另外实践上 RoPE 需要通过绝对位置来实现相对位置保证计算效率。
- Attention with Linear Biases(ALiBi)
- 直接不添加位置编码,改为加一个静态的不学习的常数负值(实际就是相对位置距离乘上每个头的对应权重),能够加快训练速度,减小参数量,外推情况下更加稳定,但在实践中使用不够多,可能有训练稳定性问题(例如 Loss Spike),也有讨论说感受野太有限,远程衰减过于显著,线性衰减可能限制了表达能力。
- 实际实验中在 Long Context 场景下显著不如 RoPE。
- 残差结构及其意义
- , 表示恒等映射,残差主要用于解决网络加深后出现的退化问题。通过加深网络可以尝试让不同层学习不同特征,进而增强模型表达能力,但是由于退化和梯度消失,需要输出逼近输入,这样就能保证深网络的效果不会比浅网络更差,也就是 残差逼近 0。
- transformer 中,对每一层的 attention 和 FFN,都采用了一次残差连接,也即每个位置当前层的输入和输出相加。
- BatchNorm 和 LayerNorm 的区别,为什么选择用 LayerNorm
- 层间输入分布偏移问题(ICS)
- 由于数据分布的变化,导致每层的参数依次受到前面的变动影响,在适应过程中训练的难度增大。
- 在过激活层的时候,容易陷入激活层的梯度饱和区,降低模型收敛速度。比如 sigmoid 函数在绝对值很大时梯度就几乎消失。
- 输入变动大,上层网络需要不断去适应下层变动,因此学习率不能设置过大,因为每一步确定性很低,但是这进一步降低了模型收敛速度。
- 而无论是采用非饱和激活函数(比如 ReLU)、使用更小的学习率,或是更细致的参数初始化方法,以及数据白化(对每层输入做线性变化,如 PCA,调整方差均值,去除特征间关联),都是饮鸩止渴的方法,增大了模型运算量,也容易陷入超参数调整的复杂情境中。
- BatchNorm
- 对每一个 batch 进行操作,使得对于这一个 batch 中所有的输入数据,它们的每一个特征都是均值为 0,方差为 1 的分布。单纯限制分布在 之间也不合理,降低了数据的表达能力,因此辅以线性变换,两个可学习超参数。
- 在训练过程里,我们等一个 batch 的数据都装满之后,再把数据装入模型,做 BN。但是在测试过程中,我们却更希望模型能来一条数据就做一次预测,而不是等到装满一个 batch 再做预测。这有两个实现方法,一个是保留训练模型中,每一组 batch 的每一个特征在每一层的 ,这样显然消耗过高存储空间。另一个方法是 momentum,也即考虑过去的均值和当前的均值,比例通过超参数 控制。
- 除了解决 ICS 问题外,在规整数据分布后,也可以相应减轻极端值导致的过拟合问题,进而缓解对 dropout 的依赖和使用。
- 后续的工作中,各个比较实验也证明了,ICS 问题并不一定导致模型表现差,同时 BN 对 ICS 问题的解决能力也是有限的,BN 有效的原因更可能是它使得优化曲线更加平滑。
- LayerNorm
- 背景:BN 在长度不一致的情况下存在问题,尤其是在文本问题中,文本中某些位置缺少足够的 batch_size 的数据,计算出的 偏差明显。另外,由于测试中需要使用累积经验统计量,所以当测试文本长度超过训练文本时会缺少对应累积统计量。不过这只是理论上的,实际工程中都是多裁少 pad。也正是因为 LN 更适合处理长度不一致的情况,LN 成为了 NLP 问题中的默认 config,相当于有一个约定俗成的感觉。
- 区别:相比 BN 在特征间进行标准化操作,LN 是在整条数据间进行标准化操作,也就是对于图像问题,BN 在某个特征通道上做各图标准化,LN 则是在一张图片所有通道和像素值范围内做标准化;对于文本问题,BN 是对整个小批量数据的所有序列 token 的所有特征维度标准化,LN 是对单个序列中单个 token 的所有特征维度标准化。
- 也就是说,对于 LN,各条数据间在进行标准化的时候相互独立,每当来一条数据时,对这条数据的指定范围内单独计算所需统计量即可。
- 简单来说,主要是处理变长序列、batch size 不敏感、训练和推理行为一致(均针对单样本,且无需保存累积统计量)以及天然更契合自注意力机制(针对单样本)。
- Transformer 论文中原始采用的是 Post-LN,而后提出的改进是使用 Pre-LN,二者的差异在 LN 和残差块(addition)的相对位置关系。Pre-LN 的优势是无需做 warmup,所以收敛速度更快,也规避了不必要的超参调整成本引入。具体原因是 Post-LN 在输出层的 gradient norm 较大(紧接在线性变换后,这里的线性变换是为了放大标准化 + 输出层梯度累积效应),往下层走则呈现下降趋势。因此这种情况下如果不使用 warmup 策略,在初期容易引起模型的震荡。然而也有观点表示 Pre-LN 会损失部分特征,因此同样条件下,Post-LN 能够借助更全面的特征信息得到更优结果。
- RMS(Root Mean Square) Norm
- 相当于 LayerNorm 的平替,大幅降低计算量,不需要计算均值(去中心化)和标准差(缩放),只需要计算均方根 来归一化输入,却能保持和 LayerNorm 相当(甚至更好)的稳定性效果。
- 原论文还提出了 RMS Norm,用前 个样本来估计 RMS,进一步减小计算复杂度。
- Deep Norm
- 在 Layer Norm 之前扩大残差连接,也即 。
- PreNorm 更容易训练,PostNorm 训练好后效果更优
- 对于 PreNorm,,梯度不会出现显著的放大或缩小,但是每项量级相似,近似等效于更宽而非更深的模型。
- 对于 PostNorm,,层数越高,梯度削弱更多,模型对靠后的层更敏感,梯度难以控制,前期需要 warmup 保持训练稳定,但是从深度角度来说后期训练效果更好。
- 层间输入分布偏移问题(ICS)
- 前馈神经网络简单描述,使用了什么激活函数,公式,有何优劣
- 线性变换(全连接层升维,提供更多的表征能力)→激活函数(非线性)→线性变换(全连接层降维)。
- ReLU 函数,优点是简单高效、减少梯度消失、稀疏激活(负数输入)和高效梯度更新(避免了梯度弥散导致的靠近输入更新缓慢),缺点是死亡 ReLU 问题(输入总负,永远无法更新参数)、输出非平衡(较多 0 造成偏置)、梯度爆炸和不平滑性( 处不连续,导数突然跳变)。
- Decoder 阶段的多头自注意力和 Encoder 有什么区别,为什么需要 sequence mask
- 编码器阶段,输入序列的所有位置都可以彼此相互关注。
- 解码器阶段,输出序列是逐步生成的,不应该看到未来的词。
- Transformer 的并行化如何体现,Decoder 端可以并行吗
- Transformer 的并行化主要体现在 self-attention 模块,在 Encoder 端 Transformer 可以并行处理整个序列,并得到整个输入序列经过 Encoder 端的输出,但是 RNN 只能从前到后的执行。Decoder 在训练的时候可以(正确的目标输出序列已经存在),但是推理输出的时候不可以。
- Encoder 是如何和 Decoder 交互的
- 编码器处理输入序列,生成每个位置的上下文表示。
- 解码器通过交叉注意力机制访问编码器的上下文表示,结合自回归生成的方式逐步生成输出序列。
- 编码器为解码器提供了全局信息,帮助解码器在每一步生成输出时参考输入序列中的关键信息,确保生成的输出序列与输入语义相关联。
- teacher forcing、exposure bias 和 scheduled sampling,原因是采用一个对角注意力掩码就可以并行计算多次循环(后面的 token 不用等前面的算完,而是直接用真实的)。另外也可以在训练时减缓误差累积导致的更难收敛的问题,但是 bias 问题也是显著的(更依赖特定的模式,而压根没有学到知识本身的信息)。
- Adam 优化器
- SGD 恒定学习率,容易陷入局部最优,需要高超的调参技术才能获得更好的效果,毕竟 transformer 模型越来越大更难收敛,因此还不如选用 AdamW,而 AdamW 的主要缺点还是 资源占用大,大模型训练时显存占用的大头。
- 模型参数如果使用 fp16,AdamW 优化器额外存储两个 fp32 参数,那么占用的显存将是模型参数的 4 倍。并且混合精度训练时,为保证梯度更新的数值稳定性,优化器还会额外保存一份 fp32 的参数。所以相当于总显存 = 激活值 + 1 倍模型参数 + 梯度(1 倍模型参数) + 优化器参数(4 倍模型参数) + fp32 模型参数(2 倍模型参数) 8 倍模型参数 + 激活值。
- N 为模型参数量,例如 7B,那么推理时至少需要 2N GB 显存(fp16 下模型加载完全),训练时需要 2N(fp16 模型参数)+2N(fp16 梯度)+12N(AdamW 优化器,4N 是 fp32 模型参数备份,4N 是 fp32 一阶动量,4N 是 fp32 二阶动量),也就是总 16N GB。
- 因此 lora 比全量微调节省显存其实不只是涉及的参数小了,同样更是因为优化器状态占用的显存也相应降低了。
- 具体实现
- Momentum 动量梯度下降:改为使用历史梯度的指数加权平均,加速收敛、减少震荡,以及逃离局部最优。
- RMSProp:计算每个参数梯度平方的指数加权平均值 ,然后在更新梯度时用当前参数除以 ,借此避免不同参数的梯度差异过大。这里也是 自适应学习率 的体现,梯度较大减步长,梯度较小加步长。如果当前所处的区域比较平坦(梯度的二阶项很小)则我们可以用较大的学习率来更新,快速走出鞍点,如果当前所处的区域比较陡峭(梯度的二阶项很大),则为了防止梯度爆炸等不稳定的情况发生,我们需要用较小的学习率谨慎地更新。
- 简单来说,动量负责方向调整,而自适应学习率根据梯度幅值调整步长。
- AdamW 的改进:增加 weight decay,也就是每次参数更新后减去一个很小的值,避免参数过大影响泛化性(如果几个参数过大则很可能主导结果,导致泛化性变弱)。原有的 Adam 是 L2 权重衰减直接加在 Loss 里,直接影响后续动量和自适应学习率更新的计算,进而导致 Adam 优化效果受影响,因此 AdamW 将权重衰减和梯度计算解耦。
- 所以需要保存的参数是动量项(一阶矩估计)和梯度平方的加权平均值(二阶矩估计),而梯度计算中值通常较小,所以必须使用 fp32 高精度保存避免下溢。
BERT
GPT 采用 Masked-Attention,对模型和训练数据的要求会更高,因为模型能读到的信息只有上文。而采用普通 Attention 的 Bert 在训练阶段就能同时读到上下文。这个性质决定了 GPT 模型越来越大的趋势。 但是,长远来看,Masked-Attention 是 push 模型更好理解文字的重要手段,毕竟在现实中,我们更希望培养模型知上文补下文,而不是单纯地做完形填空。
- Bert 本身是两个任务:MLM(完形填空)和 NSP(预测下一句是否紧跟)。全称 Bidirectional Encoder Representation from Transformers,也即语义理解的双向预训练 Transformer Encoder。
- Bert 和原生 Transformer 的主要区别
- 双向、同时在 MLM(遮罩语言模型)和 NSP(下一句预测,二元分类两个句子是否真实相连)两个任务上训练。
- MLM 中,80% 使用
<mask>,10% 随机替换为词表中的其它词,10% 不变。主要是真实任务中不会有<mask>遮罩词,同时也适当引入噪声,增强泛化。 - Bert 是 encoder-only 的,所以只用于理解任务,而非生成任务。
- 为什么会在开头加
[CLS],可以有别的替代方案吗- 序列全局语义表示,作为分类任务的输入综合决策,也用于 NSP 任务保持了一致性。可以有别的方案,例如最后一层所有词的 embedding 做池化,但是可能会弱化部分特征信息,尤其是在句子长度较长时。
- 在训练和推理时特殊字符的加减设置一定要保持一致,尤其是像
[CLS]这种添加在开头的,会吸引很多 attention,对效果影响可能很大。
- 为什么三个 embedding 可以相加(token、segment 和 position)
- 本质上是特征融合,直接相加和 concat 后过全连接层其实是一致的。
- 为什么要用 WordPiece/BPE 这样的 subword Token
- 基于空格的分词器:罕见词难表示,且容易出现大词表问题,token 的 one-hot 向量会很长,增加了 embedding 的训练参数量,并且冗余严重,同义不同形式的词算作不同词(loved,loving)。
- 基于字符的分词器:单个字符本身缺乏语义,且增加了输入序列长度。
- 基于子词的分词器:借助基本单元,相当于介于上述二者之间。实现是基于统计学的,不是基于语言学的。
- Byte Pair Encoding(BPE):统计连续字符对的出现频率,注意结尾要加上结束字符,相同的连续字符对出现在开头末尾的意义往往是不同的。
- WordPiece:BPE 关注【频率最高】,WordPiece 关注【概率最大】。WordPiece 在每次合并时将选择一对相邻的子字符,这对子字符满足:当合并它们时,它们对语料库语言模型的概率提升最大。
- Unigram Language Model (ULM):BPE 和 WordPiece 没有关注相同单词的不同划分在不同场景下的表现。ULM 首先生成大词表,然后基于这张词表,对所有语料的所有子词划分结果考察,不断丢弃对总体评分贡献较低的子词。
- 常见的解决 OOV(词表溢出)的方案
- 切分:将罕见词切分为子词单元(如 BPE),或直接退回到使用 byte 处理(LLaMA),这样甚至能够通过拼接字节实现泛化,且词表大小可控(基础字节 256 个),但是又增加了训练和文本生成时间,因为例如一个汉字切分为三个字节就明显增加了序列长度,另外模型需要学习字节粒度的表征,进一步增加了训练难度。
- Bert 中是如何处理一词多义问题的
- 主要是因为关注了上下文,尤其是在 MLM 任务下,经过 self-attention 操作后,在不同上下文语境中的词向量是不一致的。
- 以 Bert 为例,计算模型参数量
- 输入层:三个嵌入和 LayerNorm。
- 输出层:block 堆叠,注意力和 FFN,以及各自的 LayerNorm。
- Bert 为什么要使用 warmup 的学习率 trick
- 避免梯度爆炸,同时也避免了部分注意力头因初始化不佳或学习率波动而出现训练不稳定的情况。
- 为什么说 GPT 是单向的而 Bert 是双向的,对比 NTP 和 MLM 任务
- 前者只使用左边的上文,后者使用左右的上下文,所以 GPT 更适合续写生成类任务,在处理多义词和全局信息理解时可能遇到困难。
- prefix LM 和 causal LM 区别是什么?
- Prefix LM(前缀语言模型)是一种生成模型,在生成时,前缀语言模型会根据给定的前缀(即部分文本序列,可以是上文,但不一定是上文)预测下一个可能的词。这种模型可以用于指令跟随、问答等任务,比如 T5。
- Causal LM(因果语言模型)是一种自回归模型,它只能根据之前的文本生成后续的文本,而不能根据后续的文本生成之前的文本。在训练时,因果语言模型的目标是预测下一个词的概率,给定之前的所有词作为上下文。这种模型可以用于多轮对话、开放文本生成等任务,比如 GPT。
- 本质上就是掩码不同,Causal LM 是下三角矩阵,而 Prefix LM 则由开放的前缀部分和下三角矩阵的生成部分组成。
- Bert 存在的问题
- MLM 对单个 token 随机 mask,丢失了短语和实体信息,在中文语料上尤其明显。
- MLM 仅预测被 mask 的 token,其余 token 没有参与预测,而 GPT 等模型可以对每个 token 进行预测,导致 MLM 训练效率偏低,训练时间过长。
- NSP 任务可以学到句子维度的信息,但是仅为二分类,且负样本构造简单(随机采的,不一定来自同一主题),导致模型不能充分训练,消融实验显示 NSP 对下游任务作用小于 MLM。
- ALBert(A Lite Bert)
- 对嵌入参数化进行因式分解,大的词汇嵌入矩阵分解为两个小的矩阵,先映射到低维词嵌入空间,再映射到隐藏空间。另外通过跨层参数共享进一步提升参数效率。
- 提出 Sentence-order prediction (SOP) 来取代 NSP,负例是选择一篇文档中的两个连续的句子并将它们的顺序交换,这样两个句子是同主题下的,能够学到连贯性信息。
- RoBERTa
- 采用更大的参数量、数据集和 batch size。
- 动态掩码,每次输入一个序列都会生成新的掩码模式。
- 使用字节级 BPE 的文本编码来处理数据,原生 bert 使用粒度较大的字符级 BPE,缓解稀有字符 OOV(不在词表)问题。
- 改进输入格式并取消下一句预测任务,连续抽取句子直至塞满长度限制(512),限制不能跨文档抽取在实验中获得了更好的效果。
T5
- Text-to-Text Transfer Transformer,希望提出一个统一的模型框架,将各种 NLP 任务都视为 Text-to-Text 任务。
- 采用 Transformer 的 encoder-decoder 结构,以便在生成和分类任务上均取得不错的结果,因此参数量基本是 BERT-base 模型的 2 倍。
- BERT-style(MLM)、Replace Span(唯一特殊 Token、结尾特殊 Token)、15% 的内容 Mask,以及 Mask 段长度选 3 时效果最好。
- 指令前缀的使用,其实算是指令微调的雏形。
- GPT 和 UniLM 对比:输入部分的注意力改为双向不会带来收益,Encoder-Decoder 架构的优势很可能只是源于参数翻倍。
GPT
整个 GPT 系列其实都是在往更通用,更多任务的方向发展。
GPT1 提出在子任务上少量标注样本微调。 GPT2 则是不提供任何子任务相关的训练样本,在足够大的预训练模型上理解。 GPT3 则是力求改善 GPT2 的性能。 GPT4 则更高维度多元化,除了改善传统文本任务上的性能,也关注多模态和复杂推理等任务场景。
- GPT1
- 无监督预训练(next token prediction)+ 有监督微调。
- 微调时涉及文本分类、蕴含理解(判断假设是否成立)、文本相似和问答四个任务,通过构造输入形式和输出处理,均统一为了任务 + 标注的形式,不会改动其中的 Transformer 结构,这是和先前工作最大的区别。
- 由于 GPT1 面对的任务比 BERT 更难,以及数据集不如 BERT 充足,因此效果不如对方。
- GPT2
- 改为 PreNorm,输入序列长度从 512 改为 1024,参数初始化按残差层个数缩放(缩放比例为 )。
- 在模型最后一个自注意力层之后,额外增加一个层归一化,对于生成任务有利于防止输出向量的分布过于偏离(过大或过小),从而提高生成文本时的连贯性和流畅度(避免前文波动导致后面生成也变得“激动”或“消极”)。
- 增大模型层数(最大 48 层),而 GPT1 只有 12 层。
- 使用 Zero-shot 而取消微调,不引入特殊符号,而是要让整个下游任务的输入和之前在预训练的时候看到的文本形式一样,也就是 prompt/ICL 的雏形。
- GPT3
- 最大模型由 96 层组成,每层具有 96 个多头注意力头。
- 隐藏层维度为 12288,输入长度改为 2048。
- 使用了交替的密集(dense)和局部带状稀疏(locally banded sparse)注意力模式,在稀疏层中每个 token 只与它的邻近 token 进行注意力计算(类似卷积),本质上是保留信息和减少计算量和内存需求的妥协。另外在其它工作中还有一种叫分层稀疏注意力,除了关注邻近的,还关注固定步长倍数的位置,相当于适当保留长距离信息捕捉。
- 正式提出 ICL,通过 few-shot、ont-shot 和 zero-shot 三种方式评估。
- Codex:GPT3 架构上,预训练 + 微调,微调数据选取算法网站和 Github 上使用 CI 的函数,包含单元测试,后续可用于评估代码是否准确。输出采用 Nucleus Sampling,取词至总和不小于 0.95,然后采样(随机性 + 确保不采到概率太低的词)。评价使用 @ ,也即从模型的答案中随机抽取 k 个后,能从这 k 个里得到正确答案的概率。
- GPT3.5:支持编辑和插入文本,而不仅局限于续写,可能是基于特定训练数据构造和位置编码处理实现的。上下文提供 4k 和 16k。
- ChatGPT/InstructGPT:在 GPT3 的基础上 SFT 和 RLHF,尤其是借助人工标注大幅提升模型的【价值观】正确程度和人类行为模式的【真实性】。另外网页版 ChatGPT 相比 GPT API 额外做了一些优化,包括但不限于特定的 System prompt 和安全检查。
- ChatGPT3.5-Turbo:支持文件输入,多轮对话优化,稳定且经济,更适合大规模应用场景。
- GPT4
- Scaling Law 持续,更多的标注数据用于 RLHF 对齐。
- 支持图片、文件和链接作为输入,同时提供了联网功能。
- 上下文提供 8k 和 32k。
- GPT4o:o 代表 omni,更关注真实人类行为模拟和多模态。
- GPT4o-mini:响应更快,经济实惠,上下文窗口 128k,内置安全措施,主要是替代 3.5 的。
- GPT4-o1:解决复杂推理问题,在训练和推理中做了优化,而不是局限于先前的 CoT 一类的操作,详见后文思维链一节。
- GPT4-canva:回归交互,对于文本包括提供行内建议、调整文本长度、调整阅读等级(从幼儿园到研究生)、检查语法、清晰度和一致性(润色)以及添加表情符号。对于代码提供审查建议、错误修复、日志与注释添加和其它编程语言翻译。利用了数据合成技术,例如从 OpenAI o1-preview 中提取输出。在确定是否触发画布方面,文本优先改进【正确触发】,也就是暂时以在普通简单问答任务也触发的浪费情况为代价,而编码则减少触发后续基于真实用户数据持续改进。除此之外,训练模型在用户通过界面显式选择文本时执行有针对性的编辑,否则倾向于重写。
- GPT4.5:价格昂贵的半成品。
- GPT4o 更新:图像生成能力。
- GPT5
- 路由,自主判断是否需要深度思考。
LLaMA
- 基本结构
- Transformer Decoder
- PreNorm + RMSNorm
- SwiGLU 激活函数,也即 ,Swish 的公式是 ,效果类似平滑版的 ReLU,能够在取得较低困惑度方面发挥更好的效果。为了 保持和原本的 FFN 参数量相同,将维度从 4 降到了 。代码可以使用
self.w2(F.silu(self.w1(x)) * self.w3(x))实现。 - RoPE 位置编码,存在于各个层的 MHA(or GQA)之后,计算 Attention 之前,需要确保 RoPE 的 旋转(相对位置)不变性。
- BPE 分词算法,数字 digit 单独拆分,未知的字符拆分到 byte 级别,进而能够通过 byte 的方式构造出不在 vocab 的字符,词表大小 32k。
- AdamW 优化器。
- LLaMA 2.0
- 训练数据集增加约 40%。
- 上下文长度从 2048 到 4096。
- 7B 和 13B 使用与 LLaMA 相同的架构,34B 和 70B 模型采用分组查询注意力(GQA)。
- RLHF 发挥了显著的作用,二元比较标注样本(偏好差距分为四类),主要关注模型的有用性(helpfulness,如何回应以满足用户的请求和提供所需的信息)和安全性。除此之外还探索了拒绝采样(RS)和 PPO 两种算法。
- 针对最初的 RLHF 模型忘记了初始指令的问题,提出 Ghost Attention(GAtt),简单粗暴地在多轮对话每次的 Query 之前加上 Instruction,同时为了避免语句不匹配问题,将除了第一轮以外的之前的内容都 mask 掉,不参与 loss 计算。
- Code LLaMA:以 LLaMA 2.0 为起点微调。
- LLaMA 3.0
- 数据集大幅增加,代码数据和非英语数据增加,微调数据集(指令/人工注释)中还是英文为主,所以迁移到中文任务上需要中文数据集微调。说到这里,如果采用扩充词表的方式支持其它语言,是较为低效的方案,因为 embedding 的训练难度较大,对语料库的容量和质量有一定要求。
- 流水线过滤:启发式过滤器、NSFW 过滤器、语义去重方法和文本质量分类器等。
- 所有参数量的模型都使用了 GQA。
- tokenizer 由 sentence piece 更换为 tiktoken,词表从 32k 到 128k,更高效编码文本,但也导致嵌入层输入输出矩阵增大。
- 输入输出长度从 4096 增加到 8192。
- 结合了模型并行、数据并行和管道并行。
- 对齐使用了 DPO。
- 合成数据,例如代码数据中使用了执行反馈、编程语言翻译(一般是翻译为数据集中不常见的语言)和文档反向翻译回代码(进而对比一致性确定质量)。
- 对一般数据评分基于准确度(accuracy)、指令遵循(instruction following)和语气(tone/presentation),对编码数据基于缺陷检测(bug identification)和用户意图(user intention)。
- 尽量避免使用机器翻译数据,以防止可能的翻译名称偏差、性别偏差或文化偏见。推理数据考虑到这些数学问题仅使用简单的语言,因此使用了翻译数据,以提高非英语语言中定量推理性能。
- 事件性:使模型生成与预训练中存在的数据保持一致,对于预训练数据中存在与事实不一致数据的情况,收集针对性的数据。
- LLaMA 3.1
- 405B 模型,旗舰模型的参数规模预测。
- 确定数据混合比例:知识分类(便于对网络上过度代表的数据类别进行降采样,例如艺术和娱乐) + 缩放法则预测混合比例。预测确定通用 50%,数学和推理 25%,代码 17%,剩下 8% 是多语言,最终在预训练中增加了非英文比例,上采样了推理 token。
- 工具调用,和 OpenAI GPT 类似,使用各种特殊的 header tokens 和 termination tokens。前者用于指示对话中每个消息的来源和目的地,后者指示何时轮到人类和 AI 交替发言。这里主要采用了搜索引擎、Python 解释器、数学计算引擎三种工具,以及给定工具定义的场景。
- 没有使用 MOE 结构,MOE 的主要优势是减少训练和推理成本,付出的代价是训练不够稳定以及推理时额外付出大内存来存储膨胀的参数量。但当用户量大请求多的时候,推理成本占比会更高,此时使用 MOE 对于推理会更友好,所以选择 MOE 更多的是效率考量而非效果提升。在训练中,Dense 模型是可以自由选择激活哪部分神经元,而 Sparse Moe,通过训练路由来控制哪个 token 激活哪部分的 expert,所以 MOE 在训练中每个 token forward 和 backward 的实际的激活参数是远少于同等规模的 Dense 模型的。
- 上采样高质量数学、逻辑、代码等数据在小模型上提升更明显(另外两个提升小模型效果的方案是数据量数据质量和蒸馏),而在 405B 模型上作用不明显。
- 长上下文代码推理:解析 Python 文件识别导入语句,并确定它们的依赖关系。选择最常依赖的文件,特别是至少五个其他文件引用的文件,从存储库中删除这些关键文件之一,并提示模型识别丢失的依赖文件,生成必要的丢失代码。普通的长上下文 QA 对就结合分层总结 + 关键词抽取 + 摘要 + 提出全局问题。
- 消融实验发现混合 0.1% 的长上下文合成数据与原始短上下文数据,性能最佳。
- 实验结果显示 SFT 模型在长上下文任务中推理效果高质量,那么 DPO 仅使用短上下文也不会影响长上下文性能。
- LLaMA 3.2
- 小中型视觉 LLM(11B、90B)和端侧轻量级纯文本 LLM(1B、3B),后者支持 128K 上下文。
- 重点强调了安全和隐私保障。
- LLaMA 4
- 预计更关注 Agent,但是被 DeepSeek 彻底击穿心理防线🤡。
- MoE 架构和共享专家模式。
- SFT→RL→DPO,DPO 用于处理 corner cases。
- 预训练阶段就联合使用大量未标记的文本、图像和视频数据一起训练,但是不支持图像生成,只能做理解。
- MetaP 训练方式可靠地设置关键模型超参数,例如每层的学习率和初始化比例,以及使用了 FP8。
- 千万级上下文窗口,iRoPE(使用没有位置嵌入的交错注意力层),也即不同层使用不同的 θ 值,低频层(浅层)捕捉长程依赖,高频层(深层)聚焦局部细节,偶数层直接不使用 RoPE。
- 中文支持依旧不怎么样,另外总参数量 2T 的模型还在继续训练中,实际这么大参数量的模型难以部署,最大的用处是蒸馏中杯模型。
Mistral
- 基本结构
- tokenizer: BPE、回退到 byte 处理 oov。
- GQA、Sliding Window Attention (SWA),后者即滑动窗口注意力机制,每一个 token 只和包含其本身在内的前 W 个 token 做 Attention,但并不代表后面的 token 就只能关注前 W 个 token,因为前面的 token 是可以注意到更前面的 token 的。在最后一层,如果使用窗口大小 W = 4096,n_layers=32,那么理论上注意力广度约为 131K。同时使用滚动缓存,覆盖过去的 KV,因为 SWA 意味着内存开销其实是固定的数值,但是需要在注意力计算前先确认 KV cache 中元素排布,不按顺序的话先进行 unrotate 操作排布好。
- Pre-fill 和分块,主要是为了 节省显存压力,一般设置分块大小和前面的缓存大小以及滑动窗口大小一致。
- helpfulness 和 harmlessness 的权衡,LLaMA 2 的安全对齐措施非常充分严格,甚至损失了一部分实用性。
- Mistral 8×7B
- 使用 MoE 架构(并不是首次提出 MoE 架构),FFN 替换为 experts 组成的
nn.ModuleList和维度为 [hidden_dim , num_experts] 的Linear作为 gate。 - 实际参数量是 47B,而不是 56B,原因是每一层除了 8 个专家网络外,其他层如注意力机制部分均是复用的。
- 稀疏性:在训练和推理时,同时只有两个专家网络会被激活,进行前向计算,其它专家网络处于失活状态。
- 使用 MoE 架构(并不是首次提出 MoE 架构),FFN 替换为 experts 组成的
GLM
- General Language Model,希望统一 NLU 任务(例如分类任务)和生成任务。
- 融合自编码和自回归思想。
- PartA 是 Mask 后的结果,从均值为 3 的泊松分布采样 Mask 长度,直至 15% 内容被 Mask。PartB 是 Mask 的内容,每个范围前面都加上 [START] 作为输入,后面加上 [END] 作为输出。
- 二维位置编码表示跨范围和范围内的位置,也就是说第二维度是表示被采样的范围内,每个 Token 的相对位置,PartA 的第二维度均为 0。
- 相比起来 BERT 的位置编码没有学习到 masked tokens 之间的的依赖关系。同时该编码方法确保了模型不知道具体 Mask 长度,例如 XLNet 对原始位置进行编码,因此可以感知缺失标记的数量。T5 模型使用多个哨兵 token 来区分 mask 片段。而在下游任务中,仅使用一个哨兵 token,造成模型能力的浪费以及预训练和微调的不一致。
- 自注意力掩码:PartA 不能关注 PartB,但是 PartA 中的 Tokens 彼此可见。PartB 可以关注 PartA,以及在该 Token 之前的 PartB 部分。也就是模型既可以学习双向 encoder(PartA),也可以学习单向 decoder(PartB)。
- 多目标预训练:增加了生成长文本目标的任务,具体包括文档级别和句子级别,前者用于长文本生成,后者用于预测完整句子或段落。
- 改为 PreNorm,同时 ChatGLM-6B 使用了 GeLU 激活函数,ChatGLM2-6B 也替换为了 SwiGLU。
- ChatGLM 主要还是预训练和 SFT,RLHF 的探索与使用有限。
- GLM-3 及以后(现在是 GLM-4)的 GLM 系列模型都改为了 Decoder-Only 架构。
- 相比起 ChatGPT,在参数量上来之前很难达到同等水准。不过存在的一个优势是本土开发,词表中天然支持很多中文词汇,不会像使用 LLaMA 等模型(虽然 LLaMA 的多语言能力已经算领先了)一样解码效率偏低、额外消耗显存还容易 OOV 问题。
- GLM4
- QKV 不使用 bias,加速训练,外推能力微弱提升。
- RMSNorm、SwiGLU 和 GQA。
- 128K 上下文长度。
- GLM4.5
- 减少模型的宽度(隐藏维度和专家数量),增加模型的深度(层数),注意力头数量增加到 2.5 倍。
- 355B-A32B,106B-A12B
- Muon 优化器。
Qwen
- 数据集:中文和英文为主,处理流程还是较为经典的工作流。
- 文本数据抓取(包括 html 解析)→语言识别(确定语种)→去重(语言规范化及 MinHash 和 LSH 算法)→质量过滤(多个模型评分 vote)→安全控制(使用模型识别暴力颜色偏见内容)→上采样(高质量数据)→指令数据→防止数据泄露(删除和测试集超过 13-gram 重叠的文本内容)。
- 分词器:BPE、基于 tiktoken 的 cl100k 词表,添加中文词和切分数字,最后大小约 152K。
- 结构修改
- Untied Embedding,对于输入嵌入层和输出投影层不进行权重共享,代价是资源消耗。原本的 transformer 使用共享是为了节省资源,认为输入输出的语义空间差异不大。独立的权重矩阵允许输入嵌入和输出投影有更多的灵活性,可以更好地适应不同的任务需求,提高模型的准确性和性能。
- RoPE(FP32 精度)。
- 大多数层去除 bias(稳定性考量、减小参数量和过拟合风险),仅在 QKV 层保留(更好地捕捉特征、应对分布偏移,即外推)。
- PreNorm(看来还是考虑到更稳定训练收敛牺牲了少量准确率)、RMSNorm、SwiGLU(以上三者和 LLaMA 保持一致)。
- NTK-aware 插值 & Dynamic NTK 插值。
- LogN-Scaling,公式是 , 是超参数,相当于是选择 的底数。根据上下文长度与训练长度的比值,对 Q 和 V 的点积进行重新缩放,确保注意力值的熵随着上下文长度的增长而保持稳定(熵不变性),不因为新加入的 token 过度分散了原有的注意力分布。
- 在不同层使用长度不同的上下文窗口限制注意力,较低的层使用较短的窗口(因为低层更敏感上下文拓展),较高的层使用较长的窗口。
- 上下文长度 2048。
- SFT 使用 ChatML 格式,也就是利用特殊符号表示不同类型信息(如系统设置、用户输入、助手输出等,这有助于模型有效区分信息)。Loss 仅计算回复部分,用户/系统的输入 mask 掉。
- RLHF 数据不直接打分,而是选择排序,即两个回复相对而言,哪一个更优,避免标注员的个人打分整体偏好和分数绝对值统一问题。
- Qwen2
- 五个尺寸(0.5B-72B),多语言,长上下文窗口输入(128K)和输出(8K),代码和数学能力提升,结构化数据输入和输出。
- Qwen2-Math:和 DeepSeekMath 一样,使用 Group Relative Policy Optimization(GRPO) + Dense Reward 而非 0-1 Reward(避免简单题过拟合困难题一直错),目标还是去掉 Critic,而 Critic 的目标是去估计一个状态的期望值,进而用于优势计算,所以对于同一个 prompt G 个答案,平均 G 个答案的得分当作这个期望值即可。具体来说也就是优势 。通过重要性权重调整优势函数,模型可以优先提升高奖励输出的概率,从而实现策略优化。
- Qwen2.5
- 重新引入了 14B 参数和 32B 参数模型。
- YaRN 位置编码,后面 DeepSeek V3 也使用了同样的。
- 最长支持 1M 长上下文,稀疏注意力机制帮助推理速度优化。
- Qwen2.5-Math 整合了多种推理方法,包括 CoT(Chain of Thought)、PoT(Program of Thought)和 TIR(Tool-Integrated Reasoning)。
- Qwen2.5-Coder 预训练分为 file-level 和 repo-level,前者 8K 上下文长度,后者扩展到 32K。
- Qwen3
- 0.6B 推理模型,235B-A22B 大尺寸 MoE,共有 128 个专家,激活 8 个专家,去除了共享专家。
- 混合思考模式,可以通过
/think和/no_think手动控制。 - 世界知识相对较少,似乎比较适合垂类模型微调。
- 比较像做题家模型,主要优化都在写代码和做数学题,长文本的指令遵循能力不如 Qwen2.5,应该是使用了大量的 DeepSeek R1 和 V3 的合成数据。
- Qwen3-Instruct:在工具调用、高并发等场景还是需要非推理模型,Qwen3 这版其实更像是披着非推理的皮的推理模型(和新版 DeepSeek V3 类似,输出都较长)。
- Qwen3-Embedding、Qwen-Reranker
- Embedding:弱监督预训练 + 监督微调,InfoNCE。
- Reranker:LLM 聊天模板,评估 NTP 是【yes】和【no】的概率,不需要弱监督预训练。
- 球面线性插值(Slerp)合并多个 checkpoint,提升在各个数据集上的泛化性和鲁棒性。
- Qwen3-Next-80B-A3B
- 混合架构:75% 层使用 Gated DeltaNet,25% 层保留标准注意力。前者线性注意力将计算复杂度从 降为 ,在长序列建模上效率高但召回能力弱;后者标准注意力效果好但长文本上计算开销太大。
- 极致稀疏 MoE:512 个专家,仅激活 10 专家 +1 共享专家。
- MTP 完整应用。
DeepSeek V3
- FP8 混合精度训练,之前的 LLaMA、Qwen 等都是基于 BF16 混合精度训练。同时使用 BF16 来保存优化器状态,以及对部分操作进行选择性重计算(例如 RMSNorm, MLA Up-Proj, SwiGLU)
- 推理解码阶段使用 40 台 320 卡部署一个实例,每张卡只放一个 expert。 DeepSeekV3 证明了超大 MoE、超稀疏 MoE 的推理,分布式卡越多 Decode Speed 越快。
- 继续沿用 DeepSeek V2 提出的 MLA,也就是对 KV 做 LoRA,作为联合的 KV Cache 缓存,再映射回高维空间,并针对 RoPE 做了解耦,即拆开算然后再求和(不是直接不兼容,而是因为不考虑 RoPE 的情况下可以通过恒等变换实现 矩阵和 解压缩矩阵的提前计算融合,同理 解压缩矩阵也可以和 提前融合,从而不引入新的计算时间开销)。和 GQA 不同,MLA 希望不压缩任何信息,因此可以抽象理解为 KV cache 保存的是每个 token 的 K heads 以及 V heads 的共有信息,而其它的相异信息则转移至对应的 Q head 吸收(注意力计算时的矩阵乘法变换)。
- DeepSeek V3(256 个专家,671B 激活 37B)、DeepSeek V2(236B 激活 21B),分为共享专家(始终激活)和其它专家。
- MoE 结构门控函数从 softmax 更换为 sigmoid,后者(每个都在 0~1 间计算)能够允许模型在更大的专家集合上进行选择,而前者(总和为 1,而最大的几个值往往容易直接占据很大权重,较小的值区分度很差)往往只倾向于将输入分配给少数几个专家。
- 采用无辅助损失(auxiliary-loss-free)方法来实现负载均衡(仅用于路由的偏置项(过程中监控负载状况并调整)、序列级平衡损失(避免仅 batch 内均衡,同一类问题序列均由同一专家擅长)),从而不丢弃任何 token,最大限度地减少了因为鼓励负载均衡而对模型性能造成的影响。V1 采用了设备级负载均衡和专家级负载均衡,前者粒度更大,控制每个设备的计算量相当。V2 将设备级负载均衡改为了阈值限制(每个 token 的激活专家至多分配到若干个设备),增加了通信级负载均衡。
- 多 token 预测 (MTP) 训练目标
- 每个 MTP 模块包括一个共享的嵌入层、一个共享的输出头、一个 Transformer 块和一个线性投影矩阵 M,相当于用另外的小结构预测更多的 token。
- 顺序连接 MTP 模块,每个模块负责预测不同深度的未来 token,对每个预测深度计算交叉熵并加权得到最终的 MTP 损失。
- 扩展预测范围、密集化训练信号、提升模型预规划能力(相当于迫使模型多思考几步)。
- 另外也可以和 推测解码(speculative decoding) 结合,后者就是用一个小模型预先生成草稿 token,然后 LLM 实际并行验证这些 token 的正确性,提高推理解码效率。
- PD 分离:Prefilling 阶段是计算密集型(少量 Query 就打满 GPU),Decoding 阶段是访存密集型(依赖更多 Query 提高利用率)。 因此可以通过多台机器处理 Prefilling 、单台机器处理 Decoding 的 PD 分离方案,实现综合效率最大化、 首 token 延迟(TTFT)最低、 Decode Speed (TPS)最高。
- 分组路由,限制每个 token 只会激活 4 个节点上的专家,从而减半跨节点的通信流量。进而尽量将计算和通信的比例控制在 1:1,然后就可以经典通过并行调度控制来让通信部分和计算部分尽可能重叠,进一步降低时间开销。
- RLHF 中,客观题用规则方法,主观题用奖励模型。另外也采用了自奖励机制,模型自身的投票结果作为反馈源之一。
- DeepSeek-R1
- 冷启动(长思维链数据微调,稳定性和可读性,同时加速收敛)+ 两阶段强化学习,第二阶段关注有用性和无害性,同时精炼推理。
- 有用性仅关注最终总结,确保评估强调响应对用户的实用性和相关性,同时最小化对基础推理过程的干扰。
- 无害性关注全流程和总结,识别并减轻生产过程中可能出现的任何潜在风险、偏见和有害内容。
- 自我认知数据(self-cognition):在对话中正确评估局限性,避免过度自信或错误承诺。
- PRM(过程奖励模型) 实践中遇到的问题:一般推理中很难明确地定义细粒度的步骤、判断中间步骤是否正确较为困难、引入奖励模型将复杂化流程并且可能出现奖励操控(智能体通过利用奖励函数的设计缺陷来获得高奖励),除此之外也显著增加了训练成本开销。
- MCTS(蒙特卡洛搜索,选择 - 扩展 - 模拟 - 回溯)实践中遇到的问题:状态空间指数级增长(数万候选词)、容易陷入局部最优、细粒度高质量的价值模型(直接影响生成质量)难以训练。
- 规则化奖励模型:多语言场景下目标语言响应(语言一致性奖励)、回答准确性和格式遵循度。不使用神经网络的原因是奖励操控问题和难以训练。因此最终使用的是基于正则的 ORM。
- RL 在自我进化的强大潜力,探索模型能力边界可能需要大参数模型 + 纯 RL,创造力比 SFT 更强。而对于小模型,蒸馏 SFT (跳过试错)仍然是性价比最高的方案,效果也优于小模型 + RL,自我进化的能力有限。简单来说,SFT 记忆,RL 泛化。
- 奖励定义
- R1:答案奖励(这里可以提一下 DAPO 针对过长回答的改进)、格式奖励、答案微奖励(即敢于给出答案)。
- 其它复现中有提到的:推理一致性奖励(即中间推理过程要和最终结果间有映射关系)、语言一致性奖励、探索奖励(中间步骤更多)。
- 自博弈强化学习(Reinforcement Learning via Self-Play, RLSP):解耦探索和正确性信号,将鼓励搜索行为的探索奖励信号与结果验证器的正确性信号分离开。探索信号包括奖励更长的响应(中间步骤更多)和使用 LLM 评估响应的创造力、推理努力程度和其他优点,忽略响应的正确性。
- DeepSeek-GRM (Inference-Time Scaling for Generalist Reward Modeling)
- 标量(Scalar)直接为给定的查询和回复分配一个标量值表示模型对回复质量的评估,半标量(Semi-Scalar)除了标量奖励值外,还会生成一段文本评论解释给分原因,生成式(Generative) 只生成文本形式的评论作为奖励,奖励值通过从文本中提取或分析内容偏好。
- 点式(Pointwise) 独立给每一个给定回复分配分数,成对(Pairwise)主要考虑两个回复之间的相对偏好。
- GRM 关注的是 Inference-Time Scalable 和 Input Flexible。
- 对于前者,标量的输出空间有限,无法通过多次采样聚合不同结果来改进奖励评估;而半标量虽然理论上可以通过理由来达到聚合改进奖励评估的目标,但实际实验中标量数值相差很小,效果依旧不显著,因为标量可能是回归头直接预测得到的,而理由才是由模型的生成能力产生的。
- 对于后者,点式支持单独评分,可扩展性强,而成对方式虽然可以扩展到处理多个回复,但通常需要额外的技术开销。
- 因此本文采用了生成式 + 点式的方案。
- SPCT (Self-Principled Critique Tuning) 训练方式,旨在让 GRM 能够学习自适应地生成高质量的原则 (principles),并基于这些原则有效地指导批判 (critiques) 的生成。
- RFT,拒绝式微调,SPCT 的冷启动方式,使 GRM 能够生成格式正确的原则和批判,拒绝策略是针对预测奖励和真实不符的轨迹以及每次采样均一致(过于简单)的轨迹。
- 除了多数投票外,还训练了一个元奖励模型,具体是一个点式标量奖励模型,用于识别 DeepSeek-GRM 生成的原则和批判的正确性。
- DeepSeek-Prover-V2
- 将长推理链基座能力转换到短思维链模型
- 两种 SFT 样本生成:第一种是【问题,原始响应】,第二种是【系统提示词,问题,长推理链模型如 R1 响应】,系统提示词主要引导模型自主反思和验证。
- RL 阶段,高温采样,整合上面两种样本的模式。
- SFT 阶段,拒绝采样,为最终模型筛选高质量 SFT 数据。
Kimi K2
- 目标:完全继承 DeepSeek V3 结构的情况下,选择合适的参数配置,在训推成本相当的情况下,获得更低的 loss。
- 四个改动点
- 专家数量增加,256→384,预先验证发现单纯增长 MoE 总参数量,scaling law 依然成立。这个增加导致 Decode 阶段耗时和显存开销都增大了,Prefill 在节点数不同时增加的情况下相对不受影响。
- attention head 数恢复至 64,发现这个带来的负影响远低于增加 MoE 总参数的正影响。由于减少了一半 head 数,QKVO 投影从 10B 变成了 5B,这也就是从 DeepSeek V3 37B 激活变成 32B 激活的原因。同时 K2 目标长文本场景,MLA attention 计算量中将 head 减半,能够大幅降低长度平方带来的开销。
- 只保留第一层 dense,其余全用 MoE。原因是第一层的 router 很难做到负载均衡,而后面则考虑尽可能利用 MoE 的优势。
- n_group = 1(expert 无分组),当前参数规模下每个 device 上只剩少量、甚至只有一个 expert。
- MuonClip 优化器,相同 token 数量下,相比 Adam 的 loss 显著更低。训练前期 Q、K logits 出现爆炸,因此每步更新 做了放缩。
- 主要目标:在稳(不大改模型)的前提下找到损失下降最快的路径。
- 学习率和衰减率两个超参,利用 RMS 对齐到 Adam 的参数,GLM4.5 是对齐到了 0.2。
- Muon 训出来的参数熵更大,即奇异值分布更均匀,意味着这个参数越不容易压缩,这说明 Muon 更充分发挥了参数的潜能。
- 预训练和 SFT 都使用 Muon,取得的效果优于均使用 Adam 和混合使用。
- 拓展思考:过往模型结构改进后效果好,究竟是因为它本质更佳,还是因为它更匹配 Adam 了呢?
- Agent:构造数据,large-scale RL,向 Self-Improvement 迈进。
LongCat
- 零计算专家,根据 token 重要性动态分配算力。
- 快捷连接 MoE(Shortcut-Connected MoE),前一层 FFN 计算与当前层通信操作并行,扩大计算 - 通信重叠窗口。
- 方差对齐设计,MLA 缩放修正和专家初始化方差补偿(拆分为子专家导致的),确保大规模扩展时的稳定性。
大模型炼丹
以下问题提纲挈领为主,考察更多是结合任务场景,需要联系实践,大多无标准答案,主要靠积累。
基础理解
- 下一个词序列预测:其实可以被看做是包含语法解析、语义理解和风格模仿的多任务学习。
- 过去三个月,LLaMA 系模型发展如何?指令微调的核心问题又是什么?
- LLM 的训练目标
- 最大似然目标,也就是最大化模型生成训练数据中观察到的文本序列的概率。具体来说,对于每个文本序列,模型根据前面的上下文生成下一个词的条件概率分布,并通过最大化生成的词序列的概率,使用梯度下降法等优化算法来优化模型参数。
- 涌现能力及其理解
- 两类任务: In Context Learning(Few Shot Prompt) 和思维链 (CoT)
- 为什么现在的大模型大部分是 Decoder only 结构
- 泛化性能(zero shot、few shot)
- 效率问题,复用 KV-Cache,多轮对话友好,更方便 Scale UP,T5 等模型对应需要更多的训练资源,训练效率更低。
- KV-Cache
- Decoder Layer 的开销:,核心其实是 ,其余参数都是定值,而能够支持更长的序列长度是发展趋势,并且在解码过程中 NTP 任务会导致序列长度越来越大,平方级别的开销显然难以接受,而这部分开销来自 以及 softmax 后和 的计算。
- 保存了 K 和 V 矩阵投影和每层向量的乘积结果,在计算注意力时空间换时间。成立的条件是每一个 token 的输出只依赖于它自己以及之前的输入,与之后的输入无关。Bert 就不符合这个条件,另外对位置编码有特别设计,每次增加新的 token 后会改变同一 token 位置编码的 LLM 也不符合。
- 由于公式推导化简后,只有当前位置的 参与计算,而每次计算都要用到当前和过去所有的 ,所以不需要 Cache。
- 由此解码阶段可以被分为两个阶段,生成第一个 token 的叫 prefill,这个时候需要付出平方代价计算一遍注意力,后续则为利用缓存进行线性计算。
- 对于 Prefill,可以结合前端做一些优化,从用户在输入框开始输入内容,就开始预先计算前置文本的注意力内容。
- 在缓存的空间复杂度上依旧是和序列长度相关的,也有一些策略如动态分配缓冲区、把数据拆散用元数据表维护等,现在使用广泛的是 PageAttention,详见后文常见问题中 vLLM 部分。
- KV-Cache
- 满秩问题,Decoder-only 架构的 Attention 矩阵一定是满秩的,在理论上具有更强的表达能力,改为双向注意力反而会变得不足。
- 预训练难度问题,每个位置所能接触的信息比其他架构少,要预测下一个 token 难度更高,当模型足够大,数据足够多的时候,decoder-only 模型学习通用表征的上限更高。
- Encoder-Decoder 使用了不对称的模型架构,设计多卡并行困难,参数增长困难。
- 双向注意力后面的 token 会影响前面的 embedding。
- 先发优势
- 多样化解码生成
- 讨论其它架构的模型:BERT、T5、BART、UNILM
- Decoder-only 参数与计算量
- 参数量
- Self-attention: 的四个权重矩阵及 bias 的形状为 (h, h) 和 (h,),总参数量是 。
- MLP:第一个线性层权重和 bias 是 (h, 4h) 和 (4h,),第二个线性层和 bias 是 (4h, h) 和 (h,),总参数量 。
- LN:两个 layernorm,每个都有两个参数(计算公式为 ),均为 (h,),所以总参数量 。
- Embedding:词表大小 V,形状 (V, h),总参数量 。
- 位置编码:绝对位置编码 , 为最大序列长度,RoPE/ALiBi 等相对位置编码不引入额外可训练参数。
- Output:和 embedding 类似,形状 (h, V),总参数量 。
- 综合计算 层的 decoder-only 一共参数量 。
- 计算量(矩阵乘法考虑乘和加两个操作)
- Embedding:只涉及 gather 操作,不涉及 FLOPs,维度 (b, s) → (b, s, h)。
- Self-attention
- 三个映射矩阵计算为 (b, s, h) * (h, h),也就是 。
- 是 (b, num_heads, s, h/num_heads) * (b, num_heads, h/num_heads, s),也就是 。
- 是 (b, num_heads, s, s) * (b, num_heads, s, h/num_heads),也就是 。
- 线性映射是 (b, s, h) * (h, h),也就是 。
- MLP
- 第一个线性层 (b, s, h) * (h, 4h),也就是 。
- 第二个线性层 (b, s, 4h) * (4h, h),也就是 。
- Output:(b, s, h) * (h, V),也就是 。
- 综合计算 层的 decoder-only 一共参数量 。其余都是线性的, 会随序列增长显著增长计算开销,所以需要 KV Cache。
- 参数量
解码策略
- 贪婪解码:直接选择概率最高的单词,最简单的方案,但是容易导致重复现象更加明显。
- Beam Search:维护一个大小为 k 的候选序列集合,每一步从每个候选序列的概率分布中选择概率最高的 k 个单词,然后保留总概率最高的 k 个候选序列。这种方法可以平衡生成的质量和多样性,但是计算资源消耗大(长序列下甚至很容易出现组合爆炸)、k 的选择问题需要实验调参,并且可能陷入局部最优解。另外,由于概率累积问题,Beam Search 倾向于生成短句子,需要引入长度归一化或长度惩罚。
- 随机采样:按照概率分布随机选择一个单词。这种方法可以增加生成的多样性,但是可能会导致生成的文本不连贯和无意义。
- Top-k 采样:优化自贪婪解码,每次从概率最高的 k 个 token 中随机选择,也就是说 k=1 时退化为贪婪解码,缺点是同样没有考虑单词之间的语义和语法关系,以及 k 的取值难以确定,在某些极端的概率分布下可能采样到概率很低的 token。
- Top-p 采样:在每一步,只从累积概率超过某个阈值 p 的最小单词集合中进行随机采样,而不考虑其他低概率的单词,可以在没有概率低的 token 下也保留一定的多样性。top-k 和 top-p 可以同时使用,后起作用(覆盖)的是 top-p。
- Temperature 温度:来源于统计热力学,高温意味着更可能遇到低能态(logits),具体实现是在
softmax的结果上除以温度。越低的温度使模型对其首选越有信心,而高于 1 的温度会降低信心,0 温度相当于贪婪解码,而无限温度相当于均匀采样。如果想进行贪婪解码/beam search 生成确定性结果,可以设置do_sample=False,不必设置温度。但是如果使用do_sample=True选项,并且仍然希望生成结果几乎确定性,温度一般设置为非常小的值(严格浮点数,避免除以零),例如 1e-3 甚至 1e-8。 - repetition_penalty 惩罚重复:在每步时对之前出现过的词的概率做出惩罚,即降低出现过的字的采样概率,让模型趋向于选择解码为没出现过的词。
- no_repeat_ngram_size:限制 n-gram 在结果中出现次数。
- 对比解码:整体思想是通过查找到最大化强模型和弱模型之间可能性差异的字符串来生成文本。
常见问题
- 复读机问题
- 一个较早期的问题,现在(2024.11)各家预训练水平都跟上来了,相对缓解了这部分的问题。
- 数据偏差:数据集本身重复度高,多样性不足,或者存在较多的 OOV 问题,尤其是在多语言场景中。
- 训练目标:自监督学习可能使得模型更倾向于生成与输入相似的文本。
- 解码方式:贪婪搜索中 beam 和采样中温度的设置。
- Self-Reinforce
- 原因一般还是 两个角度,一个是数据多样性,一个是模型本身的能力。
- 当 SFT 的数据难度远超过预训练模型的能力时,模型会为了记住这部分数据,而过多修改原先的 attention 模式,且会打乱原始 pretrain 模型的原始分布,尤其是试图过拟合 SFT 数据时。
- 预训练的场景下更容易出现复读机问题,因为通常预训练使用 packing,模型更难学到何时该
<eos>。
- 长文本处理
- 分块或分层(段落、句子等)处理,可适当引入重复以确保连贯性。
- 领域微调后,通用能力遗忘问题
- 数据处理
- 重复信息、矛盾信息、过时信息、不安全信息以及涉及个人隐私信息的数据筛查。
- 不同任务场景下的数据集格式。
- DeepSeekMath 数据集构造
- 选择高质量数学网页,作为种子集合,用以训练打分模型。
- 利用打分模型在网页库中召回更多数学相关网页。
- 识别召回网页中和数学相关的域名并标注出和数学内容相关的路径。
- 将这些特定路径下的网页加入作为新的种子集合,重复迭代。
- n-gram 方式过滤避免和测试集的数据污染。
- 筛选指令微调数据
- IFD 指标:少量数据进行模型初学习(有的 base 模型无指令遵循能力),聚类后抽取样本确保多样性。然后利用训练好的模型计算所有数据的 IFD 指标,具体来说就是有指令的交叉熵和无指令的交叉熵的比值。较高的 IFD 分数表明模型无法将答案与给定的指令内容进行对齐,表明指令的难度更高,更需要模型训练中优化。
- MoDs:借助评分模型首先选出初步的高质量数据集,然后聚类并从中挑选出多样性数据作为种子指令数据集,基于种子指令数据集训练基础模型,拿初步高质量数据集计算 loss,保留 loss 大的作为增强指令数据集(模型遵循能力较差),最后基于这部分数据对模型微调。
- 基于 Prompt 筛选数据也有一些研究,具体参见后文微调相关中数据构建部分。
- 多样性数据
- 主题遵循:合成对话中包括故意分散注意力的轮次,有助于增强面向任务的交互过程中专注于预期主题。
- 无法自行完成的任务:例如互联网访问或实时知识相关的,人工编写示例并要求 LLM 做出拒绝回应,并收集配对。
- STEM 相关数据集。
- 推理数据
- Nemotron-4:FinQA 数据集提升数值推理、人工标注数据集提升上下文 QA,以及针对半结构数据理解能力的数据集。
- 常识推理:涉及时间换算、路线规划、几何空间推理、家庭关系判断等。
- 命题假设:演绎推理、归纳推理和反证陈述等。
- 关系判断:包含/蕴涵关系、因果关系、对立或中立关系、类比推理等。
- 多步推理:并行/串行推理、链式推理、树推理及推理链攻击。
- 博弈论:囚徒困境等经典问题。
- 破坏性问题:用以测试推理能力。
- 反事实推理:颠倒因果或联系不相关的个体,探索难以置信的情况。
- ps. 中文似乎有天然适合的推理数据集(行测)。
- 函数调用/工具调用相关数据集:单工具单步骤、多工具多步骤。
- 合成数据
- 互联网数据相对还是低质量,只能达到人类中下游水平,而模型经过三阶段后已经能产生较高质量数据。
- GLM4 发现人类真实 prompt 数据在 SFT 中优于合成数据。
- 人类书写的内容远少于思考的,而思考过程也是模型必需的,但互联网中缺少这部分数据。
- 长尾数据扩量。
- 使用合成数据的问题与改进
- 分布和真实存在差异,更容易相对单一导致过拟合某些 pattern。
- 使用合成数据预训练有所提升,但是在 SFT 阶段容易受到影响导致效果变差。
- 解决方案分为三个角度,最直接的是丢掉过拟合的合成数据,也可以重复之前的真实数据(和应对灾难性遗忘有点像)、以及通过 Loss 和真实数据训练的模型拉近 KL 散度。
- 检测代码是否 AI 生成的一个简单直觉:AI 生成的代码再让大模型重写一次,AST 修改变更内容和幅度会更小。
- RLHF的数据去噪
- 最简单的方案:计算 score 和长度的关系,去除相关性极强的样本。
- 估计 Uncertainty 和 Confidence,例如 REM 多个模型预测结果分布,甚至也可以采用 Self-Detect 的方式。
- LLM 不擅长准确数学运算的原因
- 早期 LLM tokenizer 的分词问题:每个数字需要被单独切分成一个 Token(LLama-1),这是能够做好数学运算的必要条件,但不充分,拆分了才有可能泛化未见过的数字,否则就变成背诵特定的数据了。另外,如果不拆分数字那么会导致词表大小显著增加,进而降低训练速度和推理效率。
- 数字序列输入顺序:逆序输入符合人类计算方式,同时也适配 Next Token Prediction 这种输出方式,正序计算还需要存储高位临时结果。
- 数字对齐问题:两种解决方案,一种是加入位置提示,相同位置使用相同提示符;另一种是对每个数字 Chunk 单独引入新的位置编码。
- 长度外推问题:训练时只见过 10 位加法很难完成 20 位的,这个问题主要靠位置编码解决,FIRE(Functional Interpolation for Relative Position Embeddings)目前外推能力最强。
- 幻觉,或者说本质上还是训练数据不足。
- 幻觉问题
- 什么时候容易产生幻觉
- 数值混淆,尤其是涉及数字和日期时。
- 长文本处理,长期依赖关系中可能存在自相矛盾内容。
- 逻辑推断障碍,模型错误解读了源文本中信息。
- 上下文与内置知识的冲突,比如领域知识和预训练知识中相同词语的不同意义。
- 错误的上下文知识,尤其是上下文包含错误假设时,模型可能难以识别。
- RLHF 中可能引入了人类对数据的 bias,以及 prompt 的内容,LLM 是在尽可能满足使用者意图还是在超出知识时意识到拒绝回答。
- 上下文幻觉:模型输出应与上下文中的源内容保持一致。
- 外部幻觉:关注事实正确性,以及在无法回答时承认不知道答案。
- 产生原因
- 互联网内容由于易于爬取,是预训练数据集的常见来源之一,但其中不免包含过时、缺失或错误的内容。
- 微调时大量【已知】(尤其是【可能已知】)搭配少量【未知】即可达到最佳性能,而拟合大量【未知】时可能产生幻觉。
- 检测手段
- FEVER2022:基于维基百科构建句子和文档作为事实知识库,检测幻觉命名实体(NE)和句子蕴含率。
- FActScore2023:特殊 Token、上下文 ICL 和 Token 预测概率三种方式,发现稀有实体更容易出现事实错误(训练数据不足、上下文不足、过度依赖通用模式),并且生成后面的内容时出错概率更高(上下文信息衰减、误差累积、外推问题)。
- SAFE2024:对于长篇事实性内容的模型响应,理想情况下应该同时命中精确率和召回率,因此使用 F1@K 指标来评估,是否事实准确采用 Google 迭代搜索的方式衡量,效率偏低但指标效果表现较好。
- FacTool2023:综合评估框架,基于 prompt 提取所有可验证的声明,对于知识相关的声明借助搜索引擎查询,代码和数学问题生成对应的单测用例或使用代码解释执行器,文献内容采用 Google 学术。
- SelfCheckGPT2023:通过不同的指标或 prompt 的方式检测多次随机样本之间的一致性。
- IndirectQuery2023:间接查询,例如相比直接查询文献是否存在,询问文献作者并检查一致性效果更好,尤其是在更大的模型上。
- 对抗幻觉:包括但不限于外部知识库检索(RAG)、特殊采样方案、微调与对齐和编辑神经元。
- RARR2022、FAVA2024:总体思路都是检索相关文档,然后编辑模型输出以避免幻觉错误。后者使用的编辑模型需要微调,训练数据的样本结构由三部分组成,分别是原始百科片段作为黄金上下文,包含错误的语言模型输出,带有错误标记和正确编辑的输出。
- Chain-of-Verification(CoVe)2023:生成一个初始草稿,然后基于此设计【简短】验证问题(但是似乎还是只能使用模型 + few shot 的方式),采用简短的问题分开查询能够尽可能减弱原始幻觉文本带来的影响。
- RECITE2023:采用背诵形式的输入格式,相当于利用 Transformer 的隐性知识以及注意力替代外部的信息检索,效果和外部检索大致相当。
- Factual-nucleus sampling2022:改进 top-p 采样,论文作者认为随机性对于真实性的影响在生成靠后的内容时影响更大(误差累积?),所以引入衰减系数,前期尽可能保持 top-p 注重多样性生成,后期逼近于贪婪解码(但是设置了最小系数保证不完全退回贪婪检索)。
- Inference-Time Intervention2023:对于不同的注意力头,通过在每一层的激活值上拟合线性探测器来区分真实输出和虚假输出。在推理时会将选定的注意力头的激活值朝“真实”方向移动。在部分情况下有一定提升,部分情况下与随机无异。
- TopicPrefix2022:两种极其简单的事实性增强微调方法,一种是在文档的每句话前附带文档主题,加强理解;另一种是重点关注句子的后半部分。
- Factuality-Aware Alignment2024:使用 FActScore(原子级别事实得分)指导 SFT/DPO(作为奖励信号),由于蒸馏未知知识进入模型可能反而导致幻觉,尤其是训练不足时,所以 SFT/DPO 数据集建议使用模型生成的。
- Factuality tuning2024:两种方式评估原子级别事实遵循,一种是检索外部知识库,另一种是转为问题多次查询检测一致性(类似 IndirectQuery)。
- WebGPT2022/GopherCite2022:结合网页搜索,SFT/RLHF。
- 产生原因
- 如何应对幻觉:从数据、训练和推理(包括后处理)三个角度讨论。
- 模型遗忘:用于版权、隐私保护和偏见信息处理,以及处理过期信息。
- 模型的遗忘不是完全对目标信息返回为空,而是出现幻觉,比如哈利波特的角色信息被遗忘,返回他是一个导演作家等。
- 实际策略:先对要遗忘的信息构建特定数据集,使其对该文本的预测更加倾向于原始内容。也就是改变最开始模型的相关标记,从而方便下一步,使用模型自身的预测能力,为每个标记生成替代标签。
- F-Learning:一种知识更新方法。
- 先利用期望遗忘的知识或者旧知识进一步微调模型,微调后模型参数减去原始模型参数得到旧知识参数矩阵。
- 然后用原始模型参数减去旧知识参数矩阵,获得的即是旧知识遗忘后的模型。
- 最后使用新知识微调遗忘后的模型即可。
- 尤其适用于只想调整一类知识的场景,原始预训练数据集和 SFT 数据集中均有可能包含需要调整的知识,而从头开始预训练成本过高。
- 什么时候容易产生幻觉
- 节省显存
- 存储分类
- 模型必要相关的:模型梯度、模型参数和优化器状态。
- 非模型必须的:激活值(反向传播中计算梯度更快,实际也能占用不小显存)、临时存储(例如把梯度发送到某 GPU 总聚合)、碎片化存储空间。
- 梯度累积 Gradient Accumulation
- 网络剪枝
- 量化方法:FP32、FP16 和 BF16。
- fp16 中 5 位用来表示指数位(除去全 0 和全 1,一共是 ,也就是可以表示 ),10 位用来表示小数位,剩下 1 位是符号位。其中指数位全 0 表示非规格数(+0、-0 和极其接近 0 的数字),全 1 表示特殊数(小数位全 0 表示 Inf,符号位确定 +Inf 和 -Inf,小数位不全为 0 表示 NaN),所以 fp16 的最大绝对值是 ,而最小正值是 。
- fp32 和 fp16 基本一致,符号位 1 位,指数位 8 位,尾数位 23 位,因此 fp32 的动态范围为 ~ 。
- bf16 则是保留了和 fp32 一样的指数位,也就是在尾数位做了截断,只剩下 7 位尾数位,因此就是牺牲了精度来换取几乎和 fp32 一样大的取值范围,避免 fp16 的溢出问题。bf16 的最大绝对值是 ,而最小正值是 。bf16 的动态范围为 ~ 。
- 为什么要使用半精度
- 占用内存更少,可以选用更大的 batch_size。
- 训练时通信量大幅降低(尤其是多机多卡),加快数据流通,大幅降低等待时间。
- 混合精度训练
- 不能全部替换为半精度:溢出问题(超过正负最大值)和舍入误差(比最小正值小的加减被舍去)
- 权重的高精度备份:使用 fp32 权重作为精确的 “主权重 (master weight)”进行备份,而其他所有值(weights,activations, gradients)均使用 fp16 进行计算以提高训练速度,最后在梯度更新阶段再使用半精度的梯度更新单精度的主权重,这样当很小的梯度乘上学习率后要跟权重(fp32 的)做运算时,就不会被舍弃了。
- Loss Scale:大部分的梯度值都很小,因此该方法通过让梯度乘一个 scale,从而整个分布右移、占据更多 fp16 可表示的范围。
- 算数精度:神经网络主要涉及三种计算,向量点乘(常见于全连接层),归约(Reduction,减少张量元素,常见于归一化和池化层等),点运算(pointwise,常见于 ReLU、tanh 等激活函数),其中向量点乘中加法使用 fp32 完成,存储结果则使用半精度,reduction 也要用 fp32 来做,但以半精度方式存储;而 pointwise 的运算主要受到 memory-bandwidth 限制(计算简单且可以良好并行化),因此它们是以单精度还是半精度运算,都不影响计算速度,所以单、半精度均可。
- 由于 fp16 的范围有限,因此在训练中很容易出现溢出问题(通常表现为 loss 下降一段时间后突然变成 NaN),因此一般在预训练时都使用 fp32 或 bf16,若要使用 fp16 则通常搭配 loss scale 操作。而 bf16 能够和 fp32 有相近的范围,且由于表示范围略小于 fp32,可以起到隐式正则化的作用,避免过拟合(训练时参数通常呈现幂律分布,在 bf16 中,outlier 值由于超出了表示范围而被 clipped,且大部分值仍在范围内,不会明显降低模型性能),但是 bf16 兼容的硬件相对更少。对于微调,此时经过预训练损失变化较平缓,因此可以使用混合精度训练(例如
torch.cuda.amp)自动管理精度切换,在数值敏感的操作(如梯度累积、权重更新)中使用 fp32。而在推理时,由于 fp16 精度更高,参数范围也通常趋于稳定,所以通常都直接使用 fp16。 - 由于额外增加的反量化和重量化操作的存在,推理和训练一般都不会节省时间,反而会增加时间,因此也勉强可以算是一个时间换空间的例子。
- 半精度仍然不够的情况下,也可以进行 8bit 或 4bit 量化。
- TF32:由 1 个符号位,8 位指数位(对齐 FP32)和 10 位小数位(对齐 FP16)组成,实际只有 19 位。在性能、范围和精度上实现了平衡,用于替代 FP32。
- NF4:将浮点权重在量化的同时归一化到以 0 为均值、标准差在 范围内的正态分布上。首先将浮点权重参数离散化为 4 位整数值,然后计算正态分布固定期望值,最后标准化,于 QLoRA 首次使用。
- 在实际实验中,一般大参数量模型量化后,和占用同样显存的小参数量模型相比,还是量化后的大模型表现更佳。直观感觉是因为大参数量的模型仍旧具备更强的特征表示能力,甚至可能存在冗余,能够容忍部分精度损失。
- 存储分类
- 知识蒸馏
- 大型预训练模型上进行推理,并使用其输出作为目标标签,来训练一个较小的模型。
- 可以理解为从学 Next Token Prediction 到学 Teacher 模型的 Next Token Prediction 分布,尽可能与之保持一致,也就是学到了 Teacher 的内部信息。
- 合成数据也可以理解为是一种蒸馏,即使是半合成数据那么其实也是人类扮演了 Teacher 的角色。
- 加速相关
- KV Cache:针对 注意力计算复杂度。
- Flash Attention:针对访存开销。
- vLLM
- 核心目标:KV cache 排布过于静态化,存在大量的碎片,导致申请的显存完全没有充分利用,因此希望能够动态分配,尽可能打满显存,提高吞吐量。
- PagedAttention:思想来自操作系统虚拟内存的分页管理,也就是模拟连续内存,按需分配释放,缓解碎片问题。同理,通过 block table 的映射,每个请求(prompt)都会认为自己在一个连续且充足的存储空间上操作。
- 不同场景下的优化
- Parallel Sampling:希望模型给出多次不同的回答,通过将 prompt 复制同样次数拼接为一个 batch 喂给模型可以转换为常规场景,缺点是会产生 prompt 部分的重复存储。
- Beam Search:每次将 topk 序列喂给模型时,前置 token 中有大量的 KV cache 是重复的。
- Shared Prefix:某些场景下所有的 prompt 都会共享一个前置信息,例如 System message,这部分没有必要重复存储。
- 其它一般场景:某些 prompt 可能不完全相同,但是可能存在子串是相同的,那么这部分序列不用重复存储。
- vLLM 实现了以上重复存储的优化,它们在逻辑上独立,但是在物理内存上共享相同的空间。无法复用时,再考虑开辟新的物理空间。而对于 Beam Search,还可以随着推理的进行,在已经淘汰的路径对应的物理空间上存储新的 cache。
- 分配与抢占
- 按照先来先服务(FCFS)原则处理请求。
- 当 GPU 资源不足(即所有的 prompt 请求都没处理完无法释放)时,暂停处理新到来的请求,而之前请求中,后到来的请求被抢占,暂时中止执行。被抢占的请求对应的 KV block 都被交换到 CPU,待 GPU 资源充足时,重新加载回 GPU,恢复执行。
- 在张量并行(TP)中,各卡上的输入数据相同,只是各卡负责计算不同 head 的 KV cache。所以这种情况下,各卡上的逻辑块 - 物理块的映射关系其实是相同的(用的同一张 block table),只是各卡上物理块中实际存储的数据不同而已。
- Token 顺序交换对预测的影响
- 在顶层才交换 token 位置的影响应该明显大于在底层,顶层交换相当于首先在底层也换了位置,其次先前层涉及对应 token 的注意力也全部受到了影响。
- 另外从经验来说,在 prompt 出现错别字或语序错误,有时也不影响模型输出结果,甚至不排除原模型的预训练数据集中就包括了一定量的乱序数据。
- Self-Correct
- 无外界反馈的情况下几乎无 Self-Correct 能力,即使是简单的 SFT 方法也无法实现。
- 如何保留正确的答案,如何纠正模型错误的答案,如何保证第一步答案的分布(训练时也会训练第一步答案,第一步偏离那么第二步也失效)。
- 多数投票
- 想法很简单,假设服从二项分布,那么只要成功概率大于 ,成功的概率都会显著提升,此时成功的条件转为采样 N 次中出现正确选项的次数大于出现错误答案的次数。
- 反过来想,当任务非常困难,也就是 LLM 成功的概率明显偏小时,该方案并不有效,也就是说投票集成的方式不适合解决困难任务,即使侥幸出现了正确答案,也无法成为多数被识别。
- MoE 架构的优化方案
- MMoE:为每个任务都采用一个门控网络。
- PLE(Progressive Layered Extraction):使用共享专家和任务独有专家(可能存在专家退化问题,导致主要依赖任务特定专家,共享专家贡献常低),以及考虑了不同专家间的交互。
微调相关
- 预训练和微调是哪个阶段注入知识的
- 知识注入是在预训练阶段进行的,预训练模型通过大规模通用数据的训练,学习到了丰富的语言知识和表示能力。
- 微调的目标是将预训练模型中学到的通用知识和能力迁移到特定任务上,提升模型在目标任务上的性能,也可以算作是模仿人类思考,减小后续 RLHF 阶段对齐所需要消耗的搜索空间。因为 SFT 的数据量相比 pretrain 非常少,如果因为想要学习新知识采用相同量级的数据集,那么势必影响原有的 attention 模式,进而可能造成模型效果波动,真需要知识学习倒不如将这部分数据集也放入预训练阶段。
- 在微调中,代码数据的模式相比文本更难被改变,因为代码具有严谨的结构规范,生成空间有限,而文本不确定性更高,更加灵活。
- 微调是啥
- 冻结底层权重(通常卷积层) + 替换顶层分类器(通常全连接层) + 解冻部分权重(可选)
- 数据构建
- ICL 是在 prompt 中加入 few shot 数据,不改变模型参数,而 SFT 是将 few shot 数据用于训练,因此 SFT 相比 ICL 对数据质量(如 label 准确度)要求更高。
- 更加丰富的 prompt 数据可以增强模型对人类指令的理解,提高指令遵循能力。提升 prompt 多样性的相关研究已有不少,例如通过给 prompt 打标签(例如识别词性或命名实体识别)然后调整分布、通过聚类 prompt embedding 删去极其相似的、以及使用 GPT4 或其它方案对 prompt 改写为不同难度(通常是难度升级)。
- 过多的知识注入,或者超过模型能力本身的回答过多会导致后续对齐难度和资源消耗的增加,因此微调数据中应该尽可能应该规避完全陌生无法解决的内容。例如在纯百科和新闻数据集预训练的对话模型上使用代码数据集微调,所以对于代码补全或缺陷修复这种具体代码任务,在代码大模型而非对话模型上微调更加合理。
- 另外,数据的先后顺序、类型配比、难易配比和格式模板,都是值得考虑的问题。
- 提示微调
- BitFit
- Prefix Tuning
- Prompt Tuning
- P-Tuning
- P-Tuning v2
- 适配器微调
- Adapter Tuning
- AdapterFusion
- AdapterDrop
- MAM Adapter
- UniPELT
- LoRA
- 主要思想:冻结预训练模型的参数,并选择用 A 和 B 矩阵来代替,在微调下游任务的时候,只更新 A 和 B。
- 优势:节省资源、共享模块(替换 AB 矩阵快速切换下游任务,即可插拔)、不会引入推理延迟(全量微调后需要重新读写内存、缓存失效、针对参数优化失效)、和其它许多方法正交(如 Prefix Tuning)。
- 基础模型对 LoRA 训练影响较大,增大数据量和参数量可以提升效果,但是并不是仅通过提升数据量和参数量就可以让本来学习力更低的模型效果超越学习力更强的模型。
- LoRA
- 对 A 矩阵随机高斯初始化,对 B 矩阵零初始化,保证训练开始时旁路为 0 矩阵,不会在开始引入噪声(启动更加平稳),理论上二者初始化情况也可以交换,并没有明显差异。
- 另外有缩放因子 ,其中秩 就是信息量的表现形式。已知 SVD(奇异值分解)能够关注到最强调的几项特征,而 代表旧知识,对其作 SVD 没有意义, 代表新知识,但是该项并不是确定的,只有全参微调后才能确定。因此最终只能设置秩为超参,让模型自行学习低秩矩阵 A 和 B。除此之外, 的前八个特征并不一定和 完全相同,模型会尽可能往信息最丰富的维度学,但不一定学出来的就是真实的 top 。因此我们在模型的不同部分,比如 和 ,也可能采用不同的秩。
- 则一般设置为第一次实验时的 ,第一次的 通常会设置较大,从而尽可能更近似 ,此时缩放因子为 1,意味着假定 LoRA 微调效果和全参微调持平。而后自然会逐渐减小 ,此时缩放因子随之增大,也就是保持新知识对模型权重的影响。除此之外, 较小意味着精炼但不全面,梯度下降方向更加确定,可以适当放大影响;而 较大意味着全面而有冗余或噪声,适当减小步伐也是合理的。
- 正常情况下,输入的信息因为注意力机制会关注重要信息和省略无意义信息而导致信息冗余,体现在矩阵上就是不满秩,因此使用这种方式可以大幅降低参数计算量。同时由于噪声的影响,可能在某些场景下效果甚至能超过全量微调。
- AdaLoRA
- 主要思想:LoRA 中对不同模块使用相同的秩,且秩设置不变均不合理,所以总体目标就是动态调整不同模块下的秩。微调高层参数(比如 FFN)的效果会比微调底层参数(比如 attention)的效果更好。
- LoRA 使用了 近似,而 AdaLoRA 则直接使用三个矩阵 去近似,其中中间 矩阵是对角矩阵,初始化为 0,另外二者随机高斯初始化,原因同样是保证训练开始时无噪声。
- 实际计算中, 组成三元组, 表示第 列, 表示第 行,根据重要性分数,将不重要的三元组中的 置为 0,相当于 mask,不是直接删除的原因是,模型学习是探索的过程,在一开始模型认为不重要的三元组,在后续过程中模型可能会慢慢学到它的重要性。能够保留奇异向量,也是 AdaLoRA 表现优于 LoRA 的一个原因。
- 三元组的重要性分数 = 的重要性分数 + 矩阵中所有元素重要性分数的均值 + 矩阵中所有元素重要性分数的均值。取均值的原因,是不希望参数量影响到重要性分数。而单参数的重要性分数则定义为参数权重和损失函数在该参数上的梯度乘积的绝对值,然后加以 momentum 消除不同 batch 上的波动,也就是减轻单个 batch 样本带来的重要性的评估误差,对于这个引入的不确定性 ,在计算时也考虑平滑后的差异,不可忽略真实的波动情况。
- 筛选重要三元组的策略称为 top_b,在训练刚开始的过程中逐渐增大 top_b,也就是加秩,让模型充分探索,到后期开始逐渐降低 top_b,最后以相对稳定的 top_b 进行训练,整个过程和 warmup 类似。
- AdaLoRA 的损失函数由两部分组成,一部分是正常的训练集损失函数,即预测值和真实标签之间的差距,而另一部分是 和 和满足正交矩阵性质的差异,因为真实的 SVD 中, 和 都是正交矩阵。由于 LoRA 在训练时没有引入任何和 SVD 性质相关的约束,所以往往 AdaLoRA 比之能够具有更好的效果,训练时能够更加稳定,泛化性能更好。
- QLoRA
- NF4:四位标准浮点数量化,其中结合了分位数量化(分为若干相等块,使用累积分布函数的反函数简化计算)和分块量化(将张量分成若干个块,每个块都有独立的量化常数,也就是该块最大值,对其进行归一化,反量化就借助其和存储的量化后的低精度值恢复到高精度值,分块的优势在于不容易出现极端值,导致整体量化后极大或极小)。关于 0 的处理是正数取 9 个值,负数取 8 个值,均会取到零点,然后去重,也就是确保 0 的映射值是 0,并用满 4 位数据类型的全部 16 位。
- 双重量化:由上得知,在模型保存时,除了保存量化后的低精度值,也要保存对应的量化常数。而这个常数是 FP32 高精度值,会额外占用较高显存,因此需要对这个常数也做一次 8bit 量化,QLoRA 以每 256 个量化常数为一组。同样,在反量化时,也因此需要两次操作。
- 分页优化:当显存不足时,将保存的部分梯度检查点转移到 CPU 内存上,和计算机的内存数据转移到硬盘上的常规内存分页类似,牺牲时间换空间,主要是解决显存峰值占用问题,论文的创举是在消费级显卡上训练 33B 模型,在此场景下应该是必需的。是对梯度检查点的进一步优化,梯度检查点是在牺牲显存保存前向传播的激活值,和节省显存重新依照损失函数计算激活值之间的权衡。丢失部分激活值,有保存的就用,没有就重新计算。
- QLoRA 也在原参数一侧添加了一个与原参数并行的低秩适配器,它的精度是 BF16。也就是说,QLoRA 有一个 4NF 的存储数据类型和 16BF 的计算数据类型,在计算前向和反向传播时通过反量化为 BF16 计算。
- QLoRA 主要关注点都在尽可能节省显存,而在微调训练方面几乎和 LoRA 一致。
- Loss 计算:SFT 中计算 loss 通常来讲都是样本内作 token-level mean,样本间作 sequence-level mean(两步走)。如果不同样本间作 token-level mean(即一步到位,总 loss 除以总 token 数),则会使 target token 数量多的样本更受重视。不同维度平均的优劣主要是讨论不等长序列的长度带来的影响。SFT 中非常需要样本间的均衡,因此不能使用一步到位的方案相当于 upsample 长文本样本,而预训练需要更实际的分析,也可以考虑一步到位。
RAG 检索增强生成
- 解决问题
- 长尾知识:相对通用和大众的知识结果更准确,剩下的通过增大训练集或者增加参数性价比较低,通过检索知识在上下文给出更加经济。
- 数据新鲜度:无需重新训练模型就加入更新的知识,因此频繁更新的知识建议单独作为外部知识库。
- 私有数据:在训练中加入私有数据成本较高,且有隐私信息泄露风险。
- 来源验证和可解释性:在生成的结果和信息来源之间建立关联,约束生成空间,注意力更加关注知识库中内容,可以缓解幻觉问题。
- RAG vs. SFT
- 数据方面,RAG 能够确保数据保持最新,在面对频繁变更的数据时成本消耗也偏低。
- 生成方面,RAG 不容易产生幻觉,但是无法保证形成特定风格的输出,响应阶段相对透明(比如可以提供检索的匹配度)。
- 实现方面,RAG 关注的核心是检索策略以及数据的存取和更新,SFT 则是数据集的构建、微调目标定义和计算资源准备。
- 索引形式
- 链式索引
- 典型场景是数据源包括专业文献,先根据摘要召回文献合集,然后再检索召回具体的 chunk 片段。
- 树索引
- 需要能够有多级分类的场景,例如首先依据类别关键词仅检索目标类下的内容,然后依据类下的具体标签小类进一步缩减搜索范围。甚至可以将自然语言转换为元数据,然后凡是类似 SQL 中使用
where查询的情况都可以采用类似方式直接基于元数据过滤。
- 需要能够有多级分类的场景,例如首先依据类别关键词仅检索目标类下的内容,然后依据类下的具体标签小类进一步缩减搜索范围。甚至可以将自然语言转换为元数据,然后凡是类似 SQL 中使用
- 关键词表索引
- 向量索引
- 链式索引
- 查询变换
- 同义改写/扩展查询:生成多个类似的查询,然后各个问题都去找文档,需要去重,并且可能分散注意力,因此这种方法必须要搭配重排序,所有问题找到的文档如何去重和排序,按顺序喂入 generator,对结果的影响非常大。不搭配重排序那么效果大概率 不如直接查询,因为很有可能引入了相关度偏低的噪声。最朴素的方案是直接按 reranker 的结果算不同问题下排名的加权总分。
- 查询分解:将一个查询分解为多个子问题。衍生想法是将它原始查询也一并拼接后用于检索,模仿 CV 中不同大小卷积,不同粒度的信息都能被注意到。
- HyDE:假设文档回复,也就是先生成一个答案,根据这个问答去查询,可能产生幻觉。
- 检索和重排序:初检索注重效率,选择出 TOPK 召回,然后由重排序(比如时间/时效性)进行精确比对。需要重排序:将一个文档变为向量后势必损失一些信息。另外,重排序后也可以保留尽量少但是高相关的文本,减小上下文长度提升性能。
- Bi Encoder:每个输入文本独立编码,嵌入向量可以预先计算和缓存,适合高效匹配大规模语料库场景,但是将文本压缩为一个向量表示可能丢失信息,再用于计算余弦相似度会存在偏差,因此被用于 Retriever。
- Cross Encoder:将两个文本拼接作为联合输入,通常是以
[CLS]Text1[SEP]Text2 的形式输入到一个编码器中,模型可以捕捉到它们之间的精确交互和复杂关系,不会中途丢失信息,但是效率低下(每次都需要重新计算无法预存),因此被用于 Reranker。
- Retriever 选择
- Sparse Retriever:如 BM25,效率高尤其是针对大规模文本库;可解释性强,词频词匹配等规则帮助用户理解为什么能够检索成功;部署简单,所需的计算资源相对较少。
- BM25 相比 TF-IDF 的改进
- 词频的饱和效应,随着 TF 增加到一定程度后,增加的相关性应该逐渐减少。
- 对词频进行标准化,避免长文档得到不成比例的高分。可以理解为当文档较长时,包含该单词的机会越大,所以长文档的相关性应该相比短文档更弱。
- BM25 相比 TF-IDF 的改进
- Dense Retriever:语义理解能力强,能够捕捉查询和文档之间的深层语义关系;可以处理复杂查询,尤其是需要上下文理解或多层次语义查询的场景。
- 中庸的策略是使用 Sparse Retriever 做初步检索,再使用 Dense Retriever 做精排,本质上和搜推的思路类似。
- 在训练 Dense Retriever 时,使用对比学习,构建正负样本对非常必要,尤其是负样本,需要关注难负样本的质量,否则模型在检索到无关文本时很可能效果骤降。
- Learned Sparse Retriever(e.g. BGE-M3):先通过 BERT 等深度学习模型生成 dense embedding,再引入额外的步骤对以上 dense embedding 进行稀疏化,得到一个 sparse embedding。
- BERT 在相似比较时仅关注第一个
[CLS]token,而 BGE-M3 扩大到关注序列中所有 token,dense retrieval 仍然使用[CLS]。 - 在 encoder 输出层上又增加一个线性层和 ReLU 激活函数,得到每个 token 的权重(有重复出现的 token 取 max),此时就可以使用和 TF-IDF 相似的思想,开展 sparse retrieval,计算两组向量相似度使用的是共现计算。同时 ReLU 的结果是非负的,有助于 embedding 的稀疏性。但是实际测试中效果以及可解释性都不如朴素 BM25,尤其是涉及专有名词、新兴名词的时候。
- 一阶段使用 RetroMAE(Pre-Training Retrieval-oriented Language Models Via Masked Auto-Encoder) 和对比学习,训练 dense 向量。
- RetroMAE 是由 bert 的 encoder 和一层改进后的 transformer decoder 组成,将低掩码率的的文本(mask15%~30%)输入到 encoder 中得到 embedding 向量,将该 embedding 向量与高掩码率(mask50%~70%)的文本输入到浅层的 decoder 向量中,输出完整文本。这种预训练方式迫使 encoder 生成表征能力强的 embedding 向量(decoder 阶段足够困难),在表征模型中提升效果显著。
- 除此之外,RetroMAE 论文认为先前的 decoder 学到的信息不足,因此在 decoder 中,将 encoder 获得的 embedding 重复 份加上位置编码得到输入 ,将句子 embedding token 和输入序列(无 mask)拼接并加上位置编码得到 ,最终 使用 , 和 使用 。对于 mask,每个 token 能看到的 token 都是通过采样看到的,但是看不到自己,且都能看到首个 token(encoder 产生的 sentence-level embedding 信息)。
- 二阶段使用自蒸馏学习(教师模型和学生模型相同),奖励由 dense score、sparse score 和 multi-vector score 三部分组成(1:0.3:1),也就是说模型是主要参考 dense 和 multi-vector,优化 sparse 的。
- BERT 在相似比较时仅关注第一个
- Sparse Retriever:如 BM25,效率高尤其是针对大规模文本库;可解释性强,词频词匹配等规则帮助用户理解为什么能够检索成功;部署简单,所需的计算资源相对较少。
- 评估指标
- 事实性:判断生成内容是否与事实一致。
- 正确性:生成答案与实际答案的准确性和一致性,例如通过语义相似度或用户评分。
- 相关性:答案和原始查询的相关性。
- 忠实性:生成答案和上下文的相关性(遵循程度)。
- 安全性:生成答案是否包含不安全内容。
- 上下文相关性:评估检索器,上下文和原始查询的相关性。
- 上下文精度:评估重排器,相关块的排名是否较高。
- 上下文召回:是否检索到所有相关信息。
- 现在通常还是使用 GPT4 等模型评分,因为人工评估成本过高,而语义指标等需要有参考答案或至少有关键词,这一般只有在竞赛类场景才满足。另外,用户反馈也可以是评估来源。
- 有的时候根据查找到的上下文,反向生成问题,和查询本身判定相关性,也是一条思路。
- Top10 没有出现正确答案时,检查 Top100,如果 Top100 存在符合的段落,那么是排序模型的问题,否则是检索召回模块的问题。
- 调优痛点
- chunk_size(块大小)和 similarity_top_k 的超参数选择。
- 先 embedding 再分块需要 embedding model 能够处理更长的输入,而先分块再 embedding 容易丢失一部分上下文信息。现有的方案还是倾向于后者,并且利用滑动窗口来尽可能多地召回相关内容。
- 重排序,一方面解决相关性问题(确保找到相关的文档,而不是只包含关键词的文档),另一方面缓解中间丢失问题(模型注意力基本集中于开头和结尾的信息)。
- 检索策略选择
- Basic retrieval from each index:基础检索,向量/关键词/结构化字段值。
- Advanced retrieval and search:高级检索,查询权重/句子级别/最大边际相关(考虑结果多样性和查询相关性)。
- Auto-Retrieval:自动检索,自动选择合适的策略和参数。
- Knowledge Graph Retrievers:知识图谱检索,适用于百科等场景。
- Composed/Hierarchical Retrievers:复合/层次检索,比如 BM25-SVM 复合检索器。融合检索常常能取得不错的结果,但是对效率有所损耗。
- LLamaIndex 支持并行化处理,尤其是在系统处理大量数据的场景。
- LLamaIndex切分代码
- 基于 Tree-Sitter 维护的具体语法树(Concrete Syntax Tree, CST)解析工具,速度很快,相比直接切分更关注了语法结构。缺点是依旧是基于行数控制的,可能超过嵌入模型的上下文长度;另外 Tree-Sitter 社区维护的 CST 不能处理所有情况,在文件语言和解析器语言不对应的时候不会有报错信息提示;类信息可能和函数信息相隔较远,需要单独加入。
- 有论文的策略是直接按照空行来切(模拟人类编程习惯,一块逻辑的代码写完空一行)。基于这个想法,可以首先抽取出所有的方法定义/方法体,以及类定义,进而针对较长的片段按空行额外切分,最后文件内的其它内容单独作为一个片段(包括库引用或版权信息等其它内容),应该也是相对高效的方案。
- 基于模型的方法也不是不可以,但是整个从数据集到训练到评估的过程非常繁杂,容易有过度设计的感觉,并且也影响效率。
- 结构化数据和 PDF 等文件处理,采用符号推理和文本推理结合(多数投票机制)。另外,也可以尝试使用 pdf2htmllex 将 PDF 转换为 HTML。
- Embedding、Retriever 和 Reranker 微调主要是为了提高准确率,而 generator 微调主要为了让模型能够回答【不知道】、格式输出和防止生成不安全的回答。
- 安全性,对抗提示注入,处理不安全的输出,防止敏感信息的泄露,尤其是在 toC 有未成年用户场景,相关工具有 LLama Guard。
- 过滤与压缩:上下文中的无关信息可能会对回答质量产生干扰,也浪费算力。有钱不急就使用 LLM 过滤,贫穷快速就选 Embedding 计算相似过滤。
- 时间加权:一般是两种可能,一个是类似 LRU,热点数据在 FAQ 客服场景中应该搭配更高的权重;另一个是在类似新闻检索场景,越新的数据应该获得更高的权重。
- 多轮对话中的指代消解:最 trick 的方案是用 LLM 提示工程,传入历史问题,检测并消除代词,返回新的问题。常规方案分为指代识别和指代消解两步,指代识别可以使用词性标注、命名实体识别等方法,尽可能保召回,等到消解的时候非指代词可能是孤立的,刚好直接丢掉即可。消解使用成熟的模型方案即可,另外也可以将消解看作一个层次聚类问题,随着各类中词的增多,判断会逐渐容易。
- 关键词抽取非常重要,尤其是垂直领域场景。先对问题做关键词抽取,然后直接排除掉不包含关键词的召回文本段。
- 通过 LLM 挖掘问答对也是一种策略,Q&Q 召回一般是对称相似度(问题间长度大致相等)更容易解决,而 Q&D(文档)召回是短 - 长的非对称召回任务。
- Contextual Retrieval 上下文检索:Anthropic 提出,给每个 chunk 预先添加上下文,最简单的理解是能够解决单独 chunk 语义不明或歧义的情况,需要搭配 prompt caching 使用(先前已经被用于 system prompt)。
- Graph RAG
- 主要作用是歧义/指代消解、降低幻觉以及提高可解释性。
- 本质上感觉现在对于 LLM 而言,Graph 仍旧仅仅是作为 RAG 的工具,节点关系的利用率仍不够充分。
- 多模态 RAG
- 视频:caption、字幕、语音,最后再是画面/高光时刻。
向量数据库
直观上很多想法和理解其实和数据库是相似的。
- 存在优势
- 去除无关上下文信息以及仅保留多轮对话中相关的部分,节省算力和有限上下文 Token 长度的同时也可能可以提高模型的输出质量。
- 非结构化数据的聚合存储,例如语音、图片、视频(帧)、地理信息等均可以嵌入向量后存入向量数据库。
- 常见的相似计算方法
- 欧氏距离:能够反映向量的绝对距离,适用于考虑向量长度的相似性计算,例如推荐系统需要考虑用户历史行为数量,而不能仅靠相似度。
- 余弦相似度:长度不敏感,只关注向量方向,适用于高维向量相似性计算,例如语义搜索,但如果希望生成尽可能简洁的表示则需要考虑长度特征。
- 点积:简单高效,适用场景丰富,但在高维场景可能会因为长度敏感存在问题(高维空间长度放大效应)。
- 另外,如果使用了 padding 操作,也可能干扰相似度计算。而使用平均池化则更容易损失特征信息,最大池化相对平均池化损失特征更少。
- 提高效率
- 减小向量大小,通过降维或减少表示向量值的长度。
- 缩小搜索范围,仅检索最接近的簇,主要使用聚类实现。
- K-Means 和 Faiss 算法:搜索最近的指定个质心下的区域,本质上搜索效率和搜索范围(质量)之间的权衡,这类方法都被归为近似最相邻(Approximate Nearest Neighbor, ANN)。
- 乘积量化(Product Quantization,PQ):维护聚类中心索引(浮点数坐标)消耗巨大的内存,尤其是在高维坐标系中还可能遇到维度灾难。解决方案是将向量分解为多个子向量,然后对每个子向量独立进行量化,在子向量维度上聚类,损失一定的精确度,但是节省内存和时间开销。
- 分层索引(Hierarchical Navigable Small Worlds,HNSW):本质上和跳表类似,高层索引步长大,用于快速搜索,底层索引步长小,用于准确搜索。缺点是图结构带来的显著存储和维护开销。
- 局部敏感哈希(Locality Sensitive Hashing,LSH):设计哈希函数,发生碰撞(相似)的向量被分到同一个桶中,搜索时优先计算哈希后再暴力检索最相似的向量。
- 随机投影(Random Projection for LSH):高维场景下数据会随着距离的指数级增长而更加稀疏,LSH 下最极端的情况是每个桶中就一个向量。解决方案本质上也是降维的思想,使用随机投影矩阵将向量和查询都降维到低维空间。生成合适的随机投影矩阵需要较高的计算成本,而投影矩阵的质量(更需要随机性更大的确保信息均匀扩散、泛化、相对距离保留、减少特征间相关性影响和避免过拟合,高随机也可以认为是充当噪声)将显著影响搜索质量。
- 过滤的时机选择:一般维护有向量索引和元数据索引,前者主要辅助查找,后者便于在特定业务场景下执行过滤。
- Pre-filtering:在搜索之前过滤,帮助减小搜索空间,但很容易漏掉与元数据筛选标准不符合但是相关的结果。【准确率换效率】
- Post-filtering:在搜索之后过滤,确保考虑所有相关结果,但是完整搜索后再加上执行筛选更加消耗时间。【效率换准确率】
- 选型考虑
- 分布式部署:向量数据库使用场景一般有大规模数据,需要确保高可用性和容错性,以及节点数据一致性。想到了分布式事务里的 CAP,一致性、可用性和分区容忍性只能三选二,现有的 NoSQL 主流都是放弃了一致性。
- 访问控制和备份:经典需求。
- API&SDK:向量数据库在 LLM 后迅速崛起,API&SDK 规范既不能太复杂,也不能在更新中频繁变更。
- 从传统数据库扩展:Redis 和 PostgreSQL(pgvector)都有相应的解决方案,主要优势是减轻开发者迁移维护成本,同时可以直接利用如 PostgreSQL 提供的 ACID、并发事务、备份恢复等功能支持。
- Milvus 基础
- AI Coding 场景实践中不选择向量数据库的原因
- BM25 是基于词频逆文档频率(TF-IDF)思想的检索方法,对于关键词匹配和高频短文本的情况有很好的效果,而代码场景正是其中一例(有明确的语法结构)。
- 向量数据库资源消耗更高,并且需要更积极的监控和维护,而其它的如 BM25 已经是成熟稳定的方案。
- 向量数据库常用的搜索方案主要都针对字符级,也就是尽可能找长得像的内容,但是很多时候相关内容并不一定字符级相似(这也是为什么需要引入 reranker 来实现相关性排序),而一些轻量级的方案更便于插入或实现自定义规则,在特定任务下更具有针对性。
- 嵌入模型的选择会极大地影响搜索质量。
思维链
- Test-Time Scaling Law
- 优化推理输入:Prompt。
- 优化推理输出
- base generator + Inference: 使用 verifier 的评估结果来指引模型做生成,需要在搜索前通过 prompt 诱导模型按格式产出内容后再搜索。
- base generator + formatted post training + Inference: 模型从“只产生结果”变成“同时产生中间步骤和结果”,你既可以关注格式遵循,也可以关注中间结果质量。
- base generator + formatted post training(with inference filtering method) + Inference (selective):筛选高质量自生产数据做对齐。
- 对于特别困难问题,在于提升 pretrain 阶段知识注入,Test-Time Scaling Law 作用不大。
- SFT 数据设置:训练数据是“问题 + 若干(相似)错误 attempts + 正确 attempt”的形式,这一步是让模型模拟人类思考的模式,从步步错误的 attempts 中推出正确的。
- 即使是 SFT 模型,依然会配合“verifier + 搜索方法”的方式做推理,因为不能确保一条 attempt 链一定有正确答案,也不能保证最后修正的 attempt 一定是正确的,甚至可能出现中间正确修错的情况。
- 使用MCTS增强推理能力
- 搜索树构建
- 步步推理,每一步有中间结果,最后一步得到最终答案。
- 一次性推理完毕,相当于上面的方案一次走完得到结果。
- 拆分原始问题为若干子问题并做相关回答,最后一个子问题的答案就是最终答案。
- 采用第二种方案的模板,重新回答第三种方案中的子问题。
- 重新复述原始问题/子问题,例如去掉文字描述,变成形如 condition1…,condition2…的简单格式。
- MCTS 流程:选择(Selection)- 扩展(Expansion)- 模拟(Simulation)- 回溯(Backpropagation)
- UCT(Upper Confidence Bound for Trees, 置信树上界):,其中 表示访问次数,由此平衡探索次数和奖励,避免因为单纯使用奖励陷入局部最优,以及牺牲多样性和顺序连贯性的情况。
- 搜索树构建
- LLM(Large Language Model) 向 LRM(Large Reasoning Model) 的过渡
强化学习 RLHF
- 出发点:人类偏好对齐。
- 小模型很难做 RLHF:一方面是基础能力偏弱,很难 sample 到正确的答案,尤其是 reasoning 等方向;另一方面是小模型意味着表征能力也更低,所以在相似的结果一正一负时很难有足够的表征能力分辨这两者,所以 RLHF 也很难对二者排序或者改变两者生成概率的差值。
- 相对小的模型(7B)和大模型(70B)的 RL 差异:小模型会出现更多实际意义上的负样本,而大模型的采样样本差异通常仅在语言层面,因此很多针对小参数模型才容易采样到的负样本的 RM 规则换到大模型就失效了。
- 不跳过 SFT 阶段直接进入 RLHF 阶段:先进行 SFT 缩小搜索空间(模仿学习),然后再进行对齐,降低资源消耗。
- Proximal Policy Optimization(PPO),on-policy 算法。
- 基本思想:智能体观察到环境状态 和奖励 ,输出动作 , 进一步触发环境的变化 和新的奖励 。目标是确定一个策略,能够根据当前观测到的环境状态和奖励反馈,来选择最佳的动作。
- 和 NLP 的契合点:token 是一个个被输出的,因此正好对应了输出动作,而产出的新 token 对应即时奖励,上文 → 上文 + 新 token 对应了状态的转换。实际并不是每个 token 都去更新一次参数,而是有足够的观测数据后。
- 四个基本模型
- Actor Model:演员模型,对应想要训练的目标语言模型,需要训练。
- Loss 计算:,其中 表示优势,优势大于 0 那么减小 loss 就需要增大概率,反之优势小于 0 就需要减小状态 下执行 的概率。而优势定义如下:。由于最后一个时刻优势为 0,那么从后向前计算即可。
- 为了重复利用 1 个 batch 的数据来做 次模型更新,实际使用 用于计算。
- Critic Model:评论家/教练模型,预估总收益(包含即时和未来),由于未来收益不可知仅能预测,因此也需要训练,最简单的初始化方式是采用 Reward Model。也可以理解为作为 RM 的基准,生成更稳定信息量更大的优势。
- Reward Model:奖励/裁判模型,计算即时奖励,参数冻结。
- Reference Model:参考模型,基于 KL 散度防止模型效果相对 SFT 差异过大,参数冻结。
- Actor Model:演员模型,对应想要训练的目标语言模型,需要训练。
- 损失函数:重要性权重(新旧策略)、优势、Clip。
- 稳定训练的 tricks
- reward normalize:两个思路,一个是使用历史获得过的所有 reward 的均值和方差进行标准化,另一个是按照各个任务对不同 reward 归一化。
- token KL penalty:限制模型更新方向。
- Critic Model:使用 RM 初始化,并在 PPO 之前先预训练。
- Global Gradient Clipping。
- 使用相对较小的 Experience Buffer。
- 在 PPO 训练中加入 Pretrain Language Model Loss。
- 训练 Reward Model 时加上 L2 normalize。
- Actor freeze 50 步,可以稳定 value network 的学习。
- RL 经典的分布偏移问题依旧存在,模型永远无法预估好完全没见过的 state 的 value,所以最原始的方案(LLaMA2)是边训练边标注,训一段时间就收集数据标注,但是会不可避免浪费一些本来可用的标注(离原始 response 分布相近的 state)对应的 reward。也有一些其它的解决方案,例如修改 loss 建模,考虑 探索 loss。
- 改进:Group Relative Policy Optimization(GRPO),通过组内样本得分均值来代替 Critic 估计的状态期望值结果。
- 更偏好 Dense Reward,而非 0-1 奖励。有稀疏奖励问题,即简单题一直是 1 能稳定做对,困难题一直是 0 一直做错,很容易过拟合简单题,导致部分 token 概率分布偏高从而模型不会探索困难题解法,并且有可能结果是对的但过程完全错误导致奖励是 1。
- 改进:Direct Preference Optimization(DPO),off-policy 的感觉,但是其实已经不算 RL 了。
- 经 PPO 优化目标推导变形而来,不再训练奖励模型,相当于优化掉了 reward 和 critic,直接使用标注的人类偏好数据,一步到位训练对齐模型。
- 不再使用强化学习的方法,简化对齐优化目标,最后采用类似 SFT 的方式训练对齐模型。
- RLHF 可以理解为学一段时间来考个试(reward),而 DPO 可以理解为直接有人告诉你哪里是短板要补。
- 成对回答偏好标注:BT 模型(Bradley-Terry)。
- BT 模型的训练平移不变性:L2 正则化,在损失函数中加入一个与参数大小相关的惩罚项来减少模型参数的过度增长,使得模型的学习更加稳定和一致,从而使得最终学习到的 reward 更倾向于“保守”的值。其目的是让模型学到的参数尽可能小,从而避免过拟合或出现震荡。
- 损失函数:sigmoid、win 和 lose 在新策略和参考策略比值上的差。
- 多回答偏好标注:PT 模型(Plackett-Luce),希望最优序列打败其余任何一个可能序列的期望概率尽量大。
- 泛化能力弱于RLHF,更容易受偏好数据质量影响,本质上类似 SFT 会有过拟合风险,导致出现分布外泛化问题,只有离线数据,更加关注数据中标签为正的那部分,相当于只有 0/1 标签表示决策的好坏,而实际奖励数值应该是更加平滑的。另外,off-policy 随机采样,很容易导致分布不均,例如刚好采样到一个生成概率很高和生成概率极低的,如果排序符合则无需调整,如果排序不符合很难通过 RLHF 调整,强行调整会破坏模型的平衡。
- 理论上只要生成正样本和生成负样本概率的比值,高于参考模型,则能够容易获取到更高的奖励,因此可能模型会低概率生成正样本和负样本(也就是 chosen logits 和 rejected logits 同时下降),反而生成一些分布外的内容。所以,DPO 对数据质量和多样化程度提出了更高的要求。对于这个问题,LLama3.1 中引入了 NLL loss(,强制模型对高偏好答案保持高置信度)来缓解。
- 类似问题:ChatGPT 为什么不用 Reward-Model 的数据直接 fine-tune,而用 RL
- RL 优化正例和负例之间的差别,而 SFT 可以理解为只有正反馈。
- RLHF 可以看作目标是在整个句子的维度上优化,而 SFT 是基于单个 Token 维度,RLHF 只需要最后回答得高分即可,而 SFT 希望每个 Token 都和 ground truth 一致。
- 需要划出稳定的边界,让模型能稳定回答它明确知道的东西,让模型不要回答它不知道的/错误的内容,SFT 模型更趋于编造回答,对于模型不会的问题其实在人工标注时应该标注为【不知道】。
- 过拟合导致的分布外泛化误差。
- DPO 的第 0 步 loss 固定(前提是目标模型和参考模型的初始化参数相同),值为 。
- DPO 的数据分布也值得注意,例如如果 positive 中的长句子显著更多,那么最终模型大概率会更倾向于输出长句,例如使用【而且】一类的词而不是
<eos>终止。但是 length bias 这个问题在 PPO 中也同样存在,甚至会因为左右互搏,进而更容易成倍放大这个 bias。 - 关于 不同解码方式的
pass@k,对于贪婪解码,PPO 通常优于 DPO,而在随机采样的情况下则可能相反。DPO 不能分辨 token-wise 的 reward,所以虽然整体正确 response 概率增加,但是关键 token 并不一定能到达 Top1。而 PPO 训练时,能够获得未来 reward 大于现在 state value 的 token 将被增强,得分最高的会被最大化增强,所以贪婪解码能够有效提升,而通常有些相像的 response 也被增强了,所以随机采样下效果不一定好。
- 选择 Value Network 和 Policy Network 共享底座 存在问题,共享的 motivation 是节省显存,但是两个 network 会互相影响。在传统强化学习场景两个 network 都是从头开始,因此影响较小,而 LLM 已经经过了模仿学习(SFT),RLHF 是纠正模仿学习出的 Policy,这样初始化的 Value Network 却带来了显著的 bias。所以 PPO 虽然效果最优,但是需要解决四个模型加载产生的庞大资源开销。实在想要节省显存,缓解方案是在 Value Network 构造时在 Policy Network 上加多层而非单层 MLP。
- Reward Model 的过拟合(Reward Hacking):Proxy RM score 在增长,但 Gold RM Score 却在跌。如果二者都没有增长,那么可能是过拟合泛化问题。
- RLHF 的 理想目标 应该是尽可能达到 Scale Pretrain Model 的水平,否则始终停留于 Pretrain Model 的补丁。
- DeepSeek-V2 使用了两阶段强化学习训练策略,第一阶段针对代码和数学推理任务,第二阶段针对人类偏好。
- DeepSeek-R1
- 速记损失区别:
- PPO:带裁剪的重要性权重和优势的乘积。
- GRPO:显式引入 KL 散度,组内优势估计(归一化奖励)代替专门的 critic 模型优势估计。
- DPO:最大化优选响应相对概率与劣选响应相对概率的差值。
- Sea AI Lab 改进:Dr.GRPO
- Response-level length bias: 在计算每个 response 的 loss 时,将除以的 response length 替换为一个固定值 MAX_TOKENS,即训练时设置的 response 的最大长度。
- 针对 GRPO,对于正样本,长度较短的会得到更大的梯度,也即更强的奖励。对于负样本,长度较长的会得到更小的梯度,也即更轻的惩罚。
- 也就是在 GRPO 场景下:短的正样本>长的正样本>长的负样本>短的负样本,其实是符合直觉的排序,但是无论是 DAPO 还是 Dr.GRPO 改进后效果都变好了。
- DAPO 是相当于使用组内总 token 数作除数,Dr.GRPO 则是固定值,直觉上后者更合理,因为按这个道理除了单条样本上有长度偏差,组内 tokens 总数仍然有类似的长度偏差。
- Question-level difficulty bias: 移除 GRPO 在计算优势函数时,除以标准差的操作,也就是在 group 内计算优势函数时,只减去组内均值。
- 对于标准差较高的样本,例如采样中一半正确一半错误,和原始 GRPO 并没有太大差异。
- 对于标准差较低的样本,原始 GRPO 的优势绝对值相比 Dr.GRPO 更大,但是这可以代表给不同难度的问题分配不同难度的权重,对于采样很多次都错误只有少数次正确的困难问题,确实应该被分配更高的学习权重。
- 因此这个移除标准差的操作并不符合常理。但是对于采样很多次都正确而只有少数次错误的简单问题,确实应该使用 Dr.GRPO 这个移除操作,进而能够被分配相对传统 GRPO 更低的学习权重。
- 可以有两种方案,一个是把标准差不作为除数而作为系数,另一个是根据正确答案的比例来区分问题难易程度。
- Response-level length bias: 在计算每个 response 的 loss 时,将除以的 response length 替换为一个固定值 MAX_TOKENS,即训练时设置的 response 的最大长度。
- 字节改进:DAPO(Decoupled Clip and Dynamic sAmpling Policy Optimization)
- 去除 KL 散度:针对 Base 到 LongCoT 本来变化大,因此不需要这种限制。
- Clip-Higher:训练时观察到熵坍缩现象(有的组生成结果几乎完全相同),说明探索不够。这是因为 clip 的 限制了低概率 Token 的概率增长,例如原来 0.01 的 token 在 时单次更新后也只能最高到 0.012,而原来就高概率的 token 变化范围则大得多。因此将 保持 0.2( 过大可能会导致更新后 token 概率从很大突然变成接近 0,出现采样空间崩溃), 增加至 0.28(有没有更激进的实验直接尝试更大的 或者去掉 )。
- Dynamic Sampling:准确度为 1 的样本会在训练中持续增加,此时 GRPO 一组输出的 Reward 都是 1,Advantage 等于 0,也就是说 Policy 没有优化,因此降低了样本效率。在训练前持续采样,直到批次被准确率既不为 0 也不为 1 的样本完全填充。
- Token-Level Policy Gradient Loss:GRPO 中每个样本都被平均到了 token 级别,导致较长样本的影响被稀释,也就是其中的每个 token 对总体损失的贡献更低。这导致了高质量长样本学习效率较差,而低质量长样本(如包含很多无意义和重复 token)又无法被有效惩罚。所以将两次平均 (先针对样本做长度归一化,然后再计算组内所有样本的均值) 修改为直接计算 Group 内所有 token 的平均。
- Overlong Reward Shaping:对于过长的 response,直接将 reward 设置为 -1 会引入奖励噪声,会让模型感到困惑(优秀的推理路径仅因其长度,就被赋予了很低的 reward)。因此最直接的解决方案是移除掉这些超出长度的样本(就当没有生成过),但是治标不治本,缺失了避免过长的奖励信号。进而可以使用更加平滑带缓冲的惩罚,超出不多在缓冲区时(即超出设置的最大长度 - 缓冲区长度)惩罚线性增长,超出最大长度后将恒定为 -1。
- VAPO(Value-based Augmented Proximal Policy Optimization):强化学习是个圆(之前就有关于 Rule-Based 和 RM 的讨论),又开始讨论之前 GRPO、DAPO 等没有价值模型天花板更低,价值模型训练好以后上限会更高,具体优化策略大多和 DAPO 中类似。
- Qwen 改进:GSPO(Group Sequence Policy Optimization)
- 价值模型的作用:提供一个动态基准,用来校准 RM 提供的原始奖励信号,生成更稳定、信息量更大的 Advantage 信号,从而稳定并加速 PPO 的训练。但是难以训练好这个模型,还和 actor 占据同样的空间,因此 GRPO 通过转换为组内相对得分来指导模型学习,更高的提高概率,相反则降低概率。
- 原始 GRPO 是基于 token 计算新旧策略比值(重要性权重),长文本场景噪声会逐步累积(还可能出现有的 token 正向有的负向),最终导致模型训练很容易崩盘。
- 同时,针对 MoE 模型,只要参数稍有更新,专家路由的分配情况就可能天差地别,导致新旧比较失去意义,因此先前普遍方案都是引入【路由回放】。
- 因此,引入序列级重要性权重,即: 指数表示对序列长度开方,也就是相当于几何平均。一个句子的概率是所有词元概率的连乘积,长短句的差别会非常大(长句通常是个极小值),通过这种处理能够近似看作将整个句子的「权重增益」平均分配到每个词元上,训练更稳定鲁棒。
- GSPO-token 支持对不同的 token 区别对待,在所有 token 权重相同时相当于 GSPO,但是在例如代码任务上,细粒度的词元级反馈应该远胜一刀切的方案。
- 人大/快手改进:ARPO(Agentic Reinforced Policy Optimization)
- 核心洞察:大语言模型在与外部工具交互后,其内部状态会因新信息的涌入而变得极不确定(峰值熵)。
- 不再从头开始盲目采样,而是在工具调用后判断高熵(参考生成第一个 token 的原始熵),然后决定是否要分支采样。
- 结合重要性权重,前面共享路径推理轨迹一致,重要性权重一致,相当于集体贡献奖励;后面分支路径则是获得最高奖励的那条路径上的决策会得到强化,而其他路径则会被削弱。
- 额外考虑了多工具协作奖励,鼓励模型去掌握更复杂的工具协同能力。
- 随后出现了很多在采样环节关注熵的工作。
多模态大模型
- CLIP(Contrastive Language-Image Pre-Training)
- 搜集了大量图像 - 文本对,分别使用 resnet/vit 编码图像、transformer 编码文本,两两计算余弦相似度,只有对角线上的元素对是正样本,其余都是负样本。
- ViT(Vision Transformer):将图像拆分为固定大小的 patch,然后对每个 patch 展平为一维向量(通常使用的隐藏层维度是 768),接着加入位置编码(指示每个 patch 在原始图像的位置),最后将包含位置信息的 pathc 向量输入 transformer,结尾一般添加有
CLS分类 token。 - 对比学习的训练目标(loss)就是最大化正样本的相似度预测,同时最小化负样本的相似度,ITC 损失函数由图像和文本两部分平均,分别是当前图像 - 文本对的余弦相似度相对于总余弦相似度的差距。也就是说是双向的,既要优化从图像预测文本,也要预测从文本预测图像。
- 实际文本使用“这是一张
label的图片”,能够识别从未在训练数据中出现过的类别,只需要将候选label中加入可能的未出现过类别。
- ViLT(Vision-and-Language Transformer)
- 相比起 CLIP 更注重交互而不是嵌入,图像使用 ViT 同时文本词嵌入后都加入模态类型标志,合并成统一序列输入 transformer,使用 preNorm,训练更稳定但可能丢失特征信息。
- 训练目标是 Image Text Matching(预测图像和文本是否匹配)和 Masked Language Modeling(完形填空任务)。
- 轻量级、统一使用 transformer 来处理视觉和文本特征,大幅提高了效率,推理很快但是训练较慢。
- ALBEF(Align Before Fuse)
- 动量蒸馏(MoD):解决即使是正样本对也存在不相干文本 token 或图像中其它物体的问题,本质上是提高对噪声的鲁棒性。
- BLIP(Bootstrapping Language-Image Pretraining)
- 由两个单模态编码器(Image Encoder, Text Encoder)、一个以图像为基础的编码器(Image-grounded Text Encoder)和一个以图像为基础的解码器(Image-grounded Text Decoder)组成。
- 损失函数:ITC、ITM 和 LM。
- CapFilt(Caption Filtering)机制:处理噪声。
- Captioner 是 image-grounded text decoder,它在人工标注数据集上以 LM 为目标进行微调,对给定的图像进行文本解码。
- Filter 是 image-grounded text encoder,它根据 ITC 和 ITM 的目标进行微调,以学习文本是否与图像匹配,去除原始网络文本和合成文本中的噪音文本。
- Bootstrap 过程,Captioner 生成的图文对与 Filter 过滤后的网络图文,再加上人工标注的图文对结合起来,形成一个新的数据集,重新预训练一个新模型。
- BLIP2:引入了 Q-Former,负责弥合视觉和语言两种模态的差距,由 Image Transformer 和 Text Transformer 两个子模块构成,它们共享相同自注意力层。
- 使用 ITC、ITM 和 ITG 预训练对齐,然后冻结的 Image Encoder 生成原始的图像特征,而 query tokens 和 Q-Former 从原始图像特征中生成转化好的图像特征,然后该图像特征经过全连接层映射到 LLMs 的文本 embedding 空间中。然后这些映射后的图像特征,就相当于视觉 prompts,和文本 embedding 一起,输入到冻结的 LLMs 中,最后生成目标文本。
- Q-Former 作为视觉语义提取器,本身非常难学好。
- 现有的 多模态大模型对齐方案 中,Qwen-VL 也使用了 Q-Former,LLaVA 使用更简单的 MLP。Q- former 有多个对齐模块,参数较多,容易 overfit 训练数据。
- BLIP3:过往的 BLIP 已经严重滞后,需要改进以适配 LLM 的发展。
- 数据上,构建了更大、质量更高、多样性更强的数据集。
- 训练策略上原有的多个 stage 训练流程(ITC、ITM、ITG)冗长,增大规模后训练开销显著更大。因此提出了 3 stage 的训练反式,并统一以 NTP 作为训练目标。
- 模型架构上以前只支持单图输入,应用范围窄,改进以支持交错图文输入。但是 BLIP3 架构也只能解决多模态图文交错输入,单模态文本输出。
- 架构分为三部分,Image Encoder 依旧是生成原始的图像特征,然后 VL-Connector 将 image encoder 提取的 image embedding 转为固定长度的 image token(没有用先前的 Q-Former,换成了 Perceiver Resampler,但目标一致),最后是 LLM。
- 引入了 LLaVa 中的 Any-Resolution Vision Token Sampling,第一步是通过预设的模板找到图片的最优分辨率,然后切分 patch(其中保留一个 patch 包含全局信息),接着计算每个 patch 的 image embedding,最后提取每个 patch 的 vision token 再拼接,不同模版分辨率的 image token 不同,一般 token 更多则包含更多细粒度信息,有利于感知密集型下游任务。
- 所以 LLM 的上下文窗口大小对多模态大模型的发展非常重要,只有足够长的上下文例如 128K,才可以比较好地支持中长视频输入。
- 第一个 stage 是预训练,没有使用 Any-Resolution Vision Token Sampling,数据集包括交错图文、caption 类、QA 类。
- 第二个 stage 是 SFT,分为两个部分,一部分是使用 Any-Resolution Vision Token Sampling 配合指令微调,主要是 QA 和 caption 数据,另一部分是提升图文交错场景理解能力,在这类数据集上指令微调。
- 第三个 stage 是 post-traing,也分为两个部分,关注 Truthfulness 和 Harmlessness,前者数据打分来自 GPT4-v,使用 DPO+LoRA 微调 2.5% 参数,后者在前者基础上用 VLGuard 数据集和随机 5K SFT 数据集再次用 LoRA 微调 2.5% 参数,在保留 Truthfulness 的基础上同时提升 Harmlessness。
- caption 类任务在训练 token 数达到 60B 后精度增长放缓,而相对复杂的 VQA 类任务精度还有较大提升。
- 相比以前的 BLIP 系列没有了图文对比检索功能,但是 BLIP3 的基座模型中 SigLIP(优化点是损失建模,解决正负样本分布非常不均的情况,另外 SigLIP 是 pointwise 的方式而 CLIP 是 pairwise,分布式训练下通信存储和计算复杂度都更低)本身支持图文检索。
分布式并行
- 优化目标:更快地训练更大的模型,难点在于 GPU 的内存限制(更多的参数量、中间结果和训练数据,对应更大模型)和 GPU 间的带宽限制(通信时间,对应更快训练)。
- 数据并行(DP)
- DP:计算 GPU 称为 Worker,梯度聚合 GPU 称为 Server。在实际应用中,为了尽量减少通讯量,一般可选择一个 Worker 同时作为 Server。
- 存在的问题有两个,一个是每个 GPU 都存了一份模型,冗余严重;另一个是通讯开销大,Server 一般成为瓶颈,针对这个问题可以采用异步的思想解决,但对一个 Worker 来说,只是等于 W 不变,batch 的数量增加了而已,在 SGD 下,会减慢模型的整体收敛速度。
- 受限于通信负载不均,DP 一般适用于单机多卡场景。
- DDP:分布式数据并行,首先需要解决通信负载不均的问题,思路是将 Server 的通信负载平摊到每一个 Worker 上,称为 Ring-AllReduce。
- Reduce-Scatter:每个 GPU 只和相邻两块 GPU 通信,定义一个环状数据通信路线,直至每一块 GPU 上都有一块数据拥有了对应位置完整的聚合。
- All-Gather:目标是把红色块的数据广播到其余 GPU 对应的位置上,相当于上一步是在数据合并,这一步是直接替换。
- DP 和 DDP 的总通讯量相同,只是分摊问题导致 DP 需要更多的时间搬运数据。
- ZeRO:本质上是将能切分的都切分,用较小增加的通讯量换回大量存储量的节省。
- ZeRO-DP(针对模型必要或相关存储内容的优化):模型并行的形式,数据并行的实质。模型并行是针对同样的输入,只使用自己维护的那部分参数来计算结果后整合,而 ZeRO 是在整合权重后,根据不同的输入计算结果后聚合。
- ZeRO-R(针对其余存储内容的优化,需要更灵活的手段):针对激活值,同样采用切分并且可以灵活选用保存和重新计算的比例。针对碎片空间,设置机制适时整合存储空间。针对临时存储,设置固定大小的内存 Buffer,可以提高带宽利用率(GPU 数量上升切片会减小,可以积攒数据再通讯,更好地利用带宽),也使得存储大小可控(每次通讯前积攒的存储大小是常量)。
- ZeRO-Offload/ZeRO-Infinity:核心思想是显存放不下的东西,放到其它地方。前者认为参数(fp16)、激活值计算量高,和前向反向传播相关;而参数(fp32)、优化器参数(fp32)和梯度(fp16)仅需要更新,计算量低,全部放入 CPU 也可。
- 不同 Stage 的配置:
- Stage0:不做其它优化,相当于 DDP。
- Stage1:把优化器状态分片到每个数据并行的工作进程(每个 GPU)下。
- Stage2:把优化器状态和梯度都分片。在不开梯度累积的情况下,在通讯量上没有增加,更加节省了显存。
- Stage3:把优化器状态、梯度和模型参数都分片。通讯量进一步增大,通讯量换显存的典型代表。
- 相比起这里逐步增加的通信量,各种灵活 overlap 的方案也是关键。
- FSDP
- 更激进的分片策略,按层甚至参数组分片。
- 按需激活,只涉及当前层 all-gather,和 stage3 每次都 all-gather 完整的 fp16 权重不同。
- 反向传播过程中,特定层计算后会立即 reduce-scatter 同步和累计这些分片后的梯度,而不是最后计算完再全局 reduce-scatter。
- 通常倾向于重新计算激活值,而非保存。
- torch naive,相对不兼容 bug 少于 deepspeed。
- FSDP2
- FlatParameter 被原生的独立 DTensor 替代,避免参数拼接和管理,以及 LoRA 等方案开箱即用。
- 保存 checkpoint 前不需要 all-gather,且支持异步存储,减少 I/O 阻塞。
- 通信与计算更好的 overlap。
- 兼容性和稳定的内存管理。
- 初始化
- 模型在 fake device 上被构造,只记录逻辑,不分配物理内存,随后划分为多个 FSDP Unit,然后移动到实际的 GPU 上 replay,之后分片并存储到不同的 GPU 上。
- FSDP Unit 之间存在依赖的时候只能采用类似的经典策略,在单张 GPU 上尝试初始化/CPU。
- VeRL:支持 FSDP1 和 FSDP2,强制 reference policy 开启 CPU offload 节省显存,FSDP1 actor 不支持 CPU offload,和梯度累积不兼容且同步机制存在时序问题,FSDP 支持。
- 混合模态数据(图文 + 纯文本)场景中,FSDP 需要进行一些额外的特殊处理。
- DP:计算 GPU 称为 Worker,梯度聚合 GPU 称为 Server。在实际应用中,为了尽量减少通讯量,一般可选择一个 Worker 同时作为 Server。
- 张量并行(TP)【细节较多】
- 和 ZeRO 的区别:ZeRO3 在每次计算前会先 gather 完整的参数,计算后释放;而 TP 则是在计算前后对输入和计算结果做通信。所以 ZeRO3 的中间激活值是完整的,TP 则是切分过的,、实际上可以把两个 linear 层组合在一起,在入口和出口处才做通信,中间激活值的 size 减少为 1/n。
- 基本算子:行维度拆分和列维度拆分。
- MLP 层、Self-Attention 层、Embedding 层和 Cross-Entropy 层前后向传播流程与通讯量。
- Megatron
- 可以认为 TP 和 PP 是为了解决模型参数量太大,单张卡,或者一台机器上的八张卡都不够放的问题。而当模型参数都已经放在 GPU 上了,就需要通过 DP 这种并行方式来加速训练了。
- 初始化:定义模型的切割框架,并在此框架上初始化进程,分配 GPU 和设置进程组,未来每个进程独立执行自己所维护的那部分模型的计算。
- 由于一般而言,通讯量 TP>DP>PP,因此通常优先让 TP 不跨机,同一台机器内带宽高。MP 设定原则则由 TP 和 PP 共同决定,主要需要预估峰值显存消耗。而一台服务器通常插 8 张卡,所以这也是为什么多头注意力的头数经常设置为 8 的原因,由此降低 TP 通信成本。
- 张量并行一般都在同一个机器之上,所以通过 NVLink 来进行加速,对于流水线并行,一般通过 Infiniband 交换机进行连接。
- 其中
torch.distributed.init_process_groupAPI 较为重要,init_method参数负责指明一个地址,进程组内的进程通过该地址中存放的信息进行交流(交流对象和内容),该信息只在进程 0 上存一份(避免冗余),或者也可以使用直接显式指明数据对象的Store参数,二者是互斥的。 - 数据并行组 (DP) 的大小无需确定,确定了 TP 和 PP 后,对维护有相同模型部分的 GPU,就可以做数据并行,每个 DP 组的大小是 ,前者表示全局进程数。
- 在 GPT 类模型中,输入层和输出层共享一个
word_embedding。因此,在计算完梯度,更新 embedding 权重前,输入和输出层需要进行通讯,保证word_embedding完全一致。也即 PP 组中的第一个和最后一个进程需要通讯,因此把它们也划分为一个组(分组的意义在于实现通讯)。 - TP 组中,每次计算完有 AllReduce 过程聚合结果,然后才能进行下一层计算,此时输入将变为相同,可以使用 ZeRO-R 优化激活值避免冗余。
- 模型切割:在 CPU 上定义并初始化模型,然后将其搬运到当前进程所对应的 GPU 上。
- 一般在 TP/PP 组内,设定不同的随机种子;而在 DP 组内,设定相同的随机种子。主要是要确认是初始化后切割(不同的随机种子),还是完成了 AllReduce 聚合操作此时 GPU 上的内容已经一致(相同的随机种子)。
- Pytorch 默认是先在 CPU 定义出完整的模型,并对模型参数做初始化,然后根据进程 id 取出相应子模型,搬运到 GPU 上。如果自行搬运,还需要设定权重精度(例如将 fp32 降至 fp16)和初始化 DP 组(定义 DP 组间前向/后向传播、梯度计算和通讯等方法,可以使用 torch 的 DistributedDataParallel 类)。
- MegatronModule:基类,同时令 PP 组的第一个进程和最后一个进程满足
word_embedding完全一致。 - 对
word_embedding做拆分,position_embedding和segment_embedding和输入相关,均不切割。 - logit 实际表示的是 token 和词表中每个 token 的相似度,我们希望(token 和词表中所有词相似度的总和 -token 与真值间的相似度) /token 和词表中所有词相似度的总和这个值最小,这个差值就是最终的 loss,实际就是希望模型对真实值有较高的相似度,并且减少对其他词的高相似度。
- 混合精度训练
- 总体流程:首先将参数复制一份,精度减半,fp32 的叫主权重(优化更新和最终结果均为它),fp16 叫训练权重。然后使用训练权重做前向计算(激活值也是 fp16),得到 fp32 的 loss(保证反向传播梯度计算可靠性)。接着将 loss 做 scale 处理,防止溢出(主要是梯度下溢)。使用反向传播计算梯度,以 fp16 的形式存储,但是在实际更新模型权重时,转换成 fp32(理论上此时 fp16 梯度已经无用)。最后还可以利用 Clip(由于阈值难定,一般是根据全量梯度的 L2 范数来裁剪,L2 范数也即先平方和再开方) 等操作来预防梯度爆炸/消失。
- 由于舍入误差(基于梯度更新权重时因为超出范围导致没有变化,相当于空训了一轮)和梯度下溢(训练后期频繁出现梯度低于最小单位,同时利用 Scale 操作解决),所以仅利用 fp16 精度训练不妥。另外对于 BN/LN 等部分的权重,使用 fp16 可能造成训练不稳定,因此这部分参数从头到尾都使用 fp32。
- Loss Scale:常量放大需要预先尝试不会发生梯度上溢,动态放大则先设定一个相当大的参数(因为目标是找到不会上溢的尽可能大的 scale 参数),然后出现上溢则缩小,连续若干次未遇到则放大。刚开始有上溢很正常,本来就需要经历一个探索过程。Megatron 两种方式都支持,动态情况下,连续指定次无上溢则放大,累计指定次上溢则缩小。任何 step 中任意一张卡上出现溢出均为直接作废,不使用本 step 计算的梯度更新权重。
- 由于 Loss 经过了 Scale 操作,所以计算出的梯度也是 Scale 的结果,因此在更新权重前需要将梯度 UnScale。
- 从 fp16 复制一份权重到 fp32,使用
main_param = param.detach().clone().float(),detach()用于脱离计算图,也就是requires_grad = False(反向传播不计算梯度),而clone()则避免 fp16 和 fp32 共享同一内存。
- 流水线并行(PP)
- 针对问题:朴素的模型并行 (按层拆分塞到不同的 GPU 以放下模型) 存在 GPU 利用度不足,中间结果消耗内存大的问题。
- 核心思想:在模型并行(通常更关注的是层内切成不同部分计算)的基础上,进一步引入数据并行的办法,即把原先的数据再划分成若干个 batch,送入 GPU 进行训练。
- 划分了更细的 micro-batch 后,整体利用率更高,后面的部分不用等到之前算完整个 batch 再开始,尽可能填满空闲的时间安排,但终归是无法完全打满的。
- 1F1B in PipeDream-Flush:将前向和反向交替穿插,先前的结果可以更快释放,进一步节省显存开销。
- re-materialization(active checkpoint):每个 GPU 上,只保留来自上一块最后一层计算结果的输入,相当于时间换空间。
- 对于 BN,由于划分了 micro-batch,在训练时计算和运用的是 micro-batch 里的均值和方差,但同时持续追踪全部 mini-batch 的移动平均和方差,LN 则不受影响。
- 这里的流水线并行是按层切开的,所以对于层数更深的模型,micro-batch 带来的显存节省效果更优。
- 序列并行(SP)
- 目标是降低激活值(决定一份数据是否能作为激活值保存下来的要点就在于它会不会在 bwd 的链式传导过程中被使用)的显存占用。
- 张量并行都实现了将模型参数切分,那么激活值也可以考虑切分到各个 GPU 上。
- 主要针对 LayerNorm 和 Dropout 这两个顺序无关项,边算边通讯的方式保持 TP+SP 的通讯量和只使用 TP 一致。
- 另外也可以结合选择重计算,也就是不保存占显存大,但是本身计算量不大的激活值(例如 Attention score 相关的计算,softmax 这种操作比起矩阵乘法来说就更快)。
- MoE 架构:通过对 FFN 层降低维度来节省激活参数,router 配合多个专家替代原来的 FFN 层。
- 选择 FFN 的一个原因可以认为是 self-attn 层多头其实已经包含了门控选择的目标。
- 不同 token 代表的含义不一样(例如可以从词性维度理解,主谓宾选用不同的 expert),因此我们可以用不同 expert 来对它们做解析。除了训练上也许能达到更好的效果外,MoE 还能帮助我们在扩大模型规模的同时保证计算量是非线性增加的(因为每个 token 只用过 topK 个 expert,不用过全量 expert)。
- MoE 的负载均衡:某些 expert 接收到的 token 多,某些很难接收到。不仅严重违背初衷以及影响训练效果(接收到 token 多的 expert 优化更快,router 会更加倾向于继续分配给这些 expert),还影响计算效率(例如引起分布式训练中各卡通讯时的负载不均),因此需要优化分配策略,尽可能给所有 expert 接近相同的 token 数量。
- 理想容量:token 总数/expert 总数,再乘上 TopK。
- 比较直接的思路是 Top1 稳定发送,其它的考虑随机性。DeepSpeed 从某种分布中采样噪声,然后加在概率上,mask Top1 对应的,选择剩下的里面的 Top。待 token 经过了 expert 后,可以对输出的 K 个 token 做加权计算,得到最终的输出 token。
- 对于溢出的 token,如果只有单个 expert 溢出,则正常加权计算其它的 expert 输出结果即可。而如果都溢出,则不经过任何 expert,直接通过残差连接的方式,原样发送去下一层的 attention 上。但是丢弃的太多其实算是信息损失(没有经过 expert 处理),可以通过加大理想容量的方式缓解,不过这又会引入更严重的 zero padding 和不均衡问题。
- 另外也添加了额外的辅助损失函数尽量保证 expert 的负载均衡。
- 通过 zero padding 的方式保证每个 expert 上要处理的输入数据维度是一样的,这有利于硬件层面的后续处理(例如多卡通讯间的负载均衡等)。
- 在 MoE 中,大部分论文和开源代码实践中不考虑/不讨论再对 MoE 部分做 pp 切分,deepspeed 中更是强制把 PP 维度设为 1,并在代码注释里表示不支持 PP(也许和 deepspeed 的 zero 实现有关,可能会加剧通讯量)。
- 总的来说,MoE 在推理时相对省性能,但是训练时耗费较多资源,且资源浪费问题较严重,并且也有一定的训练难度。另外对于可解释性而言,也增加了复杂度。
Agent
此时想到一段对话
“北海,要多想。” “想了以后呢?” “我只能告诉你那以前要多想。”
- 本质上是模拟人类,从接收信息,对信息进行处理和理解,然后基于理解结果形成决策,再从决策转化为具体行动(相当于比 CoT 更进 N 步)。
- 最理想的落地情况,是有大量垂直领域数据、场景封闭、问题基本可穷举。而在其他情况下可能遇到多个难题。
- 未知领域中的泛化问题,Agent 本身扩展了解决更复杂未知问题的能力,但是难题是如何体现这种能力。另外试图使用一个模型解决所有的垂直问题是困难的(不同的问题可能都需要定制化),而且 API 插件生态等还在持续建设甚至变更中,如果只能局限于较小的问题那么可能很难吸引团队持续投入(性价比偏低)。
- 过度交互问题,甚至可能陷入交互循环陷阱。
- 多智能体下的计算开销。
- 安全问题,例如隐私泄露、权限滥用和有毒信息。另外完全不同模态的信息对齐依旧关键,缺乏真正多模态的反馈。
- 评估问题,传统刷点评估方式显然不够适配,只看结果来评估也不妥当,过程准确性在评估中不应该被忽视。
- 在开放场景中,经常有新知识出现,例如法律助手场景有新法律法规和判例,所以只能退而求其次做一个帮助律师搜集整理文档和判例的提效工具。在封闭场景中,已经有成熟丰富的 API,场景问题有例可循且问题可枚举,适合 Agent 落地,例如出行预订。
- 通常是开放设计题,给个实际场景问设计一个相关的 Agent 要怎么考虑。
- 案例枚举
- 内容创作生成:最直接的使用形式,涉及企业知识通常外挂知识库 RAG。
- 数据处理分析:三种方式,Text2API(BI 工具)、Text2SQL 和 Text2Code(Python/R)。更普适的其实是流程助手,将简单重复的前端操作合并提取事务,例如筛选满足条件的用户批量联系(消费达额度等)。
- Agent 本质上的目标是完成简单的任务,或是在 AI 擅长的领域而人不习惯的(例如正则表达式)。对于较难的长尾问题不需要全部解决,而是能够让用户意识到并且可以实现通过调整 Agent 使用方式来解决。
- 实现细节与挑战
- 控制端(模拟大脑,涉及信息处理、和知识记忆交互以及规划迁移)
- 自然语言交互:多轮对话提炼、高质量文本生成(新颖多样和自纠错)、潜在意图理解。
- 知识:知识类别,过时和错误信息处理,以及调用 RAG 时领域知识和世界知识冲突。
- 记忆:短期记忆认为是 LLM 上下文长度内的内容,长期记忆包括但不限于 System prompt(角色扮演)和外部存储的信息或对话历史。
- 对于知识和记忆而言,上下文长度的限制存在,如何压缩节省空间(包括存储维度)、提取更相关的记忆,以及整合都是值得考虑的。
- 推理和规划:典型的两步走,计划制定可以理解为 CoT 和蒙特卡洛搜索,分解为子任务探索最优路径,计划反思利用来自内部、人类和环境三方面的反馈信号。
- 迁移和泛化:基座能力、情景学习(ICL)、持续学习,Agent 显然需要持续更新技能,因此在持续学习时避免灾难性遗忘非常重要。
- 感知端(接收多模态信息)
- 文本输入:基本的文字语言理解能力。
- 视觉输入:对于图像输入,文字描述的方式易操作成本低但是容易丢失信息。对于视频,其实就是如何理解不同帧的时间关系,在标注时其实很像 RAG 如何分段。
- 听觉输入:两种方案,一种是语音识别 + 风格提取,另一种是将音频频谱视作图像信息,用类似 ViT 的方式切分后处理。
- 其它输入:例如温湿度、亮度、经纬度等,设计规则或采用对齐到文本输入嵌入空间的方式处理。
- 行动端(执行交互)
- 文本输出:LLM 基本能力。
- 工具使用:感觉可以理解为 function calling 的升级版,可插拔式地利用搜索引擎等工具增强特定专业知识,借助工具完成时通常可解释性和鲁棒性更强。
- 具身行动:具身智能、转为机械臂等物理世界所需的输出信号。
- 控制端(模拟大脑,涉及信息处理、和知识记忆交互以及规划迁移)
- 当下 Agent 框架的 三大问题:黑盒思维(推理过程不可控)、知识固化(无法持续更新认知)和粗放纠错(反馈机制粗糙,只能指出答案错误无法指出哪一步推理错误)。关键还是需要拆分模块后因地制宜,前面论文框架中结合了 MoE 和探索、反馈与利用的思想。
- workflow:本质上是充分利用先验知识和特定的业务流程,减小搜索空间,直接跳到接近最优的解决方案,另外很多时候也是为了给端到端的智能体出错兜底。
- 实际像 Manus、AutoGLM 这样的产品遇到的最大问题反而是各家网站的非登录用户浏览限制,以及由此容易导致的反复访问和死循环。其它的像上下文组织一类的问题都是传统 RAG 和 WorkFlow 方案里共同的。
- 对于 Agent 而言,更多的问题或 case 并不是可以 rule-based reward 评判的,GRM 和 LLM-As-Judge 已经成为相对固定的方案,直接让 LLM 给回答打分粒度太粗,可以考虑采用 checklist 的形式替代。
- 2025 的两个方向:工具动态调用和个性化记忆,本质上都是上下文工程的再拓展。
- 工具动态调用
- Manus:避免在迭代过程中动态添加或移除工具。
- 在大多数 LLM 中,序列化后的工具定义在位于上下文的前部,通常在系统提示词之前或之后。因此任何更改都会使后续所有动作和观察的 KV Cache 失效。
- 当先前的动作和观察仍然引用当前上下文中不再定义的工具时,模型会感到困惑。如果没有约束解码,这通常会导致模式违规或幻觉动作。
- 所以不应该是移除工具,而是通过在解码过程中掩码特定 token logits,来基于上下文阻止/强制选择某些工具。
- 处理复杂任务时,创建一个 todo.md,通过不断重写待办事项列表,来尽可能避免中间丢失问题。
- Manus:避免在迭代过程中动态添加或移除工具。
- 个性化记忆(需考虑安全隐私风险)
- 记忆管理的动机
- 上下文窗口的长度,以及注意力计算的 时间复杂度。
- 大多是无状态的,缺乏个性化,用户需要重复输入,以及持续学习困难。
- 市面上提供记忆的 LLM 产品内部机制不够透明(如 ChatGPT)。
- 知识更新这个层面,无生命周期管理、无溯源、无版本控制、也无与模型内部参数化知识的深度整合。
- 不同 AI 模块/Agent 之间的记忆数据孤岛。
- MemoryOS
- 热度的评价公式:被检索次数、段落内的轮数和时间衰减系数的加权。
- 短期记忆:记录对话历史。
- 中期记忆:新来的对话历史是否能和某个历史话题相关,有则合并,否则开一个新的 session。
- 长期记忆:来自中期记忆的热度提升,主要分为三个方向,个人偏好、个人信息和助手的人设(Assistant Knowledge,确保前后一致性)。
- MemOS
- 纯文本记忆:外部知识模块,如 RAG 检索的文档、知识图谱、用户的提示词模板等。
- 优:易于理解和管理,更新成本低,容量不限。
- 劣:每次使用都需要模型编码理解,延迟相对高。
- 激活记忆:推理过程中动态生成的中间状态,主要是 KV Cache 和隐藏状态。
- 优:访问速度快,维持上下文连贯和即时响应。
- 劣:容量有限,通常是短期的。
- 参数记忆:固定在模型权重的知识和能力。
- 优:无需额外检索或计算,效率高,可泛化。
- 劣:更新成本高,可解释性差,定制化能力有限。
- 纯文本记忆→激活记忆:在多轮对话中反复引用时,预处理为 KV Cache。
- 纯文本/激活记忆→参数记忆:蒸馏、LoRA 微调等。
- 参数记忆→纯文本记忆:模型参数中编码的某些知识变得过时、不准确(例如旧的法律条文),或者很久没有被使用时,从参数中“卸载”,然后转为纯文本形式,归档到外部存储。
- 记忆接口层:用户和 MemOS 交互。
- MemReader:用户自然语言输入解析为结构化记忆操作指令,如意图、时间、类型等。
- Memory API:统一 API 接口,应用程序可以标准化增删改查记忆。
- Memory Pipeline:串联检索→增强→更新→归档的记忆工作流。
- 记忆操作层:记忆的组织规划和调度。
- MemOperator:组织异构记忆,支持规则过滤、语义相似度、分层标签、知识图谱等形式混合检索。
- MemScheduler:根据任务语义、记忆类型、访问频率、时间衰减优先级等指标,动态选择最合适的记忆类型及注入 LLM 推理的时机。
- MemLifecycle:记忆有限状态机,生成 - 激活 - 融合 - 归档 - 过期的转换,支持快照和回滚。
- 记忆基础设施层:记忆的存储、安全和迁移流动。
- MemGovernance:记忆的访问控制、合规审计,用户 - 记忆对象 - 调用上下文三元组。
- MemVault:管理和存储不同类别的记忆,向量数据库/关系数据库/Blob 存储。
- MemLoader & MemDumper:记忆的导入导出和同步。
- MemStore:发布、订阅和分发记忆模块。
- 纯文本记忆:外部知识模块,如 RAG 检索的文档、知识图谱、用户的提示词模板等。
- 记忆管理的动机
- 工具动态调用
AI Coding
- 基础能力
- 模型长上下文窗口。
- 多模态输入(错误截图、日志和代码等)。
- 工具调用、Agent 相关能力,主要是 Github 工作流和命令行各种命令操作。
- Claude Code 的设计理念:大道至简、返璞归真。
- 没有采用多智能体,对于复杂任务,最多仅派生一层 Agent。超过 50% 的任务(如网页解析、文件读取和总结长上下文)都会落到小模型(Claude-Haiku)。
- 提示词工程:
- Claude.md 传递明确偏好/长期记忆。
- 结构化提示(XML 标签/Markdown 语法)。
- 大写/加粗/反复强调等笨办法仍然有效。
- 相比【要做】/【不做】的,尽可能写成流程化算法更好。
- 引导做规划和任务拆解,优先验证思路(Claude 对于复杂任务会自动生成 TODO 列表),及时打断修正。
- 着重工具设计,而不强依赖 RAG 工程。
- 基于命令行工具做 Agentic Search,让模型自行决定什么时候需要搜索,生成
grep、find等命令。RAG 引入了相似函数选择、重排器设计、代码如何分块和超大 JSON 及日志如何处理等问题,Claude Code 选择将这个难题交回模型。例如对于超大 JSON 模型可以选择读取开头几行来决定是否继续或生成调用grep命令。同时,模型调用工具这个能力可以通过 RL 做优化。 - 不能随便引入或使用危险命令。
- MCP 工具封装,从基础的到高层的(如 WebFetch,获取 URL- 解析内容 - 提取信息)都需要,本质上是使用频率×正确率的权衡。最频繁、最关键的动作,做成专用工具,并配上清楚的说明与示例。高层次的流程封装成工具,提升稳定可控性和效率。
- TODO 列表让模型自行维护,提示词强调参考 TODO,牢记最终目标,过程中模型自行动态增删,用户可见可介入。
- 基于命令行工具做 Agentic Search,让模型自行决定什么时候需要搜索,生成
- 权限控制,默认只读,涉及写入/修改则由用户最终确认。
- 耗费了很大精力优化【总结上下文】的效果。
- 其它产品(Cursor、Copilot、Augment Code)的一些思路。
- 记忆管理,从开发者交互中学习,适应代码风格和偏好。
- 超大型代码库的理解。
- 跨编程语言的检索。
- 和 Microsoft 体系(Github、VSCode)的完美兼容。
AIGC Security
- 幻觉问题
- 隐私保护
- 合法合规
- 越狱
ps. 本文长期更新,欢迎常看淘金。