minimind
Tutorialjingyaogong/minimind
Train a 64M-parameter LLM from scratch in 2 hours with full pipeline code.
Overview
MiniMind is an educational open-source project that lets anyone train a tiny 64M-parameter language model entirely from scratch on a single consumer GPU in about two hours. It implements the full LLM pipeline in native PyTorch without high-level abstractions, covering MoE, pretraining, SFT, LoRA, DPO, RLAIF, agentic RL, tool use and distillation. It also provides open datasets, released model weights, and compatibility with mainstream inference and training ecosystems.
README Preview
\n\n\n\n\n\n\n\n\n[](https://github.com/jingyaogong/minimind/stargazers)\n[](LICENSE)\n[](https://github.com/jingyaogong/minimind/commits/master)\n[](https://github.com/jingyaogong/minimind/pulls)\n[](https://huggingface.co/collections/jingyaogong/minimind-66caf8d999f5c7fa64f399e5)\n\n\n\n\n\n\n\n\n\n\n "大道至简"\n\n\n\n\n中文 | [English](./README_en.md)\n\n\n\n* 此开源项目旨在完全从 0 开始,仅用 3 块钱成本与 2 小时训练时间,即可训练出规模约为 64M 的超小语言模型 MiniMind。\n* MiniMind 系列极其轻量,主线最小版本体积约为 GPT-3 的 $\frac{1}{2700}$,力求让普通个人 GPU 也能快速完成训练与复现。\n* 项目同时开源了大模型的极简结构与完整训练链路,覆盖 MoE、数据清洗、预训练(Pretrain)、监督微调(SFT)、LoRA、RLHF(DPO)、RLAIF(PPO / GRPO / CISPO)、Tool Use、Agentic RL、自适应思考与模型蒸馏等全过程代码。\n* MiniMind 同时拓展了视觉模态模型 [MiniMind-V](https://github.com/jingyaogong/minimind-v)、多模态 Omni 模型 [MiniMind-O](https://github.com/jingyaogong/minimind-o)、扩散语言模型(MiniMind-dLM)、线性模型(MiniMind-Linear),详见 [Discussion](https://github.com/jingyaogong/minimind/discussions)。\n* 项目所有核心算法代码均从 0 使用 PyTorch 原生实现,不依赖第三方库提供的高层抽象接口。\n* 这不仅是一个大语言模型全阶段开源复现项目,也是一套面向 LLM 入门与实践的教程。\n* 希望此项目能为更多人提供一个可复现、可理解、可扩展的起点,一起感受创造的乐趣,并推动更广泛 AI 社区的进步。\n\n> 注:本项目基于 Apache 2.0 协议开源,完全免费。“2 小时” 指 SFT 阶段在单张 NVIDIA 3090 上跑完 `1 epoch` 的实测耗时,“3 块钱” 指对应时段的 GPU 租用成本。\n\n---\n\n\n\n\n\n[🔗 在线体验](https://www.modelscope.cn/studios/gongjy/MiniMind) | [🔗 视频介绍](https://www.bilibili.com/video/BV12dHPeqE72)\n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n\n\n\n\n\n---\n\n# 📌 项目介绍\n\n大语言模型(Large Language Model, LLM)的出现,引发了全球范围内对 AI 的空前关注。无论是 ChatGPT、DeepSeek 还是 Qwen,都以惊艳的效果让人真切感受到这场技术浪潮的冲击力。然而,动辄数百亿参数的模型规模,使得它们对个人设备而言不仅难以训练,甚至连部署都显得遥不可及。打开大模型的“黑盒子”,真正去理解其内部运作机制,本应是一件令人心潮澎湃的事。遗憾的是,绝大多数探索最终都止步于使用 LoRA 等技术对现有大模型做少量微调,学习一些新指令或特定任务。这更像是在教牛顿如何使用 21 世纪的智能手机——虽然有趣,却偏离了理解物理本质的初衷。\n\n与此同时,第三方的大模型框架与工具库,如 `transformers` / `trl` / `peft` 等,往往只暴露出高度抽象的接口。只需短短十几行代码,就可以完成“加载模型 + 加载数据集 + 推理 + 强化学习”的全流程训练。这种高效封装固然便利,却也在一定程度上把开发者与底层实现隔离开来,削弱了深入理解 LLM 核心代码的机会。我认为 “用乐高自己拼出一架飞机,远比坐在头等舱里飞行更让人兴奋”,然而更现实的问题是,互联网上充斥着大量付费课程和营销内容,用漏洞百出FAQ (3)
TroubleshootingWhy do all tool calls return "tool not found" error when running train_agent.py on Windows?
Root cause: signal.SIGALRM and signal.alarm() are POSIX-only and do not exist on Windows. In trainer/train_agent.py, execute_tool() calls signal.signal(signal.SIGALRM, ...) inside a bare except:, so the resulting AttributeError is silently swallowed and the function returns None. rollout_single then converts that None into {"error": "tool not found"} for every single tool call — no crash, no warning. This also silently corrupts Agentic RL training: the format reward still scores, but the main ground-truth reward (weight 2.5) becomes unreachable because the model never sees real tool results.
Fix (PR #847): update to a version containing the AST-based safe_math_eval() in trainer/trainer_utils.py. It replaces raw eval() on model-generated strings and removes the signal-based timeout entirely (an AST evaluator cannot enter an infinite loop, so signal.alarm is unnecessary — the cross-platform problem disappears with no platform guard needed). The same fix is applied to all three eval() call sites: trainer/train_agent.py, scripts/eval_toolcall.py, and scripts/web_demo.py.
If you are stuck on an older commit, apply this minimal cross-platform patch:
_HAS_ALARM = hasattr(signal, "SIGALRM")
def execute_tool(name, args):
fn = MOCK_RESULTS.get(name)
if not fn: return None
try:
if _HAS_ALARM:
signal.signal(signal.SIGALRM, lambda *_: (_ for _ in ()).throw(TimeoutError()))
signal.alarm(1)
return fn(args)
except Exception as e:
print(f"[tool] {name} failed: {type(e).__name__}: {e}")
return None
finally:
if _HAS_ALARM: signal.alarm(0)
Key rules: never use a bare except: (it hid the AttributeError and made the failure silent), and always guard signal.SIGALRM/signal.alarm with hasattr(signal, 'SIGALRM') on cross-platform code.
TroubleshootingHow to fix CUDA Out of Memory (OOM) error when running train_agent.py or train_grpo.py on a 24GB GPU (RTX 4090)?
The effective training batch is batch_size × num_generations (Agent RL: 2×4=8, GRPO: 2×6=12), and training simultaneously loads the Policy model, Reference model, and a 1.8B Reward Model, so 24GB VRAM may be insufficient. Fixes:
1. For train_agent.py, reduce batch size and total length:
python train_agent.py --batch_size 1 --max_total_len 1536
2. For train_grpo.py (no max_total_len option), reduce batch size, generations, and sequence lengths:
python train_grpo.py --batch_size 1 --num_generations 4 --max_seq_len 512 --max_gen_len 768
3. If PyTorch reports large 'reserved but unallocated memory', reduce fragmentation by setting:
export PYTORCH_CUDA_ALLOC_CONF=expandable_segments:TrueTroubleshootingWhy is original_max_position_embeddings (2048) different from the pretrain max_seq_len (340) when using YaRN rope_scaling in MiniMind?
original_max_position_embeddings must reference the context length the checkpoint was actually trained on *before* enabling YaRN — not the value in train_pretrain.py. MiniMind's pipeline is: (1) Pretrain with original RoPE at max_seq_len=340 (YaRN disabled); (2) Full SFT with original RoPE at max_seq_len=2048 (YaRN disabled — the released full_sft weights were trained at 2048 tokens, not the 768 used in the quick mini-dataset reproduction script); (3) Inference with YaRN enabled, extrapolating from the 2048 window (e.g., python eval_llm.py --weight full_sft --inference_rope_scaling). Therefore original_max_position_embeddings=2048 matches the Full SFT checkpoint. RoPE itself is a deterministic position rotation with no trainable parameters; what needs training is the model weights' adaptation to the position range, which already happened during the 2048-token Full SFT stage. Only if you skip Full SFT and apply YaRN directly to a pretrain-only checkpoint trained at length 340 should you set original_max_position_embeddings=340.