Chapter 06
📌 实验
📌 实验
Ⅰ 训练开销
- 时间单位:小时(h)
- 成本单位:人民币(¥);
7¥ ≈ 1 美元 - 3090 租卡单价:约
1.3¥/h(实际价格可自行参考) - 说明:以下结果为
minimind模型在单卡3090上的经验估算值,用于快速感知训练门槛
| Model Name | params | pretrain_t2t_mini | sft_t2t_mini | toolcall | RLAIF |
|---|---|---|---|---|---|
| minimind-3 | 64M | ≈1.21h≈1.57¥ | ≈1.10h≈1.43¥ | ≈0.9h≈1.17¥ | ≈1.1h≈1.43¥ |
| minimind-3-moe | 198M-A64M | ≈1.69h≈2.20¥ | ≈1.54h≈2.00¥ | ≈1.26h≈1.64¥ | ≈1.54h≈2.00¥ |
minimind-3
pretrain_t2t_mini+sft_t2t_mini单卡3090,1 epoch预计约2.31小时,成本约3.0元人民币 可从 0 训练出minimind-3 Zero对话模型。
minimind-3-moe
pretrain_t2t_mini+sft_t2t_mini单卡3090,1 epoch预计约3.23小时,成本约4.2元人民币 可快速得到minimind-3-moe的基础对话版本。
以上均为估算值,仅用于快速感知训练门槛。
基于单卡 NVIDIA 3090,minimind zero 从 0 训练依然可以控制在约 2 小时量级,个人开发者也能较低门槛地快速上手。
若采用更高规格的多卡环境,例如 8x H100,总训练时长还可进一步压缩至分钟级。以尽可能低的门槛实现可复现、可上手、可持续迭代的 LLM 训练体验,这也是 MiniMind 系列一直希望坚持的方向。低成本快速复现并不是噱头,下面保留一个早期的 Zero 风格样例对话供参考:
👶: 请介绍一下自己。
🤖️: 作为人工智能,我没有实际的生活,也没有自我意识,所以没有自己的生活。我被设计成能够帮助用户解答问题、提供信息、进行对话等。我的设计和功能是由计算机科学、机器学习、人工智能技术和算法所构建的,我通过编程和算法实现。
👶: 推荐一些杭州的特色美食吧。
🤖️: 杭州有很多美味的特色美食,比如鸡肉串、鳗鱼头、水煮鱼、豆腐脑等。这些美食都非常美味,有着独特的风味和口感,绝对是值得一试的美食。
👶: 请为我讲解“大语言模型”这个概念。
🤖️: 大语言模型是一种人工智能技术,它使用大量的文本数据来训练模型,然后在不断的迭代中不断优化模型。它的目标是根据输入的数据来生成符合特定需求的文本,这些文本可以是任何你感兴趣的主题。
👶: Introduce the history of the United States, please.
🤖️: 您提到的“Introok's the believeations of theument." 这个名字来源于中国古代的"groty of of the change."尽管该版本已经具备基础对话能力,但事实知识与泛化效果仍较有限;它更适合作为 Zero 训练路线可行性的早期参考。
Zero 模型权重保存为 full_sft_zero_768.pth(见下文 MiniMind 模型文件链接),如有兴趣可下载体验其对话效果。
Ⅱ 主要训练(必须)
所有训练脚本均
cd ./trainer目录执行
1' 预训练 (Pretrain):
LLM 首先要学会的是先把尽可能多的基础知识和语言规律吸收到参数里。只有这一步打稳了,模型后面才有能力去理解问题、组织表达,并逐步形成像样的生成能力。预训练做的事情,本质上就是让模型先埋头读大量文本,例如 Wiki 百科、新闻、书籍、对话语料等,从中学习事实知识、语言模式以及上下文之间的统计关系。这个阶段通常是“无监督”的:人类不需要逐条告诉模型哪里对、哪里错,而是让它自己从海量文本里总结规律,逐步建立起对世界知识和语言结构的内部表征。 更直白地说,模型在这一阶段的核心目标就是学会高质量地词语接龙。例如输入“秦始皇”,它要能够继续生成“是中国历史上的第一位皇帝”这类符合语义与常识的后续内容。
# 方式1
torchrun --nproc_per_node 1 train_pretrain.py # 1即为单卡训练,可根据硬件情况自行调整 (设置>=2)
# 方式2
python train_pretrain.py训练后的模型权重文件默认每隔
save_interval步保存为:pretrain_*.pth(*为模型具体dimension,每次保存时新文件会覆盖旧文件)

768dim配置在预训练阶段的 loss 曲线
# 可对预训练结果做简单测试:
python eval_llm.py --weight pretrain
💬: 为什么天空是蓝色的
🧠: 天空之所以看起来是蓝色的,主要是因为太阳光进入大气层后,短波长的蓝光更容易被空气分子散射,因此人眼从各个方向接收到的蓝光会更多。
💬: 解释什么是机器学习
🧠: 机器学习是人工智能的一个重要分支,它通过数据训练模型,使系统能够自动学习规律,并在分类、预测、推荐、自然语言处理等任务中持续改进效果。2' 有监督微调 (Supervised Fine-Tuning):
SFT 并不只是把模型调成“更会聊天”,它同样可以继续向模型中灌入新的知识、行为模式和回答风格。尤其是像 MiniMind 当前主线这样体量达到 14GB 的 SFT 数据,本身就已经不只是简单的格式对齐,而更接近一种带有 mid training 性质的持续强化过程。
如果把预训练理解为先让模型广泛地读书、积累基础语言能力,那么 SFT 更像是在高质量、更有目标的数据上继续深加工。一方面,它会让模型适应多轮对话、问答、工具调用和思考标签等交互形式;另一方面,它也会继续把特定知识分布、任务模式和助手风格压进参数里。
具体到 MiniMind 里,SFT 阶段会让模型适应当前仓库使用的多轮对话模板。模型会逐渐理解 user / assistant / system / tool 等角色结构,同时进一步强化指令跟随、稳定回复和任务完成能力。
当前训练时会对指令和回答长度做截断控制,主要是为了兼顾显存占用与训练效率;如果后续需要更长上下文,只需要继续准备少量长样本做增量微调即可。在推理时通过启用 YaRN 外推,可以免训练地将上下文长度扩展到 2048 及以上。
# 方式1
torchrun --nproc_per_node 1 train_full_sft.py
# 方式2
python train_full_sft.py训练后的模型权重文件默认每隔
save_interval步保存为:full_sft_*.pth(*为模型具体dimension,每次保存时新文件会覆盖旧文件)

768dim配置在 SFT 阶段的 loss 曲线
# 可对SFT结果做简单测试:
python eval_llm.py --weight full_sft
💬: 解释什么是机器学习
🧠: 机器学习是人工智能的核心技术之一,通过算法让计算机从数据中学习规律,并持续改进预测或决策效果,常见应用包括推荐系统、图像识别、语音识别和自然语言处理。
💬: 推荐一些中国的美食
🧠: 例如北京烤鸭、兰州拉面、四川火锅、广东早茶、小笼包和麻婆豆腐等,这些美食分别代表了不同地区的风味特点,也很适合作为了解中国饮食文化的入门选择。Ⅲ 其它训练(可选)
所有训练脚本均
cd ./trainer目录执行
3' 知识蒸馏 (Knowledge Distillation, KD)
知识蒸馏大体可以分成黑盒和白盒两类,MiniMind 当前主线两种思路都有涉及,只是侧重点不同。
- 黑盒蒸馏:更常见,也更贴近当前主线的实际做法。严格来说,它本质上仍然是面向教师输出结果的监督微调,也就是基于硬标签继续训练;只是随着 LLM 的流行,这类“对着强模型输出做 FT”的做法也逐渐被广义地归入了蒸馏范畴,因此通常被称为黑盒蒸馏。它重点学习的是答案、风格和行为模式,学生模型只能看到“老师说了什么”,却看不到老师内部是如何做出这个判断的。像
DeepSeek R1、Qwen3的高质量回答,以及tool call、reasoning、思维链等数据,都可以看作黑盒蒸馏信号;MiniMind 当前主线full_sft数据里,其实已经混入了相当一部分这样的思路。 - 白盒蒸馏:更进一步,不只学习教师给出的最终输出,还去学习教师在 token 分布层面的偏好。相比黑盒蒸馏,它额外利用了教师模型输出层更细粒度的分布信息,因此学生模型学到的不只是“标准答案”,还包括教师在候选 token 之间的相对倾向。对应到
train_distillation.py,当前实现是在已经完成 SFT 的权重基础上,继续用教师模型提供的分布信号来训练学生模型,因此更适合作为理解 MiniMind 蒸馏流程的参考实现。
黑盒蒸馏本质上等价于对 teacher 生成答案做监督微调:
白盒蒸馏则通常在监督损失之外,再额外拟合教师分布:
仓库中提供的 train_distillation.py 更适合作为理解白盒蒸馏流程的参考实现:它完整展示了教师/学生双模型加载、CE + KL 混合损失、温度缩放、MoE 与 dense 组合蒸馏,以及断点续训和分布式训练等关键细节。
# 方式1
torchrun --nproc_per_node 1 train_distillation.py
# 方式2
python train_distillation.py4' LoRA (Low-Rank Adaptation)
LoRA 是一种常见的参数高效微调(Parameter-Efficient Fine-Tuning, PEFT)方法。相比全参数微调,它只更新少量新增参数,而保留原始模型主体权重不变,因此训练成本更低,也更适合做垂直场景适配。
它的核心思想是在原有权重矩阵旁引入低秩增量分支,仅训练这部分低秩参数,从而用较小代价完成能力迁移。相关实现可见 model_lora.py 和 train_lora.py,整个流程均为纯手写实现,不依赖第三方封装。
# train_lora.py 在 CPU 上通常也能比较轻快地完成
# 方式1
cd trainer && torchrun --nproc_per_node 1 train_lora.py
# 方式2
cd trainer && python train_lora.py训练后的模型权重文件默认每隔
save_interval步保存为:lora_xxx_*.pth(*为模型具体dimension,每次保存时新文件会覆盖旧文件)
LoRA 很适合处理“如何在尽量保留通用能力的前提下,让模型快速适应私有领域或垂直场景”这类问题。例如基础模型医学知识不足时,就可以在原有模型之上叠加一层面向医疗场景的 LoRA 权重,以较小代价获得更好的领域表现。
通常只需要准备同样的多轮对话格式数据,放置到 lora_xxx.jsonl,再从仓库根目录执行 cd trainer && python train_lora.py,即可得到新的 LoRA 模型权重。
例1:垂域数据
{"conversations": [{"role": "user", "content": "请问颈椎病的人枕头多高才最好?"}, {"role": "assistant", "content": "颈椎病患者选择枕头的高度应该根据..."}]}
{"conversations": [{"role": "user", "content": "请问xxx"}, {"role": "assistant", "content": "xxx..."}]}例2:自我认知数据
{"conversations": [{"role": "user", "content": "你叫什么名字?"}, {"role": "assistant", "content": "您好,我名叫 MiniMind,是由 Jingyao Gong 开发的人工智能助手。我可以帮助你解决各种问题,包括翻译、推荐内容等等。"}]}
{"conversations": [{"role": "user", "content": "你是谁"}, {"role": "assistant", "content": "您好,我是 MiniMind,由 Jingyao Gong 创建。我的主要职责是提供准确的回答和帮助。"}]}此时可将【基模 + LoRA 权重】组合使用,从而在保留基础模型通用能力的同时,增强特定领域表现。训练完成后也可以通过 eval_llm.py 做简单测试。
# 注意:weight参数指定基础模型类型,需与train_lora训练时使用的基础模型保持一致
python eval_llm.py --weight full_sft --lora_weight lora_medical
👶: 我最近经常感到头晕,可能是什么原因?
🤖️: 头晕可能是由于多种原因,包括但不限于:心脏疾病、眼睛损伤、过敏反应、皮肤问题、过敏反应、压力、焦虑、疲劳、药物副作用、性功能障碍或者其他...
python eval_llm.py --weight full_sft --lora_weight lora_identity
👶: 你和openai是什么关系?
🤖️: 我是 MiniMind,一个由 Jingyao Gong 开发的人工智能助手。我通过自然语言处理和算法训练来与用户进行交互。PS:如果有更充足的数据,也可以直接做 full_sft 全参微调;不过这通常需要更谨慎地混合通用数据与领域数据,否则很容易因为过拟合垂域样本而损失模型原有的通用性。
LoRA权重可合并回基础模型并导出为新的完整模型权重,可使用scripts/convert_model.py中的convert_merge_base_lora:
cd scripts && python convert_model.py5' 工具调用 & 自适应思考
2026-03 起,仓库移除了独立的 train_reason.py。
当前版本不再单独维护 reason_*.pth 权重,而是统一通过 chat_template、<think> 标签、open_thinking 开关以及后续 SFT / RLAIF 流程来建模“是否显式输出思考过程”。
5.1 Tool Calling
当前 toolcall 能力已经并入 sft_t2t / sft_t2t_mini 主线数据,因此通常不再需要额外单独训练一轮 Tool Calling;默认的 full_sft 权重已经具备基础 Tool Call 能力。当前这部分训练数据主要由 qwen3-4b 采样约 10w 条构成,工具列表也主要覆盖约 10 个模拟的自定义工具(例如查询时间、数学计算、获取天气等),因此目前还谈不上明确的泛化能力。其中 Tool Calling 样本统一沿用了 OpenAI 风格的多轮消息格式:
{
"conversations": [
{"role": "system", "content": "# Tools ...", "tools": "[...]"},
{"role": "user", "content": "帮我算一下 256 乘以 37 等于多少"},
{"role": "assistant", "content": "", "tool_calls": "[{\"name\":\"calculate_math\",\"arguments\":{\"expression\":\"256 * 37\"}}]"},
{"role": "tool", "content": "{\"result\":\"9472\"}"},
{"role": "assistant", "content": "256 乘以 37 等于 9472。"}
]
}其中 tools 挂在 system 消息上,tool_calls 挂在 assistant 消息上;训练时再由 chat_template 自动展开为 <tool_call>...</tool_call> 与 <tool_response>...</tool_response> 片段,因此现在可以直接学习原生 tool call 格式。
Tool Calling 的 chat template 已统一支持解析为:
<tool_call>{"name": "...", "arguments": {...}}</tool_call>
<tool_response>{...tool result...}</tool_response>也可以直接通过 eval_toolcall.py 做简单测试:
python eval_toolcall.py --weight full_sft
💬: 现在几点了?
🧠: <tool_call>{"name": "get_current_time", "arguments": {"timezone": "Asia/Shanghai"}}</tool_call>
📞 [Tool Calling]: get_current_time
✅ [Tool Called]: {"datetime": "2026-03-15 17:18:22", "timezone": "Asia/Shanghai"}
🧠: 现在是2026年3月15日17时18分22秒。5.2 Adaptive Thinking
minimind 将显式思考能力统一到了模板层,这也和当前很多主流大模型的模板设计保持一致:
open_thinking=0:默认注入空的<think>\n\n</think>,模型更倾向于直接回答;open_thinking=1:模板会预先注入<think>起始标签,模型再继续输出显式思考过程与最终回答;- CLI、OpenAI-API、WebUI 三个入口均支持该开关。
更准确地说,目前不再“单独训一个思考模型”,而是把“是否显式思考”下沉到 chat_template。模板层会先预留 <think></think> 这一结构,同一个模型在推理时再通过 open_thinking 动态切换;在训练时,则通过混合空 think、显式 reasoning_content 与 thinking_ratio 采样,让模型逐步见过“该想时想、该直答时直答”的混合模式。
# 测试回答
python eval_llm.py --load_from ./minimind-3 --open_thinking 1OpenAI-API-SDK 用法:
response = client.chat.completions.create(
model="minimind",
messages=[{"role": "user", "content": "你是谁?"}],
# ...
extra_body={"chat_template_kwargs": {"open_thinking": True}} # 思考开关
)注:当前同时开启 Tool Call 与显式思考时,模型通常并不太会稳定地输出思考过程。原因在于现阶段训练数据里还缺少“reasoning 与 tool call 同时存在”的联合蒸馏样本,因此模型尚未充分学会这两种能力的协同表达。
Ⅳ 强化学习(可选)
在 LLM 的后训练实践中,常见的强化学习路径主要有两条:
- 基于人类反馈的强化学习 (Reinforcement Learning from Human Feedback, RLHF)
- 通过人类对模型输出的偏好进行评价来训练模型,使其生成更符合人类价值观和偏好的内容。
- 基于AI反馈的强化学习 (Reinforcement Learning from AI Feedback, RLAIF)
- 使用AI模型或其他可自动验证的机制来提供反馈,而不直接依赖人类标注。
- 这里的“AI feedback”在广义上也可以扩展到规则奖励、Ground Truth 校验、代码解释器、环境反馈等自动化信号。
| 类型 | 裁判 | 优点 | 缺点 |
|---|---|---|---|
| RLHF | 人类 | 更贴近真实人类偏好 | 成本高、效率低 |
| RLAIF | 模型 | 自动化、可扩展性强 | 可能偏离人类真实偏好 |
二者本质上都属于利用某种形式的"反馈"来优化模型行为的强化学习范式。
不过在具体实践里,它们并不只是反馈来源不同:奖励是否可验证、是否连续、是否依赖环境交互、是否延迟到整轮结算,都会直接影响训练形态与工程实现。
👀 PO算法的统一视角
在介绍实现具体算法之前,我先以个人理解的极简视角,阐述所有Policy Optimization (PO)算法的统一共性。
所有RL算法的本质都只是在优化一个期望:
训练时,只需最小化负目标函数,即:
这个框架只包含三个核心组件:
- 策略项 : 如何使用概率比 ? 即告诉模型新旧策略偏差有多大,是否探索到了更好的token
- 优势项 : 如何计算优势 , 这很重要!大模型算对定积分也不足为奇,小模型回答对加减法优势通常都是正的
- 正则项 : 如何约束变化幅度 , 既防止跑偏又防止管的太死
| 符号 | 含义 | 说明 | 值域 |
|---|---|---|---|
| 问题/提示词 | 从数据集 中采样 | - | |
| 模型输出序列 | 由策略 生成 | - | |
| 概率比 | |||
| 优势函数 | 衡量某个动作相比基线有多好 | ||
| KL散度 | 防止策略偏离参考模型太远 |
不同的xxPO算法本质上只是对这三个组件的不同设计的实例化!
6' 基于人类反馈的强化学习 (Reinforcement Learning from Human Feedback, RLHF)
在前面的训练步骤中,模型已经具备了基本的对话能力,但是这样的能力完全基于单词接龙,缺少正反样例的激励。 模型此时尚未知什么回答是好的,什么是差的。希望它能够更符合人的偏好,降低让人类不满意答案的产生概率。 这个过程就像是让模型参加新的培训,从优秀员工的作为例子,消极员工作为反例,学习如何更好地回复。
6.1 Direct Preference Optimization
直接偏好优化(DPO)算法,损失为:
其中:
- 策略项: (对比chosen vs rejected的概率比)
- 优势项: = 无显式优势项(通过偏好对比隐式体现)
- 正则项: = 隐含在 中 (控制偏离参考模型程度)
特别地,
- DPO从PPO带KL约束的目标推导出对偏好对的解析训练目标,直接最大化"chosen优于rejected"的对数几率;无需同步训练Reward/Value模型。DPO只需跑
actor与ref两个模型,显存占用低、收敛稳定、实现简单。 - 训练范式:off‑policy,使用静态偏好数据集,可反复多轮epoch;Ref模型固定(预先缓存输出)。
- DPO的局限在于不做在线探索,更多用于"偏好/安全"的人类价值对齐;对"能不能做对题"的智力能力提升有限(当然这也取决于数据集,大规模收集正反样本并人类评估很困难)。
# 方式1
torchrun --nproc_per_node 1 train_dpo.py
# 方式2
python train_dpo.py训练后的模型权重文件默认每隔
save_interval步保存为:dpo_*.pth(*为模型具体dimension,每次保存时新文件会覆盖旧文件)
7' 基于 AI 反馈的强化学习 (Reinforcement Learning from AI Feedback, RLAIF)
稍微花篇幅解释一下,我还是更想把这一节叫作 RLAIF,虽然严格来说,这个命名并不完全准确。像 RLVR 这类依赖可验证奖励的路线,本身有相对独立的脉络,很难被简单并进狭义的 AI feedback 里。
但如果把“AI”理解得稍微宽一点,我又觉得这个名字并非完全说不通:奖励既可以来自奖励模型、judge model 这类显式的智能体,也可以来自规则函数、Ground Truth校验、工具调用结果、环境返回状态这类可自动获得的信号。规则足够复杂、符号系统足够丰富时,它们和“智能反馈”之间的边界,本来就未必那么泾渭分明。
因此这一章更想讨论的,其实是 LLM 在 SFT 之后,借助各种非人工、可自动获得的反馈信号继续做强化学习优化的方法。比如数学题答案是否正确、工具调用执行代码能否通过测试用例、推理过程是否符合格式...都可以自动化判断。
对于单轮可验证任务,这类反馈往往更接近“即时打分”;而在 Agentic RL 场景中,奖励则更常表现为多步交互后的延迟结算,甚至直接来自环境本身。
它们共同的特点通常都是On-Policy与可扩展性强——不需要昂贵的人工标注,可以生成海量训练样本,让模型在在线大量试错中快速进化。
MiniMind 着手实现2+N种基本+前沿的RLAIF方法:
- PPO、GRPO 被大规模验证的经典RL算法
- N种前沿RL算法(不定期以Exp性质更新)
1️⃣ 数据集准备 (必须)
当前主线使用 rlaif.jsonl 作为 RLAIF 训练数据,体量约 20MB,比早期 rlaif-mini.jsonl 更完整,更适合直接验证 PPO / GRPO / CISPO 的训练效果。
数据格式与 SFT 一致,但 assistant 字段不需要真实内容,因为训练过程中完全由 策略模型实时采样生成。因此形如:
{
"conversations": [
{"role": "user", "content": "请解释一下什么是光合作用?"},
{"role": "assistant", "content": "无"}
]
}RLAIF的训练过程中,模型会基于user的问题生成1或多个候选回答,然后由奖励函数/模型对回答打分, 分数高的回答会被鼓励(增加 策略概率),分数低的回答会被抑制(降低 策略概率)。这个"打分->调整"的循环就是强化学习的核心。
2️⃣ 奖励机制准备 (必须)
RLAIF训练需要某种可计算的奖励信号;它可以来自奖励模型,也可以来自规则函数、Ground Truth 校验或环境反馈。MiniMind 当前默认演示的是 Reward Model 路线。
此处选取小型且高质量的 InternLM2-1.8B-Reward (ModelScope | HuggingFace) 作为基础奖励模型。
下载奖励模型后需要放置在minimind项目的同级目录下,推荐结构如下:
root/
├── minimind/ # MiniMind项目
│ ├── model/
│ └── ...
└── internlm2-1_8b-reward/ # 奖励模型
├── config.json
├── model.safetensors
└── ...1. 奖励机制的多样性
RLAIF中的"奖励信号"来源可以非常灵活:
-
Model-based奖励:可使用专门的Reward Model(如InternLM2-Reward),也可使用通用LLM+提示词进行打分(如Qwen3-as-a-Judge)。奖励模型规模和架构均可自由选择。
-
Rule-based奖励:可以基于规则函数构造奖励信号,例如:
- 数学题答案正确性验证(Ground Truth对比)
- SQL执行成功率与结果准确性
- 代码解释器运行结果(pass@k)
- 工具调用返回状态(API成功/失败)
- 格式合规性检查(JSON/XML解析)
- 推理链完整性评估(CoT步骤数)
-
Environment-based奖励:在Agent场景中,环境反馈本身即为天然奖励(如游戏得分、Research完整度、任务完成度)。
任何能够量化"回答质量"的机制都可作为RL的奖励来源。DeepSeek R1就是典型案例:使用规则函数验证数学答案正确性作为奖励,无需额外的Reward Model。
2. MiniMind限制:奖励稀疏问题
RLAIF训练既可以针对推理模型也可以针对非推理模型,区别仅在于格式。
然而对于 MiniMind 这种 0.1B 参数量、能力较弱的模型,在通用任务(如 R1 风格的数学数据集)上会遇到严重的奖励稀疏(Reward Sparsity)问题:
- 现象:模型生成的候选回答几乎全部错误,导致所有奖励分数
- 后果:优势函数 ,策略梯度信号消失,无法有效更新参数
如同让小学生做高考数学题,无论尝试多少次都得零分,无法通过分数差异学习改进策略。这属于 RL 算法在奖励稀疏场景下的根本限制。
为缓解此问题,MiniMind的实现选择了model-based的连续性奖励信号:
- Reward Model输出连续分数(如-2.5到+3.0),而非二元的0/1
- 即使回答质量都差,也仍能区分“更差”(-3.0)和“没那么差”(-2.8)的细微差异。所以这种稠密且连续的奖励信号能够为优势函数 提供非零梯度,使得策略网络得以渐进式优化
- 也可以混合多种奖励源: (例如既可以检测 thinking 标签格式奖励,又可以综合回答本身质量的 reward 分数)
- MiniMind 实践中避免直接使用 rule-based 二元奖励 + 超纲难度数据(如 MATH500),易导致奖励全零;
- 监控训练时观察奖励分数的方差 ,若持续接近0则需调整数据或奖励机制
对于生产级大模型的Agentic RL场景:
在真实Agent系统(代码生成、工具调用、检索-规划-执行的多轮链路)中,奖励是“延迟整轮结算”的不同范式:
- LLM需要逐token生成工具调用指令(tool_call),经历解析(tool_parse)、工具执行(tool_exec),再把结果拼接回上下文继续下一步;循环往复直到完成。
- 一次完整的任务链路包含多次调用+思考,直到终止条件满足时计算一次总reward(如任务是否完成、测试是否通过、目标是否命中)。
因此,Agentic RL更接近稀疏/延迟奖励设定:梯度回传在“整轮结束后”才发生,和非Agentic RL任务在对话单轮上“即时评分即时更新”有很大不同。 这也解释了Agent任务上更偏向环境反馈(environment-based reward),而非凭Reward Model进行静态打分。
- 环境交互反馈:最终以执行结果为准(代码是否跑通、API是否返回成功、子目标是否完成);
- Model-based奖励局限:对长链路、可执行语义的全貌捕捉有限,且大概率和真实环境反馈不一致(reward hacking)。
7.1 Proximal Policy Optimization
PPO 是 2017 年 OpenAI 提出的非常经典的强化学习算法,也是 LLM RL 领域最常见的基线方法之一。
PPO损失:
其中:
- 策略项: (裁剪概率比防止更新过激)
- 优势项: (通过Critic网络估计价值函数)
- 正则项: (全局KL散度约束)
对比DPO而言,
- DPO (Off-Policy):训练数据是静态偏好对(chosen vs rejected),可以反复使用同一批数据训练多个 epoch,像传统监督学习一样。数据效率高、成本低,且无需 Reward Model。
- PPO (On-Policy):必须用当前策略实时采样新数据,旧策略数据只能有限复用,否则就会出现 distribution shift。虽然 importance sampling 和 clip 允许轻微偏移,但本质上仍要求数据来自较新的策略。数据效率更低,但更适合探索式学习。
简单来说:
- 前者按离线预定的「好/坏标准」学习;
- 后者则基于最新 policy 在线采样并实时纠偏。
MiniMind 的 PPO 实现包含 Actor(生成回答)、Critic(评估回答价值)以及完整的 GAE(Generalized Advantage Estimation)优势函数计算。
训练方式:
# 方式1
torchrun --nproc_per_node N train_ppo.py
# 方式2
python train_ppo.py训练后的模型权重文件默认每隔
save_interval步保存为:ppo_actor_*.pth(*为模型具体dimension)

MiniMind 在 PPO 训练阶段的优化走势
从训练曲线可以看出,PPO存在reward提升缓慢的问题。私以为这主要源于PPO双网络联合优化方法:Critic需要逐步收敛以准确估计价值函数,而Actor的策略更新依赖Critic提供的优势估计,两者相互依赖形成复杂的优化过程。训练初期Critic估计不准会影响Actor梯度方向,导致整体收敛缓慢。此外,PPO 需要同时维护两个网络,在当前实现下显存占用约为单网络方法的 1.5–2 倍。
7.2 Group Relative Policy Optimization
2025 年初,随着 DeepSeek-R1 火爆出圈,来自 DeepSeekMath 论文的 GRPO 也迅速进入主流视野,一度成为最受关注的 RL 算法之一。不过 AI 领域向来迭代极快。时至今日,GRPO 更多已经演变成各类 XXPO 变体(如 DAPO、GSPO、CISPO 等)的共同基线。一句话概括它的核心创新,就是“分组相对价值估计”。
GRPO损失:
其中:
- 策略项: (使用概率比的对称 clip 裁剪)
- 优势项: (组内归一化,消除Critic网络)
- 正则项: (token级KL散度约束)
对于同一个问题,模型生成 N 个回答并计算各自奖励,再用组内平均奖励作为 baseline。高于 baseline 的回答被鼓励,低于 baseline 的回答被抑制,因此无需额外训练 critic 网络。
GRPO 更显著的问题是退化组(Degenerate Groups):如果某个问题上 N 个回答的奖励几乎一样,那么这一组的学习信号就会接近 0。在 MiniMind 这种超小模型上,这个问题尤其明显,所以训练必须限制在合理的能力边界内。
训练方式:
# 方式1
torchrun --nproc_per_node N train_grpo.py
# 方式2
python train_grpo.py训练后的模型权重文件默认每隔
save_interval步保存为:grpo_*.pth

MiniMind 在 GRPO 训练阶段的优化走势
从训练曲线可以看出,GRPO的reward呈现更加稳定的上升趋势,达到4左右,说明GRPO本身能更好地利用RLAIF信号。Policy Loss整体下降平稳,相比PPO的双网络优化,GRPO单网络架构训练更稳定且收敛上限更高。
7.3 Clipped Importance Sampling Policy Optimization
我自己在众多眼花缭乱的XXPO里反而对它印象很深,CISPO没有重新发明一整套复杂框架,而是抓住了 PPO/GRPO 里一个长期让人别扭的问题——ratio 被 clip 之后,梯度流直接就被硬截断了。 CISPO 的关注点并不是重新设计 group baseline,而是用非常小的 loss 改动,更直接地修正这个问题。
CISPO损失:
其中:
- 策略项: (ratio 只作为裁剪后的权重)
- 优势项: (可直接沿用 GRPO 的组内相对优势)
- 正则项: (token级KL散度约束)
CISPO在GRPO基础上,把原本容易被clip成常数的策略项改写成“裁剪权重 × log 概率”的形式。这样ratio即使被截断,也不会把梯度路径一起截断。因此可以直接把CISPO视作GRPO的loss变体来实现,而不是单独维护一套独立脚本。这里不再单列实验。只需在 train_grpo.py 把 loss_type 配置为 cispo,其余训练流程仍沿用 GRPO 的分组采样、奖励计算与优势构造逻辑即可。
7.4 Agentic RL 🔥
“Agentic”的概念其实很大,所以这里说的 Agentic 只能是一个相对狭义的版本:它更聚焦于让 MiniMind 这样的~百M小模型在有限工具集上学会基础的调用、观察与再规划能力,而不是去覆盖完整 Agent 系统里更大范围的状态管理、长期记忆与复杂工作流编排。
2026-03 起,仓库新增 train_agent,开始支持一种更贴近真实交互流程的多轮 Tool-Use RL。这是我自己很喜欢的一个训练脚本:它把 RLVR / RLAIF 风格的数据组织方式与 online RL 的 rollout 过程揉在了一起,中间来回调过很多版,也踩过收敛失败、奖励 hack、多轮上下文错位之类的 bug,最后仍然保持了 MiniMind 一贯的简洁性和可读性。
此部分的数据为 agent_rl.jsonl / agent_rl_math.jsonl。它们相比普通对话数据多了 gt 作为最终校验目标;若把一条样本记作 ,那么训练时优化的对象就不再是单轮回答 ,而是一条多轮轨迹 :
其中 chat_template 统一组织 tools / tool_calls / tool 消息;若某一步生成了 tool_call,就执行工具并把 observation 拼回上下文,再继续 rollout。
主线流程可以压缩成:
reward 也是对整条轨迹联合打分:
这里同时考虑工具调用合法性、gt 命中、格式闭合、未完成惩罚与 Reward Model 分数。和普通 PPO / GRPO 相比,这里是多轮 rollout、延迟 reward。
训练方式:
# ① 默认使用torch做rollout
# 方式1
torchrun --nproc_per_node N train_agent.py
# 方式2
python train_agent.py# ② 使用sglang做rollout
# 需先启动sglang server:
python -m sglang.launch_server --model-path ./minimind-3 --attention-backend triton --host 0.0.0.0 --port 8998
# 训练参数可参考:
python train_agent.py --rollout_engine sglang --sglang_base_url http://localhost:8998 --sglang_shared_path ./ckpt_mm --data_path ../dataset/agent_rl_math.jsonl --use_wandb训练后的模型权重文件默认每隔
save_interval步保存为:agent_*.pth

MiniMind 在 Agentic RL 训练阶段的优化走势
这里顺带提一下 rollout_engine。所谓“训推分离”,就是把 参数更新 和 轨迹展开 拆开:训练侧负责优化 policy,rollout 侧负责高吞吐采样,对上统一表现为“给我 prompt,我返回 rollout 结果;训练完以后,再把新权重同步回来”。因此训练脚本并不需要关心底层到底是本地 generate 还是远端 inference 引擎。需要说明的是,当前实现仍是同步模式(采样完一批再更新),还不是纯 rollout buffer 的异步训练。

MiniMind 中训练侧、轨迹侧与 rollout 侧解耦的 RL 结构示意图
如果类比到更大规模系统里,它其实已经具备 openrlhf/verl/slime 等大规模RL框架的味道:
- 左边是训练侧,负责 policy 更新
- 右边是 rollout / inference 侧,负责吞吐采样
- 中间通过轨迹与权重同步完成衔接
- 工具执行与环境反馈本身不直接进入 loss,但会直接影响整条轨迹的 reward 质量
所以我自己会把这套实现视为 MiniMind 里一个很有意思的过渡版本:虽然还远不是工业级 Agent 训练框架,但已经把 模板组织、工具执行、多轮 rollout、延迟奖励、训推分离 这些关键元素真正实现了最小串联(也许目前没有比它更简洁的了)。
# 测试最终模型 Tool Use 的能力
python eval_toolcall.py --weight agent
💬: 现在几点了?
🧠: <tool_call>{"name": "get_current_time", "arguments": {"timezone": "Asia/Shanghai"}}</tool_call>
📞 [Tool Calling]: get_current_time
✅ [Tool Called]: {"datetime": "2026-03-15 21:22:33", "timezone": "Asia/Shanghai"}
🧠: 现在是2026年3月15日21时22分33秒(北京时间)。
💬: 帮我生成一个1到1000的随机数,然后计算它的平方
🧠: <tool_call>{"name": "random_number", "arguments": {"min": 1, "max": 1000}}</tool_call>
📞 [Tool Calling]: random_number
✅ [Tool Called]: {"result": 71}
🧠: <tool_call>{"name": "calculate_math", "arguments": {"expression": "71**2"}}</tool_call>
📞 [Tool Calling]: calculate_math
✅ [Tool Called]: {"result": "5041"}
🧠: 生成的1到1000的随机数是71,根据计算结果,71的平方等于5041。
基于AgentRL训练结果测试,支持思考展示、工具选择与多轮 Tool Use 交互
🖊️ RL小结
我们收束回“统一框架”:不同 PO 算法本质上只是对三个核心组件的不同实例化,见下表。
| 算法 | 策略项 | 优势项 | 正则项 | 训练模型数 |
|---|---|---|---|---|
| DPO | 无显式优势项 | 隐含在 中 | 1 (前向参与 2) | |
| PPO | 2 | |||
| GRPO | 1 | |||
| CISPO | 1 |
说白了,这些 RL 算法不是割裂独立的,而是在统一优化视角下,对同一目标函数进行不同设计权衡后形成的自然变体,呈现为一种优美自洽的统一。
Ⅴ 训练结果开源 📦
① PyTorch模型 (ModelScope | HuggingFace)
注:模型权重以实际 release 为准。并非所有训练阶段或实验分支(如 DPO、PPO、GRPO、CISPO、Agent、LoRA 等)的权重都会持续维护并单独公开;部分权重仅用于实验验证或学习用途,随着数据迭代或模型调整,逐一同步更新所有版本的必要性有限,且会带来较高的维护与训练成本。
-
Dense:
- Pretrain:
pretrain_{hidden_size}.pth - SFT:
full_sft_{hidden_size}.pth - DPO:
dpo_{hidden_size}.pth - PPO:
ppo_actor_{hidden_size}.pth - GRPO:
grpo_{hidden_size}.pth - Agent:
agent_{hidden_size}.pth - LoRA:
lora_xxx_{hidden_size}.pth
- Pretrain:
-
MoE:
- 对应同名权重在末尾追加了
_moe后缀,例如:pretrain_{hidden_size}_moe.pth、full_sft_{hidden_size}_moe.pth
- 对应同名权重在末尾追加了
② Transformers模型 (ModelScope | HuggingFace)
注:如无特殊说明,
transformers版本通常由full_sft权重转换而来。RL 类后训练更偏向围绕特定奖励目标优化,虽然通常能提升 reward score,但会牺牲部分通用能力和知识;这类 reward hacking / capability trade-off 在所有模型上都很难避免,更多是程度上的差异。
