""" Qualitative eval of a MiniMind pretrain (or post-pretrain) checkpoint by sampling text continuations from a fixed prompt set spanning multiple domains. For pretrain (base) models — does NOT use chat template. Use eval_llm.py for SFT models. Usage: # Default: 64M pretrain ckpt python eval_completion.py # 1B ckpt python eval_completion.py --hidden_size 2048 --num_hidden_layers 20 --weight pretrain # Custom prompt python eval_completion.py --prompt "今天天气不错,我打算" # Save outputs to markdown for design doc python eval_completion.py --output_md eval_results.md What to look for: ✓ Grammatically coherent Chinese, no repetition loops ✓ Stays roughly on topic of the prefix ✓ Variety across runs (different temperature should give different outputs) ✗ Repetitive: "的的的的" / 同一句重复 → 训练或解码有问题 ✗ 全部生成都一模一样 → 模型坍缩 / temperature 太低 ✗ 乱码 / 非中文片段(如果是纯中文 prefix)→ tokenizer 问题 """ import os import sys __package__ = "scripts" sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) import argparse import warnings import time import torch from transformers import AutoTokenizer, TextStreamer from model.model_minimind import MiniMindConfig, MiniMindForCausalLM warnings.filterwarnings('ignore') DEFAULT_PROMPTS = [ ("叙事", "在很久很久以前,有一个小村庄,"), ("日常", "今天天气不错,我打算"), ("技术", "Python 是一种编程语言,它的特点是"), ("事实", "中华人民共和国成立于"), ("代码", "def fibonacci(n):\n if n < 2:"), ("科学", "牛顿第一定律说的是"), ("文学", "李白是唐代著名的"), ("数学", "二的十次方等于"), ("常识", "水的沸点在标准大气压下是"), ("地理", "中国的首都是"), ] def load_model(args): cfg = MiniMindConfig( hidden_size=args.hidden_size, num_hidden_layers=args.num_hidden_layers, use_moe=bool(args.use_moe), ) model = MiniMindForCausalLM(cfg) moe_suffix = '_moe' if args.use_moe else '' ckp = f'{args.save_dir}/{args.weight}_{args.hidden_size}{moe_suffix}.pth' state_dict = torch.load(ckp, map_location='cpu', weights_only=True) model.load_state_dict(state_dict, strict=False) model = model.half().eval().to(args.device) return model, cfg @torch.no_grad() def main(): parser = argparse.ArgumentParser(description="MiniMind Pretrain Completion Eval") parser.add_argument('--hidden_size', type=int, default=768) parser.add_argument('--num_hidden_layers', type=int, default=8) parser.add_argument('--use_moe', type=int, default=0, choices=[0, 1]) parser.add_argument('--save_dir', type=str, default='../out') parser.add_argument('--weight', type=str, default='pretrain') parser.add_argument('--tokenizer_path', type=str, default='../model') parser.add_argument('--prompt', type=str, default=None, help='单条自定义 prompt;不指定则跑 DEFAULT_PROMPTS 全集') parser.add_argument('--max_new_tokens', type=int, default=80) parser.add_argument('--temperature', type=float, default=0.7) parser.add_argument('--top_p', type=float, default=0.9) parser.add_argument('--top_k', type=int, default=50) parser.add_argument('--repetition_penalty', type=float, default=1.1) parser.add_argument('--num_samples_per_prompt', type=int, default=1, help='每个 prompt 采样几次(验证多样性)') parser.add_argument('--output_md', type=str, default=None, help='输出 markdown 报告路径(可选)') parser.add_argument('--device', type=str, default='cuda' if torch.cuda.is_available() else 'cpu') parser.add_argument('--seed', type=int, default=42) args = parser.parse_args() torch.manual_seed(args.seed) print(f'[load] ckpt={args.save_dir}/{args.weight}_{args.hidden_size}.pth') model, cfg = load_model(args) tokenizer = AutoTokenizer.from_pretrained(args.tokenizer_path) prompts = [('custom', args.prompt)] if args.prompt else DEFAULT_PROMPTS md_lines = [] md_lines.append(f'# Completion Eval: `{args.weight}_{args.hidden_size}.pth`') md_lines.append(f'temperature={args.temperature}, top_p={args.top_p}, top_k={args.top_k}, ' f'rep_penalty={args.repetition_penalty}, max_new={args.max_new_tokens}\n') for label, prompt in prompts: print(f'\n{"=" * 60}') print(f'[{label}] Prompt: {prompt!r}') md_lines.append(f'## [{label}] `{prompt}`\n') for sample_i in range(args.num_samples_per_prompt): print(f'\n--- Sample {sample_i + 1} ---') # 必须加 BOS:训练时所有样本以 [BOS] 开头,eval 时不加 BOS 模型会产生 degenerate # 输出("的的的"/"是是是" 重复),因为它从没见过"无 BOS 开头"的序列 body = tokenizer(prompt, add_special_tokens=False).input_ids ids = torch.tensor([[tokenizer.bos_token_id] + body], device=args.device) t0 = time.time() output_ids = model.generate( ids, max_new_tokens=args.max_new_tokens, temperature=args.temperature, top_p=args.top_p, top_k=args.top_k, repetition_penalty=args.repetition_penalty, do_sample=True, eos_token_id=tokenizer.eos_token_id, ) elapsed = time.time() - t0 text = tokenizer.decode(output_ids[0].tolist(), skip_special_tokens=True) continuation = text[len(prompt):] print(continuation) new_tokens = output_ids.shape[1] - ids.shape[1] print(f' [{new_tokens} new tokens in {elapsed:.2f}s = {new_tokens/elapsed:.1f} tok/s]') md_lines.append(f'**Sample {sample_i + 1}**: {continuation.strip()}\n') md_lines.append('') if args.output_md: with open(args.output_md, 'w') as f: f.write('\n'.join(md_lines)) print(f'\n[saved] {args.output_md}') if __name__ == "__main__": main()