add Chinese annotations pretrain
parent
dddedc6881
commit
9c23a92922
|
|
@ -4,3 +4,5 @@ __pycache__/
|
||||||
out
|
out
|
||||||
website/
|
website/
|
||||||
docs-minimind/
|
docs-minimind/
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
@ -39,6 +39,7 @@ class PretrainDataset(Dataset):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.tokenizer = tokenizer
|
self.tokenizer = tokenizer
|
||||||
self.max_length = max_length
|
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')
|
self.samples = load_dataset('json', data_files=data_path, split='train')
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
|
|
||||||
|
|
@ -54,43 +54,76 @@ class RMSNorm(torch.nn.Module):
|
||||||
self.weight = nn.Parameter(torch.ones(dim))
|
self.weight = nn.Parameter(torch.ones(dim))
|
||||||
|
|
||||||
def norm(self, x):
|
def norm(self, x):
|
||||||
|
#rsqrt是sqrt的倒数,keepdim是保持维度不变,这里是把求平均的维度位置为1
|
||||||
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps)
|
||||||
|
|
||||||
def forward(self, x):
|
def forward(self, x):
|
||||||
|
#self.weight是一个可学习的参数
|
||||||
return (self.weight * self.norm(x.float())).type_as(x)
|
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):
|
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
|
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
|
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 = (
|
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("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)
|
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:
|
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))
|
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)
|
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)
|
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)
|
freqs = freqs * (1 - ramp + ramp / factor)
|
||||||
t = torch.arange(end, device=freqs.device)
|
t = torch.arange(end, device=freqs.device)
|
||||||
|
#[end,dim//2]
|
||||||
freqs = torch.outer(t, freqs).float()
|
freqs = torch.outer(t, freqs).float()
|
||||||
|
#[end,dim]
|
||||||
freqs_cos = torch.cat([torch.cos(freqs), torch.cos(freqs)], dim=-1) * attn_factor
|
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
|
freqs_sin = torch.cat([torch.sin(freqs), torch.sin(freqs)], dim=-1) * attn_factor
|
||||||
return freqs_cos, freqs_sin
|
return freqs_cos, freqs_sin
|
||||||
|
|
||||||
def apply_rotary_pos_emb(q, k, cos, sin, unsqueeze_dim=1):
|
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)
|
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)
|
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)
|
k_embed = ((k * cos.unsqueeze(unsqueeze_dim)) + (rotate_half(k) * sin.unsqueeze(unsqueeze_dim))).to(k.dtype)
|
||||||
return q_embed, k_embed
|
return q_embed, k_embed
|
||||||
|
|
||||||
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
|
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
|
bs, slen, num_key_value_heads, head_dim = x.shape
|
||||||
if n_rep == 1: return x
|
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))
|
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):
|
class Attention(nn.Module):
|
||||||
def __init__(self, config: MiniMindConfig):
|
def __init__(self, config: MiniMindConfig):
|
||||||
super().__init__()
|
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.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_heads = config.num_attention_heads
|
||||||
self.n_local_kv_heads = self.num_key_value_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.attn_dropout = nn.Dropout(config.dropout)
|
||||||
self.resid_dropout = nn.Dropout(config.dropout)
|
self.resid_dropout = nn.Dropout(config.dropout)
|
||||||
self.dropout = config.dropout
|
self.dropout = config.dropout
|
||||||
|
#self.flash是布尔值
|
||||||
self.flash = hasattr(torch.nn.functional, 'scaled_dot_product_attention') and config.flash_attn
|
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):
|
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)
|
xk = torch.cat([past_key_value[0], xk], dim=1)
|
||||||
xv = torch.cat([past_key_value[1], xv], dim=1)
|
xv = torch.cat([past_key_value[1], xv], dim=1)
|
||||||
past_kv = (xk, xv) if use_cache else None
|
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))
|
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)):
|
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)
|
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:
|
else:
|
||||||
|
#@是矩阵乘法,*是逐元素相乘,两个张量形状必须相同
|
||||||
scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
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)
|
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
|
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 = self.attn_dropout(F.softmax(scores.float(), dim=-1).type_as(xq)) @ xv
|
||||||
output = output.transpose(1, 2).reshape(bsz, seq_len, -1)
|
output = output.transpose(1, 2).reshape(bsz, seq_len, -1)
|
||||||
|
#还要对输出结果做一个正则化
|
||||||
output = self.resid_dropout(self.o_proj(output))
|
output = self.resid_dropout(self.o_proj(output))
|
||||||
return output, past_kv
|
return output, past_kv
|
||||||
|
|
||||||
|
|
@ -155,20 +199,34 @@ class MOEFeedForward(nn.Module):
|
||||||
|
|
||||||
def forward(self, x):
|
def forward(self, x):
|
||||||
batch_size, seq_len, hidden_dim = x.shape
|
batch_size, seq_len, hidden_dim = x.shape
|
||||||
|
#[bs*seq_len,hidden_dim]
|
||||||
x_flat = x.view(-1, hidden_dim)
|
x_flat = x.view(-1, hidden_dim)
|
||||||
|
#[bs*seq_len,num_experts]
|
||||||
scores = F.softmax(self.gate(x_flat), dim=-1)
|
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)
|
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)
|
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)
|
y = torch.zeros_like(x_flat)
|
||||||
for i, expert in enumerate(self.experts):
|
for i, expert in enumerate(self.experts):
|
||||||
|
#[bs*seq_len,num_experts_per_tok]
|
||||||
mask = (topk_idx == i)
|
mask = (topk_idx == i)
|
||||||
|
#any()用来判断是否至少有一个元素为真
|
||||||
if mask.any():
|
if mask.any():
|
||||||
|
#nonzero用来返回非零元素的索引,flatten将结果展成一维向量
|
||||||
|
#[num_selected_tokens]
|
||||||
token_idx = mask.any(dim=-1).nonzero().flatten()
|
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)
|
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))
|
y.index_add_(0, token_idx, (expert(x_flat[token_idx]) * weight).to(y.dtype))
|
||||||
elif self.training:
|
elif self.training:
|
||||||
|
#某个专家没参与计算,也把它挂到计算图上
|
||||||
y[0, 0] += 0 * sum(p.sum() for p in expert.parameters())
|
y[0, 0] += 0 * sum(p.sum() for p in expert.parameters())
|
||||||
if self.training and self.config.router_aux_loss_coef > 0:
|
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)
|
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
|
self.aux_loss = (load * scores.mean(0)).sum() * self.config.num_experts * self.config.router_aux_loss_coef
|
||||||
else:
|
else:
|
||||||
|
|
@ -202,6 +260,7 @@ class MiniMindModel(nn.Module):
|
||||||
self.dropout = nn.Dropout(config.dropout)
|
self.dropout = nn.Dropout(config.dropout)
|
||||||
self.layers = nn.ModuleList([MiniMindBlock(l, config) for l in range(self.num_hidden_layers)])
|
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)
|
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)
|
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_cos", freqs_cos, persistent=False)
|
||||||
self.register_buffer("freqs_sin", freqs_sin, persistent=False)
|
self.register_buffer("freqs_sin", freqs_sin, persistent=False)
|
||||||
|
|
@ -228,6 +287,7 @@ class MiniMindModel(nn.Module):
|
||||||
)
|
)
|
||||||
presents.append(present)
|
presents.append(present)
|
||||||
hidden_states = self.norm(hidden_states)
|
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())
|
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
|
return hidden_states, presents, aux_loss
|
||||||
|
|
||||||
|
|
@ -239,7 +299,9 @@ class MiniMindForCausalLM(PreTrainedModel, GenerationMixin):
|
||||||
super().__init__(self.config)
|
super().__init__(self.config)
|
||||||
self.model = MiniMindModel(self.config)
|
self.model = MiniMindModel(self.config)
|
||||||
self.lm_head = nn.Linear(self.config.hidden_size, self.config.vocab_size, bias=False)
|
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
|
if self.config.tie_word_embeddings: self.model.embed_tokens.weight = self.lm_head.weight
|
||||||
|
#在模型所有子模块创建完成后,执行Huggingface规定的后处理初始化逻辑
|
||||||
self.post_init()
|
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):
|
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:
|
if labels is not None:
|
||||||
x, y = logits[..., :-1, :].contiguous(), labels[..., 1:].contiguous()
|
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 = 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)
|
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
|
# https://github.com/jingyaogong/minimind/discussions/611
|
||||||
|
#表示这个函数在推理模式下运行,不计算梯度
|
||||||
@torch.inference_mode()
|
@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):
|
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)
|
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
|
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)
|
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)
|
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
|
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
|
logits = outputs.logits[:, -1, :] / temperature
|
||||||
|
#重复惩罚,如果某个token已经在当前生成序列里出现过,就降低它下一次再次被选中的概率
|
||||||
if repetition_penalty != 1.0:
|
if repetition_penalty != 1.0:
|
||||||
|
#torch.unique是去重,给张量
|
||||||
for i in range(input_ids.shape[0]):
|
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)
|
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:
|
if top_k > 0:
|
||||||
logits[logits < torch.topk(logits, top_k)[0][..., -1, None]] = -float('inf')
|
logits[logits < torch.topk(logits, top_k)[0][..., -1, None]] = -float('inf')
|
||||||
|
#按概率从高到低,只保留累计概率达到top_P之前的token
|
||||||
if top_p < 1.0:
|
if top_p < 1.0:
|
||||||
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
||||||
mask = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1) > top_p
|
mask = torch.cumsum(torch.softmax(sorted_logits, dim=-1), dim=-1) > top_p
|
||||||
mask[..., 1:], mask[..., 0] = mask[..., :-1].clone(), 0
|
mask[..., 1:], mask[..., 0] = mask[..., :-1].clone(), 0
|
||||||
logits[mask.scatter(1, sorted_indices, mask)] = -float('inf')
|
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)
|
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)
|
input_ids = torch.cat([input_ids, next_token], dim=-1)
|
||||||
past_key_values = outputs.past_key_values if use_cache else None
|
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)
|
res = model(input_ids, labels=labels)
|
||||||
loss = res.loss + res.aux_loss
|
loss = res.loss + res.aux_loss
|
||||||
loss = loss / args.accumulation_steps
|
loss = loss / args.accumulation_steps
|
||||||
|
#将loss放大后再反向传播,防止fp16下梯度下溢
|
||||||
scaler.scale(loss).backward()
|
scaler.scale(loss).backward()
|
||||||
|
|
||||||
if step % args.accumulation_steps == 0:
|
if step % args.accumulation_steps == 0:
|
||||||
scaler.unscale_(optimizer)
|
scaler.unscale_(optimizer)
|
||||||
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
||||||
|
#step是让优化器更新参数,同时更新优化器内部状态
|
||||||
scaler.step(optimizer)
|
scaler.step(optimizer)
|
||||||
|
#动态调整scale值,如果scale太大,loss会出现上溢,scale太小会发生下溢
|
||||||
scaler.update()
|
scaler.update()
|
||||||
|
#把梯度全部设置为None,不用保留全0梯度的tensor,可以节省内存和加速下一次反向传播
|
||||||
optimizer.zero_grad(set_to_none=True)
|
optimizer.zero_grad(set_to_none=True)
|
||||||
|
|
||||||
if step % args.log_interval == 0 or step == iters:
|
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 = model.module if isinstance(model, DistributedDataParallel) else model
|
||||||
raw_model = getattr(raw_model, '_orig_mod', raw_model)
|
raw_model = getattr(raw_model, '_orig_mod', raw_model)
|
||||||
state_dict = raw_model.state_dict()
|
state_dict = raw_model.state_dict()
|
||||||
|
#转成fp16,减小文件大小,转到cpu,避免保存gpu tensor
|
||||||
torch.save({k: v.half().cpu() for k, v in state_dict.items()}, ckp)
|
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')
|
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()
|
model.train()
|
||||||
del state_dict
|
del state_dict
|
||||||
|
|
||||||
del input_ids, labels, res, loss
|
del input_ids, labels, res, loss
|
||||||
|
#处理还没更新的梯度
|
||||||
if last_step > start_step and last_step % args.accumulation_steps != 0:
|
if last_step > start_step and last_step % args.accumulation_steps != 0:
|
||||||
scaler.unscale_(optimizer)
|
scaler.unscale_(optimizer)
|
||||||
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
torch.nn.utils.clip_grad_norm_(model.parameters(), args.grad_clip)
|
||||||
|
|
@ -118,6 +120,7 @@ if __name__ == "__main__":
|
||||||
# ========== 3. 设置混合精度 ==========
|
# ========== 3. 设置混合精度 ==========
|
||||||
device_type = "cuda" if "cuda" in args.device else "cpu"
|
device_type = "cuda" if "cuda" in args.device else "cpu"
|
||||||
dtype = torch.bfloat16 if args.dtype == "bfloat16" else torch.float16
|
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)
|
autocast_ctx = nullcontext() if device_type == "cpu" else torch.cuda.amp.autocast(dtype=dtype)
|
||||||
|
|
||||||
# ========== 4. 配wandb ==========
|
# ========== 4. 配wandb ==========
|
||||||
|
|
@ -127,12 +130,17 @@ if __name__ == "__main__":
|
||||||
wandb_id = ckp_data.get('wandb_id') if ckp_data else None
|
wandb_id = ckp_data.get('wandb_id') if ckp_data else None
|
||||||
resume = 'must' if wandb_id 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}"
|
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)
|
wandb.init(project=args.wandb_project, name=wandb_run_name, id=wandb_id, resume=resume)
|
||||||
|
|
||||||
# ========== 5. 定义模型、数据、优化器 ==========
|
# ========== 5. 定义模型、数据、优化器 ==========
|
||||||
model, tokenizer = init_model(lm_config, args.from_weight, device=args.device)
|
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)
|
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
|
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'))
|
scaler = torch.cuda.amp.GradScaler(enabled=(args.dtype == 'float16'))
|
||||||
optimizer = optim.AdamW(model.parameters(), lr=args.learning_rate)
|
optimizer = optim.AdamW(model.parameters(), lr=args.learning_rate)
|
||||||
|
|
||||||
|
|
@ -147,6 +155,7 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# ========== 7. 编译和分布式包装 ==========
|
# ========== 7. 编译和分布式包装 ==========
|
||||||
if args.use_compile == 1:
|
if args.use_compile == 1:
|
||||||
|
#这行代码会对模型进行编译,生成一个优化过的版本,优化过程包括算子融合、生成更高效的底层代码,让模型跑的更快
|
||||||
model = torch.compile(model)
|
model = torch.compile(model)
|
||||||
Logger('torch.compile enabled')
|
Logger('torch.compile enabled')
|
||||||
if dist.is_initialized():
|
if dist.is_initialized():
|
||||||
|
|
@ -154,10 +163,17 @@ if __name__ == "__main__":
|
||||||
|
|
||||||
# ========== 8. 开始训练 ==========
|
# ========== 8. 开始训练 ==========
|
||||||
for epoch in range(start_epoch, args.epochs):
|
for epoch in range(start_epoch, args.epochs):
|
||||||
|
#set_epoch是在每个epoch重新设置数据集的采样顺序,会根据epoch的索引,重新设置随机数种子
|
||||||
|
#使每个epoch产生不同的随机顺序,从而确保每个epoch用不同的样本顺序进行训练
|
||||||
train_sampler and train_sampler.set_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()
|
setup_seed(42 + epoch); indices = torch.randperm(len(train_ds)).tolist()
|
||||||
skip = start_step if (epoch == start_epoch and start_step > 0) else 0
|
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 = 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)
|
loader = DataLoader(train_ds, batch_sampler=batch_sampler, num_workers=args.num_workers, pin_memory=True)
|
||||||
if skip > 0:
|
if skip > 0:
|
||||||
Logger(f'Epoch [{epoch + 1}/{args.epochs}]: 跳过前{start_step}个step,从step {start_step + 1}开始')
|
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,
|
vocab_size=vocab_size,
|
||||||
show_progress=True,
|
show_progress=True,
|
||||||
initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
|
initial_alphabet=pre_tokenizers.ByteLevel.alphabet(),
|
||||||
|
#BpeTrainer会优先给special_token分配id,而且一般是按special_tokens列表顺序分配
|
||||||
special_tokens=all_special_tokens
|
special_tokens=all_special_tokens
|
||||||
)
|
)
|
||||||
texts = get_texts(data_path)
|
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)
|
idx = tokenizer.token_to_id(token)
|
||||||
added_tokens_decoder[str(idx)] = {
|
added_tokens_decoder[str(idx)] = {
|
||||||
"content": token,
|
"content": token,
|
||||||
|
#lstrip表示匹配这个token时,是否把它左边的空白字符一起吃掉
|
||||||
"lstrip": False,
|
"lstrip": False,
|
||||||
|
#表示这个token按原始字符串精确匹配,不被normalizer改动
|
||||||
"normalized": False,
|
"normalized": False,
|
||||||
|
#是否把右边的字符吃掉
|
||||||
"rstrip": False,
|
"rstrip": False,
|
||||||
|
#这个token是否只能作为一个完整单词匹配
|
||||||
"single_word": False,
|
"single_word": False,
|
||||||
|
#被tokenizer的special token逻辑特殊处理
|
||||||
"special": True if token in special_tokens_list else False
|
"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):
|
def get_model_params(model, config):
|
||||||
total = sum(p.numel() for p in model.parameters()) / 1e6
|
total = sum(p.numel() for p in model.parameters()) / 1e6
|
||||||
|
#getattr用于动态获取对象的属性或方法
|
||||||
n_routed = getattr(config, 'n_routed_experts', getattr(config, 'num_experts', 0))
|
n_routed = getattr(config, 'n_routed_experts', getattr(config, 'num_experts', 0))
|
||||||
n_active = getattr(config, 'num_experts_per_tok', 0)
|
n_active = getattr(config, 'num_experts_per_tok', 0)
|
||||||
n_shared = getattr(config, 'n_shared_experts', 0)
|
n_shared = getattr(config, 'n_shared_experts', 0)
|
||||||
|
|
@ -52,13 +53,13 @@ def init_distributed_mode():
|
||||||
|
|
||||||
|
|
||||||
def setup_seed(seed: int):
|
def setup_seed(seed: int):
|
||||||
random.seed(seed)
|
random.seed(seed)#设置python的随机种子
|
||||||
np.random.seed(seed)
|
np.random.seed(seed)#设置numpy的随机种子
|
||||||
torch.manual_seed(seed)
|
torch.manual_seed(seed)#设置cpu随机种子
|
||||||
torch.cuda.manual_seed(seed)
|
torch.cuda.manual_seed(seed)#设置pytorch在当前GPU上的随机种子
|
||||||
torch.cuda.manual_seed_all(seed)
|
torch.cuda.manual_seed_all(seed)#设置pytorch在所有GPU上的随机种子
|
||||||
torch.backends.cudnn.deterministic = True
|
torch.backends.cudnn.deterministic = True#使用确定性算法
|
||||||
torch.backends.cudnn.benchmark = False
|
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):
|
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)
|
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'
|
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'
|
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 = model.module if isinstance(model, DistributedDataParallel) else model
|
||||||
raw_model = getattr(raw_model, '_orig_mod', raw_model)
|
raw_model = getattr(raw_model, '_orig_mod', raw_model)
|
||||||
state_dict = raw_model.state_dict()
|
state_dict = raw_model.state_dict()
|
||||||
|
#half表示转成float16,然后移到cpu上
|
||||||
state_dict = {k: v.half().cpu() for k, v in state_dict.items()}
|
state_dict = {k: v.half().cpu() for k, v in state_dict.items()}
|
||||||
ckp_tmp = ckp_path + '.tmp'
|
ckp_tmp = ckp_path + '.tmp'
|
||||||
torch.save(state_dict, ckp_tmp)
|
torch.save(state_dict, ckp_tmp)
|
||||||
|
#os.replace用于将一个文件替换成另一个文件 os.replace(src, dst) 将 src 文件替换为 dst 文件,如果 dst 已经存在,则会被替换掉。这是一个原子操作,确保在替换过程中不会出现文件损坏或丢失的情况。
|
||||||
os.replace(ckp_tmp, ckp_path)
|
os.replace(ckp_tmp, ckp_path)
|
||||||
wandb_id = None
|
wandb_id = None
|
||||||
if wandb:
|
if wandb:
|
||||||
|
|
@ -87,6 +91,7 @@ def lm_checkpoint(lm_config, weight='full_sft', model=None, optimizer=None, epoc
|
||||||
'optimizer': optimizer.state_dict(),
|
'optimizer': optimizer.state_dict(),
|
||||||
'epoch': epoch,
|
'epoch': epoch,
|
||||||
'step': step,
|
'step': step,
|
||||||
|
#训练时用了几张GPU
|
||||||
'world_size': dist.get_world_size() if dist.is_initialized() else 1,
|
'world_size': dist.get_world_size() if dist.is_initialized() else 1,
|
||||||
'wandb_id': wandb_id
|
'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'
|
resume_tmp = resume_path + '.tmp'
|
||||||
torch.save(resume_data, resume_tmp)
|
torch.save(resume_data, resume_tmp)
|
||||||
os.replace(resume_tmp, resume_path)
|
os.replace(resume_tmp, resume_path)
|
||||||
|
#删除临时变量,然后清理cuda缓存
|
||||||
del state_dict, resume_data
|
del state_dict, resume_data
|
||||||
torch.cuda.empty_cache()
|
torch.cuda.empty_cache()
|
||||||
else: # 加载模式
|
else: # 加载模式
|
||||||
if os.path.exists(resume_path):
|
if os.path.exists(resume_path):
|
||||||
|
#加载到gpu
|
||||||
ckp_data = torch.load(resume_path, map_location='cpu')
|
ckp_data = torch.load(resume_path, map_location='cpu')
|
||||||
saved_ws = ckp_data.get('world_size', 1)
|
saved_ws = ckp_data.get('world_size', 1)
|
||||||
current_ws = dist.get_world_size() if dist.is_initialized() else 1
|
current_ws = dist.get_world_size() if dist.is_initialized() else 1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue