happy-llm 阅读笔记
NLP
基础任务
- 中文分词
- 字词切分:unhappiness = un + happi + ness
- 词性标注
- 文本分类
- 实体识别:识别 人名、地名等
- 关系抽离:识别实体间的关系,如判断因果关系、xx关系
- 文本摘要:概括,抽取式、生成式
- 机器翻译
- 自动问答
文本表示
如何将自然语言数字化
“静态参数 → 动态参数”
向量空间模型
每个维度代表一个特征项,每个维度是词表中的一个词 → 数据稀疏性、维数灾难问题(5/16384)
词向量之间是绝对的
n-gram语言模型
基于统计(条件概率,考虑前n-1个词来估计第n个词
问题:预测能力差;n较大时出现数据稀疏性问题,大部分词的概率是0;模型参数急剧增加;忽略词之间的范围关系;
word2vec
学习词之间的上下文关系生成词的密集向量表示、捕捉词之间的语义关系,使得语义相近的词在向量空间中的距离较近
CBOW:上下文 → 中心词
Skip-Gram:中心词 → 上下文
词表中的每个词一开始都会被随机分配一个向量,然后训练会不断调整这些向量,使得中心词和它周围出现的词的向量更加接近,和不常同时出现的词的向量更远。
词向量之间是相对的
ELMo
同一个词在不同句子中有不同的向量表示
- 训练:
- 双向LSTM:根据前文(前文隐藏状态)预测下一个词,根据后文(后文隐藏状态)预测上一个词
- 计算出隐藏状态公式的固定参数
- model学会了如何计算上下文隐藏向量,进而学会理解上下文
- 下游任务应用:利用model的上下文理解能力,计算输入中每个词的上下文向量,作为新的特征输入
但这样的计算量过大,每次作业都要重新计算每个词的向量
因此后面又出现了注意力机制(缓存kv,更新q)
神经网络
- 全连接神经网络:
n*n*n - 卷积神经网络:
a*b*c*b - 循环神经网络:RNN,历史信息作为输入,存在环
- 依次输入,依次计算(先输入历史信息,才能计算新的向量),限制并行能力
- 难捕捉长相关
注意力机制
情景
输入序列1(,2)
输出上下文向量
原理
黑盒model,通过结果反推原理的抽象理解
本质是获得两个序列中token之间的相关度,根据这个相关度分配注意力
Q 来自于 Decoder 的输入,K 与 V 来自于 Encoder 的输出
Query:序列1-查询向量
Key:序列2-输入中所有 token 向量形成的矩阵,代表token之间关系
Value:序列2-输入中所有 token 向量形成的矩阵,代表token本身的含义
- Q*K 得到查询(序列1)和序列2中每一个token的关系(关联度、相似度)
- softmax(Q*K/…) 获得概率分布进行归一化(activate function),得到权重(还需要进行放缩)
- softmax(Q*K/…) * V得到注意力分配

自注意力
计算本身序列中每个元素对其他元素的注意力分布,获得上下文向量
encoder
核心
将一个词向量映射为三个向量
Q=xWQ
K=xWK
V=xWV
其他一样
训练手段
掩码自注意力
普通self-sttention是可以看到历史信息和未来信息的,通过mask使得model只能获取历史信息

多头注意力
本质上就是设定 n组W矩阵,与输入进行内积,计算多个注意力向量,然后拼接起来
简化计算 → 先拼接,再内积
- 依旧是
seq_len*hidden_len的矩阵W,但它是由n个seq_len*d拼接而成(因此要求 n|hidden_len,其实就是增加一个维度的事 - 后续内积操作不变
- Concat(head1,head2,…,headn)⋅WO:Wo即为最后的线性变换

代码流程示意:重点在于把head和seq维度交换一下,这样就相当于是对head_i进行计算,实现了多头注意力机制

基本模块:encoder/decoder
前反馈神经FNN
一种MLP多层感知机,模拟连续函数,提取特征(线性扩展维度。非线性将高维空间切分地更细致,最后恢复原维度)
全连接,处理attention得到的上下文向量
- 线性层1
- relu
- 线性层2
- dropout过拟合
层归一化
由于每一层输出的分布是不同的,所以将其进行归一化转换成标准正态分布
批归一化
层归一化
计算每个样本在所有层上的均值和方差,使得每个样本的分布稳定
残差连接
- 下一层的输入 = 上一层的输入 and 上一层的输出
- 防止model的退化
1 | h = x + self.attention.forward(self.attention_norm(x)) |
encoder
encoder = n个encoder layer + 层归一化
encoder layer = 层归一化 + self-attention + 残差连接 + 层归一化 + FNN + 残差连接
positional encoding 加入位置信息
decoder
- decoder layer = 掩码自注意力层 + 多头注意力层 + FNN (每一个还有对应的归一层)
架构:transformer
embedding层
将index转换成向量,≈ 存储固定大小字典的向量查找表
本质是一个查表的过程,但首先需要先将这个表训练出来(表的形状为vocab_size*dim)
流程:
分词器:将自然语言切分,获得index (bsz,seq_len,1)
embedding层:对应index转换成长度为dim的向量
输出(bsz,seq_len,embedding_dim)
位置编码
- 对于简单的注意力机制,各个位置token的地位是一样的,为保留序列中的相对位置信息,采用位置编码机制
- 采取正余弦函数编码(绝对位置编码

- 可以在短长度embedding的基础上计算更长长度的embedding
- 对于固定间距k,PE(pos+k)可由PE(pos)计算得到
流程
input→ embedding层 → 位置编码 → encoder layers → decoder layers → 线性层 → softmax → output
结构
encoder-only(BERT
- 实现 监督学习、泛化能力弱 → 预训练+微调(泛化能力强)
- 局限:
- MLM 和下游任务微调不一致
- max_query_len
结构
embedding → encoder → linear+activation+linear → softmax → 输出类别
encoder:attention、残差连接、前反馈层
其中attention中包含positional embedding

Non-Autoregressive 预测并行,但不能生成整个sequence,只是填空
340M parameters 参数量巨大
干细胞
bert使用transformer实现的encoder + MLM(能看到左右?两侧的token)
预训练
- 大规模数据 → 无监督
Masked language model(MLM)
预测下文内容,是直接拟合从左到右的语义关系,忽略了双向的语义关系
使用mask实现拟合双向语义
完形填空:将输入中的token随机遮住(用特殊符号mask取代
改进:15%进行遮蔽(最终要计算交叉熵),其中
原因:实际训练过程中,后续的微调也需要model关注非mask内容,单纯的完形填空使得model只将注意力放在mask上,并且只进行预测任务(model走捷径时会忽视上下文的学习
80% mask → 拟合双向语义
10% 替换为随机token → 保持对上下文的学习(不要偷懒,每个token都要理解他的上下文)
10% 不变 → 让model不仅仅预测【mask】部分
数据处理的代码思路:
1 | encoding = self.tokenizer( |
下一句子预测NSP
拟合句子之间的关系
- 判断两个句子是否是连续的上下文关系
fine-tune
较低成本适配下游任务
[CLS] 代表整个句子的语义表征,在一些下游任务中很有用
RoBERTa
BERT基础上
去掉NSP
动态mask策略:将mask操作放在训练过程中进行,使得每一个epoch训练数据的mask都不一样
更高效、易实现
更大规模的预训练数据和步长(epoch↑
更大的 bpe 词表
ALBERT
减小model参数,保持model能力
embedding参数进行分解:将大查找表变成一个小查找表+线性变化
V*H = V*h + h*H在embedding层后面加一个线性层,将维度恢复为hidden_dimencoder跨层参数共享
各个encoder层参数高度一致,因此只保留一层encoder参数,也就可以扩大隐藏层维度(更宽的model)
参数规模大大减小,但训练和推理速度很低
SOP预训练任务
正例:两个正序句子 反例:两个逆序句子
要求model能够拟合句子关系、学习顺序关系
encoder-decoder(T5
将所有NLP任务统一为 文本到文本的转换,增强了model的通用性
任务描述前缀
将一个序列转换成另一个序列
tokenizer。。
transformer
encoder(双向) → 获取上下文向量
- self-attention(no mask)
- Add & Norm
- FFN
- Add & Norm
decoder
self-attention:
输入为x(decoder历史输出)
mask,让model从左到右进行预测输出(单向)
Add & Norm
cross-attention:
- 输入为 decoder self-attention输出,encoder输出
- q通过decoder self-attention的输出计算,kv通过encoder的输出进行计算
- no mask,让model关注encoder的全部输出进行输出(双向)
Add & Norm
FFN
Add & Norm
预训练
任务 MLM:
给定文本序列,随机选取token进行遮蔽,用占位符[token]代替,被遮蔽的token序列为目标序列
主要是针对encoder的训练
decoder-only
文本生成 → 使用掩码注意力机制
基本结构
tokenizer
embedding - dropout
decoder
- norm
- 掩码自注意力:核心不同点
- 计算xq xk xv,调整形状(分出维度head来)
- xq, xk 旋转位置编码
- (kv repeat)
- 注意进行维度顺序调整
- q@k,+ mask
- softmax
- attn_dropout
- @v
- 线性wo
- resid_dropout
- 残差连接
- norm
- MLP:拟合连续函数,进行特征提取
- ConV1D (相比线性矩阵,重点关注局部特征
- 激活函数
- *ConV1D
- ConV1D
- dropout
- 残差连接
归一化norm
区分 train eval
- train:
- linear,获取..vocab_size向量
- 计算loss:cross_entropy
- eval:
- 只需要输出最后一个位置的vocab_size向量,用它来预测下一token
- loss=0
1
2
3
4
5
6
7if targets is not None:
logits = self.output(h)
self.last_loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=0, reduction='none')
else:
# 推理时的小优化:只对最后一个位置的输出进行前向传播
logits = self.output(h[:, [-1], :])
self.last_loss = None- train:
GPT
预训练
CLM - 因果语言模型
虽说是单向的,但由于任务难度大,model很难偷懒,会深入理解上下文,更符合“生成”的要求
对于一个输入序列长256,期待输出序列长256的任务,模型会不断根据前 256 个 token、输入+预测出来的token……进行256次计算,最后生成⼀个序列长为512的文本
GPT1
GPT2:
增加了参数规模(训练数据、模型体量)
pre-norm
以 零样本学习 为目标:不进行微调,直接向预训练模型描述问题,即可输出
GPT3:
增加了参数规模(训练数据、模型体量)
稀疏注意力机制:无需每两个token之间都进行注意力计算
few-shot:给模型提供少量示例(一般是直接写在prompt中,3-5个),也称上下文学习
LLaMA
目前LLM的普适架构
结构与GPT基本类似
发展:支持更长文本输入,分组查询注意力机制
GLM
中文LLM,独特model架构
不同之处:
使用post-norm:先完成残差计算,再进行norm(参数正则化效果更好
GPT、LLaMA使用pre-norm,先进行norm,再进行残差计算,这样可以避免梯度爆炸或梯度消失
解释:计算归一化参数时,需注意反向传播的梯度(pre-norm:

单个线性层,而非MLP(无非线性)
激活函数GeLUs:更平滑
预训练
GLM 通用语言模型:自编码+自回归
输入序列随机遮蔽一连串token,要求model输出
model既要理解上下文 预测遮蔽部分(MLM),还要在遮蔽内部逐个预测token(CLM)
但在超大规模的预训练中,CLM优势远超于MLM,因此后续LLM重点还是放在CLM上
LLM
逐渐接替PLM预训练语言模型(预训练后续还要单独微调)
GPT-3开始
预训练+对齐(SFT、RLHF)
能力
涌现能力:相同模型架构与预训练任务的前提下,某些能力在小型模型中不明显,在大型模型中表现突出
上下文学习能力:无需进行微调,在prompt中添加几个示例(调整prompt)即可
指令遵循
逐步推理能力:CoT思维链推理策略
特点
- 多语言支持:训练数据就是多语言的
- 长文本处理:采用旋转位置编码,具有长度外推作用
- 拓展多模态:引入Adapter层(将其他模态的向量映射到LLM可接受的范围,对齐作用)和图像编码器。。。
- 幻觉:杜撰 → 在prompt中进行限制,通过RAG指导生成
预训练
LLaMA架构
CLM任务,预测句子的下一token,loss的计算是所有token
庞大的参数量和语料库
分布式训练框架:Deepspeed……
数据并行
每张GPU上运行一个模型示例,将1个batch的训练数据分配给不同GPU,计算出每张GPU的梯度
将这些梯度聚合,更新所有GPU上的模型参数,重复。
这和顺序训练并不完全相同,但这样模拟了更大的batch_size,达到了更好的效果
模型并行
当一张GPU无法存放完整的模型参数时,将不同层放到不同GPU上
……
训练数据:数据配比、处理与清洗(框架)
数据质量往往比体量更重要
处理流程:
文档准备
语料过滤:
- 基于model:训练一个分类器
- 计算语料的质量指标
语料去重:大量重复文本会显著影响模型的泛化能力
hash算法计算相似性、基于子串
Deepspeed
ZeRO零冗余优化器
优化数据并行时每张卡的显存占用,从而支持更大规模的模型
显存占用包括:
模型状态:模型参数、梯度、Adam状态参数
剩余状态
优化策略:
对Adam状态参数进行分片,每张GPU中存储1/n
对梯度进行分片
对模型参数进行分片
问题:
随着分片的增加,GPU的通讯开销也在增加,GPU利用率下降
SFT- 有监督微调
预训练得到的model能够流程的接出下文,但不知道问题的含义,无法适配下游任务
因此还是需要进行微调
本质仍是CLM任务:向下预测序列,但loss的计算只关注assistent的content部分,这也就使得model能对指令进行理解
与微调的不同之处:
- 传统预训练模型的微调需要针对具体的下游任务,不同任务需要分别进行对应的微调
- SFT 通过指令微调,使模型获得 泛化的通用指令遵循能力
数据
{指令,input,output}
{system,user,assistent}
- 多且多样
- 配比
- 高质量:人工标注数据/model生成
- 多轮对话的形式
多轮对话能力
- 构造多轮对话样本:直接要求model预测每一轮对话的assistent的content
RLHF - 人类反馈强化学习
RM - 奖励模型
拟合人类偏好
本质是文本分类模型,在传统LLM框架后面接一个分类层
将提示输入至model中,得到模型输出进行打分
但打分得到的标量奖励会放大差异,一般是对同一问题的不同回复进行排名
具体流程:
- 将prompt输入至model,得到两种输出,人类对输出进行比较,谁更优?
- 获得一个**(提示, 获胜的响应/Chosen Response, 失败的响应/Rejected Response)** 的三元组,这就RM训练所需的、最标准的原始数据格式
- 将chosen_example、rejected_example(一对好坏)分别输入reward model中得到标量奖励,模型通过最大化二者的奖励差异来计算loss,反向传播训练reward model
- 重复上述操作,获得多个三元组,进行多次训练
PPO训练 - 近端策略优化算法
组成:
LLM:
- actor(参数更新):智能体
- ref(不进行参数更新):保证actor不偏航,防止model失去之前经过pretrain、sft获得的能力
RM
- critic(参数更新):为每个位置的token预测后续输出的评分,评估生成当前token的长期价值
- reward(不进行参数更新):只评价当前的输入,即时奖励
流程:(逐token)
将prompt分别输入至 actor model、ref model,计算二者输出token的KL散度(要求actor不要偏离原始model太远,确保模型输出合理连贯的文本而不是乱码)

reward、critic分别对actor的输出token进行打分(reward输出对当前token的评分,critic输出预测从当前token到最后的累加奖励)
计算奖励,更新参数(actor、critic):


代码实现
RMSNorm
均方根norm
1 | # 这里*是逐元素乘法 |

attention
由于Q与KV的运算需求,采用kv_head时需保证KV矩阵能够正常repeat扩展成Q的形状,因此要满足head_dim%kv_head_dim == 0

位置编码-旋转嵌入

对词向量进行旋转,使得两个位置向量的内积只取决于相对位置
- 位置向量表示绝对位置信息
- 位置向量的内积表示相对位置信息

通过上式我们发现,对词向量进行旋转(*e_imθ)即可实现 内积→相对位置
先确定旋转角度(频率)
其中的t展示的是seq位置

然后计算三角函数,获得旋转嵌入的实部和虚部
1 | def precomoute_freqs_cis(dim: int, end: int , theta: float = 10000.0): |
- 将得到的实部和虚部矩阵的形状调整好(没有的维度设置为1)
1 | def reshape_for_broadcast(freqs_cis: torch.Tensor, x: torch.Tensor): |
- 旋转
实部和虚部:约定俗成将输入的最后一维进行拆分,一半作为实部,一半作为虚部
实质是一种向量旋转:x向量 和 freqs_cis向量
xq.shape[:-1]表示xq除了最后一维的所有维度。(-1, 2)表示将最后一维重新划分为两部分,其中-1表示自动推断大小,2表示最后一维的大小固定为 2。.unbind(-1):unbind(dim)是 PyTorch 的一个操作,用于沿指定维度将张量分解为多个张量。-1表示沿最后一维分解。
1 | def apply_rot_emb( |
repeat_kv
使用分组查询注意力机制,实现多个Q使用相同的k v(也就是将kv简化,减少存储)
在Q与V进行乘积计算注意力权重过程中,两个矩阵需要对齐,因此需要repeat k v
注意,head和kv_head的head_dim是相同的,它们最后一维是相同的。
1 | # n_rep = n_heads / n_kv_heads, 一个kv头重复了n_rep遍(对应了n_rep个q) |
attention
注意这里的mask:
此时得到的score是一个seq*seq的矩阵,第i行第j列是指 第i个token对第j个token的注意力,因此mask的形状这样的:第1行只保留第1列,因此第1个token只能看到自己,第2行保留1、2列,因此第2个token只能看到第1、2个token,以此类推。
1 | class Attention(nn.Module): |
MLP
拟合连续函数,注意维度变化(就是过三层线性层+dropout)
1 | class MLP(nn.Module): |
transformer
DecoderLayer
1 | class DecoderLayer(nn.Module): |
generate
循环生成每一个token
- 取出最大上下文token
- 前向传播(forward)获取最后时间的logits
- 根据logits计算索引,temperature缩放logits
- 贪心:找最大概率对应的token id
- 前k:将概率小于第k大的赋值为-inf
- softmax+multinomial得到最终id
- 合并至输入(x)中重复上述过程,继续预测下一个token id
1 | class Transformer(nn.Module): |
tokenizer
将文本分割成较小单位
- word-base:根据空格和标点进行分割
- character-base:根据字符分割,token序列太长、丢失词级别语义
- subword:比单词小,但比字符大
- BPE:在词汇表中 迭代地 合并 最频繁出现的 相邻字符对,作为一个字词
- wordpiece:合并能最大化训练数据总概率的词对
- unigram:将分词任务视为一个在给定词汇表下的概率问题
数据预处理
根据原始结构 获取 现有结构
1 | import json |
训练与测试
1 | import random |
LLM预训练
预训练数据集
1 | class PretrainDataset(Dataset): |
SFTDataset
多轮对话数据集。输入上一轮对话内容,输出当前轮对话内容
1 | class SFTDataset(Dataset): |
预训练
parse args
init:包括model创建,tokenizer加载,多GPU设置,to device等
dataset、dataloader、scaler、optimizer
scaler:GradScaler用于动态调整loss的缩放因子
由于混合精度,使用float16 时可能存不下太小的loss,导致梯度变成0,训练停滞
因此先使用scaler将loss放大,梯度放大至float16能够存下,然后计算高梯度,最后使用scaler将梯度恢复

train_epoch
学习率计算
三个阶段:预热、余弦退火、保持最小
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def lr(it, all):
warmup_iters = args.warmup_iters
lr_decay_iters = all
min_lr = args.learning_rate / 10
if it < warmup_iters:
return args.learning_rate * it / warmup_iters
if it > warmup_iters:
return min_lr
decay_ratio = (it - warmup_iters) / (lr_decay_iters - warmup_iters)
assert 0 <= decay_ratio <=1
coeff = 0.5*(1.0 + math.cos(math.pi * decay_ratio))
return min_lr + (args.learning_rate - min_lr) * coeff获取loss,scaler→optimizer(每accumulation_steps更新一次)→梯度清零 (ctx范围内的计算操作会按照底层指定的精度进行)
日志、状态保存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56def train_epoch(epoch):
start_time = time.time()
for step, (X, Y, loss_mask) in enumerate(train_loader):
X = X.to(args.device)
Y = Y.to(args.device)
loss_mask = loss_mask.to(args.device)
lr = get_lr(epoch * iter_per_epoch + step, args.epochs * iter_per_epoch)
for param_group in optimizer.param_groups:
param_group['lr'] = lr
with ctx:
out = model(X, Y)
loss = out.last_loss / args.accumulation_steps
loss_mask = loss_mask.view(-1)
loss = torch.sum(loss_mask * loss) / loss_mask.sum()
scaler.scale(loss).backward() # 将loss放大,计算放大后的梯度
if (step+1) % args.accumulation_steps == 0: # 每accumulation_steps更新一次model,模拟大batch
scaler.unscale_(optimizer) # 恢复放大的梯度
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip) # 梯度裁剪,防止梯度爆炸
scaler.step(optimizer) # 优化器步进,参数更新
scaler.update() # 更新缩放因子
optimizer.zero_grad(set_to_none=True) # 清空梯度
if step % args.log_interval == 0:
spend_time = time.time() - start_time
Logger(
f'Epoch:[{epoch+1}]/[{args.epochs}]({step}/{args.iter_per_epoch}) loss: {loss.item()*args.accumulation_steps:.3f} lr: {optimizer.param_groups[-1]['lr']} epoch_time: {spend_time / (step+1)*iter_per_epoch // 60 - spend_time // 60} min;'
)
if args.use_swanlab:
swanlab.log({
"loss" : loss.item() * args.accumulation_steps,
"lr" : optimizer.param_group[-1]['lr']
})
# 每save_interval步保存一次model
if (step+1) % args.save_interval == 0:
model.eval()
ckp = f'{args.save_dir}/pretrain_{lm_config.dim}_{lm_config.n_layers}_{lm_config.vocab_size}.path'
state_dict = model.module.state_dict() if isinstance(model, torch.nn.DataParallel) else model.state_dict()
torch.save(state_dict, ckp)
model.train()
# 每20000步保存一个带步数的检查点文件名
if (step+1) % 20000 == 0:
model.eval()
ckp = f"{args.save_dir}/pretrain_{lm_config.dim}_{lm_config.n_layers}_{lm_config.batch_size}_step{step+1}.pth"
state_dict = model.module.state_dict() if isinstance(model, torch.nn.DataParallel) else model.state_dict()
torch.save(state_dict, ckp)
model.train()
SFT训练
与预训练的不同之处在于,dataset使用SFTDataset、初始化时加载预训练model(权重)
训练时:只计算assistent content的loss(mask矩阵),对照XY相同位置的每一token计算loss,所以train时直接传入X、Y即可,也不需要将assistent content去掉
等后面model进行推理时,会直接根据现有token对下一token进行预测,然后拼接、再推理预测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69def train_epoch(epoch):
"""训练一个epoch"""
start_time = time.time()
for step, (X, Y, loss_mask) in enumerate(train_loader):
X = X.to(args.device)
Y = Y.to(args.device)
loss_mask = loss_mask.to(args.device)
# 获取学习率并更新优化器
lr = get_lr(epoch * iter_per_epoch + step, args.epochs * iter_per_epoch)
for param_group in optimizer.param_groups:
param_group['lr'] = lr
# 前向传播
with ctx:
out = model(X, Y)
loss = out.last_loss / args.accumulation_steps
loss_mask = loss_mask.view(-1)
loss = torch.sum(loss * loss_mask) / loss_mask.sum()
# 反向传播
scaler.scale(loss).backward()
# 更新权重
if (step + 1) % args.accumulation_steps == 0:
scaler.unscale_(optimizer)
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
scaler.step(optimizer)
scaler.update()
optimizer.zero_grad(set_to_none=True)
# 打印日志
if step % args.log_interval == 0:
spend_time = time.time() - start_time
Logger(
'Epoch:[{}/{}]({}/{}) loss:{:.3f} lr:{:.7f} epoch_Time:{}min:'.format(
epoch + 1,
args.epochs,
step,
iter_per_epoch,
loss.item() * args.accumulation_steps,
optimizer.param_groups[-1]['lr'],
spend_time / (step + 1) * iter_per_epoch // 60 - spend_time // 60))
if args.use_swanlab:
swanlab.log({
"loss": loss.item() * args.accumulation_steps,
"lr": optimizer.param_groups[-1]['lr']
})
# 保存模型
if (step + 1) % args.save_interval == 0:
model.eval()
ckp = f'{args.save_dir}/sft_dim{lm_config.dim}_layers{lm_config.n_layers}_vocab_size{lm_config.vocab_size}.pth'
# 处理多卡保存
state_dict = model.module.state_dict() if isinstance(model, torch.nn.DataParallel) else model.state_dict()
torch.save(state_dict, ckp)
model.train()
# 定期保存模型
if (step + 1) % 20000 == 0:
model.eval()
ckp = f'{args.save_dir}/sft_dim{lm_config.dim}_layers{lm_config.n_layers}_vocab_size{lm_config.vocab_size}_step{step+1}.pth'
state_dict = model.module.state_dict() if isinstance(model, torch.nn.DataParallel) else model.state_dict()
torch.save(state_dict, ckp)
model.train()
model的使用
1 | from contextlib import nullcontext |
1 | import torch |
- Title: happy-llm 阅读笔记
- Author: dawn_r1sing
- Created at : 2025-10-24 19:39:15
- Updated at : 2025-10-24 19:40:07
- Link: https://dawnrisingdong.github.io/2025/10/24/happy-llm-阅读笔记/
- License: This work is licensed under CC BY-NC-SA 4.0.


