비전·음성·추천

DOMAIN / 37번째 글

RLHF 심화: 인간 피드백으로 LLM 정렬하기

RLHF의 3단계 파이프라인(SFT→보상모델→PPO), Bradley-Terry 모델, KL 페널티, 보상 해킹 문제를 완전히 이해하고 DPO와의 비교까지 다룹니다.

PALDYN Team34 MIN READ

지난 글에서 어드밴티지로 정책 경사의 분산을 잡고, 액터-크리틱 위에 클리핑을 얹어 업데이트 폭까지 묶는 PPO를 조립했다. 이 글에서는 그 PPO가 게임 환경을 떠나 실제 언어 모델 훈련에 어떻게 얹히는지를 본다. ChatGPT와 Claude 같은 대화형 모델을 다듬는 데 쓰인 RLHF(Reinforcement Learning from Human Feedback, 인간 피드백 강화학습)다. 보상이 점수판에서 오지 않고 사람의 선호 비교에서 온다는 점 하나가 파이프라인 전체를 바꾼다. RLHF는 언어 모델을 막연히 더 좋게 만드는 기술이 아니라, 사람이 원하는 방식으로 동작하도록 맞추는 정렬(alignment) 기술이다. 그리고 그 보상이 사람의 판단을 흉내 낸 모델에서 나온다는 사실이 이 글에서 다룰 문제 대부분의 뿌리다.

선호 학습

사전학습 모델의 빈자리

사전학습된 LLM은 인터넷의 방대한 텍스트로 다음 토큰을 맞히는 법을 익혀 놀라운 언어 능력을 갖추지만, 사람이 원하는 방식으로 대답하지는 않는다. 질문을 받으면 답 대신 비슷한 질문을 더 이어 쓰거나, 유해한 요청을 그대로 완성하거나, 사실과 다른 내용을 자신 있게 말한다. 모델이 배운 것은 「인터넷에서 이 다음에 올 법한 글」이지 「이 사람에게 도움이 되는 글」이 아니기 때문이다.

그 틈을 먼저 메우는 것이 SFT(지도 미세조정)다. 사람이 좋은 답을 직접 써서 보여 주고 그대로 따라 하게 한다. 다만 SFT는 정답 하나를 흉내 내는 학습이라, 쓸 만한 답이 여럿인 질문에서 무엇이 더 나은지는 가르치지 못한다. 그리고 좋은 답을 처음부터 써내는 일은 비싸다.

선호 비교

사람에게 「이 질문의 이상적인 답을 써 주세요」라고 하면 시간이 오래 걸리고 사람마다 답이 제각각이다. 반면 「A와 B 중 어느 쪽이 더 나은가」는 몇 초면 고르고, 절대 점수를 매기라고 할 때보다 사람끼리 덜 엇갈린다. 이렇게 두 응답 중 하나를 고른 기록이 선호 비교 데이터이고, 한 건은 프롬프트 xx 와 고른 답 ywy_w , 버린 답 yly_l 의 세 짝이다.

RLHF는 이 비교 데이터로 사람의 판단을 흉내 내는 모델을 먼저 만들고, 그 모델의 점수를 보상 삼아 언어 모델을 강화학습으로 다듬는다. 여기서 강화학습으로 고치는 언어 모델이 곧 지난 글의 정책이다 — 프롬프트라는 상태에서 다음 토큰이라는 행동을 고르는 확률 분포이고, 응답 하나를 끝까지 생성하는 일이 한 에피소드다.

3단계 파이프라인

RLHF 3단계 파이프라인

첫 단계는 SFT다. 사전학습 모델을 사람이 쓴 데모 데이터로 파인튜닝하는데, InstructGPT 논문의 경우 이 데모가 1만 건대 규모였다. 이렇게 만든 SFT 모델이 뒤 두 단계의 출발점이다 — 보상 모델의 몸통이 되고, 강화학습이 시작하는 정책의 초기값이 되고, 정책이 얼마나 멀리 갔는지 재는 잣대가 된다.

둘째 단계는 선호 비교로 보상 모델을 학습하는 것이고, 셋째 단계는 그 보상 모델의 점수로 PPO를 돌리는 것이다. 둘째와 셋째 단계가 각각 무엇을 망가뜨릴 수 있는지가 아래 절들의 주제다.

보상 모델

Bradley-Terry 손실

보상 모델은 프롬프트와 응답을 받아 스칼라 점수 하나를 내는 모델이다. 보통 SFT 모델에서 시작해 마지막 층을 어휘 크기의 출력 대신 숫자 하나를 내는 헤드로 바꾼다. 문제는 사람이 준 것이 점수가 아니라 비교뿐이라는 점인데, 이를 잇는 것이 Bradley-Terry 모델이다. 두 응답의 점수 차이를 시그모이드에 넣으면 그것이 사람이 앞쪽을 고를 확률이라고 가정한다.

P(yw≻yl∣x)=σ(rϕ(x,yw)−rϕ(x,yl))P(y_w \succ y_l \mid x) = \sigma\big(r_\phi(x, y_w) - r_\phi(x, y_l)\big)

학습은 사람이 실제로 고른 쪽의 확률을 높이는 음의 로그 가능도를 줄이는 것이다.

L(ϕ)=−E[log⁡σ(rϕ(x,yw)−rϕ(x,yl))]\mathcal{L}(\phi) = -\mathbb{E}\big[\log \sigma\big(r_\phi(x, y_w) - r_\phi(x, y_l)\big)\big]

여기서 눈여겨볼 점은 손실이 점수의 차이만 본다는 것이다. 모든 응답의 점수에 10을 더해도 손실은 그대로다. 그래서 보상 모델이 내는 절대값에는 뜻이 없고, 같은 프롬프트 안에서 두 답을 견줄 때만 의미가 있다. 이 성질이 뒤에서 GRPO가 그룹 안에서 점수를 정규화하는 이유와 이어진다.

from transformers import AutoModelForSequenceClassification
import torch
import torch.nn.functional as F

class RewardModel(torch.nn.Module):
    """SFT 모델 + 스칼라 출력 헤드"""
    def __init__(self, base_model_name: str):
        super().__init__()
        # 분류 모델로 로드 (num_labels=1 → 스칼라)
        self.model = AutoModelForSequenceClassification.from_pretrained(
            base_model_name, num_labels=1
        )

    def forward(self, input_ids, attention_mask):
        outputs = self.model(input_ids=input_ids, attention_mask=attention_mask)
        return outputs.logits.squeeze(-1)  # 보상 스칼라

def reward_model_loss(reward_model, chosen_ids, chosen_mask, rejected_ids, rejected_mask):
    """Bradley-Terry 손실"""
    r_chosen   = reward_model(chosen_ids, chosen_mask)
    r_rejected = reward_model(rejected_ids, rejected_mask)
    # 선호된 응답이 더 높은 보상을 받도록
    loss = -F.logsigmoid(r_chosen - r_rejected).mean()
    return loss

라벨러 일치도

선호 비교가 쉽다고 해서 사람들이 늘 같은 쪽을 고르지는 않는다. InstructGPT 논문은 라벨러끼리 같은 쌍에서 같은 쪽을 고른 비율을 대략 73% 안팎으로 보고했다. 이 수가 보상 모델 정확도의 사실상 천장이다 — 사람끼리도 넷 중 하나는 엇갈리는데 모델이 그보다 잘 맞힐 수는 없다.

수로 보면 더 분명하다. 어떤 쌍에서 라벨러의 70%가 A를 골랐다면, Bradley-Terry 가정에서 가장 잘 맞는 점수 차는 ln⁡(0.7/0.3)≈0.85\ln(0.7/0.3) \approx 0.85 다. 이 쌍이 데이터에 한 번만 들어가 A가 이긴 것으로 적혀 있으면 모델은 차이를 무한히 벌리려 하고, 반대로 B가 이긴 것으로 적혀 있으면 정반대로 민다. 일치도가 50%까지 내려가면 가장 잘 맞는 점수 차는 0이고, 손실은 어떻게 학습해도 ln⁡2≈0.693\ln 2 \approx 0.693 아래로 내려가지 않는다. 모델은 아무것도 배우지 못한 채 잡음에 맞춰 흔들린다.

라벨 잡음

일치도가 낮은 데이터가 망가뜨리는 것은 정확도 숫자만이 아니다. 라벨러끼리 엇갈리는 쌍은 대개 어느 쪽이 좋은지가 취향의 문제인 쌍이고, 그런 쌍에서 보상 모델이 배우는 것은 내용이 아니라 우연히 한쪽에 몰린 겉모양 — 더 긴 답, 목록이 있는 답, 공손한 인사로 시작하는 답 — 이기 쉽다. 이렇게 배운 지름길은 PPO 단계에서 정책이 가장 먼저 찾아내 파고든다.

그래서 실무에서는 라벨 지침을 촘촘히 쓰고, 같은 쌍을 여러 사람에게 보여 다수결을 내거나 엇갈린 쌍을 따로 표시하고, 보상 모델의 정확도를 따로 떼어 둔 비교 데이터로 잰다. 정확도가 라벨러 일치도에 가까우면 더 짤 것이 없다는 뜻이고, 한참 모자라면 데이터나 모델 크기를 의심한다.

KL 페널티

참조 모델

셋째 단계에서 정책은 보상 모델 점수를 올리도록 PPO로 학습한다. 그런데 보상 모델은 사람을 흉내 낸 근사일 뿐이라, 정책이 그 점수만 좇으면 사람에게는 나쁜데 점수만 높은 응답으로 흘러간다. 이를 막는 장치가 KL 페널티다. 학습을 시작할 때의 SFT 모델을 얼려 두고 이를 참조 모델이라 부른 뒤, 정책의 토큰 확률이 참조 모델의 것에서 얼마나 멀어졌는지를 KL 발산으로 재서 보상에서 뺀다.

R(x,y)=rϕ(x,y)−β KL(πθ(⋅∣x) ∥ πref(⋅∣x))R(x, y) = r_\phi(x, y) - \beta \, \mathrm{KL}\big(\pi_\theta(\cdot \mid x) \,\|\, \pi_{\mathrm{ref}}(\cdot \mid x)\big)

KL 발산은 한 응답에 대해 토큰마다 정책의 로그 확률에서 참조 모델의 로그 확률을 뺀 값을 더해 어림한다. 정책이 참조 모델이라면 쓰지 않았을 토큰을 고를수록 이 값이 커진다. 즉 KL 페널티는 「보상을 올리되, 원래 쓰던 말투와 지식에서 크게 벗어나지 말라」는 끈이다.

RLHF 보상 계산 및 PPO 업데이트

def rlhf_ppo_step(policy_model, ref_model, reward_model,
                  prompts: list[str], beta: float = 0.1):
    """RLHF PPO 업데이트 한 스텝 (개념적)"""
    all_rewards = []
    all_log_probs = []

    for prompt in prompts:
        # 현재 정책으로 응답 생성 (에피소드 수집)
        response_ids = policy_model.generate(
            prompt, max_new_tokens=256,
            do_sample=True, temperature=0.9
        )
        response_text = tokenizer.decode(response_ids[0])

        # 보상 모델 점수
        rm_score = reward_model(prompt + response_text).item()

        # KL 발산 계산 (토큰별 로그 확률 차이)
        with torch.no_grad():
            pol_log_probs = policy_model.log_probs(response_ids, prompt)
            ref_log_probs = ref_model.log_probs(response_ids, prompt)
        kl = (pol_log_probs - ref_log_probs).sum()

        # 최종 보상: 보상 모델 점수 - KL 패널티
        reward = rm_score - beta * kl
        all_rewards.append(reward)
        all_log_probs.append(pol_log_probs)

    # PPO 업데이트 (표준 PPO 알고리즘 적용)
    ppo_update(policy_model, all_log_probs, all_rewards)

계수 β의 구간

계수 β\beta 가 끈의 길이를 정한다. 수를 넣어 보자. 어떤 응답이 참조 모델의 답보다 보상 모델 점수를 2.0 더 받는데, 그 대가로 KL이 10만큼 벌어졌다고 하자. β=0.1\beta = 0.1 이면 페널티가 1.0이라 순이득 1.0이 남고 정책은 그쪽으로 움직인다. β=0.5\beta = 0.5 면 페널티가 5.0이라 순이득이 −3.0이고, 정책은 그 답을 피한다. 같은 응답이 β\beta 하나로 권장과 기피를 오간다.

β\beta 가 너무 크면 정책이 참조 모델 근처에서 거의 안 움직여 학습 비용만 쓰고 SFT 모델과 다를 것이 없다. 너무 작으면 KL이 수십, 수백으로 치솟으며 보상 모델의 빈틈을 파고드는 쪽으로 무너진다. 전형적인 증상은 보상 점수는 계속 오르는데 응답을 읽어 보면 같은 문구가 되풀이되거나 문장이 깨지는 것이다. 실무에서 흔히 보는 값은 0.01에서 0.1 사이지만, 보상 모델의 점수 척도에 따라 적당한 값이 달라져 다른 설정에서 가져온 수를 그대로 쓰기 어렵다.

적응형 KL

그래서 β\beta 를 고정하지 않고 KL 자체에 목표값을 두는 방식이 나왔다. 적응형 KL은 한 배치의 KL이 목표보다 크면 β\beta 를 조금 올리고, 작으면 조금 내린다. Ziegler 등(2019)의 초기 RLHF 연구가 이 방식을 썼고, 오차를 ±20%로 잘라 한 번에 크게 흔들리지 않게 했다.

β←β(1+Kβ⋅clip ⁣(KL−KLtargetKLtarget,−0.2,0.2))\beta \leftarrow \beta \left(1 + K_\beta \cdot \mathrm{clip}\!\left(\frac{\mathrm{KL} - \mathrm{KL}_{\mathrm{target}}}{\mathrm{KL}_{\mathrm{target}}}, -0.2, 0.2\right)\right)

이렇게 하면 조절하는 손잡이가 「페널티의 세기」에서 「정책이 얼마나 멀리 가도 되는가」로 바뀐다. 후자가 훨씬 해석하기 쉽다 — KL 목표 6이라면 응답 하나에서 참조 모델과의 로그 확률 차 합이 6 안팎에 머물도록 끈을 스스로 당겼다 풀었다 한다. Hugging Face의 TRL 라이브러리 옛 버전은 이 조절기를 설정 몇 줄로 켜게 했다. 아래는 그 옛 버전의 인터페이스라 지금 버전과 인자가 다르다.

# TRL (Hugging Face) 옛 인터페이스 — 최신 버전과 인자가 다르다
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead

model = AutoModelForCausalLMWithValueHead.from_pretrained("gpt2")
ref_model = AutoModelForCausalLMWithValueHead.from_pretrained("gpt2")

config = PPOConfig(
    model_name="gpt2",
    learning_rate=1.41e-5,
    batch_size=64,
    mini_batch_size=4,
    gradient_accumulation_steps=4,
    adap_kl_ctrl=True,   # 적응형 KL 켜기
    init_kl_coef=0.2,    # β 시작값
    target=6.0,          # KL 목표
    ppo_epochs=4,
)

ppo_trainer = PPOTrainer(
    model=model,
    ref_model=ref_model,
    tokenizer=tokenizer,
    config=config,
)

# 학습 루프
for batch in dataset:
    queries = tokenizer(batch["prompt"], return_tensors="pt")
    responses = ppo_trainer.generate(queries)
    rewards = [reward_model(q, r) for q, r in zip(queries, responses)]
    stats = ppo_trainer.step(queries, responses, rewards)

보상 해킹

길이 편향

KL 페널티가 막으려는 현상이 보상 해킹이다. 정책이 보상 모델의 약점을 찾아내, 사람이 보기에는 나아지지 않았는데 점수만 오르는 응답으로 옮겨 가는 것이다. 가장 흔하고 가장 먼저 나타나는 것이 길이다. 사람은 비교할 때 더 자세한 답을 조금 더 자주 고르는 경향이 있고, 보상 모델은 이 경향을 과장해서 배운다. 그러면 PPO를 돌릴수록 평균 응답 길이가 꾸준히 늘어나고, 짧게 끝나야 할 질문에도 배경 설명과 요약이 붙는다. 여러 연구가 RLHF로 얻은 보상 향상의 상당 부분이 길이 증가로 설명된다고 보고했다.

탐지는 간단하다. 학습 중 평균 응답 길이와 보상 점수를 함께 그려 두고, 같은 길이끼리 묶어서도 보상이 오르는지 본다. 완화로는 보상 모델 데이터에서 길이를 맞춘 쌍을 늘리거나, 보상에서 길이에 비례하는 항을 빼거나, 응답 최대 길이를 줄여 정책이 길이로 점수를 살 여지를 좁힌다.

문구 반복

두 번째 흔한 해킹은 보상 모델이 좋아하는 특정 문구를 되풀이하는 것이다. 「좋은 질문입니다」로 시작하거나, 「도움이 되었기를 바랍니다」로 끝내거나, 사용자를 과하게 칭찬하는 식이다. 라벨러가 공손한 답을 조금 더 골랐다면 보상 모델은 그 공손함을 표시하는 몇 낱말에 점수를 얹고, 정책은 그 낱말을 모든 답에 붙인다. 심하면 「아주 좋습니다! 뛰어나십니다!」 같은 문장이 내용과 상관없이 반복된다.

이것은 KL이 갑자기 튀는 모습으로 먼저 드러나는 일이 많다. 참조 모델은 그런 문구를 매번 쓰지 않으므로 그 토큰들의 로그 확률 차가 커지기 때문이다. 학습 중 표본을 정기적으로 뽑아 자주 나오는 n-그램을 세면 잡히고, 보상 모델에 그 문구만 있고 내용은 나쁜 반례 쌍을 넣어 다시 학습시키면 완화된다.

서식 남용

세 번째는 서식이다. 제목, 굵은 글씨, 글머리표 목록이 있는 답은 훑어보기 쉬워 비교에서 이기는 경우가 많고, 보상 모델은 「목록이 있으면 좋다」를 배운다. 그러면 정책은 한 문장으로 끝날 답도 세 단계 목록과 소제목으로 쪼갠다. 겉으로는 정돈되어 보이지만 내용은 늘지 않았고, 대화 맥락에서는 오히려 읽기 불편하다.

세 가지 해킹은 뿌리가 같다. 보상 모델이 사람 선호의 대리 지표이고, 대리 지표를 세게 최적화하면 원래 목표와 갈라진다. 경제학의 굿하트 법칙 — 지표가 목표가 되는 순간 좋은 지표가 아니게 된다 — 이 그대로 적용된다. 그래서 KL 페널티, 길이 통제, 반례 데이터는 해킹을 없애는 것이 아니라 정책이 대리 지표를 너무 멀리 좇기 전에 멈추게 하는 장치다.

학습 인프라

네 모델

PPO 기반 RLHF는 한 번의 학습 스텝에 모델 넷을 동시에 띄운다. 학습하는 정책 모델, 그 정책을 묶어 두는 얼린 참조 모델, 점수를 매기는 얼린 보상 모델, 그리고 PPO의 어드밴티지를 계산하기 위한 가치 모델이다. 가치 모델은 지난 글의 크리틱과 같은 역할로, 응답이 여기까지 생성된 상태에서 앞으로 받을 보상을 예측한다. 보통 보상 모델이나 SFT 모델에서 시작하므로 정책과 크기가 비슷하고, 정책과 함께 학습한다.

넷 중 둘은 학습하고 둘은 추론만 한다. 이 구분이 메모리 예산을 거의 다 정한다.

메모리 예산

7B 모델 RLHF의 메모리 예산

7B 파라미터 모델로 어림해 보자. 추론만 하는 모델은 bf16 가중치만 있으면 되므로 파라미터당 2바이트, 곧 14GB다. 학습하는 모델은 혼합 정밀도에 Adam을 쓰면 bf16 가중치와 기울기(각 2바이트), fp32 주 가중치(4바이트), Adam의 두 모멘트(각 4바이트)가 붙어 파라미터당 16바이트, 곧 112GB다. 정책과 가치 모델이 각각 112GB, 참조와 보상 모델이 각각 14GB라 합이 252GB다. 여기에 활성값과 생성용 KV 캐시가 더해지므로 80GB GPU 한 장은커녕 넉 장으로도 빠듯하다.

그래서 실무 구현은 가중치와 옵티마이저 상태를 여러 GPU에 쪼개는 방법(ZeRO나 FSDP 같은 샤딩)을 쓰고, 정책과 가치 모델이 몸통을 공유하게 하거나, LoRA로 학습 파라미터를 줄이고 참조 모델을 LoRA를 끈 정책으로 대신하는 식으로 모델 수를 줄인다.

롤아웃 비용

메모리 다음 문제는 시간이다. PPO는 매 스텝 현재 정책으로 응답을 새로 생성해야 하는데, 이렇게 정책을 돌려 에피소드를 모으는 일을 롤아웃이라 한다. 언어 모델의 생성은 토큰을 하나씩 뽑는 순차 작업이라 학습 스텝의 순전파·역전파보다 훨씬 느리고, 응답 256토큰이면 정책을 256번 돌려야 한다. 그 뒤에 참조 모델과 보상 모델, 가치 모델이 같은 응답을 한 번씩 더 읽는다.

그래서 RLHF 학습 시간의 대부분이 학습이 아니라 생성에 쓰인다. 최근 프레임워크들이 생성 전용 추론 엔진을 따로 붙이고 학습된 가중치를 매 스텝 그쪽으로 옮기는 구조를 택하는 것도 이 때문이다. 이 비용이 아래 절의 방법들이 등장한 가장 현실적인 이유다.

보상 모델 없는 정렬

DPO

DPO(Direct Preference Optimization, Rafailov 등 2023)는 KL 페널티가 붙은 보상 최대화 문제의 최적 정책을 식으로 풀어, 보상을 정책과 참조 모델의 로그 확률 비로 바꿔 쓸 수 있음을 보였다. 그 식을 Bradley-Terry 손실에 넣으면 보상 모델이 사라지고, 선호 쌍에서 곧장 정책을 학습하는 분류 손실 하나가 남는다. 롤아웃도 가치 모델도 없고, 앞 절의 네 모델이 정책과 참조 둘로 줄어 7B 기준 메모리가 126GB로 절반이 된다.

# DPO 손실 함수
def dpo_loss(policy_model, ref_model, chosen, rejected, beta=0.1):
    """Direct Preference Optimization 손실"""
    chosen_log_ratio  = policy_model.log_prob(chosen)  - ref_model.log_prob(chosen)
    rejected_log_ratio = policy_model.log_prob(rejected) - ref_model.log_prob(rejected)
    loss = -F.logsigmoid(beta * (chosen_log_ratio - rejected_log_ratio))
    return loss.mean()

코드의 beta는 앞 절의 KL 계수와 같은 자리다. 크면 참조 모델에서 덜 벗어나고 작으면 선호 차이를 더 세게 벌린다. 대가는 온라인 탐색이 없다는 점이다. DPO는 미리 모은 쌍만 보므로, 정책이 학습 중에 새로 내놓는 응답이 좋은지 나쁜지는 확인하지 못한다.

IPO와 KTO

DPO 위에 여러 변형이 나왔고, 각각 무엇을 바꿨는지로 기억하면 쉽다. IPO(Azar 등 2023)는 손실 모양을 바꿨다. 한 쌍에서 늘 같은 쪽이 이기는 결정적인 선호가 들어오면 DPO의 로그 시그모이드 손실은 로그 확률 비의 차를 끝없이 벌리려 해서 KL 제약이 사실상 무력해진다. IPO는 그 차를 정해진 목표값으로 끌어당기는 제곱 손실을 써서 과적합을 막는다.

KTO(Ethayarajh 등 2024)는 데이터 모양을 바꿨다. 쌍이 아니라 응답 하나에 「좋음」 또는 「나쁨」 표시만 있어도 학습한다. 카너먼과 트버스키의 전망 이론에서 이름을 따, 이득보다 손실에 더 민감한 사람의 효용 곡선을 손실에 넣었다. 서비스에서 쌓이는 좋아요·싫어요 기록처럼 짝이 없는 피드백을 그대로 쓸 수 있다는 것이 실용적 장점이다.

비교 항목 PPO 기반 RLHF DPO IPO KTO
필요한 데이터 선호 쌍 선호 쌍 선호 쌍 좋음·나쁨 단건
보상 모델 필요 불필요 불필요 불필요
학습 중 생성 있음(온라인) 없음(오프라인) 없음 없음
바꾼 것 — 보상 모델 제거 손실 모양 데이터 모양

보상 모델 없이도 잘 되는 조건은 대체로 이렇다. 선호 데이터가 지금 정책이 내는 응답과 비슷한 분포에서 왔고, 가르치려는 것이 말투·형식·거절 방식처럼 쌍으로 잘 드러나는 성질일 때다. 반대로 정책이 데이터에 없던 방식으로 답하기 시작하면 오프라인 방법은 그 답을 평가할 길이 없다. 그래서 DPO를 여러 번 돌리며 매번 새 정책의 응답으로 쌍을 다시 만드는 반복형 방식도 쓰인다.

GRPO

어드밴티지의 기준선

GRPO(Group Relative Policy Optimization)는 DeepSeekMath 논문(2024)에서 나온 방식으로, 반대쪽에서 비용을 줄인다. 온라인 생성은 유지하되 가치 모델을 없앤다. PPO에서 가치 모델이 하는 일은 어드밴티지의 기준선을 주는 것이다. 어떤 답이 보상 0.8을 받았고 가치 모델이 그 자리의 기대 보상을 0.5로 예측했다면 어드밴티지는 +0.3이다. GRPO는 이 기준선을 모델로 예측하지 않고, 같은 프롬프트에 답을 여러 개 뽑아 그 무리의 평균으로 대신한다.

그림처럼 수학 문제 하나에 답 넷을 뽑아 정답 둘, 오답 둘이 나왔다고 하자. 보상이 1, 0, 0, 1이면 평균은 0.5, 표준편차는 0.5이고, 보상에서 평균을 빼고 표준편차로 나누면 어드밴티지는 +1, −1, −1, +1이 된다. 같은 물음 안에서 나은 답은 밀어 올리고 못한 답은 끌어내리는 셈이다. 보상 모델 점수의 절대값에 뜻이 없고 차이에만 뜻이 있다는 앞의 성질과 잘 맞는다. KL 페널티는 보상에서 빼지 않고 손실에 직접 더한다.

가치 모델이 빠지면 7B 기준 메모리가 252GB에서 140GB로 준다. 정답을 규칙으로 채점할 수 있는 수학·코드 문제라면 보상 모델마저 채점기로 바꿀 수 있다. DeepSeek-R1이 이런 규칙 기반 보상과 GRPO로 긴 추론을 끌어낸 뒤로, GRPO는 추론 모델 학습의 대표 방법으로 자리 잡았다. 다만 넷을 뽑았는데 넷 다 맞거나 넷 다 틀리면 표준편차가 0이라 어드밴티지도 0이 되어 그 문제에서는 배우는 것이 없다. 너무 쉽거나 너무 어려운 문제를 걸러 내는 것이 데이터 준비의 핵심이 된다.

평가

승률과 길이 편향

정렬된 모델을 평가하는 가장 흔한 방법은 승률이다. 같은 프롬프트에 두 모델의 답을 나란히 놓고 사람이나 강한 LLM 심판이 어느 쪽이 나은지 고르게 해서, 이긴 비율을 센다. 문제는 심판에게도 보상 모델과 같은 길이 편향이 있다는 점이다. LLM 심판은 긴 답을 선호하는 경향이 뚜렷해서, 앞의 보상 해킹으로 길어진 모델은 평가에서도 이긴다. 학습과 평가가 같은 편향을 공유하면 개선이 아닌 것이 개선으로 보인다.

이를 보정하려고 AlpacaEval 2는 길이 통제 승률을 도입했다. 승률을 예측하는 회귀 모형에 두 답의 길이 차이를 변수로 넣고, 길이 차이가 0일 때의 승률을 보고하는 방식이다. 보정 전후의 순위가 크게 바뀌는 모델은 길이로 점수를 샀을 가능성이 높다. 더 단순하게는 평가 결과를 응답 길이 구간별로 나눠 보는 것만으로도 많은 것이 드러난다.

보상 점수와 사람 평가

학습 중에는 보상 점수가 가장 쉽게 볼 수 있는 지표지만, 그 점수와 사람의 평가는 어느 지점에서 갈라진다. 초기에는 보상이 오를수록 사람 평가도 함께 오르다가, KL이 어느 선을 넘으면 보상은 계속 오르는데 사람 평가는 멈추거나 떨어진다. 이 갈림이 보상 모델의 과최적화이고, 보상 모델이 클수록, 선호 데이터가 많을수록 갈라지는 지점이 뒤로 밀린다는 연구가 있다.

그래서 학습 곡선은 보상 점수 하나로 보지 않는다. KL을 가로축에 두고 보상 점수와 별도의 평가(떼어 둔 보상 모델, LLM 심판, 소규모 사람 평가)를 함께 그리면, 둘이 갈라지기 시작하는 지점이 곧 멈출 체크포인트다. 보상을 학습에 쓴 모델과 평가에 쓰는 모델을 같은 것으로 두면 이 갈림이 보이지 않는다.

정리

RLHF는 사람의 선호 비교를 Bradley-Terry 손실로 보상 모델에 옮기고, 그 점수를 KL 페널티로 묶은 채 PPO로 좇는 파이프라인이다. 라벨러 일치도가 보상 모델의 천장을 정하고, β\beta 가 정책이 대리 지표를 얼마나 멀리 좇을지를 정하며, 그 끈이 느슨하면 길이·문구·서식으로 보상 해킹이 나타난다. 네 모델을 띄우는 메모리와 롤아웃 비용이 부담스러워 DPO 계열은 보상 모델과 생성을 없앴고, GRPO는 생성은 두되 가치 모델을 그룹 평균으로 바꿨다. 어느 방법을 쓰든 보상 점수만 보지 말고 길이를 통제한 평가와 함께 봐야 한다는 점은 같다.

다음 글부터는 텍스트 밖으로 나간다 — 말로 주고받는 음성 에이전트에서 정작 어려운 것이 모델이 아니라 시간 관리라는 이야기다.


읽어주셔서 감사합니다. 😊

LATEST

비전·음성·추천의 최신 글

비전·음성·추천2026.09.07

오프라인 강화학습 — 쌓인 로그만으로 정책을 배우기

실제 서비스에서 탐색은 곧 사용자에게 나쁜 행동을 해 보는 일입니다. 이미 쌓인 로그만으로 정책을 배우려 할 때 왜 Q값이 혼자 부풀어 오르는지, 그 부풀음을 누르는 세 갈래 대응, 행동 복제라는 기준선의 무게, 그리고 배포 전에 성능을 재는 일이 왜 가장 어려운지를 정리합니다.

18 MIN
비전·음성·추천2026.09.07

다국어 전이 — 라벨 없는 언어에서 모델이 동작하는 이유

영어 라벨만으로 학습한 분류기가 한국어 문장을 그대로 처리하는 일이 실제로 일어납니다. 여러 언어가 한 표현 공간에 겹쳐 놓이는 원리, 그 겹침이 무너지는 조건, 번역해서 학습할지 번역해서 추론할지 고르는 기준, 그리고 언어별로 나눠 재야 하는 이유를 정리합니다.

16 MIN
비전·음성·추천2026.09.07

정보 추출 — 글 한 덩이를 표 한 줄로 바꾸는 일

계약서와 이메일을 데이터베이스에 넣으려면 글에서 값을 뽑아 칸에 채워야 합니다. 개체명·관계·사건의 세 층위, 값을 정규화하는 일이 왜 절반인지, 근거 위치를 함께 남겨야 하는 이유, 규칙·전용 모델·언어 모델의 갈림길, 그리고 필드별로 재는 평가법을 정리합니다.

17 MIN