LLM-CS336
从头构建大模型说是🔥☢️
标记化
字节对编码标记器(BPE)
在字符分割中,可以直接使用Unicode转为UTF-8的方式产生字节编码序列
使用 re.escape 防止特殊字符被误解释(通过加入转义的方式避免正则判断出现错误),并使用 re.split 将特殊字符作为分割点分离原始文本
1 | import regex as re |
一些加速方法,例如 BPE 算法迭代地统计每对字节,并识别出出现频率最高的字节对,将字节对表示为一个新的词库(在 0 ~ 255 外的一个数字,样例中定义为 256 )。该方法能够通过迭代一定的次数减少token的长度
这样做,token中所包含的信息会不会进一步让机器难以总结?🤔🤔
与此同时,可以将大的数据文件切分成4KB左右大小的区块(保证一篇完整的文章不会从中间被切分)以便并行训练模型
pytorch
张量
张量的连续与否取决于该张量在内存中的储存是否连续,当你遇到 view() 报错( view() 要求张量在内存中是连续的,因为它只是重新解释一个连续内存块的含义)或追求极致性能时,就需要考虑并使用 .contiguous() 来修复它。
对于提取张量中的列,行,提取试图,转置等均不会创建原始张量的副本,**但.contiguous() 和 .reshape() 操作会返回一个在内存中连续存储的、包含相同数据的张量副本。**在使用时要谨慎
架构与超参数
核心架构
标准化
layernorm 等正则化虽然只使用了极少的 FLOP ,但仍会耗费较多运行时间在内存移动上
相比于 layernorm ,RMS norm 不会减去均值并去除 β 参数,使得内存效率提高
去除偏差项会提高模型训练的稳定度,目前大多数大模型完全省略偏差项,仅在矩阵乘法类型上进行运算
目前基本上正确的做法是 pre-norm ,并且在残差流之外进行 LayerNorms,可以获得更好的梯度传播,更稳定的训练
激活函数
目前最新的模型都使用门控线性单元 SwiGLU 、 GeGLU
GLU 有一个额外的超参数 V ,对于门控单元,要将待乘矩阵缩小一些以确保一切保持参数匹配
架构
串行 or 并行
目前大多数模型依旧采用串行架构
位置嵌入
目前大多数模型采用 Rope embeddings
Rope embeddings 旋转矢量,旋转角度由每个单词的位置和旋转决定
其中确定角度的 θ 有一个时间表(类似于 sin cos 位置嵌入),可覆盖不同的频率范围以获取更高或更低频率的信息
相对定位信息至关重要
Rope 作用原理仍需补充♿♿♿
超参数
前馈层维度
从经验角度上讲,前馈层的维度等于 4 倍模型维度是较优的
若使用 GLU ,前馈层的维度等于 8/3 倍模型维度
前馈层矩阵越宽,能得到更多的并行计算,更好的系统优化收益,与扩大隐藏单元的作用方式不同
头部维度与头部数量
头部维度 == 模型维度 / 头部数量
这只是一般共识,可以作出一些改变
纵横比
模型维度 / 网络层数
通常最佳点为 128 ,早期模型的该比例一般较小
较好的区间为 100 - 200
词库大小(vocabulary size)
呈一个上升趋势
已出世的生产化模型在 100 - 250k token 数量范围
剪枝及一些其他正则化
剪枝(drop out)逐渐不再受青睐(大模型目前不存在过拟合的问题,甚至无法对训练数据进行一次迭代),权重衰减(weight decay)成为新兴
不是为了控制过拟合(权重衰减大小与训练集和验证集的损失比例无关), 而是与优化器的学习率发生作用,存在一些非常复杂的交互,因此其适用于更好的训练效果而不是避免过拟合
稳定性技巧
在训练时范数梯度和损失会遇到很多异常的由梯度爆炸造成的峰值
一般在 softmax 函数上出现该问题
可以使用 z-loss 替代 output softmax 来缓解这一问题
使用 QK norm 替代注意力机制中的 softmax 产生稳定输出,是种非常严厉的干预
也可以无脑加 layer norm (乐🤣)
在推理时仍然使用 layer norm ,layer norm 已经学习到了参数,去除会大幅改变模型参数
Logit soft-capping 没见过的新技术,通过tanh转换一些最大值
注意力头
GQA / MQA
算术强度:总计算操作开销 / 总内存访问开销,越大越好。GPU内存访问开销相对较大,目的是在单次内存访问中做尽可能多的计算(大数量的头,大的批量大小,大的序列长度都能提高这一指标)
在推理时需要 KV cache 技术来增量式更新注意力来进行高校推理,避免整个 QK 矩阵的计算,处理绝对必要的 Key 和 Value 矩阵部分,但这会带来更高的内存访问量🤔
MQA 设置多个查询头,但对于键和值只能设置一个维度或一个头
GQA 增加键和值的头
稀疏注意力机制仅关注部分矩阵,权衡各种表达能力和运行时间,以此获得更大的注意力窗口
滑动窗口注意力机制,在每一层只关注当前位置周围的一小块区域,控制所需的资源总量
Transfomer Block,可以定义一些层使用完全自注意力或者滑动窗口自注意力(使用RoPE)
专家混合系统(MoE)
MoE对比密集架构模型就是好用😋
架构
用选择器层和许多较小的层来替换左侧的大的前馈层,在每次前向传递或每次推理中选择较少数量的副本
专家网络:一组不同的“子模型”(即专家)。每个专家通常是一个前馈神经网络,它们各自擅长处理特定类型或特定模式的数据。
门控网络:一个“路由器”。它的作用是针对每一个输入(比如一个词或一个token),计算应该将输入分配给哪个(或哪些)专家,并决定最终组合这些专家输出时的权重。
专家并行性:可以把每个专家分片到不同的设备上,只需要获取 token 并路由到适当的设备
基础设施很复杂,只有在多节点时才能发挥最大优势,并且路由选择的训练和所涉及的优化问题,其目标要么是启发式的,要么是不稳定的,所以没有将其推广到大模型之外
传统方法是把 MLP 层替换为 MoE 层,如今可以推广到注意力机制作为注意力头,但很少见
设计
有三种路由选择方法
-
token 选择专家 (Top-K 个专家)
-
专家选择 token (每个专家选择 Top-K 个 token)
-
全局专家与 token 的映射任务,可以尽可能平衡映射 (需要解决一些复杂的优化问题)
几乎所有 MoE 都选择了 token choice topK 路由
K 这个超参数等于 2 是一个规范选择
Top-K 路由公式,没错!是有一个公式在里面的
共享专家和细粒度专家
训练
RL
强化学习是“正确的解决方案”,但梯度变化和复杂性意味着它尚未得到广泛应用
随机近似
随即探索策略,通过随机添加扰动并对齐退火或执行各种操作,可控制MoE将进行的探索-利用平衡。专家会得到有时意想不到的 token ,这会使专家的专业性降低,但可能更加强大
或者对路由器对数进行乘性扰动,获得不那么脆弱的专家,但效果不如一些基于启发式损失方法
启发式平衡损失
专家与 token 映射的平衡公式
以及 deepseek 提出的每个批次专家的 token 平衡和每个批次设备的 token 平衡
DeepSeek v3 变种,给每个专家加入一个设置学习率的偏差进行平衡(辅助无损平衡)
如果不进行专家平衡损失,实际上多专家会退化为 2 专家,事实上该 MoE 可以做的更好
随机性
MoE 有一个有趣的随机性来源——
令牌丢弃(token dropping) :某个专家没有足够的内存容纳被分配的 token ,多余的 token 会被直接丢弃
稳定性
MoE 很难进行微调,使用 Float32 计算专家路由 (有时使用 z-loss )替换softmax
此处应有 z-loss 的公式
微调
使用稀疏模型会出现很多过拟合的情况
使用大量SFT数据或者将一些层替换为密集层,并只在密集层上微调
升级再造(upcycling)
从原始密集模型中提取 MLP 层,复制多个副本后加入一些扰乱,新增一个从头初始化的路由器,然后就可以当成一个 MoE 开始训练
MoE十分擅长推理,并非每个 MLP 在推理时都处于活动状态
DeepSeek V3 - 非MoE
MLA:多头潜在注意力
将头部投射到较低维的空间中,但与 RoPE 不兼容
MTP:对损失函数进行些许改变,可并行预测多个标记
总结
MoE 充分利用了稀疏性——并非所有输入都需要完整的模型。离散路由很难,但 Top-k 启发式算法似乎有效。目前有大量经验证据表明,MoE 有效且经济高效。
缩放定律
使用小模型将其放大来改进工程技术
随着数据量或者模型大小的改变,我们期待模型表现出某些行为
参数量与误差的关系,数据和模型大小应该如何与性能相关
关键 Bacth size
推理
给定训练过的固定模型,根据提示生成响应
一些评价指标:
-
首次令牌生成时间 (TTFT):用户在令牌生成前等待的时间(对于交互式应用很重要)
-
延迟(秒/令牌):用户获取令牌的速度(对于交互式应用很重要)
-
吞吐量(令牌/秒):对于批处理应用很有用
训练是可以并行的,基于transformer架构的推理还是只能自回归串行工作
KV cache 使用空间换时间,减少推理的令牌生成时间复杂度
延迟与吞吐量之间的权衡:
较小的批次大小可带来更好的延迟,但吞吐量较差
较大的批次大小可带来更好的吞吐量,但延迟较差
推理优化——减小 KV cache
GQA(Group-query-attention)
键和值的数量会减少,查询会增多,跨头部共享
在减少内存使用的同时提高吞吐量
MLA(Multi-head latent attention)
不改变键和值的数量,但会将其投影至低维空间
并且 MLA 在准确性上比 GQA 的表现更好
CLA(Cross-layer attention)
跨层注意力,在各个层之间使用相同的键值投影
局部注意力
只关注最相关的局部区域
有效上下文的规模与层数呈线性关系
KV Cache 大小可以独立于序列长度并保持一定大小,不会因为序列长度增加而增加
但表达能力不强,会损失一定准确度,因此将局部注意力和全局注意力层混合搭建
推理模型架构替换
Transformer 在设计时并没有考虑到繁重的推理工作负载
状态空间模型(State-Space model)
思想来源于信号处理,对长上下文序列进行建模,但时间复杂度大幅下降
在一些联想回忆任务中表现不佳
Mamba 很受欢迎,并成功移植到 Transformer 和 MoE 的模型中
线性注意力,类似于RNN,这种想法被成功扩大,MiniMax(linear attention + full attention)正在训练相当有效的模型
扩散模型(Diffusion model)
不再受自回归约束,并行生成 token ,并持续迭代改良
量化
减少数字的精度( float 转 int 或者转为更低精度的 float)
越少的内存意味着传输的字节数越多,延迟越低,但准确度会有所降低
考虑选择量化具体那一部分的权重
模型剪枝
拆掉昂贵模型的零件,让其变得便宜然后修好它
-
定义更快的模型架构
-
使用原始模型(架构不同)初始化权重
-
修复更快的模型(蒸馏)
推测采样
以上措施都是有损的,但该方法会让你鱼和熊掌兼得
检查比生成更快
使用廉价的草稿模型来生成,甚至说是猜测一些 tokens,并使用目标模型来评估这些 token,这可以并行执行
保证从目标模型获得精确的样本
可用 Medusa 和 EAGLE 更进一步优化
数据
训练阶段:
预训练:使用原始文本(例如,来自网络的文档)进行训练
训练中期:使用更多高质量数据进行训练以增强能力
后训练:使用指令跟踪数据进行微调(或进行强化学习)以实现指令跟踪
在实践中,界限模糊,可能会有更多阶段。
……但基本思路是从[大量低质量数据]到[少量高质量数据]。
预训练
基于书籍和维基百科的数据
来源于网络的数据非常容易受到污染,造成大预言模型的数据中毒
Common crawl 网络爬虫,需要对其中的内容进行处理和筛选
Q&A 格式数据集与用户输入的内容及其响应十分相似
不同的模型采用了不同数据集,并采用并不完全相同的处理方式
如何合法获取数据以及处理得到高质量的数据
过滤与去重
过滤算法
带有 kneser-Ney 平滑的 n-gram 模型,速度很快,但很粗糙
快速文本分类器,词袋嵌入
词袋可扩展至 g-gram,并使用哈希实现(线性分类器)
DSIR(重要性采样)
通用框架
给定目标 T 和原始 R,找到与 T 相似的 R 子集,根据 R 和 T 估计某个模型并推导出评分函数,根据得分将样本保留在 R 中
T 的生成模型 (KenLM):score(x) = p_T(x)
保留 score(x) >= 阈值的样本 x(随机)
判别分类器 (fastText):score(x) = p(T | x)
保留 score(x) >= 阈值的样本 x(随机)
重要性重采样 (DSIR):score(x) = p_T(x) / p_R(x)
以与 score(x) 成比例的概率对样本 x 进行重采样
过滤应用
语言识别,质量过滤,毒性过滤
去重
完全重复,近似重复(相同的文本,但有几个标记不同)
主要使用哈希函数,存在最佳超参数用以限定桶的数量及哈希函数的数量,由插入数据的数量决定
Jaccard similarity
Jaccard (A, B) = |A 交 B| / |A 并 B|,用以判断两种集合的近似程度
minhash 的哈希冲突概率即为 Jaccard similarity ,可根据相似性控制适当的哈希冲突级别
局部敏感哈希,波段 b 和每段的哈希函数数量 r 的超参数,使低于阈值的数据碰撞概率很小,高于阈值的数据碰撞概率很大,r 越大使得曲线右移并更尖锐,b越大使得曲线左移
SFT/RLHF对齐
对LM输出进行更好、更严格的控制
标准方法——模仿(SFT)然后强化(‘RL’ HF)
deepseek R1 基于结果的奖励和 GRPO 很有效果
RL
CLIP 方面的知识



