通过100步GRPO微调350M模型以获得更好的结构化输出

Hugging Face Blog 新闻

摘要

本指南详细介绍了使用TRL库中的Group Relative Policy Optimization(GRPO)进行微调的方案,以提升LFM2.5-350M模型的结构化输出合规性,将IFStruct基准性能从22.6%提高到29.7%。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/09/03 11:49

使用GRPO在100步内微调350M模型以获得更优的结构化输出

来源:https://huggingface.co/blog/grpo-with-trl-ifstruct 本指南提供了一套完全公开且低成本的方案,旨在显著提升小型模型在结构化输出合规性方面的表现。我们将使用TRL库(https://huggingface.co/docs/trl/en/index)通过群组相对策略优化对LFM2.5-350M模型(https://huggingface.co/LiquidAI/LFM2.5-350M)进行微调,并在IFStruct基准测试(https://huggingface.co/datasets/LiquidAI/ifstruct-v1.0)上进行评估。整个流程仅需约500个样本和100个训练步骤,规模足够在免费版Colab或Kaggle GPU上运行,相关代码已发布在GitHub(https://github.com/Liquid4All/cookbook/blob/main/finetuning/notebooks/grpo_with_trl_ifstruct.ipynb)。结果表明,即使是轻量级的微调流程也能在IFStruct基准测试中将性能从22.6%提升至29.7%。

结构化输出是大语言模型最常见的实际任务之一,然而大多数基准测试将其纳入更广泛的推理或提取分数中,而非单独进行评估。模型能否以请求的格式和结构稳定返回有效、可解析的输出——即模式合规性——往往决定了它能否被接入下游系统。

请注意,本文描述的训练流程并非用于训练IFStruct博客(https://www.liquid.ai/blog/ifstruct-v1.0)中所述的强化学习模型。本笔记本旨在展示如何通过针对特定任务的微调来提升较小模型的性能,使其达到远大于其规模的模型的效果,而非复现IFStruct基准测试分数。

前提条件

本指南分为两部分,分别在不同环境中运行:

  • 微调部分在GPU上运行。配套笔记本针对免费版Colab或Kaggle GPU设计。
  • 评估部分可在MacBook本地运行(此处为配备Apple M5 Max芯片和36GB统一内存的MacBook Pro),通过llama.cpp(一个提供OpenAI兼容服务器的工具)与IFStruct评估器通信。

我们需要使用uv(https://docs.astral.sh/uv/)进行Python工具管理,并使用llama.cpp提供服务。请参考Liquid AI的llama.cpp部署文档(https://docs.liquid.ai/deployment/on-device/llama-cpp),使用Homebrew安装llama.cpp并验证llama-server是否可用:

brew install llama.cpp
llama-server --version

在LFM2.5-350M基础模型上进行IFStruct评估

在开始之前,让我们先在IFStruct基准测试(https://huggingface.co/datasets/LiquidAI/ifstruct-v1.0)上评估LFM2.5-350M,看看能否复现其报告的21.1%分数。

IFStruct是一个用于测试大语言模型输出有效性及模式遵循度的基准测试。该基准测试在Liquid4All/ifstruct(https://github.com/Liquid4All/ifstruct)中开源,公共基准测试数据集可在Hugging Face的LiquidAI/ifstruct-v1.0(https://huggingface.co/datasets/LiquidAI/ifstruct-v1.0)上获取。

git clone https://github.com/Liquid4All/ifstruct.git

为进行评估对比,我们在MacBook上使用llama.cpp本地部署模型。我们将使用BF16格式的GGUF文件(LiquidAI/LFM2.5-350M-GGUF:https://huggingface.co/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
  • -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%)
平均延迟:1453毫秒

按格式:
  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%(https://www.liquid.ai/blog/ifstruct-v1.0)。我们本地的llama.cpp/BF16设置测得22.6%,与IFStruct博客报告的21.1%接近。我们使用此本地结果作为相同服务栈下的基线。

使用TRL对结构化输出进行GRPO微调

完整可运行的流程位于配套笔记本(https://github.com/Liquid4All/cookbook/blob/main/finetuning/notebooks/grpo_with_trl_ifstruct.ipynb)中。本节仅涵盖相关部分。

训练数据

我们使用nvidia/Nemotron-RL-instruction_following-structured_outputs(https://huggingface.co/datasets/nvidia/Nemotron-RL-instruction_following-structured_outputs),其中每个提示都配有目标JSON模式和期望字段数量。我们使用约500个样本进行训练。

由于Nemotron数据分布与IFStruct评估存在差异,我们对提示进行增强以弥补两者之间的两个差距:

  • **40%**的提示附加了“在带代码围栏的代码块中返回输出”指令,使模型学会遵循格式指令,而非总是直接输出原始JSON。
  • 另外20%(与前述不重叠)被转换为顶层数组任务(模式被包裹在需要项目数量的array中),用于训练裸列表输出和项目数量合规性。

模型与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",
    ],
)

这将训练约600万参数,约占模型总参数的1.66%。

奖励函数

然后我们定义三个奖励函数,每个函数的评分范围为[0, 1],用于评估每个生成结果提取的结构是否正确:

  • json_format_reward:输出是否可解析,并符合请求的形式?符合请求形式(带围栏/原始)得满分(1.0),形式错误但可解析得0.2,不可解析输出得0.0。
  • field_count_reward:对象是否具有期望的顶层字段数量?完全匹配得1.0,得分随差异程度线性衰减。
  • schema_validation_reward:输出是否能通过该行的JSON模式验证?它计算每个约束违反情况,并在必填键覆盖率基础上控制部分得分。

我们将三者以reward_weights=[1.0, 0.5, 2.0]的权重相加组合。

训练

我们使用8个生成/提示组的配置进行100步训练,适用于免费版16GB 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_format, field_count, schema_validation
    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%)
平均延迟:1518毫秒

按格式:
  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
JSON18.0%31.9%+13.9
YAML27.2%27.5%+0.3
包装键28.5%29.7%+1.2
裸列表16.6%29.7%+13.1

收益恰好落在训练目标上:JSON通过率提升了近14个百分点(18.0% → 31.9%),而YAML基本保持不变。虽然这仍低于Qwen3.5-2B的33.15%得分(https://www.liquid.ai/blog/ifstruct-v1.0),但它表明即使是轻量级的特定任务微调也能让小模型接近更大模型的表现。

结论

一个简短的GRPO训练流程,仅需约500个样本和100个步数,就能将350M参数的小模型在IFStruct上的得分从22.6%提升至29.7%。关键结论是,一个低成本、特定任务的奖励信号能够显著提升小型模型在形式方面的可靠性,大幅缩小与其数倍大小模型之间的差距。

要复现或扩展此工作,请参阅原始的IFStruct v1.0博客文章(https://www.liquid.ai/blog/ifstruct-v1.0)、Liquid4All/ifstruct(https://github.com/Liquid4All/ifstruct)基准测试仓库以及LiquidAI/ifstruct-v1.0(https://huggingface.co/datasets/LiquidAI/ifstruct-v1.0)数据集。

相似文章

多模块 GRPO:组合策略梯度与提示优化的语言模型程序方法

Papers with Code Trending

本文提出 mmGRPO,一种多模块扩展的群体相对策略优化(GRPO)方法,通过优化语言模型调用和提示来提升模块化 AI 系统的准确率。实验表明,该方法在各类任务上平均带来 11% 的准确率提升,并在 DSPy 中提供了开源实现。