Fine-tuning a 350M Model for Better Structured Outputs in 100 GRPO Steps
TL;DR · AI 摘要
使用GRPO方法微调350M模型可在100步内提升IFStruct基准性能至29.7%。
核心要点
- GRPO微调使350M模型在IFStruct基准上提升7.1个百分点(22.6%→29.7%)
- TRL库与llama.cpp组合支持免费GPU环境下的高效微调
- MacBook Pro M5 Max可本地运行IFStruct评估服务
结构提纲
按章节快速跳转。
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- GRPO微调实践
- 方法论
- TRL库实现GRPO
- 实验环境
- Colab/Kaggle GPU
- MacBook llama.cpp
- 评估结果
- IFStruct 29.7%得分
金句 / Highlights
值得收藏与分享的关键句。
500样本+100训练步即可在免费GPU上完成微调
llama.cpp支持MacBook本地运行OpenAI兼容服务
BF16 GGUF格式模型在-32768上下文长度下表现最佳
通过 100 步 GRPO 微调 350M 模型以实现更优结构化输出
返回文章列表
[-1
]
[0
发布于 2026 年 9 月 3 日
GitHub 上的更新
赞同
107
[
- +101
Leonie Monigatti
iamleonie
关注
LiquidAI
ben burtenshaw
burtenshaw
Sergio Paniego
sergiopaniego
本指南提供了一种完全公开且成本低廉的方案,使小型模型在结构化输出合规性方面显著提升。我们使用 TRL 库通过 Group Relative Policy Optimization (GRPO) 对 LFM2.5-350M 进行微调,并在 IFStruct 基准测试中进行评估。完整训练过程约需 500 个样本和 100 个训练步骤,规模足够小,可在免费版 Colab 或 Kaggle GPU 上运行,相关代码已开源在 GitHub。结果显示,即使进行轻量级微调,模型在 IFStruct 基准测试中的表现也能从 22.6% 提升至 29.7%。
结构化输出是 LLM 最常见的现实任务之一,但大多数基准测试会将其纳入更广泛的推理或提取评分中,而非单独衡量。模型是否能可靠地返回符合请求格式和形状的有效、可解析输出(即模式合规性),往往是决定其能否接入下游系统的关键因素。
请注意,此处描述的训练流程与 IFStruct 博客中描述的 RL 模型训练流程不同。本笔记本的目标不是重现 IFStruct 基准测试的评分,而是展示如何通过针对特定任务的微调提升小型模型性能,使其达到远超自身规模的大型模型水平。
先决条件
本指南分为两部分,分别在不同环境中运行:
- 微调在 GPU 上执行。配套的笔记本已针对免费版 Colab 或 Kaggle GPU 进行优化。
- 评估可在 MacBook 上本地运行(此处使用配备 Apple M5 Max 和 36 GB 统一内存的 MacBook Pro),通过 llama.cpp 提供的 OpenAI 兼容服务器进行,该服务器由 IFStruct 评估器访问。
我们需要 uv 用于 Python 工具链,以及 llama.cpp 用于服务。按照 Liquid AI 的 llama.cpp 部署文档,通过 Homebrew 安装 llama.cpp 并验证 llama-server 是否可用:
brew install llama.cpp
llama-server --versionLFM2.5-350M(基础模型)的 IFStruct 评估
在开始之前,让我们先在 IFStruct 基准测试中评估 LFM2.5-350M,看看是否能复现报告的 21.1% 评分。
IFStruct 是一个用于测试 LLM 输出有效性和模式遵循度的基准测试。该基准测试在 Liquid4All/ifstruct 中开源,公开的基准测试数据集可在 Hugging Face 的 LiquidAI/ifstruct-v1.0 中获取。
git clone https://github.com/Liquid4All/ifstruct.git为了进行评估比较,我们使用 llama.cpp 在 MacBook 上本地运行模型。我们将使用 BF16 GGUF 格式(LiquidAI/LFM2.5-350M-GGUF)。
然后使用以下命令启动基础模型服务器:
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- --alias:IFStruct 发送到 OpenAI 兼容端点的模型名称
- -ngl 99:要求 llama.cpp 在 GPU 可用时将所有层卸载到 GPU
- -np 4:并行处理四个请求
- -c 32768:提示上下文的大小
服务器启动后,我们可以使用 2000 个样本运行完整的基准测试:
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/lfm2.5-350m-llamacpp-base.json \
--n-threads 4 \
--max-tokens 2048 \
-v============================================================
模型: LiquidAI/LFM2.5-350M
============================================================
总体: 452/2000 通过 (22.6%)
平均延迟: 1453ms
按格式划分:
JSON: 180/1000 通过 (18.0%)
YAML: 272/1000 通过 (27.2%)
按顶层结构划分:
包裹键 288/1011 通过 (28.5%)
纯列表 164/989 通过 (16.6%)
按实体类型划分:
test__camera_review 6/83 通过 (7.2%)
test__clinical_trial 20/104 通过 (19.2%)
test__conference_schedule 7/87 通过 (8.0%)
test__escaping__bug_report_batch 24/89 通过 (27.0%)
test__escaping__config_snippet_audit 15/85 通过 (17.6%)
test__escaping__customer_email_thread 5/73 通过 (6.8%)
test__escaping__dialogue_sample 14/95 通过 (14.7%)
test__escaping__interview_transcript_segment 21/80 通过 (26.2%)
test__escaping__log_parser_examples 21/72 通过 (29.2%)
test__escaping__pr_discussion 22/87 通过 (25.3%)
test__escaping__repro_steps_batch 16/73 通过 (21.9%)
test__escaping__screenplay_scene 16/92 通过 (17.4%)
test__escaping__short_story_chapter 15/84 通过 (17.9%)
test__escaping__support_ticket_batch 27/73 通过 (37.0%)
test__escaping__terminal_session_notes 20/70 通过 (28.6%)
test__event_ticket_booking 49/107 通过 (45.8%)
test__gpu_review 6/94 通过 (6.4%)
test__invoice 28/86 通过 (32.6%)
test__job_posting 25/85 通过 (29.4%)
test__real_estate_listing 31/82 通过 (37.8%)
test__recipe 3/70 通过 (4.3%)
test__rental_car_booking 27/79 通过 (34.2%)
test__scientific_experiment 13/69 通过 (18.8%)
test__travel_itinerary 21/81 通过 (25.9%)
常见错误:
7228x 缺少必填字段
738x 项目数量错误
540x 类型不匹配
317x 未闭合的代码块
190x 多余字段 'notes'
181x 多余字段 'path'
175x 多余字段 'constraints'
170x 多余字段 'type'
170x 缺少代码块
100x 期望纯列表,但得到包装器IFStruct 发布博客中报告 LFM2.5-350M 的得分为 21.1%。我们本地的 llama.cpp/BF16 配置测得 22.6%,与 IFStruct 博客中报告的 21.1% 接近。我们使用这个本地结果作为相同服务堆栈比较的基准。
使用 TRL 对 GRPO 进行结构化输出微调
完整的可运行流水线位于配套的 notebook 中。本节仅涵盖相关内容。
训练数据
我们使用 nvidia/Nemotron-RL-instruction_following-structured_outputs 数据集,该数据集为每个提示配对目标 JSON Schema 和预期字段数量。我们使用约 500 个样本进行训练。
由于 Nemotron 数据分布与 IFStruct 评估存在差异,我们通过增强提示来弥合两者之间的两个差距:
- 40% 的提示附加了 "将输出放入代码块中" 的指令,使模型学会遵循格式指令而非始终输出原始 JSON。
- 20% 的独立数据被转换为顶级数组任务(模式被封装在数组中并指定必需的项目数量),这训练了裸列表输出和项目数量合规性。
模型与 LoRA
我们加载 LiquidAI/LFM2.5-350M 并附加 LoRA 适配器。由于 LFM2.5 使用混合注意力/卷积架构,我们针对 LFM 特定的模块名称:
lora_config = LoraConfig(
r=
16
,
lora_alpha=
32
,
bias=
"none"
,
task_type=
"CAUSAL_LM"
,
target_modules=[
"q_proj"
,
"k_proj"
,
"v_proj"
,
"out_proj"
,
"in_proj"
,
"w1"
,
"w2"
,
"w3"
,
],
)这训练了约 6M 参数,约占模型总参数的 1.66%。
奖励函数
然后我们定义了三个奖励函数,每个函数的评分范围为 [0, 1],用于评估每次生成的结构是否正确:
- json_format_reward : 输出是否可解析且符合要求的格式?若符合要求格式(带代码块或原始格式)得满分(1.0),若格式错误但可解析得 0.2 分,若无法解析得 0 分。
- field_count_reward : 对象是否具有预期数量的顶级字段?完全匹配得 1.0 分,匹配偏差越大得分线性衰减。
- schema_validation_reward : 输出是否符合该行的 JSON Schema?该函数统计所有约束违规项,并根据必需字段覆盖率决定部分得分。
我们将三者按权重求和,权重为 [1.0, 0.5, 2.0]。
训练
我们使用 8 个生成样本/提示组进行 100 步训练,参数规模适配免费层级的 16 GB GPU:
from
trl
import
GRPOConfig
training_args = GRPOConfig(
output_dir=
"./outputs/lfm25-350m-nemotron-schema-grpo"
,
learning_rate=
5e-5
,
max_steps=
100
,
warmup_steps=
10
,
num_generations=
8
,
# 每个提示组生成的样本数
per_device_train_batch_size=
4
,
gradient_accumulation_steps=
8
,
# 每个优化器步骤处理 4 个提示组
steps_per_generation=
2
,
max_completion_length=
1024
,
# 为嵌套 JSON 留出空间
mask_truncated_completions=
False
,
temperature=
1.1
,
# 更热的采样保持组间多样性
beta=
0.01
,
# 向参考模型的 KL 惩罚
reward_weights=[
1.0
,
0.5
,
2.0
],
# JSON 格式、字段数量、Schema 验证
logging_steps=
1
,
save_steps=
100
,
)如笔记本所示,在训练过程中,所有三个奖励组件得分均上升,参考模型的 KL 散度在预热后脱离零点,截断完成比例始终保持在接近零的水平。
合并与保存模型
最后,我们将 LoRA 适配器重新合并到基础权重中,并保存为一个自包含的检查点,准备转换为 GGUF 格式进行服务:
MERGED_DIR =
f"
{training_args.output_dir}
-merged"
merged_model = trainer.model.merge_and_unload()
merged_model.save_pretrained(MERGED_DIR)
tokenizer.save_pretrained(MERGED_DIR)GRPO 微调后的 LFM2.5-350M 上的 IFStruct 评估
完成 GRPO 微调后,我们重新运行 IFStruct 评估。为此,需要将合并后的模型检查点转换为 BF16 GGUF 格式。转换脚本包含在 llama.cpp 源码中,因此我们只需克隆仓库一次并安装 gguf 包:
git clone --depth 1 https://github.com/ggml-org/llama.cpp
pip install ./llama.cpp/gguf-py
mkdir -p models
python llama.cpp/convert_hf_to_gguf.py \
PATH_TO_YOUR_MERGED_MODEL \
--outfile ./models/lfm25-350m-grpo-bf16.gguf \
--outtype bf16然后使用以下命令启动合并后的模型服务:
llama-server \
-m ./models/lfm25-350m-grpo-bf16.gguf \
--alias lfm25-350m-grpo-structured-output \
-c 32768 \
-np 4 \
-ngl 99 \
--host 127.0.0.1 \
--port 8081然后,我们将使用微调后的模型再次运行完整的 IFStruct 评估:
uv run ifstruct-eval \
--model lfm25-350m-grpo-structured-output \
--base-url http://localhost:8081/v1 \
--api-key dummy \
--dataset data/test.jsonl \
--results-file results/lfm25-350m-grpo.json \
--n-threads 4 \
--max-tokens 2048 \
-v============================================================
模型: lfm25-350m-grpo-structured-output
============================================================
总体: 594/2000 通过 (29.7%)
平均延迟: 1518ms
按格式分类:
JSON: 319/1000 通过 (31.9%)
YAML: 275/1000 通过 (27.5%)
按顶层结构分类:
包裹键 300/1011 通过 (29.7%)
纯列表 294/989 通过 (29.7%)
按实体类型分类:
test__camera_review 5/83 通过 (6.0%)
test__clinical_trial 31/104 通过 (29.8%)
test__conference_schedule 11/87 通过 (12.6%)
test__escaping__bug_report_batch 32/89 通过 (36.0%)
test__escaping__config_snippet_audit 24/85 通过 (28.2%)
test__escaping__customer_email_thread 9/73 通过 (12.3%)
test__escaping__dialogue_sample 17/95 通过 (17.9%)
test__escaping__interview_transcript_segment 13/80 通过 (16.2%)
test__escaping__log_parser_examples 33/72 通过 (45.8%)
test__escaping__pr_discussion 26/87 通过 (29.9%)
test__escaping__repro_steps_batch 23/73 通过 (31.5%)
test__escaping__screenplay_scene 34/92 通过 (37.0%)
test__escaping__short_story_chapter 24/84 通过 (28.6%)
test__escaping__support_ticket_batch 36/73 通过 (49.3%)
test__escaping__terminal_session_notes 23/70 通过 (32.9%)
test__event_ticket_booking 62/107 通过 (57.9%)
test__gpu_review 7/94 通过 (7.4%)
test__invoice 36/86 通过 (41.9%)
test__job_posting 33/85 通过 (38.8%)
test__real_estate_listing 32/82 通过 (39.0%)
test__recipe 7/70 通过 (10.0%)
test__rental_car_booking 37/79 通过 (46.8%)
test__scientific_experiment 14/69 通过 (20.3%)
test__travel_itinerary 25/81 通过 (30.9%)
常见错误:
7331x 缺少必填字段
890x 项目数量错误
555x 类型不匹配
102x 期望纯列表,得到包裹结构
62x 多余字段 'metadata.tone'
55x 6 大于最大值 5
49x 多余字段 'speaker_labels'
47x 多余字段 'tone'
44x 'cups' 不在允许值 ['mg', 'g', 'kg', 'oz', 'lb', 'ml', 'l', 'cl', 'dl'
44x 多余字段 'notes'在相同服务栈上的两次运行对比:
IFStruct 组
基准
GRPO 微调
Δ
总体
22.6%
29.7%
+7.1
JSON
18.0%
31.9%
+13.9
YAML
27.2%
27.5%
+0.3
包裹键
28.5%
+1.2
纯列表
16.6%
+13.1
提升效果完全符合训练目标:JSON 通过率提升近 14 个百分点(18.0% → 31.9%),而 YAML 基本保持不变。尽管这仍然低于 Qwen3.5-2B 的 33.15% 分数,但表明即使是轻量级任务特定微调,也能让小型模型接近大型模型的表现。
结论
A short GRPO run with about 500 samples and 100 steps can lift a small 350M parameter model from 22.6% to 29.7% on IFStruct. The takeaway is that a cheap, task-specific reward signal can make a small model substantially more reliable about form , closing much of the gap to models several times its size.
To reproduce or extend this work, see the original IFStruct v1.0 blog post , the Liquid4All/ifstruct benchmark repo, and the LiquidAI/ifstruct-v1.0 dataset.
Models mentioned in this article 2
Datasets mentioned in this article 2
More Articles from our Blog
nlp
guide
community
使用Sentence Transformers进行多向量嵌入模型的训练和微调
128
2026年8月26日
使用Sentence Transformers的多向量(晚期交互)嵌入模型
111
2026年8月18日
社区
helpmehere
9天前
[2
这是我们的安全扫描测试评论
查看翻译
回复
未提供描述。
x0mhb
8天前
clickme
编辑
预览
通过将文件拖拽到文本输入框、粘贴或
点击此处
上传图片、音频和视频。
轻点或粘贴此处上传图片
评论
· 注册或登录以发表评论
- +95