add Chinese annotations pretrain
parent
dddedc6881
commit
9c23a92922
|
|
@ -4,3 +4,5 @@ __pycache__/
|
|||
out
|
||||
website/
|
||||
docs-minimind/
|
||||
.idea/
|
||||
.vscode/
|
||||
|
|
@ -39,6 +39,7 @@ class PretrainDataset(Dataset):
|
|||
super().__init__()
|
||||
self.tokenizer = tokenizer
|
||||
self.max_length = max_length
|
||||
#load_dataset是HuggingFace datasets库上用来加载数据集的函数,可以加载HuggingFace Hub上的数据,也可以加载本地的json/csv/txt等文件
|
||||
self.samples = load_dataset('json', data_files=data_path, split='train')
|
||||
|
||||
def __len__(self):
|
||||
|
|
|
|||
|
|
@ -54,43 +54,76 @@ class RMSNorm(torch.nn.Module):
|
|||
self.weight = nn.Parameter(torch.ones(dim))
|
||||
|
||||
def norm(self, x):
|
||||
#rsqrt是sqrt的倒数,keepdim是保持维度不变,这里是把求平均的维度位置为1
|
||||
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
||||
|
||||
def forward(self, x):
|
||||
#self.weight是一个可学习的参数
|
||||
return (self.weight * self.norm(x.float())).type_as(x)
|
||||
|
||||
def precompute_freqs_cis(dim: int, end: int = int(32 * 1024), rope_base: float = 1e6, rope_scaling: dict = None):
|
||||
freqs, attn_factor = 1.0 / (rope_base ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)), 1.0
|
||||
|
||||
"""
|
||||
把原本只能支持较短上下文的RoPE,平滑地扩展到更长上下文
|
||||
普通rope的频率是f,位置m的旋转角度是mf,如果上下文长度很长,m很大,角度会转的太快,模型泛化不好
|
||||
YaRN的做法是对一部分频率进行缩放,让他们转的慢一点
|
||||
一部分频率保持原样,保留短上下文能力
|
||||
一部分频率平滑过渡
|
||||
一部分频率缩小,用于支持长上下文
|
||||
"""
|
||||
if rope_scaling is not None: # YaRN: f'(i) = f(i)((1-γ) + γ/s), where γ∈[0,1] is linear ramp
|
||||
"""
|
||||
orig_max是原模型训练时支持的最大位置长度
|
||||
factor是上下文扩展倍数
|
||||
beta_fast和beat_slow用来决定哪些维度的频率保持不变
|
||||
attn_factir是乘在cos/sin上的attention的缩放因子
|
||||
"""
|
||||
orig_max, factor, beta_fast, beta_slow, attn_factor = (
|
||||
rope_scaling.get("original_max_position_embeddings", 2048), rope_scaling.get("factor", 16),
|
||||
rope_scaling.get("beta_fast", 32.0), rope_scaling.get("beta_slow", 1.0), rope_scaling.get("attention_factor", 1.0)
|
||||
)
|
||||
#只有当你实际预计算的长度end超过原始最大长度时,才需要做rope scaling
|
||||
if end / orig_max > 1.0:
|
||||
#inv_dim推理公式,用beta,也就是这里的b控制周期和原始上下文长度之间的关系
|
||||
inv_dim = lambda b: (dim * math.log(orig_max / (b * 2 * math.pi))) / (2 * math.log(rope_base))
|
||||
#low,high是两个分界点
|
||||
#0到low的区间,ramp=0,频率不变,low到high的区间,ramp从0平滑加到1,high到dim//2的区间,ramp=1,频率除以factor
|
||||
low, high = max(math.floor(inv_dim(beta_fast)), 0), min(math.ceil(inv_dim(beta_slow)), dim // 2 - 1)
|
||||
#torch.clamp是裁剪或限制张量中元素的数值范围,torch.clamp(input,min,max),不满足范围内的用最大最小值代替
|
||||
ramp = torch.clamp((torch.arange(dim // 2, device=freqs.device).float() - low) / max(high - low, 0.001), 0, 1)
|
||||
freqs = freqs * (1 - ramp + ramp / factor)
|
||||
t = torch.arange(end, device=freqs.device)
|
||||
#[end,dim//2]
|
||||
freqs = torch.outer(t, freqs).float()
|
||||
#[end,dim]
|
||||
freqs_cos = torch.cat([torch.cos(freqs), torch.cos(freqs)], dim=-1) * attn_factor
|
||||
freqs_sin = torch.cat([torch.sin(freqs), torch.sin(freqs)], dim=-1) * attn_factor
|
||||
return freqs_cos, freqs_sin
|
||||
|
||||
def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
|
||||
|
||||
# rope作用在qk上,传统的正弦位置编码一般加在token embedding上,不用作用在v上,因为注意力分数计算只用qk
|
||||
#常见的qk shape是[baych,num_heads,seq_len,head_dim]要对头和seq_len做一个维度交换以适应硬件要求
|
||||
#...表示省略前面所有的维度,前面的维度都按原样保留
|
||||
def rotate_half(x): return torch.cat((-x[..., x.shape[-1] // 2:], x[..., : x.shape[-1] // 2]), dim=-1)
|
||||
q_embed = ((q * cos.unsqueeze(unsqueeze_dim)) + (rotate_half(q) * sin.unsqueeze(unsqueeze_dim))).to(q.dtype)
|
||||
k_embed = ((k * cos.unsqueeze(unsqueeze_dim)) + (rotate_half(k) * sin.unsqueeze(unsqueeze_dim))).to(k.dtype)
|
||||
return q_embed, k_embed
|
||||
|
||||
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
|
||||
"""
|
||||
用于GQA和MQA attention 把k/v heads复制成和query_heads一样多,方便后面做attention计算
|
||||
"""
|
||||
bs, slen, num_key_value_heads, head_dim = x.shape
|
||||
if n_rep == 1: return x
|
||||
#None是在这里插入一个新维度,expand通常不真的复制维度,只是创建一个广播视图,所以更省内存
|
||||
return (x[:, :, :, None, :].expand(bs, slen, num_key_value_heads, n_rep, head_dim).reshape(bs, slen, num_key_value_heads * n_rep, head_dim))
|
||||
|
||||
class Attention(nn.Module):
|
||||
def __init__(self, config: MiniMindConfig):
|
||||
super().__init__()
|
||||
#qk用几个头
|
||||
self.num_key_value_heads = config.num_attention_heads if config.num_key_value_heads is None else config.num_key_value_heads
|
||||
self.n_local_heads = config.num_attention_heads
|
||||
self.n_local_kv_heads = self.num_key_value_heads
|
||||
|
|
@ -106,6 +139,7 @@ class Attention(nn.Module):
|
|||
self.attn_dropout = nn.Dropout(config.dropout)
|
||||
self.resid_dropout = nn.Dropout(config.dropout)
|
||||
self.dropout = config.dropout
|
||||
#self.flash是布尔值
|
||||
self.flash = hasattr(torch.nn.functional, 'scaled_dot_product_attention') and config.flash_attn
|
||||
|
||||
def forward(self, x, position_embeddings, past_key_value=None, use_cache=False, attention_mask=None):
|
||||
|
|
@ -121,15 +155,25 @@ class Attention(nn.Module):
|
|||
xk = torch.cat([past_key_value[0], xk], dim=1)
|
||||
xv = torch.cat([past_key_value[1], xv], dim=1)
|
||||
past_kv = (xk, xv) if use_cache else None
|
||||
#这段是在判断,当前情况能不能走Pytorch中内置的高效attention实现,scaled_dot_product_attention
|
||||
xq, xk, xv = (xq.transpose(1, 2), repeat_kv(xk, self.n_rep).transpose(1, 2), repeat_kv(xv, self.n_rep).transpose(1, 2))
|
||||
if self.flash and (seq_len > 1) and (not self.is_causal or past_key_value is None) and (attention_mask is None or torch.all(attention_mask == 1)):
|
||||
output = F.scaled_dot_product_attention(xq, xk, xv, dropout_p=self.dropout if self.training else 0.0, is_causal=self.is_causal)
|
||||
else:
|
||||
#@是矩阵乘法,*是逐元素相乘,两个张量形状必须相同
|
||||
scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
||||
#torch.full是生成一个形状指定,且所有元素都填充为同一个固定值的张量
|
||||
#triu是pytorch中用于提取矩阵上三角的函数,diagonal参数=0时,保留主对角线及以上的元素,>0时保留主对角线上方的第N条对角线及以上的元素,<0时保留主对角线下方及以下的元素,其余用0替代
|
||||
#triu只对张量的后两个维度进行操作
|
||||
if self.is_causal: scores[:, :, :, -seq_len:] += torch.full((seq_len, seq_len), float("-inf"), device=scores.device).triu(1)
|
||||
#casual mask和attention_mask管的是两件事
|
||||
#casual_mask是阻止当前位置看到未来token,attention_mask是告诉模型哪些是有效的,哪些token不应该看见
|
||||
#attention_mask是在padding时忽略padding部分的注意力分数
|
||||
if attention_mask is not None: scores += (1.0 - attention_mask.unsqueeze(1).unsqueeze(2)) * -1e9
|
||||
#对注意力分数做一个正则化
|
||||
output = self.attn_dropout(F.softmax(scores.float(), dim=-1).type_as(xq)) @ xv
|
||||
output = output.transpose(1, 2).reshape(bsz, seq_len, -1)
|
||||
#还要对输出结果做一个正则化
|
||||
output = self.resid_dropout(self.o_proj(output))
|
||||
return output, past_kv
|
||||
|
||||
|
|
@ -155,20 +199,34 @@ class MOEFeedForward(nn.Module):
|
|||
|
||||
def forward(self, x):
|
||||
batch_size, seq_len, hidden_dim = x.shape
|
||||
#[bs*seq_len,hidden_dim]
|
||||
x_flat = x.view(-1, hidden_dim)
|
||||
#[bs*seq_len,num_experts]
|
||||
scores = F.softmax(self.gate(x_flat), dim=-1)
|
||||
#[bs*seq_len,num_experts_per_tok]
|
||||
topk_weight, topk_idx = torch.topk(scores, k=self.config.num_experts_per_tok, dim=-1, sorted=False)
|
||||
if self.config.norm_topk_prob: topk_weight = topk_weight / (topk_weight.sum(dim=-1, keepdim=True) + 1e-20)
|
||||
#[bs*seq_len,hidden_dim]
|
||||
y = torch.zeros_like(x_flat)
|
||||
for i, expert in enumerate(self.experts):
|
||||
#[bs*seq_len,num_experts_per_tok]
|
||||
mask = (topk_idx == i)
|
||||
#any()用来判断是否至少有一个元素为真
|
||||
if mask.any():
|
||||
#nonzero用来返回非零元素的索引,flatten将结果展成一维向量
|
||||
#[num_selected_tokens]
|
||||
token_idx = mask.any(dim=-1).nonzero().flatten()
|
||||
#[num_selected_tokens,1],这里mask是一个和top_weight同维度的布尔矩阵,会把top_weight中和mask矩阵位置相同且为True的部分提取出来
|
||||
weight = topk_weight[mask].view(-1, 1)
|
||||
#x_flat[token_idx]->[num_selected_tokens,hidden_dim]
|
||||
#y[token_idx] += expert_output,index_add_(dim,index,source)
|
||||
y.index_add_(0, token_idx, (expert(x_flat[token_idx]) * weight).to(y.dtype))
|
||||
elif self.training:
|
||||
#某个专家没参与计算,也把它挂到计算图上
|
||||
y[0, 0] += 0 * sum(p.sum() for p in expert.parameters())
|
||||
if self.training and self.config.router_aux_loss_coef > 0:
|
||||
#F.one_hot(topk_idx, self.config.num_experts)->[bs*seq_len,num_experts_per_tok,num_experts],会把原来的每一个数字变成一组one-hot向量
|
||||
#做mean(0)后,会在第一个维度做平均,[num_experts_per_tok,num_experts]
|
||||
load = F.one_hot(topk_idx, self.config.num_experts).float().mean(0)
|
||||
self.aux_loss = (load * scores.mean(0)).sum() * self.config.num_experts * self.config.router_aux_loss_coef
|
||||
else:
|
||||
|
|
@ -202,6 +260,7 @@ class MiniMindModel(nn.Module):
|
|||
self.dropout = nn.Dropout(config.dropout)
|
||||
self.layers = nn.ModuleList([MiniMindBlock(l, config) for l in range(self.num_hidden_layers)])
|
||||
self.norm = RMSNorm(config.hidden_size, eps=config.rms_norm_eps)
|
||||
#[max_position_embeddings,head_dim]
|
||||
freqs_cos, freqs_sin = precompute_freqs_cis(dim=config.head_dim, end=config.max_position_embeddings, rope_base=config.rope_theta, rope_scaling=config.rope_scaling)
|
||||
self.register_buffer("freqs_cos", freqs_cos, persistent=False)
|
||||
self.register_buffer("freqs_sin", freqs_sin, persistent=False)
|
||||
|
|
@ -228,6 +287,7 @@ class MiniMindModel(nn.Module):
|
|||
)
|
||||
presents.append(present)
|
||||
hidden_states = self.norm(hidden_states)
|
||||
#sum方法中第二个参数为初始值,new_zeros创建与当前张量同类型,同设备的新零张量。里面的参数是形状,设置一个初值0
|
||||
aux_loss = sum([l.mlp.aux_loss for l in self.layers if isinstance(l.mlp, MOEFeedForward)], hidden_states.new_zeros(1).squeeze())
|
||||
return hidden_states, presents, aux_loss
|
||||
|
||||
|
|
@ -239,7 +299,9 @@ class MiniMindForCausalLM(PreTrainedModel, GenerationMixin):
|
|||
super().__init__(self.config)
|
||||
self.model = MiniMindModel(self.config)
|
||||
self.lm_head = nn.Linear(self.config.hidden_size, self.config.vocab_size, bias=False)
|
||||
#nn.Embedding的权重矩阵存储是(input_dim,output_dim),nn.Linear的权重矩阵存储是转秩过后的
|
||||
if self.config.tie_word_embeddings: self.model.embed_tokens.weight = self.lm_head.weight
|
||||
#在模型所有子模块创建完成后,执行Huggingface规定的后处理初始化逻辑
|
||||
self.post_init()
|
||||
|
||||
def forward(self, input_ids, attention_mask=None, past_key_values=None, use_cache=False, logits_to_keep=0, labels=None, **kwargs):
|
||||
|
|
@ -250,11 +312,14 @@ class MiniMindForCausalLM(PreTrainedModel, GenerationMixin):
|
|||
if labels is not None:
|
||||
x, y = logits[..., :-1, :].contiguous(), labels[..., 1:].contiguous()
|
||||
loss = F.cross_entropy(x.view(-1, x.size(-1)), y.view(-1), ignore_index=-100)
|
||||
#把loss,aux_loss,logits等封装成一个标准输出对象,方便训练,推理,以后可以用字典的方式访问
|
||||
return MoeCausalLMOutputWithPast(loss=loss, aux_loss=aux_loss, logits=logits, past_key_values=past_key_values, hidden_states=hidden_states)
|
||||
|
||||
# https://github.com/jingyaogong/minimind/discussions/611
|
||||
#表示这个函数在推理模式下运行,不计算梯度
|
||||
@torch.inference_mode()
|
||||
def generate(self, inputs=None, attention_mask=None, max_new_tokens=8192, temperature=0.85, top_p=0.85, top_k=50, eos_token_id=2, streamer=None, use_cache=True, num_return_sequences=1, do_sample=True, repetition_penalty=1.0, **kwargs):
|
||||
#这里的repeat是向量复制,这里是在第0维复制num_return_sequences次,第1维复制1次,也就是不变
|
||||
input_ids = kwargs.pop("input_ids", inputs).repeat(num_return_sequences, 1)
|
||||
attention_mask = attention_mask.repeat(num_return_sequences, 1) if attention_mask is not None else None
|
||||
past_key_values = kwargs.pop("past_key_values", None)
|
||||
|
|
@ -265,17 +330,21 @@ class MiniMindForCausalLM(PreTrainedModel, GenerationMixin):
|
|||
outputs = self.forward(input_ids[:, past_len:], attention_mask, past_key_values, use_cache=use_cache, **kwargs)
|
||||
attention_mask = torch.cat([attention_mask, attention_mask.new_ones(attention_mask.shape[0], 1)], -1) if attention_mask is not None else None
|
||||
logits = outputs.logits[:, -1, :] / temperature
|
||||
#重复惩罚,如果某个token已经在当前生成序列里出现过,就降低它下一次再次被选中的概率
|
||||
if repetition_penalty != 1.0:
|
||||
#torch.unique是去重,给张量
|
||||
for i in range(input_ids.shape[0]):
|
||||
seen = torch.unique(input_ids[i]); score = logits[i, seen]; logits[i, seen] = torch.where(score > 0, score / repetition_penalty, score * repetition_penalty)
|
||||
if top_k > 0:
|
||||
logits[logits < torch.topk(logits, top_k)[0][..., -1, None]] = -float('inf')
|
||||
#按概率从高到低,只保留累计概率达到top_P之前的token
|
||||
if top_p < 1.0:
|
||||
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
||||
mask = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1) > top_p
|
||||
mask[..., 1:], mask[..., 0] = mask[..., :-1].clone(), 0
|
||||
logits[mask.scatter(1, sorted_indices, mask)] = -float('inf')
|
||||
next_token = torch.multinomial(torch.softmax(logits, dim=-1), num_samples=1) if do_sample else torch.argmax(logits, dim=-1, keepdim=True)
|
||||
next_token = torch.multinomial(torch.softmax(logits, dim=-1),num_samples=1) if do_sample else torch.argmax(logits, dim=-1, keepdim=True)
|
||||
#torch.where是pytorch的条件选择函数,类似三元运算符torch.where(condition,x,y)
|
||||
if eos_token_id is not None: next_token = torch.where(finished.unsqueeze(-1), next_token.new_full((next_token.shape[0], 1), eos_token_id), next_token)
|
||||
input_ids = torch.cat([input_ids, next_token], dim=-1)
|
||||
past_key_values = outputs.past_key_values if use_cache else None
|
||||
|
|
|
|||
|
|
@ -35,16 +35,17 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
|||
res = model(input_ids, labels=labels)
|
||||
loss = res.loss + res.aux_loss
|
||||
loss = loss / args.accumulation_steps
|
||||
|
||||
#将loss放大后再反向传播,防止fp16下梯度下溢
|
||||
scaler.scale(loss).backward()
|
||||
|
||||
if step % args.accumulation_steps == 0:
|
||||
scaler.unscale_(optimizer)
|
||||
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
||||
|
||||
#step是让优化器更新参数,同时更新优化器内部状态
|
||||
scaler.step(optimizer)
|
||||
#动态调整scale值,如果scale太大,loss会出现上溢,scale太小会发生下溢
|
||||
scaler.update()
|
||||
|
||||
#把梯度全部设置为None,不用保留全0梯度的tensor,可以节省内存和加速下一次反向传播
|
||||
optimizer.zero_grad(set_to_none=True)
|
||||
|
||||
if step % args.log_interval == 0 or step == iters:
|
||||
|
|
@ -64,13 +65,14 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
|||
raw_model = model.module if isinstance(model, DistributedDataParallel) else model
|
||||
raw_model = getattr(raw_model, '_orig_mod', raw_model)
|
||||
state_dict = raw_model.state_dict()
|
||||
#转成fp16,减小文件大小,转到cpu,避免保存gpu tensor
|
||||
torch.save({k: v.half().cpu() for k, v in state_dict.items()}, ckp)
|
||||
lm_checkpoint(lm_config, weight=args.save_weight, model=model, optimizer=optimizer, scaler=scaler, epoch=epoch, step=step, wandb=wandb, save_dir='../checkpoints')
|
||||
model.train()
|
||||
del state_dict
|
||||
|
||||
del input_ids, labels, res, loss
|
||||
|
||||
#处理还没更新的梯度
|
||||
if last_step > start_step and last_step % args.accumulation_steps != 0:
|
||||
scaler.unscale_(optimizer)
|
||||
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
||||
|
|
@ -118,6 +120,7 @@ if __name__ == "__main__":
|
|||
# ========== 3. 设置混合精度 ==========
|
||||
device_type = "cuda" if "cuda" in args.device else "cpu"
|
||||
dtype = torch.bfloat16 if args.dtype == "bfloat16" else torch.float16
|
||||
#根据设备自动选择是否转半精度。cuda自动混精度加速
|
||||
autocast_ctx = nullcontext() if device_type == "cpu" else torch.cuda.amp.autocast(dtype=dtype)
|
||||
|
||||
# ========== 4. 配wandb ==========
|
||||
|
|
@ -127,12 +130,17 @@ if __name__ == "__main__":
|
|||
wandb_id = ckp_data.get('wandb_id') if ckp_data else None
|
||||
resume = 'must' if wandb_id else None
|
||||
wandb_run_name = f"MiniMind-Pretrain-Epoch-{args.epochs}-BatchSize-{args.batch_size}-LearningRate-{args.learning_rate}"
|
||||
#project是实验的项目名称,id是指定或恢复一个实验运行的唯一标识符,resume是当提供的id存在时是否继续运行('must'表示必须继续,None表示不继续)。如果提供了wandb_id并且resume设置为'must',则会尝试恢复之前的运行,否则会创建一个新的运行。
|
||||
#id等于none时会认为正在启动一个全新的实验运行
|
||||
wandb.init(project=args.wandb_project, name=wandb_run_name, id=wandb_id, resume=resume)
|
||||
|
||||
# ========== 5. 定义模型、数据、优化器 ==========
|
||||
model, tokenizer = init_model(lm_config, args.from_weight, device=args.device)
|
||||
train_ds = PretrainDataset(args.data_path, tokenizer, max_length=args.max_seq_len)
|
||||
#当处于分布式环境时,实例化DistributedSampler会将数据集切分成若干份,确保每个gpu只读取它自己那一部分数据
|
||||
train_sampler = DistributedSampler(train_ds) if dist.is_initialized() else None
|
||||
#梯度缩放器,在用fp16计算时,可能会出现梯度下溢现象,GradScaler会将loss乘一个很大的数,从而放大梯度,在优化器更新权重之前,再把放大的梯度恢复到原本的大小
|
||||
#当dtype=float16时启动
|
||||
scaler = torch.cuda.amp.GradScaler(enabled=(args.dtype == 'float16'))
|
||||
optimizer = optim.AdamW(model.parameters(), lr=args.learning_rate)
|
||||
|
||||
|
|
@ -147,6 +155,7 @@ if __name__ == "__main__":
|
|||
|
||||
# ========== 7. 编译和分布式包装 ==========
|
||||
if args.use_compile == 1:
|
||||
#这行代码会对模型进行编译,生成一个优化过的版本,优化过程包括算子融合、生成更高效的底层代码,让模型跑的更快
|
||||
model = torch.compile(model)
|
||||
Logger('torch.compile enabled')
|
||||
if dist.is_initialized():
|
||||
|
|
@ -154,10 +163,17 @@ if __name__ == "__main__":
|
|||
|
||||
# ========== 8. 开始训练 ==========
|
||||
for epoch in range(start_epoch, args.epochs):
|
||||
#set_epoch是在每个epoch重新设置数据集的采样顺序,会根据epoch的索引,重新设置随机数种子
|
||||
#使每个epoch产生不同的随机顺序,从而确保每个epoch用不同的样本顺序进行训练
|
||||
train_sampler and train_sampler.set_epoch(epoch)
|
||||
#代码中的分号;是python中的语法特性,可以用来在同一行中执行多个语句
|
||||
#torch.randperm是在生成一个随机打乱后的数据索引列表,会生成从0到len(train_ds)的随机排列,然后再转为list
|
||||
setup_seed(42 + epoch); indices = torch.randperm(len(train_ds)).tolist()
|
||||
skip = start_step if (epoch == start_epoch and start_step > 0) else 0
|
||||
#dataset负责给它一个idx,返回一条数据,Sampler负责决定按上面顺序给dataset提供idx,所以sampler本质上就是一个索引生成器
|
||||
batch_sampler = SkipBatchSampler(train_sampler or indices, args.batch_size, skip)
|
||||
#这里传的是batch_sampler,dataloader不再自己决定batch怎么组成,而是听sampler的
|
||||
#根据sampler提供的索引来取数据
|
||||
loader = DataLoader(train_ds, batch_sampler=batch_sampler, num_workers=args.num_workers, pin_memory=True)
|
||||
if skip > 0:
|
||||
Logger(f'Epoch [{epoch + 1}/{args.epochs}]: 跳过前{start_step}个step,从step {start_step + 1}开始')
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ def train_tokenizer(data_path, tokenizer_dir, vocab_size, special_tokens_num=SPE
|
|||
vocab_size=vocab_size,
|
||||
show_progress=True,
|
||||
initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
|
||||
#BpeTrainer会优先给special_token分配id,而且一般是按special_tokens列表顺序分配
|
||||
special_tokens=all_special_tokens
|
||||
)
|
||||
texts = get_texts(data_path)
|
||||
|
|
@ -68,10 +69,15 @@ def train_tokenizer(data_path, tokenizer_dir, vocab_size, special_tokens_num=SPE
|
|||
idx = tokenizer.token_to_id(token)
|
||||
added_tokens_decoder[str(idx)] = {
|
||||
"content": token,
|
||||
#lstrip表示匹配这个token时,是否把它左边的空白字符一起吃掉
|
||||
"lstrip": False,
|
||||
#表示这个token按原始字符串精确匹配,不被normalizer改动
|
||||
"normalized": False,
|
||||
#是否把右边的字符吃掉
|
||||
"rstrip": False,
|
||||
#这个token是否只能作为一个完整单词匹配
|
||||
"single_word": False,
|
||||
#被tokenizer的special token逻辑特殊处理
|
||||
"special": True if token in special_tokens_list else False
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from model.model_minimind import MiniMindForCausalLM
|
|||
|
||||
def get_model_params(model, config):
|
||||
total = sum(p.numel() for p in model.parameters()) / 1e6
|
||||
#getattr用于动态获取对象的属性或方法
|
||||
n_routed = getattr(config, 'n_routed_experts', getattr(config, 'num_experts', 0))
|
||||
n_active = getattr(config, 'num_experts_per_tok', 0)
|
||||
n_shared = getattr(config, 'n_shared_experts', 0)
|
||||
|
|
@ -52,13 +53,13 @@ def init_distributed_mode():
|
|||
|
||||
|
||||
def setup_seed(seed: int):
|
||||
random.seed(seed)
|
||||
np.random.seed(seed)
|
||||
torch.manual_seed(seed)
|
||||
torch.cuda.manual_seed(seed)
|
||||
torch.cuda.manual_seed_all(seed)
|
||||
torch.backends.cudnn.deterministic = True
|
||||
torch.backends.cudnn.benchmark = False
|
||||
random.seed(seed)#设置python的随机种子
|
||||
np.random.seed(seed)#设置numpy的随机种子
|
||||
torch.manual_seed(seed)#设置cpu随机种子
|
||||
torch.cuda.manual_seed(seed)#设置pytorch在当前GPU上的随机种子
|
||||
torch.cuda.manual_seed_all(seed)#设置pytorch在所有GPU上的随机种子
|
||||
torch.backends.cudnn.deterministic = True#使用确定性算法
|
||||
torch.backends.cudnn.benchmark = False#禁用cudnn的自动优化
|
||||
|
||||
def lm_checkpoint(lm_config, weight='full_sft', model=None, optimizer=None, epoch=0, step=0, wandb=None, save_dir='../checkpoints', **kwargs):
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
|
|
@ -66,13 +67,16 @@ def lm_checkpoint(lm_config, weight='full_sft', model=None, optimizer=None, epoc
|
|||
ckp_path = f'{save_dir}/{weight}_{lm_config.hidden_size}{moe_path}.pth'
|
||||
resume_path = f'{save_dir}/{weight}_{lm_config.hidden_size}{moe_path}_resume.pth'
|
||||
|
||||
if model is not None:
|
||||
if model is not None:#存储模型模式
|
||||
#如果模型被DDP包了,真正的模型在model.module里
|
||||
raw_model = model.module if isinstance(model, DistributedDataParallel) else model
|
||||
raw_model = getattr(raw_model, '_orig_mod', raw_model)
|
||||
state_dict = raw_model.state_dict()
|
||||
#half表示转成float16,然后移到cpu上
|
||||
state_dict = {k: v.half().cpu() for k, v in state_dict.items()}
|
||||
ckp_tmp = ckp_path + '.tmp'
|
||||
torch.save(state_dict, ckp_tmp)
|
||||
#os.replace用于将一个文件替换成另一个文件 os.replace(src, dst) 将 src 文件替换为 dst 文件,如果 dst 已经存在,则会被替换掉。这是一个原子操作,确保在替换过程中不会出现文件损坏或丢失的情况。
|
||||
os.replace(ckp_tmp, ckp_path)
|
||||
wandb_id = None
|
||||
if wandb:
|
||||
|
|
@ -87,6 +91,7 @@ def lm_checkpoint(lm_config, weight='full_sft', model=None, optimizer=None, epoc
|
|||
'optimizer': optimizer.state_dict(),
|
||||
'epoch': epoch,
|
||||
'step': step,
|
||||
#训练时用了几张GPU
|
||||
'world_size': dist.get_world_size() if dist.is_initialized() else 1,
|
||||
'wandb_id': wandb_id
|
||||
}
|
||||
|
|
@ -102,10 +107,12 @@ def lm_checkpoint(lm_config, weight='full_sft', model=None, optimizer=None, epoc
|
|||
resume_tmp = resume_path + '.tmp'
|
||||
torch.save(resume_data, resume_tmp)
|
||||
os.replace(resume_tmp, resume_path)
|
||||
#删除临时变量,然后清理cuda缓存
|
||||
del state_dict, resume_data
|
||||
torch.cuda.empty_cache()
|
||||
else: # 加载模式
|
||||
if os.path.exists(resume_path):
|
||||
#加载到gpu
|
||||
ckp_data = torch.load(resume_path, map_location='cpu')
|
||||
saved_ws = ckp_data.get('world_size', 1)
|
||||
current_ws = dist.get_world_size() if dist.is_initialized() else 1
|
||||
|
|
|
|||
Loading…
Reference in New Issue