3억 5천만 개 매개변수 모델을 100 GRPO 단계로 미세 조정하여 구조화된 출력 개선하기
요약
본 가이드는 소형 모델(3억 5천만 개 매개변수)을 작업별로 미세 조정하여 구조화된 출력 성능을 개선하는 방법을 다룹니다. IFStruct 벤치마크를 통해 스키마 준수 능력을 테스트하며, `llama.cpp`와 같은 로컬 도구를 사용하여 평가 과정을 설명합니다.
핵심 포인트
- 소형 모델도 작업별 미세 조정으로 큰 모델에 근접한 성능을 보임.
- IFStruct는 LLM의 구조화된 출력(스키마 준수) 능력을 측정하는 벤치마크임.
- `llama.cpp`를 활용하여 로컬 환경에서 OpenAI 호환 서버를 구축하고 평가할 수 있음.
- 실제 개발 워크플로우에 적용 가능한 구체적인 도구 사용법을 제시함.
3억 5천만 개 매개변수 모델을 100 GRPO 단계로 미세 조정하여 구조화된 출력 개선하기
IFStruct 벤치마크에서 22.6%에서 29.7%로 향상.
구조화된 출력(Structured output)은 LLM의 가장 일반적인 실제 작업 중 하나이지만, 대부분의 벤치마크에서는 이를 독립적으로 측정하기보다는 광범위한 추론 또는 추출 점수에 통합합니다. 모델이 요청된 형식과 구조에서 유효하고 파싱 가능한 출력을 신뢰성 있게 반환할 수 있는지(스키마 준수, schema compliance)가 다운스트림 시스템에 연결될 수 있는지를 결정하는 경우가 많습니다.
여기에 설명된 학습 파이프라인은 IFStruct 블로그에서 설명한 RL 모델을 훈련하는 데 사용된 파이프라인이 아닙니다. 이 노트북의 목표는 IFStruct 벤치마크 점수를 재현하는 것이 아니라, 소형 모델에 대한 작업별 미세 조정(task-specific fine-tuning)이 어떻게 성능을 향상시키고 훨씬 더 큰 모델과 동등하게 만들 수 있는지 보여주는 것입니다.
이 가이드는 서로 다른 장소에서 실행되는 두 부분으로 구성되어 있습니다:
**미세 조정 (Fine-tuning)**은 GPU에서 실행됩니다. 첨부된 노트북은 무료 티어 Colab 또는 Kaggle GPU에 맞춰져 있습니다.**평가 (Evaluation)**는 llama.cpp를 통해 MacBook(여기서는 Apple M5 Max 및 36GB 통합 메모리를 갖춘 MacBook Pro)에서 로컬로 실행할 수 있으며, 이는 IFStruct 평가기가 통신하는 OpenAI와 호환되는 서버를 노출합니다.
우리는 Python 도구링을 위해 uv가 필요하고, 서비스(serving)를 위해 llama.cpp가 필요합니다. Liquid AI llama.cpp 배포 문서를 따라 Homebrew로 llama.cpp를 설치하고 llama-server가 사용 가능한지 확인하십시오:
brew install llama.cpp
llama-server --version
시작하기 전에, LFM2.5-350M을 IFStruct 벤치마크에서 평가하여 보고된 점수인 21.1%를 재현할 수 있는지 확인해 보겠습니다.
IFStruct는 LLM 출력의 유효성과 스키마 준수(schema adherence)를 테스트하기 위한 벤치마크입니다. 이 벤치마크는 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 \
...
--alias: IFStruct가 OpenAI 호환 엔드포인트로 전송하는 모델 이름
-ngl 99: 사용 가능한 경우 모든 레이어를 GPU에 오프로드하도록 llama.cpp에 요청
-np 4: 병렬로 네 개의 요청 처리
-c 32768: 프롬프트 컨텍스트 크기
서버가 실행되면, 2000개의 샘플을 사용하여 전체 벤치마크를 실행할 수 있습니다:
uv run ifstruct-eval \
--model LiquidAI/LFM2.5-350M \
--base-url http://localhost:8080/v1 \
...
============================================================
Model: LiquidAI/LFM2.5-350M
============================================================
...
IFStruct 릴리스 블로그는 LFM2.5-350M에 대해 21.1%를 보고합니다. 우리의 로컬 llama.cpp/BF16 설정은 22.6%로, IFStruct 블로그에서 보고된 21.1%와 근접합니다. 우리는 이 로컬 결과를 동일한 서비스 스택 비교의 기준선(baseline)으로 사용합니다.
전체 실행 가능한 파이프라인은 첨부된 노트북에 있습니다. 여기서는 관련 부분만 다룰 것입니다.
우리는 nvidia/Nemotron-RL-instruction_following-structured_outputs를 사용하는데, 이는 각 프롬프트에 대상 JSON Schema와 예상 필드 개수를 쌍으로 연결합니다. 우리는 훈련을 위해 약 500개의 샘플을 사용합니다.
Nemotron 데이터 분포가 IFStruct 평가와 다르기 때문에, 우리는 두 가지 격차를 줄이기 위해 프롬프트를 증강(augment)합니다:
**40%**에는
lora_config = LoraConfig(
r=16,
lora_alpha=32,
...
이것은 약 6M 개의 매개변수를 학습시키며, 모델의 약 1.66%에 해당합니다.
다음으로 우리는 세 가지 보상 함수를 정의하는데, 각 함수는 [0, 1] 범위로 측정되며 추출된 구조가 올바른지 점수를 매깁니다:
json_format_reward
: 출력물이 파싱 가능한지, 그리고 요청된 형식인지? 요청된 형식(fence 처리 여부 vs. 원본)에 대해 만점(1.0)을 부여하고, 잘못되었지만 파싱 가능한 형식에는 0.2, 파싱 불가능한 출력물에는 0.0을 부여합니다.
field_count_reward
: 객체가 예상되는 수의 최상위 필드를 가지고 있는지? 정확히 일치하면 1.0을 얻고, 누락된 필드에 따라 점수가 선형적으로 감소합니다.
schema_validation_reward
: 출력물이 해당 행의 JSON Schema를 검증하는지? 모든 제약 조건 위반 횟수를 계산하고 필수 키(required-key) 커버리지에 대해 부분 점수 제한을 겁니다.
우리는 이 세 가지 보상 함수를 reward_weights=[1.0, 0.5, 2.0]로 가중 합산하여 결합합니다.
무료 티어의 16 GB GPU에 맞춰진 설정으로 프롬프트 그룹당 8개의 생성(generation)을 사용하여 100 스텝 동안 학습시킵니다:
from trl import GRPOConfig
training_args = GRPOConfig(
output_dir="./outputs/lfm25-350m-nemotron-schema-grpo",
...
노트북에서 볼 수 있듯이, 실행 과정 내내 세 가지 보상 구성 요소가 상승하고, 참조 모델(reference model)으로부터의 KL 값은 워밍업(warmup) 이후 0에서 치솟으며, 잘린 완료 분율(truncated-completion fraction)은 0 근처를 유지합니다.
마지막으로, LoRA 어댑터를 기본 가중치에 병합하고 단일 독립적인 체크포인트로 저장하여 서비스용 GGUF로 변환할 준비를 합니다:
MERGED_DIR = f"{training_args.output_dir}-merged"
merged_model = trainer.model.merge_and_unload()
merged_model.save_pretrained(MERGED_DIR)
...
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
...
다음 명령어를 사용하여 병합된 모델을 서비스합니다:
llama-server \
-m ./models/lfm25-350m-grpo-bf16.gguf \
--alias lfm25-350m-grpo-structured-output \
...
다음으로, 미세 조정된 모델을 사용하여 전체 IFStruct 평가를 다시 실행합니다:
uv run ifstruct-eval \
--model lfm25-350m-grpo-structured-output \
--base-url http://localhost:8081/v1 \
...
============================================================
Model: lfm25-350m-grpo-structured-output
============================================================
...
동일한 서비스 스택에서 두 실행을 비교하면 다음과 같습니다:
| IFStruct 그룹 | base | GRPO-tuned | Δ |
|---|---|---|---|
| Overall | 22.6% | 29.7% | +7.1 |
| JSON | 18.0% | 31.9% | +13.9 |
| YAML | 27.2% | 27.5% | +0.3 |
| Wrapper key | 28.5% | 29.7% | +1.2 |
| Bare list | 16.6% | 29.7% | +13.1 |
개선된 점은 학습이 목표했던 지점과 정확히 일치합니다. JSON 패스율은 거의 14포인트(18.0% → 31.9%) 상승한 반면, YAML은 거의 변화가 없습니다. 비록 이것이 여전히 Qwen3.5-2B의 33.15% 점수보다는 낮지만, 가벼운 작업별 미세 조정만으로도 작은 모델을 더 큰 모델에 근접하게 만들 수 있음을 보여줍니다.
약 500개의 샘플과 100단계로 진행된 짧은 GRPO 실행만으로도 350M 매개변수의 작은 모델의 IFStruct 점수를 22.6%에서 29.7%까지 끌어올릴 수 있습니다. 핵심은 저렴하고 작업별 보상 신호가 작은 모델이 *형식(form)*에 대해 훨씬 더 신뢰할 수 있게 만들고, 크기가 몇 배나 큰 모델과의 격차를 많이 줄일 수 있다는 것입니다.
이 작업을 재현하거나 확장하려면 원본 IFStruct v1.0 블로그 게시물, Liquid4All/ifstruct 벤치마크 리포지토리, 그리고 LiquidAI/ifstruct-v1.0 데이터셋을 참조하십시오.
AI 자동 생성 콘텐츠
본 콘텐츠는 Hugging Face Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기