diff --git a/README.md b/README.md index 2316c33..8973fd8 100644 --- a/README.md +++ b/README.md @@ -1580,13 +1580,28 @@ DPO和在线PPO的区别在于reject和chosen都是离线准备的,和minimind ## Ⅳ RoPE长度外推 MiniMind支持通过YaRN算法进行RoPE位置编码的长度外推,使模型能够处理超出训练长度的文本序列。 -在使用`eval_llm.py`进行推理时,只需添加`--inference_rope_scaling`参数即可启用RoPE外推: + +原生torch模型在使用`eval_llm.py`进行推理时,只需添加`--inference_rope_scaling`参数即可启用RoPE外推: ```bash python eval_llm.py --weight full_sft --inference_rope_scaling ``` -下图展示了在不同文本「西游记」白话文小说长度下,使用RoPE scaling前后的困惑度(PPL)对比。可以看出,启用RoPE scaling后,模型在长文本上的表现显著提升: +对于Transformers格式的模型,可以在config.json中添加以下配置实现长度外推: + +```json +"rope_scaling": { + "type": "yarn", + "factor": 16.0, + "original_max_position_embeddings": 2048, + "beta_fast": 32.0, + "beta_slow": 1.0, + "attention_factor": 1.0 +} +``` + +在MiniMind-Small模型上,测试输入不同长度的「西游记」白话文小说,评估RoPE scaling前后的困惑度(PPL)对比。 +可以看出,启用YaRN外推后,模型在长文本上的PPL表现显著下降:
diff --git a/README_en.md b/README_en.md index d4e2d3d..8c492b8 100644 --- a/README_en.md +++ b/README_en.md @@ -1547,13 +1547,32 @@ Personal subjective evaluation basically aligns with DeepSeek-R1, where: ## Ⅳ RoPE Long-text Extrapolation MiniMind supports RoPE position encoding length extrapolation through YaRN algorithm, enabling models to handle text sequences exceeding training length. -When using `eval_llm.py` for inference, just add `--inference_rope_scaling` parameter to enable RoPE extrapolation: + +For native torch models, when using `eval_llm.py` for inference, just add `--inference_rope_scaling` parameter to enable RoPE extrapolation: ```bash python eval_llm.py --weight full_sft --inference_rope_scaling ``` -The chart below shows perplexity (PPL) comparison before and after RoPE scaling on different lengths of "Journey to the West" vernacular fiction text. You can see that after enabling RoPE scaling, model performance on long texts is significantly improved. +For Transformers format models, add the following configuration to config.json to enable length extrapolation: + +```json +"rope_scaling": { + "type": "yarn", + "factor": 16.0, + "original_max_position_embeddings": 2048, + "beta_fast": 32.0, + "beta_slow": 1.0, + "attention_factor": 1.0 +} +``` + +Testing on MiniMind-Small model with different lengths of "Journey to the West" vernacular fiction text to evaluate perplexity (PPL) comparison before and after RoPE scaling. +You can see that after enabling YaRN extrapolation, the model's PPL performance on long texts significantly decreases: + +
+ +
## Ⅴ Objective Benchmarks diff --git a/images/rope_ppl.png b/images/rope_ppl.png index 223292e..ea07260 100644 Binary files a/images/rope_ppl.png and b/images/rope_ppl.png differ diff --git a/model/model_minimind.py b/model/model_minimind.py index a522b03..78e243c 100755 --- a/model/model_minimind.py +++ b/model/model_minimind.py @@ -54,12 +54,13 @@ class MiniMindConfig(PretrainedConfig): self.rms_norm_eps = rms_norm_eps self.rope_theta = rope_theta self.inference_rope_scaling = inference_rope_scaling - # 外推长度 = factor * original_max_position_embeddings + # 外推长度 = factor * original_max_position_embeddings = 32768 self.rope_scaling = { - "beta_fast": 4, + "beta_fast": 32, "beta_slow": 1, - "factor": 4, + "factor": 16, "original_max_position_embeddings": 2048, + "attention_factor": 1.0, "type": "yarn" } if self.inference_rope_scaling else None self.flash_attn = flash_attn @@ -107,24 +108,23 @@ class RMSNorm(torch.nn.Module): def precompute_freqs_cis(dim: int, end: int = int(32 * 1024), rope_base: float = 1e6, rope_scaling: Optional[dict] = None): - freqs = 1.0 / (rope_base ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)) + freqs, attn_factor = 1.0 / (rope_base ** (torch.arange(0, dim, 2)[: (dim // 2)].float() / dim)), 1.0 if rope_scaling is not None: - orig_max, factor, beta_fast, beta_slow = ( - rope_scaling.get("original_max_position_embeddings", 2048), rope_scaling.get("factor", 4), - rope_scaling.get("beta_fast", 4.0), rope_scaling.get("beta_slow", 1.0) + 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) ) if end / orig_max > 1.0: - corr_dim = next((i for i in range(dim // 2) if 2 * math.pi / freqs[i] > orig_max), dim // 2) - power = torch.arange(0, dim // 2, device=freqs.device).float() / max(dim // 2 - 1, 1) - beta = beta_slow + (beta_fast - beta_slow) * power - # λ = (β·α - β + 1)/(β·α) YaRN标准公式 - scale = torch.where(torch.arange(dim // 2, device=freqs.device) < corr_dim, (beta * factor - beta + 1) / (beta * factor), 1.0 / factor) - freqs = freqs * scale + # YaRN: f'(i) = f(i)((1-γ) + γ/s), where γ∈[0,1] is linear ramp + inv_dim = lambda b: (dim * math.log(orig_max / (b * 2 * math.pi))) / (2 * math.log(rope_base)) + low, high = max(math.floor(inv_dim(beta_fast)), 0), min(math.ceil(inv_dim(beta_slow)), dim // 2 - 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) t = torch.arange(end, device=freqs.device) freqs = torch.outer(t, freqs).float() - freqs_cos = torch.cat([torch.cos(freqs), torch.cos(freqs)], dim=-1) - freqs_sin = torch.cat([torch.sin(freqs), torch.sin(freqs)], dim=-1) + 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 diff --git a/trainer/train_distill_reason.py b/trainer/train_distill_reason.py index be04798..111fe92 100644 --- a/trainer/train_distill_reason.py +++ b/trainer/train_distill_reason.py @@ -85,10 +85,11 @@ def train_epoch(epoch, loader, iters, tokenizer, lm_config, start_step=0, wandb= state_dict = model.module.state_dict() else: state_dict = model.state_dict() - state_dict = {k: v.half() for k, v in state_dict.items()} # 半精度保存 + state_dict = {k: v.half().cpu() for k, v in state_dict.items()} torch.save(state_dict, 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 X, Y, loss_mask, res, loss diff --git a/trainer/train_distillation.py b/trainer/train_distillation.py index 8f5e3f9..58d2b87 100644 --- a/trainer/train_distillation.py +++ b/trainer/train_distillation.py @@ -123,10 +123,11 @@ def train_epoch(epoch, loader, iters, teacher_model, lm_config_student, start_st state_dict = model.module.state_dict() else: state_dict = model.state_dict() - state_dict = {k: v.half() for k, v in state_dict.items()} # 半精度保存 + state_dict = {k: v.half().cpu() for k, v in state_dict.items()} torch.save(state_dict, ckp) lm_checkpoint(lm_config_student, 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 X, Y, loss_mask, res, student_logits, teacher_logits, ce_loss, distill_loss, loss diff --git a/trainer/train_dpo.py b/trainer/train_dpo.py index afa51f4..c48f32d 100644 --- a/trainer/train_dpo.py +++ b/trainer/train_dpo.py @@ -110,10 +110,11 @@ def train_epoch(epoch, loader, iters, ref_model, lm_config, start_step=0, wandb= state_dict = model.module.state_dict() else: state_dict = model.state_dict() - state_dict = {k: v.half() for k, v in state_dict.items()} # 半精度保存 + state_dict = {k: v.half().cpu() for k, v in state_dict.items()} torch.save(state_dict, 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 x_chosen, x_rejected, y_chosen, y_rejected, mask_chosen, mask_rejected, x, y, mask del ref_outputs, ref_logits, ref_log_probs, outputs, logits, policy_log_probs, loss diff --git a/trainer/train_full_sft.py b/trainer/train_full_sft.py index 4d0a181..8b380c7 100644 --- a/trainer/train_full_sft.py +++ b/trainer/train_full_sft.py @@ -72,11 +72,12 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None): state_dict = model.module.state_dict() else: state_dict = model.state_dict() - state_dict = {k: v.half() for k, v in state_dict.items()} # 半精度保存 + state_dict = {k: v.half().cpu() for k, v in state_dict.items()} torch.save(state_dict, ckp) lm_checkpoint(lm_config, weight=args.save_weight, model=model, optimizer=optimizer, epoch=epoch, step=step, wandb=wandb, save_dir='../checkpoints', scaler=scaler) model.train() + del state_dict del X, Y, loss_mask, res, loss diff --git a/trainer/train_grpo.py b/trainer/train_grpo.py index 52f6562..4e11f22 100755 --- a/trainer/train_grpo.py +++ b/trainer/train_grpo.py @@ -175,10 +175,11 @@ def grpo_train_epoch(epoch, loader, iters, ref_model, reward_model, reward_token moe_suffix = '_moe' if lm_config.use_moe else '' ckp = f'{args.save_dir}/{args.save_weight}_{lm_config.hidden_size}{moe_suffix}.pth' state_dict = model.module.state_dict() if isinstance(model, DistributedDataParallel) else model.state_dict() - torch.save({k: v.half() 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, epoch=epoch, step=step, wandb=wandb, save_dir='../checkpoints', scheduler=scheduler) model.train() + del state_dict del prompt_inputs, outputs, completion_ids, per_token_logps, ref_per_token_logps del completions, rewards, grouped_rewards, mean_r, std_r, advantages, completion_mask diff --git a/trainer/train_ppo.py b/trainer/train_ppo.py index cb6d7ff..a5ca18b 100644 --- a/trainer/train_ppo.py +++ b/trainer/train_ppo.py @@ -224,7 +224,7 @@ def ppo_train_epoch(epoch, loader, iters, old_actor_model, ref_model, actor_sche moe_suffix = '_moe' if lm_config.use_moe else '' ckp = f'{args.save_dir}/{args.save_weight}_{lm_config.hidden_size}{moe_suffix}.pth' actor_state = actor_model.module.state_dict() if isinstance(actor_model, DistributedDataParallel) else actor_model.state_dict() - torch.save({k: v.half() for k, v in actor_state.items()}, ckp) + torch.save({k: v.half().cpu() for k, v in actor_state.items()}, ckp) # 使用 lm_checkpoint 保存完整状态(包括 critic) lm_checkpoint(lm_config, weight=args.save_weight, model=actor_model, optimizer=actor_optimizer, @@ -232,6 +232,7 @@ def ppo_train_epoch(epoch, loader, iters, old_actor_model, ref_model, actor_sche scheduler=actor_scheduler, critic_model=critic_model, critic_optimizer=critic_optimizer, critic_scheduler=critic_scheduler) actor_model.train() + del actor_state del enc, gen_out, responses_text, rewards, full_mask, values_seq, values, advantages del logits, labels, logp_tokens, final_mask, actor_logp, old_logits, old_logp, ref_logits, ref_logp diff --git a/trainer/train_pretrain.py b/trainer/train_pretrain.py index 5de170b..5bc8fc8 100644 --- a/trainer/train_pretrain.py +++ b/trainer/train_pretrain.py @@ -117,10 +117,11 @@ def train_epoch(epoch, loader, iters, start_step=0, wandb=None): state_dict = model.module.state_dict() else: state_dict = model.state_dict() - state_dict = {k: v.half() for k, v in state_dict.items()} # 半精度保存 + state_dict = {k: v.half().cpu() for k, v in state_dict.items()} torch.save(state_dict, 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 X, Y, loss_mask, res, loss diff --git a/trainer/train_spo.py b/trainer/train_spo.py index c250b14..bac7c14 100755 --- a/trainer/train_spo.py +++ b/trainer/train_spo.py @@ -223,10 +223,11 @@ def spo_train_epoch(epoch, loader, iters, ref_model, reward_model, reward_tokeni moe_suffix = '_moe' if lm_config.use_moe else '' ckp = f'{args.save_dir}/{args.save_weight}_{lm_config.hidden_size}{moe_suffix}.pth' state_dict = model.module.state_dict() if isinstance(model, DistributedDataParallel) else model.state_dict() - torch.save({k: v.half() 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, epoch=epoch, step=step, wandb=wandb, save_dir='../checkpoints', scheduler=scheduler) model.train() + del state_dict del prompt_inputs, outputs, completion_ids, per_token_logps, ref_per_token_logps del completions, rewards, advantages, completion_mask, baselines, response_masks diff --git a/trainer/trainer_utils.py b/trainer/trainer_utils.py index a7e5058..528868e 100644 --- a/trainer/trainer_utils.py +++ b/trainer/trainer_utils.py @@ -1,6 +1,7 @@ """ 训练工具函数集合 """ +import gc import os import random import math @@ -77,8 +78,9 @@ def lm_checkpoint(lm_config, weight='full_sft', model=None, optimizer=None, epoc 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() + state_dict = {k: v.half().cpu() for k, v in state_dict.items()} ckp_tmp = ckp_path + '.tmp' - torch.save({k: v.half() for k, v in state_dict.items()}, ckp_tmp) + torch.save(state_dict, ckp_tmp) os.replace(ckp_tmp, ckp_path) wandb_id = None if wandb: @@ -109,6 +111,9 @@ 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) + del state_dict, resume_data + gc.collect() + torch.cuda.empty_cache() else: # 加载模式 if os.path.exists(resume_path): ckp_data = torch.load(resume_path, map_location='cpu')