[update] sync minimind
parent
d2bdff3269
commit
326b24727e
|
|
@ -23,13 +23,13 @@ class VLMDataset(Dataset):
|
|||
self.max_length = max_length
|
||||
self.preprocess = preprocess
|
||||
self.image_token = image_special_token
|
||||
self.bos_id = tokenizer('<|im_start|>assistant', add_special_tokens=False).input_ids
|
||||
self.eos_id = tokenizer('<|im_end|>', add_special_tokens=False).input_ids
|
||||
self.bos_id = tokenizer(f'{tokenizer.bos_token}assistant\n', add_special_tokens=False).input_ids
|
||||
self.eos_id = tokenizer(f'{tokenizer.eos_token}\n', add_special_tokens=False).input_ids
|
||||
|
||||
def __len__(self):
|
||||
return len(self.table)
|
||||
|
||||
def _create_chat_prompt(self, conversations):
|
||||
def create_chat_prompt(self, conversations):
|
||||
messages = []
|
||||
for i, turn in enumerate(conversations):
|
||||
role = 'user' if i % 2 == 0 else 'assistant'
|
||||
|
|
@ -40,8 +40,8 @@ class VLMDataset(Dataset):
|
|||
add_generation_prompt=False
|
||||
)
|
||||
|
||||
def _generate_loss_mask(self, input_ids):
|
||||
loss_mask = [0] * len(input_ids)
|
||||
def generate_labels(self, input_ids):
|
||||
labels = [-100] * len(input_ids)
|
||||
i = 0
|
||||
while i < len(input_ids):
|
||||
if input_ids[i:i + len(self.bos_id)] == self.bos_id:
|
||||
|
|
@ -51,30 +51,31 @@ class VLMDataset(Dataset):
|
|||
if input_ids[end:end + len(self.eos_id)] == self.eos_id:
|
||||
break
|
||||
end += 1
|
||||
for j in range(start + 1, min(end + len(self.eos_id) + 1, self.max_length)):
|
||||
loss_mask[j] = 1
|
||||
for j in range(start, min(end + len(self.eos_id), self.max_length)):
|
||||
labels[j] = input_ids[j]
|
||||
i = end + len(self.eos_id) if end < len(input_ids) else len(input_ids)
|
||||
else:
|
||||
i += 1
|
||||
return loss_mask
|
||||
return labels
|
||||
|
||||
def __getitem__(self, index: int):
|
||||
conversations = json.loads(self.table['conversations'][index].as_py())
|
||||
image_bytes = self.table['image_bytes'][index].as_py()
|
||||
|
||||
prompt = self._create_chat_prompt(conversations)
|
||||
prompt = self.create_chat_prompt(conversations)
|
||||
input_ids = self.tokenizer(prompt).input_ids[:self.max_length]
|
||||
input_ids += [self.tokenizer.pad_token_id] * (self.max_length - len(input_ids))
|
||||
loss_mask = self._generate_loss_mask(input_ids)
|
||||
|
||||
X = torch.tensor(input_ids[:-1], dtype=torch.long)
|
||||
Y = torch.tensor(input_ids[1:], dtype=torch.long)
|
||||
loss_mask = torch.tensor(loss_mask[1:], dtype=torch.long)
|
||||
labels = self.generate_labels(input_ids)
|
||||
|
||||
image = Image.open(io.BytesIO(image_bytes))
|
||||
image_tensor = MiniMindVLM.image2tensor(image, self.preprocess).unsqueeze(0)
|
||||
# # === 调试打印 ===
|
||||
# print(f"\n--- Sample {index} ---")
|
||||
# for i, (x, y) in enumerate(zip(input_ids[:-1], labels[1:])):
|
||||
# print(f"{i:3d}: X={self.tokenizer.decode([x])!r:16s} ---> Y={self.tokenizer.decode([input_ids[i+1]])!r:16s} label={y}")
|
||||
# # ================
|
||||
|
||||
return X, Y, loss_mask, image_tensor
|
||||
return torch.tensor(input_ids, dtype=torch.long), torch.tensor(labels, dtype=torch.long), image_tensor
|
||||
|
||||
# 测试parquet数据读取和可视化
|
||||
if __name__ == '__main__':
|
||||
|
|
|
|||
17
eval_vlm.py
17
eval_vlm.py
|
|
@ -1,3 +1,4 @@
|
|||
import time
|
||||
import argparse
|
||||
import os
|
||||
import warnings
|
||||
|
|
@ -5,7 +6,7 @@ import torch
|
|||
from PIL import Image
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM, TextStreamer
|
||||
from model.model_vlm import MiniMindVLM, VLMConfig
|
||||
from trainer.trainer_utils import setup_seed
|
||||
from trainer.trainer_utils import setup_seed, get_model_params
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
def init_model(args):
|
||||
|
|
@ -22,8 +23,7 @@ def init_model(args):
|
|||
else:
|
||||
model = AutoModelForCausalLM.from_pretrained(args.load_from, trust_remote_code=True)
|
||||
model.vision_encoder, model.processor = MiniMindVLM.get_vision_model("./model/vision_model/clip-vit-base-patch16")
|
||||
|
||||
print(f'VLM模型参数: {sum(p.numel() for p in model.parameters() if p.requires_grad) / 1e6:.2f} M(illion)')
|
||||
get_model_params(model, model.params)
|
||||
preprocess = model.processor
|
||||
return model.eval().to(args.device), tokenizer, preprocess
|
||||
|
||||
|
|
@ -40,6 +40,7 @@ def main():
|
|||
parser.add_argument('--temperature', default=0.65, type=float, help="生成温度,控制随机性(0-1,越大越随机)")
|
||||
parser.add_argument('--top_p', default=0.85, type=float, help="nucleus采样阈值(0-1)")
|
||||
parser.add_argument('--image_dir', default='./dataset/eval_images/', type=str, help="测试图像目录")
|
||||
parser.add_argument('--show_speed', default=1, type=int, help="显示decode速度(tokens/s)")
|
||||
parser.add_argument('--device', default='cuda' if torch.cuda.is_available() else 'cpu', type=str, help="运行设备")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
@ -59,15 +60,17 @@ def main():
|
|||
inputs = tokenizer(inputs_text, return_tensors="pt", truncation=True).to(args.device)
|
||||
|
||||
print(f'[图像]: {image_file}')
|
||||
print(f'👶: {prompt.replace('\n', '\\n')}')
|
||||
print('🤖️: ', end='')
|
||||
model.generate(
|
||||
print(f'💬: {prompt.replace('\n', '\\n')}')
|
||||
print('🤖: ', end='')
|
||||
st = time.time()
|
||||
generated_ids = model.generate(
|
||||
inputs=inputs["input_ids"], attention_mask=inputs["attention_mask"],
|
||||
max_new_tokens=args.max_new_tokens, do_sample=True, streamer=streamer,
|
||||
pad_token_id=tokenizer.pad_token_id, eos_token_id=tokenizer.eos_token_id,
|
||||
top_p=args.top_p, temperature=args.temperature, pixel_values=pixel_values
|
||||
)
|
||||
print('\n\n')
|
||||
gen_tokens = len(generated_ids[0]) - len(inputs["input_ids"][0])
|
||||
print(f'\n[Speed]: {gen_tokens / (time.time() - st):.2f} tokens/s\n\n') if args.show_speed else print('\n\n')
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class Attention(nn.Module):
|
|||
xv = xv.view(bsz, seq_len, self.n_local_kv_heads, self.head_dim)
|
||||
|
||||
cos, sin = position_embeddings
|
||||
xq, xk = apply_rotary_pos_emb(xq, xk, cos[:seq_len], sin[:seq_len])
|
||||
xq, xk = apply_rotary_pos_emb(xq, xk, cos, sin)
|
||||
|
||||
# kv_cache实现
|
||||
if past_key_value is not None:
|
||||
|
|
@ -193,14 +193,11 @@ class Attention(nn.Module):
|
|||
repeat_kv(xv, self.n_rep).transpose(1, 2)
|
||||
)
|
||||
|
||||
if self.flash and seq_len > 1 and (attention_mask is None or torch.all(attention_mask == 1)):
|
||||
if self.flash and (seq_len > 1) and (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=True)
|
||||
else:
|
||||
scores = (xq @ xk.transpose(-2, -1)) / math.sqrt(self.head_dim)
|
||||
scores = scores + torch.triu(
|
||||
torch.full((seq_len, seq_len), float("-inf"), device=scores.device),
|
||||
diagonal=1
|
||||
).unsqueeze(0).unsqueeze(0) # scores+mask
|
||||
scores[:, :, :, -seq_len:] += torch.triu(torch.full((seq_len, seq_len), float("-inf"), device=scores.device), diagonal=1)
|
||||
|
||||
if attention_mask is not None:
|
||||
extended_attention_mask = attention_mask.unsqueeze(1).unsqueeze(2)
|
||||
|
|
@ -284,7 +281,7 @@ class MoEGate(nn.Module):
|
|||
fi = ce * self.n_routed_experts
|
||||
aux_loss = (Pi * fi).sum() * self.alpha
|
||||
else:
|
||||
aux_loss = 0
|
||||
aux_loss = scores.new_zeros(1).squeeze()
|
||||
return topk_idx, topk_weight, aux_loss
|
||||
|
||||
|
||||
|
|
@ -313,9 +310,11 @@ class MOEFeedForward(nn.Module):
|
|||
flat_topk_idx = topk_idx.view(-1)
|
||||
if self.training:
|
||||
x = x.repeat_interleave(self.config.num_experts_per_tok, dim=0)
|
||||
y = torch.empty_like(x, dtype=torch.float16)
|
||||
y = torch.empty_like(x, dtype=x.dtype)
|
||||
for i, expert in enumerate(self.experts):
|
||||
y[flat_topk_idx == i] = expert(x[flat_topk_idx == i]).to(y.dtype) # 确保类型一致
|
||||
expert_out = expert(x[flat_topk_idx == i])
|
||||
if expert_out.shape[0] > 0: y[flat_topk_idx == i] = expert_out.to(y.dtype)
|
||||
else: y[flat_topk_idx == i] = expert_out.to(y.dtype) + 0 * sum(p.sum() for p in expert.parameters())
|
||||
y = (y.view(*topk_weight.shape, -1) * topk_weight.unsqueeze(-1)).sum(dim=1)
|
||||
y = y.view(*orig_shape)
|
||||
else:
|
||||
|
|
@ -421,12 +420,7 @@ class MiniMindModel(nn.Module):
|
|||
|
||||
hidden_states = self.norm(hidden_states)
|
||||
|
||||
aux_loss = sum(
|
||||
layer.mlp.aux_loss
|
||||
for layer in self.layers
|
||||
if isinstance(layer.mlp, MOEFeedForward)
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
|
@ -443,6 +437,7 @@ class MiniMindForCausalLM(PreTrainedModel, GenerationMixin):
|
|||
def forward(self,
|
||||
input_ids: Optional[torch.Tensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
labels: Optional[torch.Tensor] = None,
|
||||
past_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = None,
|
||||
use_cache: bool = False,
|
||||
logits_to_keep: Union[int, torch.Tensor] = 0,
|
||||
|
|
@ -456,6 +451,13 @@ class MiniMindForCausalLM(PreTrainedModel, GenerationMixin):
|
|||
)
|
||||
slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
||||
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
||||
output = CausalLMOutputWithPast(logits=logits, past_key_values=past_key_values, hidden_states=hidden_states)
|
||||
|
||||
loss = None
|
||||
if labels is not None:
|
||||
shift_logits = logits[..., :-1, :].contiguous()
|
||||
shift_labels = labels[..., 1:].contiguous()
|
||||
loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1), ignore_index=-100)
|
||||
|
||||
output = CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=past_key_values, hidden_states=hidden_states)
|
||||
output.aux_loss = aux_loss
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class MiniMindVLM(MiniMindForCausalLM):
|
|||
def forward(self,
|
||||
input_ids: Optional[torch.Tensor] = None,
|
||||
attention_mask: Optional[torch.Tensor] = None,
|
||||
labels: Optional[torch.Tensor] = None,
|
||||
past_key_values: Optional[List[Tuple[torch.Tensor, torch.Tensor]]] = None,
|
||||
use_cache: bool = False,
|
||||
logits_to_keep: Union[int, torch.Tensor] = 0,
|
||||
|
|
@ -155,13 +156,16 @@ class MiniMindVLM(MiniMindForCausalLM):
|
|||
|
||||
hidden_states = self.model.norm(hidden_states)
|
||||
|
||||
aux_loss = sum(
|
||||
layer.mlp.aux_loss
|
||||
for layer in self.model.layers
|
||||
if isinstance(layer.mlp, MOEFeedForward)
|
||||
)
|
||||
aux_loss = sum([l.mlp.aux_loss for l in self.model.layers if isinstance(l.mlp, MOEFeedForward)], hidden_states.new_zeros(1).squeeze())
|
||||
slice_indices = slice(-logits_to_keep, None) if isinstance(logits_to_keep, int) else logits_to_keep
|
||||
logits = self.lm_head(hidden_states[:, slice_indices, :])
|
||||
output = CausalLMOutputWithPast(logits=logits, past_key_values=presents, hidden_states=hidden_states)
|
||||
|
||||
loss = None
|
||||
if labels is not None:
|
||||
shift_logits = logits[..., :-1, :].contiguous()
|
||||
shift_labels = labels[..., 1:].contiguous()
|
||||
loss = F.cross_entropy(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1), ignore_index=-100)
|
||||
|
||||
output = CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=presents, hidden_states=hidden_states)
|
||||
output.aux_loss = aux_loss
|
||||
return output
|
||||
|
|
|
|||
|
|
@ -22,26 +22,18 @@ warnings.filterwarnings('ignore')
|
|||
|
||||
|
||||
def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
||||
loss_fct = nn.CrossEntropyLoss(reduction='none')
|
||||
start_time = time.time()
|
||||
for step, (X, Y, loss_mask, pixel_values) in enumerate(loader, start=start_step + 1):
|
||||
X = X.to(args.device)
|
||||
Y = Y.to(args.device)
|
||||
loss_mask = loss_mask.to(args.device)
|
||||
for step, (input_ids, labels, pixel_values) in enumerate(loader, start=start_step + 1):
|
||||
input_ids = input_ids.to(args.device)
|
||||
labels = labels.to(args.device)
|
||||
pixel_values = pixel_values.to(args.device)
|
||||
lr = get_lr(epoch * iters + step, args.epochs * iters, args.learning_rate)
|
||||
for param_group in optimizer.param_groups:
|
||||
param_group['lr'] = lr
|
||||
|
||||
with autocast_ctx:
|
||||
res = model(X, pixel_values=pixel_values)
|
||||
loss = loss_fct(
|
||||
res.logits.view(-1, res.logits.size(-1)),
|
||||
Y.view(-1)
|
||||
).view(Y.size())
|
||||
|
||||
loss = (loss * loss_mask).sum() / loss_mask.sum()
|
||||
loss += res.aux_loss
|
||||
res = model(input_ids, labels=labels, pixel_values=pixel_values)
|
||||
loss = res.loss + res.aux_loss
|
||||
loss = loss / args.accumulation_steps
|
||||
|
||||
scaler.scale(loss).backward()
|
||||
|
|
@ -58,21 +50,20 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
|||
if step % args.log_interval == 0 or step == iters - 1:
|
||||
spend_time = time.time() - start_time
|
||||
current_loss = loss.item() * args.accumulation_steps
|
||||
current_aux_loss = res.aux_loss.item() if res.aux_loss is not None else 0.0
|
||||
current_logits_loss = current_loss - current_aux_loss
|
||||
current_lr = optimizer.param_groups[-1]['lr']
|
||||
eta_min = spend_time / (step + 1) * iters // 60 - spend_time // 60
|
||||
|
||||
Logger(f'Epoch:[{epoch+1}/{args.epochs}]({step}/{iters}) loss:{current_loss:.6f} lr:{current_lr:.12f} epoch_Time:{eta_min}min:')
|
||||
|
||||
if wandb: wandb.log({"loss": current_loss, "lr": current_lr, "epoch_Time": eta_min})
|
||||
Logger(f'Epoch:[{epoch + 1}/{args.epochs}]({step}/{iters}), loss: {current_loss:.4f}, logits_loss: {current_logits_loss:.4f}, aux_loss: {current_aux_loss:.4f}, lr: {current_lr:.8f}, epoch_time: {eta_min:.1f}min')
|
||||
if wandb: wandb.log({"loss": current_loss, "logits_loss": current_logits_loss, "aux_loss": current_aux_loss, "learning_rate": current_lr, "epoch_time": eta_min})
|
||||
|
||||
if (step % args.save_interval == 0 or step == iters - 1) and is_main_process():
|
||||
model.eval()
|
||||
moe_suffix = '_moe' if vlm_config.use_moe else ''
|
||||
ckp = f'{args.save_dir}/{args.save_weight}_{vlm_config.hidden_size}{moe_suffix}.pth'
|
||||
if isinstance(model, torch.nn.parallel.DistributedDataParallel):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
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()
|
||||
clean_state_dict = {
|
||||
key: value for key, value in state_dict.items() if not key.startswith('vision_encoder.')
|
||||
}
|
||||
|
|
@ -83,7 +74,7 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
|||
model.train()
|
||||
del state_dict, clean_state_dict
|
||||
|
||||
del X, Y, loss_mask, pixel_values, res, loss
|
||||
del input_ids, labels, pixel_values, res, loss
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
@ -108,6 +99,7 @@ if __name__ == "__main__":
|
|||
parser.add_argument('--from_weight', default='llm', type=str, help="基于哪个权重训练,为none则不基于任何权重训练")
|
||||
parser.add_argument('--from_resume', default=0, type=int, choices=[0, 1], help="是否自动检测&续训(0=否,1=是)")
|
||||
parser.add_argument('--freeze_llm', default=1, type=int, choices=[0, 1], help="是否冻结LLM参数(0=否,1=是,仅训练vision_proj)")
|
||||
parser.add_argument("--use_compile", default=0, type=int, choices=[0, 1], help="是否使用torch.compile加速(0=否,1=是)")
|
||||
parser.add_argument("--use_wandb", action="store_true", help="是否使用wandb")
|
||||
parser.add_argument("--wandb_project", type=str, default="MiniMind-V-Pretrain", help="wandb项目名")
|
||||
args = parser.parse_args()
|
||||
|
|
@ -138,8 +130,10 @@ if __name__ == "__main__":
|
|||
wandb.init(project=args.wandb_project, name=wandb_run_name, id=wandb_id, resume=resume)
|
||||
|
||||
# ========== 5. 定义模型、数据、优化器 ==========
|
||||
model, tokenizer, preprocess = init_vlm_model(vlm_config, from_weight=args.from_weight,
|
||||
device=args.device, freeze_llm=bool(args.freeze_llm))
|
||||
model, tokenizer, preprocess = init_vlm_model(vlm_config, from_weight=args.from_weight, device=args.device, freeze_llm=bool(args.freeze_llm))
|
||||
if args.use_compile == 1:
|
||||
model = torch.compile(model)
|
||||
Logger('torch.compile enabled')
|
||||
train_ds = VLMDataset(args.data_path, tokenizer, preprocess=preprocess,
|
||||
image_special_token=vlm_config.image_special_token,
|
||||
max_length=vlm_config.max_seq_len)
|
||||
|
|
@ -158,7 +152,7 @@ if __name__ == "__main__":
|
|||
|
||||
# ========== 7. DDP包模型 ==========
|
||||
if dist.is_initialized():
|
||||
model._ddp_params_and_buffers_to_ignore = {"pos_cis"}
|
||||
model._ddp_params_and_buffers_to_ignore = {"freqs_cos", "freqs_sin"}
|
||||
model = DistributedDataParallel(model, device_ids=[local_rank])
|
||||
|
||||
# ========== 8. 开始训练 ==========
|
||||
|
|
@ -172,3 +166,6 @@ if __name__ == "__main__":
|
|||
else: # 默认从头开始
|
||||
loader = DataLoader(train_ds, batch_size=args.batch_size, shuffle=(train_sampler is None), sampler=train_sampler, num_workers=args.num_workers, pin_memory=True)
|
||||
train_epoch(epoch, loader, len(loader), 0, wandb)
|
||||
|
||||
# ========== 9. 清理分布进程 ==========
|
||||
if dist.is_initialized(): dist.destroy_process_group()
|
||||
|
|
|
|||
|
|
@ -22,26 +22,18 @@ warnings.filterwarnings('ignore')
|
|||
|
||||
|
||||
def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
||||
loss_fct = nn.CrossEntropyLoss(reduction='none')
|
||||
start_time = time.time()
|
||||
for step, (X, Y, loss_mask, pixel_values) in enumerate(loader, start=start_step + 1):
|
||||
X = X.to(args.device)
|
||||
Y = Y.to(args.device)
|
||||
loss_mask = loss_mask.to(args.device)
|
||||
for step, (input_ids, labels, pixel_values) in enumerate(loader, start=start_step + 1):
|
||||
input_ids = input_ids.to(args.device)
|
||||
labels = labels.to(args.device)
|
||||
pixel_values = pixel_values.to(args.device)
|
||||
lr = get_lr(epoch * iters + step, args.epochs * iters, args.learning_rate)
|
||||
for param_group in optimizer.param_groups:
|
||||
param_group['lr'] = lr
|
||||
|
||||
with autocast_ctx:
|
||||
res = model(X, pixel_values=pixel_values)
|
||||
loss = loss_fct(
|
||||
res.logits.view(-1, res.logits.size(-1)),
|
||||
Y.view(-1)
|
||||
).view(Y.size())
|
||||
|
||||
loss = (loss * loss_mask).sum() / loss_mask.sum()
|
||||
loss += res.aux_loss
|
||||
res = model(input_ids, labels=labels, pixel_values=pixel_values)
|
||||
loss = res.loss + res.aux_loss
|
||||
loss = loss / args.accumulation_steps
|
||||
|
||||
scaler.scale(loss).backward()
|
||||
|
|
@ -58,21 +50,20 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
|||
if step % args.log_interval == 0 or step == iters - 1:
|
||||
spend_time = time.time() - start_time
|
||||
current_loss = loss.item() * args.accumulation_steps
|
||||
current_aux_loss = res.aux_loss.item() if res.aux_loss is not None else 0.0
|
||||
current_logits_loss = current_loss - current_aux_loss
|
||||
current_lr = optimizer.param_groups[-1]['lr']
|
||||
eta_min = spend_time / (step + 1) * iters // 60 - spend_time // 60
|
||||
|
||||
Logger(f'Epoch:[{epoch+1}/{args.epochs}]({step}/{iters}) loss:{current_loss:.6f} lr:{current_lr:.12f} epoch_Time:{eta_min}min:')
|
||||
|
||||
if wandb: wandb.log({"loss": current_loss, "lr": current_lr, "epoch_Time": eta_min})
|
||||
Logger(f'Epoch:[{epoch + 1}/{args.epochs}]({step}/{iters}), loss: {current_loss:.4f}, logits_loss: {current_logits_loss:.4f}, aux_loss: {current_aux_loss:.4f}, lr: {current_lr:.8f}, epoch_time: {eta_min:.1f}min')
|
||||
if wandb: wandb.log({"loss": current_loss, "logits_loss": current_logits_loss, "aux_loss": current_aux_loss, "learning_rate": current_lr, "epoch_time": eta_min})
|
||||
|
||||
if (step % args.save_interval == 0 or step == iters - 1) and is_main_process():
|
||||
model.eval()
|
||||
moe_suffix = '_moe' if vlm_config.use_moe else ''
|
||||
ckp = f'{args.save_dir}/{args.save_weight}_{vlm_config.hidden_size}{moe_suffix}.pth'
|
||||
if isinstance(model, torch.nn.parallel.DistributedDataParallel):
|
||||
state_dict = model.module.state_dict()
|
||||
else:
|
||||
state_dict = model.state_dict()
|
||||
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()
|
||||
clean_state_dict = {
|
||||
key: value for key, value in state_dict.items() if not key.startswith('vision_encoder.')
|
||||
}
|
||||
|
|
@ -83,7 +74,7 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None):
|
|||
model.train()
|
||||
del state_dict, clean_state_dict
|
||||
|
||||
del X, Y, loss_mask, pixel_values, res, loss
|
||||
del input_ids, labels, pixel_values, res, loss
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
@ -107,6 +98,7 @@ if __name__ == "__main__":
|
|||
parser.add_argument("--data_path", type=str, default="../dataset/sft_data.parquet", help="训练数据路径")
|
||||
parser.add_argument('--from_weight', default='pretrain_vlm', type=str, help="基于哪个权重训练,为none则不基于任何权重训练")
|
||||
parser.add_argument('--from_resume', default=0, type=int, choices=[0, 1], help="是否自动检测&续训(0=否,1=是)")
|
||||
parser.add_argument("--use_compile", default=0, type=int, choices=[0, 1], help="是否使用torch.compile加速(0=否,1=是)")
|
||||
parser.add_argument("--use_wandb", action="store_true", help="是否使用wandb")
|
||||
parser.add_argument("--wandb_project", type=str, default="MiniMind-V-SFT", help="wandb项目名")
|
||||
args = parser.parse_args()
|
||||
|
|
@ -137,8 +129,10 @@ if __name__ == "__main__":
|
|||
wandb.init(project=args.wandb_project, name=wandb_run_name, id=wandb_id, resume=resume)
|
||||
|
||||
# ========== 5. 定义模型、数据、优化器 ==========
|
||||
model, tokenizer, preprocess = init_vlm_model(vlm_config, from_weight=args.from_weight,
|
||||
device=args.device)
|
||||
model, tokenizer, preprocess = init_vlm_model(vlm_config, from_weight=args.from_weight, device=args.device)
|
||||
if args.use_compile == 1:
|
||||
model = torch.compile(model)
|
||||
Logger('torch.compile enabled')
|
||||
train_ds = VLMDataset(args.data_path, tokenizer, preprocess=preprocess,
|
||||
image_special_token=vlm_config.image_special_token,
|
||||
max_length=vlm_config.max_seq_len)
|
||||
|
|
@ -157,7 +151,7 @@ if __name__ == "__main__":
|
|||
|
||||
# ========== 7. DDP包模型 ==========
|
||||
if dist.is_initialized():
|
||||
model._ddp_params_and_buffers_to_ignore = {"pos_cis"}
|
||||
model._ddp_params_and_buffers_to_ignore = {"freqs_cos", "freqs_sin"}
|
||||
model = DistributedDataParallel(model, device_ids=[local_rank])
|
||||
|
||||
# ========== 8. 开始训练 ==========
|
||||
|
|
@ -171,3 +165,6 @@ if __name__ == "__main__":
|
|||
else: # 默认从头开始
|
||||
loader = DataLoader(train_ds, batch_size=args.batch_size, shuffle=(train_sampler is None), sampler=train_sampler, num_workers=args.num_workers, pin_memory=True)
|
||||
train_epoch(epoch, loader, len(loader), 0, wandb)
|
||||
|
||||
# ========== 9. 清理分布进程 ==========
|
||||
if dist.is_initialized(): dist.destroy_process_group()
|
||||
|
|
|
|||
|
|
@ -2,16 +2,32 @@
|
|||
训练工具函数集合
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
__package__ = "trainer"
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
||||
import random
|
||||
import math
|
||||
import gc
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
from torch.nn.parallel import DistributedDataParallel
|
||||
from torch.utils.data import Sampler
|
||||
from transformers import AutoTokenizer
|
||||
from model.model_vlm import MiniMindVLM
|
||||
|
||||
|
||||
|
||||
def get_model_params(model, config, ignore_patterns=['vision_encoder']):
|
||||
def should_count(n): return not any(p in n for p in ignore_patterns)
|
||||
total = sum(p.numel() for n, p in model.named_parameters() if should_count(n)) / 1e6
|
||||
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)
|
||||
expert = sum(p.numel() for n, p in model.named_parameters() if 'mlp.experts.0.' in n and should_count(n)) / 1e6
|
||||
shared_expert = sum(p.numel() for n, p in model.named_parameters() if 'mlp.shared_experts.0.' in n and should_count(n)) / 1e6
|
||||
base = total - (expert * n_routed) - (shared_expert * n_shared)
|
||||
active = base + (expert * n_active) + (shared_expert * n_shared)
|
||||
if active < total: Logger(f'Model Params: {total:.2f}M-A{active:.2f}M')
|
||||
else: Logger(f'Model Params: {total:.2f}M')
|
||||
|
||||
|
||||
def is_main_process():
|
||||
|
|
@ -24,7 +40,7 @@ def Logger(content):
|
|||
|
||||
|
||||
def get_lr(current_step, total_steps, lr):
|
||||
return lr / 10 + 0.5 * lr * (1 + math.cos(math.pi * current_step / total_steps))
|
||||
return lr*(0.1 + 0.45*(1 + math.cos(math.pi * current_step / total_steps)))
|
||||
|
||||
|
||||
def init_distributed_mode():
|
||||
|
|
@ -70,8 +86,9 @@ def init_vlm_model(vlm_config, from_weight='pretrain_vlm', tokenizer_path='../mo
|
|||
for name, param in model.model.named_parameters():
|
||||
if f'layers.{last_layer_idx}.' in name:
|
||||
param.requires_grad = True
|
||||
|
||||
Logger(f'所加载VLM Model可训练参数:{sum(p.numel() for p in model.parameters() if p.requires_grad) / 1e6:.3f} 百万')
|
||||
|
||||
get_model_params(model, vlm_config)
|
||||
Logger(f'Trainable Params: {sum(p.numel() for p in model.parameters() if p.requires_grad) / 1e6:.3f}M')
|
||||
preprocess = model.processor
|
||||
return model.to(device), tokenizer, preprocess
|
||||
|
||||
|
|
@ -83,8 +100,9 @@ def vlm_checkpoint(vlm_config, weight='pretrain_vlm', model=None, optimizer=None
|
|||
resume_path = f'{save_dir}/{weight}_{vlm_config.hidden_size}{moe_path}_resume.pth'
|
||||
|
||||
if model is not None:
|
||||
from torch.nn.parallel import DistributedDataParallel
|
||||
state_dict = model.module.state_dict() if isinstance(model, DistributedDataParallel) else model.state_dict()
|
||||
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()
|
||||
# 移除vision_encoder参数(不需要保存,因为是预训练的)
|
||||
clean_state_dict = {k: v for k, v in state_dict.items() if not k.startswith('vision_encoder.')}
|
||||
ckp_tmp = ckp_path + '.tmp'
|
||||
|
|
@ -110,10 +128,9 @@ def vlm_checkpoint(vlm_config, weight='pretrain_vlm', model=None, optimizer=None
|
|||
for key, value in kwargs.items():
|
||||
if value is not None:
|
||||
if hasattr(value, 'state_dict'):
|
||||
if isinstance(value, DistributedDataParallel):
|
||||
resume_data[key] = value.module.state_dict()
|
||||
else:
|
||||
resume_data[key] = value.state_dict()
|
||||
raw_value = value.module if isinstance(value, DistributedDataParallel) else value
|
||||
raw_value = getattr(raw_value, '_orig_mod', raw_value)
|
||||
resume_data[key] = raw_value.state_dict()
|
||||
else:
|
||||
resume_data[key] = value
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue