open-r1 训练代码逐文件解析版本基于huggingface/open-r1主分支公开仓库整理目标讲清楚 open-r1 的目录结构、主调用链、关键脚本职责以及它与 DeepSeek-R1 官方论文之间的对应关系。1. open-r1 是什么open-r1 的官方定位很明确A fully open reproduction of DeepSeek-R1README 写得很直接这个项目的目标是把 R1 管线里官方没有完整公开的部分补出来让社区可以在透明环境中复现并扩展 R1 路线。README 对工程主体的概括也很清楚src/open_r1/grpo.py做 GRPO 强化学习训练src/open_r1/sft.py做监督微调src/open_r1/generate.py做 synthetic data generationMakefile把安装、开发检查等常用动作简化recipes/存各种可直接运行的配置slurm/集群训练与评测脚本因此open-r1 本质上是一套“R1-like post-training 工程框架”而不是只放论文结论的模型仓库。2. 根目录结构总览根据当前仓库结构根目录可以抽象成下面这样open-r1/ ├── Makefile ├── README.md ├── recipes/ ├── scripts/ ├── slurm/ ├── src/open_r1/ │ ├── configs.py │ ├── sft.py │ ├── grpo.py │ ├── generate.py │ ├── rewards.py │ └── utils/ └── tests/理解这个结构最关键的是src/open_r1/训练逻辑核心recipes/实验配置与配方slurm/分布式/多节点调度utils/数据、模型、评测、回调、代码执行沙盒等公共组件3. 整体调用链从 README 到训练入口open-r1 README 已经把三条主线写得很清楚3.1 SFT 主线accelerate launch--config_filerecipes/accelerate_configs/zero3.yaml\src/open_r1/sft.py\--configrecipes/OpenR1-Distill-7B/sft/config_distill.yaml3.2 GRPO 主线accelerate launch--config_filerecipes/accelerate_configs/zero3.yaml\src/open_r1/grpo.py\--configrecipes/DeepSeek-R1-Distill-Qwen-1.5B/grpo/config_demo.yaml\--vllm_modecolocate3.3 生成数据主线generate.py负责把模型接成一个 Distilabel pipeline批量生成训练数据或蒸馏 traces。所以真正的工程骨架是recipes/*.yaml - TrlParser 解析 - configs.py dataclass - sft.py / grpo.py / generate.py - utils/*5. Makefile最小开发入口Makefile很短但很实用。它主要负责install初始化uv环境、安装 vLLM、flash-attn、开发依赖style格式化quality静态检查。代码不复杂但有两个工程含义5.1 它把 repo 默认的开发环境固定下来了例如Python 3.11vllm0.8.5.post1flash-attn.[dev]开发依赖安装方式5.2 它强调了 repo 是“可开发工程”不是只读仓库你不是只把它当模型说明看而是按这个 Makefile 去建环境和开发。6. recipes/把“实验思想”具体化成配置recipes/是 open-r1 非常关键的目录。它把抽象训练思想变成了“某个模型 某个任务 某个配置”的可运行配方。README 和 recipes 目录显示当前比较重要的条目包括OpenR1-Distill-7B/sftDeepSeek-R1-Distill-Qwen-1.5B/grpoQwen2.5-1.5B-Instruct/grpoQwen2.5-Coder-7B-Instruct/grpoaccelerate_configsdataset_filtering从工程视角看recipes/解决的是同一套训练脚本如何快速切换不同实验配置。也就是说open-r1 的脚本本身是“通用训练引擎”而 recipes 才是“具体实验实例”。7. slurm/多节点训练与评测调度层README 说明open-r1 提供了slurm/train.slurm来启动训练作业。其意图不是重写训练逻辑而是把下面这些因素统一起来节点数data parallel / tensor parallelvLLM server 节点训练节点benchmark 节点accelerate/deepspeed/fsdp 等不同后端这意味着 open-r1 的系统分层是训练逻辑层src/open_r1/*.py 实验配置层recipes/*.yaml 集群调度层slurm/*.slurm这是一个非常标准、也很适合大模型工程扩展的分层方式。8. src/open_r1/configs.py参数系统中枢configs.py是 open-r1 的参数核心几乎所有脚本都会经过它。8.1 ScriptArguments统一数据集入口ScriptArguments继承自trl.ScriptArguments但扩展了一个很重要的能力支持 dataset mixture。它允许你不是只加载单个数据集而是把多个数据集按权重、列配置、随机种子混合起来。这对 reasoning 训练非常关键因为真实训练往往不会只喂一个纯数学集而会混合mathcodesciencereasoning QAsynthetic traces所以它是 open-r1 能够做“多源 reasoning 训练”的前提。8.2 GRPOConfig / SFTConfig训练配置扩展GRPOConfig继承trl.GRPOConfig增加了benchmarkscallbackschat_templatehub_model_revisionsystem_promptwandb_*SFTConfig继承trl.SFTConfig也加入了类似字段。它们的意义是TRL 自带 config 只管“训练”open-r1 额外把“评测、回调、Hub 推送、聊天模板、wandb 记录”统一纳入配置层。8.3 GRPOScriptArguments把 reward 系统参数化GRPOScriptArguments是 GRPO 专属参数集。它最关键的一点是reward 几乎都通过名字字符串来启用例如accuracyformatreasoning_stepscosinerepetition_penaltylengthtag_countcodeioi_codecode_formatsoft_overlong_punishment这意味着grpo.py本身不硬编码某一类 reward真正的 reward 组合是在配置层决定的同一套 GRPOTrainer 可以很容易切到 math/code/format 等不同任务。9. src/open_r1/sft.py监督微调主入口sft.py的职责非常纯粹把一个 base model 用某个 reasoning 数据集做 SFT。9.1 它导入了什么核心导入包括ScriptArguments, SFTConfigget_dataset, get_model, get_tokenizerget_callbacksinit_wandb_trainingtrl.SFTTrainer这说明 sft.py 本身更像“装配脚本”而不是把所有逻辑写死在一个文件里。9.2 主流程sft.py的主流程大致是解析参数 - 设置日志与 seed - 检查 checkpoint - 初始化 wandb - 加载 dataset - 加载 tokenizer - 加载 model - 如果 tokenizer 没有 chat_template则默认切到 ChatML - 初始化 SFTTrainer - train() - save / log / push / benchmark9.3 为什么要自动处理 chat templatesft.py里有一段很关键的逻辑如果 tokenizer 没有 chat template就默认使用 ChatML如果用户在 config 里传入了chat_template则会覆盖 tokenizer 原配置。这很重要因为 reasoning SFT 极度依赖消息格式是否一致。如果训练时模板和推理时模板不一致很容易出现EOS 位置错乱think与 answer 区域错位训练损失正常但推理风格异常。9.4 SFTTrainer 初始化sft.py的核心调用非常直接trainerSFTTrainer(modelmodel,argstraining_args,train_dataset...,eval_dataset...,processing_classtokenizer,peft_configget_peft_config(model_args),callbacksget_callbacks(training_args,model_args),)所以训练主体直接委托给 TRLopen-r1 主要负责把数据、模型、模板、回调这些周边条件装配好。10. src/open_r1/grpo.py强化学习主入口grpo.py是整个 open-r1 最核心的文件之一。10.1 它导入了什么关键依赖包括GRPOConfig, GRPOScriptArgumentsget_reward_funcsget_dataset, get_model, get_tokenizerget_callbacksinit_wandb_trainingtrl.GRPOTrainer这说明它的架构和sft.py类似也是在做“高层 orchestration”。10.2 数据流转主线grpo.py的流程大致可以概括为解析参数 - 设 seed / 日志 - 检查 checkpoint - 初始化 wandb - 加载 dataset - 加载 tokenizer - 加载 model - 从 rewards.py 注册 reward_funcs - 把样本转成 conversation 格式 - 初始化 GRPOTrainer - train() - 保存模型 / 指标 / 状态 - 可选 benchmark / Hub push10.3 conversation 格式构造grpo.py里有一个make_conversation()它会把数据集中的 prompt 字段包装成[{role:system,content:training_args.system_prompt},# 可选{role:user,content:example[prompt_column]}]然后把原始messages列移除。这说明 open-r1 在 GRPO 阶段默认也是按“chat conversation”格式训练而不是把 prompt 当作一段裸文本。10.4 奖励函数的注册方式grpo.py本身不关心 reward 细节它只做一件事reward_funcsget_reward_funcs(script_args)然后把结果喂给trainerGRPOTrainer(modelmodel,reward_funcsreward_funcs,argstraining_args,train_dataset...,eval_dataset...,peft_config...,callbacks...,processing_classtokenizer,)这种设计的优点很明显reward 可以自由组合trainer 主循环不用重写同一套训练框架可服务于 math、code、format、length-control 等不同任务。10.5 它与官方 R1 的对应关系grpo.py不是官方 R1 训练代码但它是一个很合理的“公开复现版本”官方论文说R1-Zero / R1 的关键是 GRPO 可验证奖励open-r1 把这件事落成了GRPOTrainer rewards.py dataset/config system。所以如果你要“学 R1 工程怎么搭”grpo.py是最值得读的入口之一。11. src/open_r1/rewards.py整个项目最有价值的文件之一如果说grpo.py是训练调度中心rewards.py就是 open-r1 的任务语义中心。它的作用是定义各类 reward function把 reward 做成可注册组件支持 math / code / format / length / repetition 等不同方向让 GRPO 训练从“纯算法”变成“可定制任务系统”。11.1 accuracy_rewardaccuracy_reward()的作用是校验 completion 是否与 ground truth 一致。对数学类任务它会做解析、归一化和答案提取而不是简单字符串匹配。这说明 open-r1 非常重视“可验证 reward”的鲁棒性。11.2 format_reward / reasoning_steps_reward这些 reward 用来鼓励输出符合预期格式显式出现推理步骤reasoning block 足够规范。它们是复现 R1 “先 reasoning、后 answer”风格的重要抓手。11.3 cosine / repetition_penalty / length / tag_count这些 reward 更像“行为整形器”cosine对答案正确/错误时的长度收益做缩放repetition_penalty减少重复 n-gramlength控制 completion 长度tag_count约束标签结构soft_overlong_punishment惩罚过长输出。这些都非常符合 reasoning-RL 的实际工程需求不是只看答对还要控制“怎么答”。11.4 code / binary_code / ioi_code / cf_code这是 open-r1 很强的一部分它把 code reasoning 奖励也工程化了。code_reward执行代码并按通过率/结果打分binary_code_reward把连续 reward 二值化ioi_code偏 IOI 风格cf_code偏 Codeforces 风格code_format检查代码格式。11.5 reward 注册表get_reward_funcs()里维护一个REWARD_FUNCS_REGISTRY。这是整个文件最重要的结构之一。它把 reward 名称映射到具体函数或带 partial 包装的函数。这带来的好处是配置文件里写名字即可启用同一 reward 可以通过 script args 注入参数math/code/task-specific reward 能在一个统一系统里共存。如果你要给 open-r1 加自定义任务通常第一步就是改这个注册表。12. src/open_r1/generate.py生成 synthetic data 的入口generate.py的定位非常清楚不是训练而是用模型去生成训练数据。12.1 它依赖 Distilabel文件顶部直接使用distilabel.llms.OpenAILLMdistilabel.pipeline.Pipelinedistilabel.steps.tasks.TextGeneration这意味着 generate.py 的职责是把一个兼容 OpenAI API 的模型服务包装成一个批量生成数据的流水线。12.2 build_distilabel_pipeline()这个函数会接收model 名称base_urlprompt_columnprompt_templatetemperature / top_pmax_new_tokensnum_generationsinput_batch_sizeclient_replicastimeout / retries然后创建一个 DistilabelPipeline().ray()内部挂一个TextGeneration步骤。它的工程意义是把“单次模型推理”提升为“可分布式、可批处理、可复制的 synthetic data generation 管线”。12.3 generate.py 的场景它很适合用于从 DeepSeek-R1 或其他 teacher 生成 reasoning traces构建蒸馏数据做 rejection sampling 前的多样本采样大规模 prompt - response 数据生产。13. src/open_r1/utils/data.py数据加载与混合集data.py的核心函数是get_dataset(args)。它支持两种模式13.1 单数据集模式如果只设置dataset_name就直接datasets.load_dataset(...)。13.2 dataset mixture 模式如果配置了dataset_mixture它会读取多个数据集配置按配置加载记录列与权重进行混合与拼接。这对 reasoning 训练尤其关键。因为现实中的推理训练几乎都会混合纯数学代码题科学问答synthetic traces过滤后的教师推理数据所以data.py虽短但它支撑了 open-r1 的“多源数据喂养能力”。14. src/open_r1/utils/model_utils.py模型与 tokenizer 装配器这个文件的核心函数有两个14.1 get_tokenizer()主要职责AutoTokenizer.from_pretrained(...)按配置覆盖chat_template这非常关键因为 open-r1 的训练高度依赖 prompt/message 模板的一致性。14.2 get_model()主要职责解析torch_dtype处理 quantization config组织model_kwargs按需设置use_cache通过AutoModelForCausalLM.from_pretrained(...)加载模型这里能看到一个细节如果开启gradient_checkpointing则会把use_cacheFalse这很常见因为 checkpointing 与 KV cache 通常不同时用于训练。所以model_utils.py是一个典型的“训练端模型装配层”。15. src/open_r1/utils/callbacks.py训练后处理与自动动作callbacks.py的价值在于它让训练脚本不只会“train”还会在保存节点上自动触发附加动作。15.1 PushToHubRevisionCallback这是最重要的 callback 之一。它会在on_save()时把当前 checkpoint 以新 revision 的形式推到 Hub。也就是说训练过程中的 checkpoint 不只是本地文件夹还可以变成 Hub 上可追踪的版本。15.2 get_callbacks()get_callbacks(train_config, model_config)会按配置名称把 callback 实例化出来。这延续了 open-r1 的一贯风格行为不写死在训练脚本里统一由 config 决定。这对大规模实验管理非常有用。16. src/open_r1/utils/evaluation.py训练后的 benchmark 启动器evaluation.py的目标不是自己计算所有 benchmark而是负责发起 benchmark 作业。run_benchmark_jobs()会读取training_args.benchmarks判断 benchmark 名称是否合法对支持的 benchmark 调用对应评测作业通过 slurm / subprocess 等方式发起实际评测这说明 open-r1 的 benchmark 设计是“异步作业型”而不是“trainer 内嵌评测型”。优点是训练脚本保持简洁benchmark 可以独立扩展更适合多节点/多模型实验。17. src/open_r1/utils/code_providers.py把代码奖励真正落地这是 open-r1 很“工程”的一个文件。做 code reward 时不能只看字符串要真的执行代码。这个文件把执行后端抽象成 provider。17.1 get_provider()当前公开代码能看到它支持e2bmorph这说明 open-r1 的目标不是把代码奖励锁死在一个沙盒服务上而是做成可替换后端。17.2 E2BProviderE2BProvider支持两种模式直接异步沙盒执行通过 router 批量执行。它会处理并发控制timeoutrequest timeout异常回退sandbox 关闭。这说明 open-r1 的 code reward 不是“概念 demo”而是真在考虑大规模训练中的执行开销与稳定性。17.3 MorphProviderMorphProvider也承担类似职责只是换了另一套沙盒后端。这种 provider abstraction 对于复杂 code-RL 非常有必要。18. src/open_r1/utils/wandb_logging.py极简但必要这个文件非常短只负责把WANDB_ENTITYWANDB_PROJECTWANDB_RUN_GROUP从训练参数写入环境变量。虽然简单但它把训练记录的组织规则统一到了配置层。19. 一条完整的 SFT 调用链把 open-r1 的 SFT 路线串起来可以写成recipes/.../sft/config_*.yaml - TrlParser - configs.py (ScriptArguments SFTConfig ModelConfig) - utils/data.py::get_dataset - utils/model_utils.py::get_tokenizer - utils/model_utils.py::get_model - sft.py::setup_chat_format (必要时) - trl.SFTTrainer - callbacks.py / evaluation.py / hub push你在改 SFT 任务时一般会动的地方是recipe yamldataset source / mixturechat template / eos tokenbenchmark 配置20. 一条完整的 GRPO 调用链GRPO 主线则是recipes/.../grpo/config_*.yaml - TrlParser - configs.py (GRPOScriptArguments GRPOConfig ModelConfig) - utils/data.py::get_dataset - utils/model_utils.py::get_tokenizer - utils/model_utils.py::get_model - rewards.py::get_reward_funcs - grpo.py::make_conversation - trl.GRPOTrainer - callbacks / evaluation / hub push真正决定训练语义的不是GRPOTrainer本身而是数据集长什么样prompt 列选哪列chat template 怎么设置reward 组合怎么配是否加入 code sandbox / language consistency / format reward这也是 open-r1 最值得参考的地方。21. open-r1 与 DeepSeek-R1 官方路线的关系要避免一个常见误区open-r1 不是 DeepSeek 官方 R1 训练代码的镜像。它更像是以 DeepSeek-R1 论文为指导思想用 Hugging Face / TRL / vLLM / Distilabel 生态重构出一条“足够透明、足够可扩展”的复现路线。因此两者关系是DeepSeek 官方回答“R1 为什么有效”open-r1回答“R1 风格工程怎么搭起来”22. 你读 open-r1 时最应该先读哪几个文件如果你时间有限建议这个顺序第一层先看路线README.mdrecipes/README.md一个具体 recipe yaml第二层看训练入口src/open_r1/sft.pysrc/open_r1/grpo.py第三层看训练语义src/open_r1/configs.pysrc/open_r1/rewards.py第四层看公共装配src/open_r1/utils/data.pysrc/open_r1/utils/model_utils.pysrc/open_r1/utils/callbacks.pysrc/open_r1/utils/evaluation.py第五层看扩展场景src/open_r1/generate.pysrc/open_r1/utils/code_providers.py23. 如何基于 open-r1 改你自己的任务如果你想把 open-r1 改成自己的 reasoning 项目通常最实用的改法是23.1 换数据改dataset_name或dataset_mixture23.2 换 prompt 字段改dataset_prompt_column23.3 换模板改chat_template/eos_token23.4 换奖励在rewards.py新增函数并注册到REWARD_FUNCS_REGISTRY23.5 换评测在evaluation.py加 benchmark 入口23.6 换代码执行后端扩展code_providers.py所以 open-r1 的扩展点设计是比较清晰的。24. 总结open-r1 最值得学习的不只是“复现了 R1”而是它把 reasoning post-training 拆成了 5 个彼此清晰解耦的层配置层 数据层 模型装配层 奖励层 训练入口层 调度 / 评测 / Hub 层如有解释错误请指正25. 参考资料open-r1 官方仓库https://github.com/huggingface/open-r1DeepSeek-R1 官方仓库https://github.com/deepseek-ai/DeepSeek-R1DeepSeek-R1 论文https://arxiv.org/abs/2501.12948DeepSeek-V3 官方仓库https://github.com/deepseek-ai/DeepSeek-V3