AI PLAY玩法更新持续收录
← 回到更新流

2026-09-03 · 原文发布日

100 步 GRPO 微调让小模型输出更守格式

一篇可复现的微调教程:用 TRL 库的 GRPO 算法在约 500 条样本、100 步训练内微调 350M 小模型 LFM2.5-350M,专门提升结构化输出的格式合规率,作者称 IFStruct 基准通过率从 22.6% 升到 29.7%。训练量小到可在 Colab 或 Kaggle 免费 GPU 上跑,评测用 llama.cpp 在本地起 OpenAI 兼容服务,代码已在 GitHub 公开。

原文要点

Hugging Face 博客公开了一套廉价可复现流程:用 TRL 的 GRPO 在 nvidia/Nemotron-RL-instruction_following-structured_outputs 约 500 条样本、100 步内微调 LiquidAI/LFM2.5-350M,作者称训练量适配免费档 Colab/Kaggle 的 16GB GPU。训练侧为 LoRA(r=16,目标模块为 LFM 特有的 q/k/v/out_proj、in_proj、w1/w2/w3,约 6M 参数),三个奖励函数按 [1.0, 0.5, 2.0] 加权,分别打分可解析性与 fenced/raw 形式、顶层字段数、JSON Schema 校验。评测侧在本机用 llama.cpp 起 OpenAI 兼容服务跑 IFStruct:作者本地基线 22.6%(博客报告 21.1%),微调后 29.7%,其中 JSON 18.0%→31.9%、裸列表 16.6%→29.7%,YAML 基本持平。作者说明该流程并非 IFStruct RL 模型的训练管线,也不旨在复刻官方分数。

这次做成什么

在本机(Mac 或其他能跑 llama.cpp 的机器)+免费 GPU 笔记本的组合上,按“先测基线→小规模 GRPO→合并转 GGUF→同栈复测对比”的顺序跑通一遍,理解任务型奖励如何只提升格式合规率而不动其他能力;本站另给一个 20 步的缩减练习配置用于快速验证流程。

准备什么

  • 一台可运行 llama.cpp 的本机(作者示例为 MacBook,用 Homebrew 安装),磁盘能放下约 1GB 级 GGUF 模型
  • 免费档 Colab 或 Kaggle GPU 笔记本(按作者说法 16GB 显存可跑原配置),并安装 trl、peft、transformers、datasets
  • uv 用于运行 IFStruct 评测器,llama.cpp 源码用于 HF→GGUF 转换
  • Hugging Face 账号以下载 LiquidAI/LFM2.5-350M、LiquidAI/LFM2.5-350M-GGUF 与 LiquidAI/ifstruct-v1.0 数据集
  • Git,用于克隆 Liquid4All/ifstruct 与 ggml-org/llama.cpp

具体怎么做

  1. 装评测环境

    本机执行 brew install llama.cpp 并用 llama-server --version 验证;git clone https://github.com/Liquid4All/ifstruct.git 获取评测器与 data/test.jsonl。

  2. 起基线模型服务并测基线

    执行:llama-server -hf LiquidAI/LFM2.5-350M-GGUF:BF16 -c 32768 -np 4 -ngl 99 --alias LiquidAI/LFM2.5-350M --host 127.0.0.1 --port 8080。随后 uv run ifstruct-eval --model LiquidAI/LFM2.5-350M --base-url http://localhost:8080/v1 --api-key dummy --dataset data/test.jsonl --results-file results/base.json --n-threads 4 --max-tokens 2048 -v,对 2000 条样本得基线分数。

  3. 准备训练数据与增强

    在 GPU 笔记本加载 nvidia/Nemotron-RL-instruction_following-structured_outputs 约 500 条;按原文做法给 40% 样本追加“在围栏代码块中返回输出”指令,另把不相交的 20% 改写为顶层数组任务(包裹 schema 并规定 item count),以覆盖 IFStruct 的 fenced 形式与裸列表两类要求。可用下方 JSONL 样例先核对字段结构。

  4. LoRA + GRPO 训练

    加载 LiquidAI/LFM2.5-350M 并挂 LoRA:r=16、alpha=32、target_modules 用 LFM 特有的 q_proj/k_proj/v_proj/out_proj/in_proj/w1/w2/w3。定义三个 [0,1] 奖励(可解析且形式正确、顶层字段数匹配、schema 校验),按 [1.0, 0.5, 2.0] 加权。GRPOConfig 关键项:learning_rate=5e-5、max_steps=100、num_generations=8、per_device_train_batch_size=4、gradient_accumulation_steps=8、max_completion_length=1024、temperature=1.1、beta=0.01。先跑流程验证时可用下方 20 步缩减配置。

  5. 合并并转 GGUF

    trainer.model.merge_and_unload() 后用 save_pretrained 保存合并权重与 tokenizer;本机 git clone --depth 1 llama.cpp 并 pip install ./llama.cpp/gguf-py,再 python llama.cpp/convert_hf_to_gguf.py <合并目录> --outfile ./models/lfm25-350m-grpo-bf16.gguf --outtype bf16。

  6. 同栈复测并对比

    用 llama-server -m ./models/lfm25-350m-grpo-bf16.gguf --alias lfm25-350m-grpo --port 8081(其余参数同基线)起服务,同一 ifstruct-eval 命令换 --model 与 --base-url 复测 2000 条;与基线按 Overall/JSON/YAML/Wrapper/Bare list 五行对比,预期增益集中在 JSON 与裸列表。

可直接复制的材料

Nemotron 风格合成训练样本 JSONL(3 行,含两种增强)

本站虚构的练习样例,仅仿照原文描述的数据结构(prompt+schema+期望字段数),不是 Nemotron 数据集原文;真实训练请下载原始数据集再做 40%/20% 增强。

{"prompt": "根据给定 schema 输出一条相机评测记录。请把输出放在 ```json 围栏代码块中返回。", "schema": {"type": "object", "required": ["model", "score", "pros"], "properties": {"model": {"type": "string"}, "score": {"type": "integer", "minimum": 1, "maximum": 10}, "pros": {"type": "array", "items": {"type": "string"}}}}, "expected_field_count": 3, "format": "fenced"}
{"prompt": "根据给定 schema 输出一张会议日程。直接输出原始 JSON,不要代码块。", "schema": {"type": "object", "required": ["title", "date", "sessions"], "properties": {"title": {"type": "string"}, "date": {"type": "string"}, "sessions": {"type": "array", "items": {"type": "object"}}}}, "expected_field_count": 3, "format": "raw"}
{"prompt": "输出恰好 2 条物流单记录,结果为顶层 JSON 数组,每项符合给定 schema。", "schema": {"type": "array", "minItems": 2, "maxItems": 2, "items": {"type": "object", "required": ["tracking_id", "status"], "properties": {"tracking_id": {"type": "string"}, "status": {"enum": ["pending", "shipped", "delivered"]}}}}, "expected_item_count": 2, "format": "bare_list"}

缩减验证用 GRPO 练习配置(本站设计)

本站为快速跑通流程设计的缩减配置,参数语义沿用原文 GRPOConfig;它不对应原文 100 步结果,预期收益会明显低于原文数字。原文完整参数为 max_steps=100、num_generations=8、batch_size=4、accumulation=8、max_completion_length=1024。

{
  "output_dir": "./outputs/practice-grpo-smoke",
  "learning_rate": 5e-5,
  "max_steps": 20,
  "warmup_steps": 2,
  "num_generations": 4,
  "per_device_train_batch_size": 2,
  "gradient_accumulation_steps": 4,
  "steps_per_generation": 2,
  "max_completion_length": 512,
  "temperature": 1.1,
  "beta": 0.01,
  "reward_weights": [1.0, 0.5, 2.0],
  "logging_steps": 1,
  "save_steps": 20
}

输入与输出示例

本站虚构演示,模型输出与打分为手工示意,非实际训练或调用结果。

输入

第 3 行样本:prompt 要求“输出恰好 2 条物流单记录,结果为顶层 JSON 数组”,schema 为 minItems=2/maxItems=2、每项必填 tracking_id 与 status(status 枚举 pending/shipped/delivered)

对应输出

一条应被判为合规的模型输出示意(raw 顶层数组,无围栏、无多余字段):
[{"tracking_id": "SF20260903001", "status": "shipped"}, {"tracking_id": "SF20260903002", "status": "pending"}]
按三个奖励打分:可解析且为要求的裸数组形式得 1.0;项数恰为 2 得 1.0;两项均含必填字段且 status 在枚举内,schema 校验得 1.0;加权总分 = 1.0*1.0 + 1.0*0.5 + 1.0*2.0 = 3.5。

做完检查这些

  • llama-server --version 有输出,基线服务在 127.0.0.1:8080 起来后 ifstruct-eval 能跑完 2000 条并生成 results/base.json
  • 基线总分应与作者本地 22.6% 或博客 21.1% 处于相近量级;若差异巨大,先查 GGUF 量化类型与 -c/-ngl 参数而非怀疑流程
  • 训练日志中三个奖励分量随步数上升、截断完成比例接近零(作者在 notebook 中的观察),否则先查数据增强与 max_completion_length
  • 微调后与基线在同一 llama.cpp 栈上复测,增益应集中在 JSON 与 Bare list 两行;YAML 持平属原文观察到的正常现象

容易卡住的地方

  • 原文 22.6%→29.7% 是作者在特定 llama.cpp/BF16 栈与 100 步配置下测得的数字,不是任何环境下都能复现的保证
  • LoRA 的 target_modules 是 LFM2.5 混合架构特有名称,换其他模型必须改为对应模块名,否则适配器不生效
  • convert_hf_to_gguf.py 需先安装 llama.cpp 仓库内的 gguf-py 包,直接用 pip 装 gguf 同名包可能版本不符
  • 作者明确说明该 notebook 不是 IFStruct RL 模型的训练管线,也不以复刻官方基准分数为目标,对比时应引用同一服务栈下的本地基线
  • 缩减练习配置只是跑通流程,不能拿它的小幅提升与原文 100 步结果直接比较

原文与作者资料 ↗

本站借助AI整理来源事实,独立编写中文操作指南与示例。示例不代表实际运行结果;来源效果尚未由本站独立复现。付费模板与模型、服务费用分别计算。

原文标题
Fine-tuning a 350M Model for Better Structured Outputs in 100 GRPO Steps
原作者
Hugging Face / 文章署名作者
原始发布日期
2026-09-03T00:00:00+00:00
来源
https://huggingface.co/blog/grpo-with-trl-ifstruct