목록으로

프로그래밍 · Python

Python 완전정복 시리즈 44: RLOOTrainer 완벽 이해하기

BeanCon
Python에서 Hugging Face TRL RLOOTrainer로 Leave-One-Out Reward Baseline을 이용해 AI를 강화학습하는 방법을 설명하는 대표 이미지

Python 완전정복 시리즈 44편입니다. Hugging Face TRL의 RLOOTrainer로 Value Model 없이 자기 자신을 제외한 평균 Reward를 Baseline으로 사용해 생성형 AI를 강화학습하는 방법을 정리했습니다. RLOO와 REINFORCE, PPO·GRPO와의 차이, Leave-One-Out Baseline, Advantage 계산, num_generations, Reward Function, Accuracy Reward, Format Reward, 여러 Reward 조합, KL Penalty, Reference Model, Importance Sampling Ratio, Policy Clipping, RLOOConfig, LoRA·QLoRA, vLLM 가속, 실전 수학 프로젝트, Reward Hacking과 오류 해결까지 다룹니다.

목차

메타 설명
PPO의 무거운 Value Model 없이도 생성형 AI를 강화학습할 수 있을까요? Hugging Face TRL의 RLOOTrainer를 이용해 하나의 질문에서 여러 답변을 생성하고, 자기 자신을 제외한 나머지 답변의 평균 Reward를 Baseline으로 사용하는 REINFORCE Leave-One-Out의 원리부터 Advantage, KL Penalty, Policy Clipping, LoRA·QLoRA, Custom Reward, vLLM 가속과 실전 수학 프로젝트까지 알아봅니다.

지난 시간에는 GRPOTrainer를 이용해 하나의 Prompt에서 여러 답변을 생성하고 서로의 Reward를 비교해 Policy를 강화학습하는 방법을 알아보았습니다.

예를 들어 모델에게 다음 문제를 줬습니다.

문제:

15 × 8은?

모델이 네 개의 답변을 생성합니다.

답변 A:
120

답변 B:
110

답변 C:
120

답변 D:
130

Reward:

A → 1

B → 0

C → 1

D → 0

그리고 그룹 안에서 상대적으로 잘한 답변을 강화했습니다.

그런데 오늘은 평균을 계산하는 방법을 살짝 바꿔보겠습니다.

답변 A의 성적을 평가한다고 하겠습니다.

GRPO 스타일의 단순한 그룹 평균을 생각하면:

A
B
C
D

모두 포함

할 수 있습니다.

하지만 RLOO는 다릅니다.

A를 평가할 때

A는 평균에서 제외


B
C
D

평균만 계산

답변 B를 평가할 때는?

B 제외


A
C
D

평균 계산

답변 C는?

C 제외


A
B
D

평균 계산

바로 이것이 오늘의 핵심입니다.

Leave One Out

한국어로 풀면:

하나를 빼고 계산한다.

이 아이디어가 적용된 강화학습 알고리즘이 바로 RLOO, REINFORCE Leave-One-Out입니다.

현재 Hugging Face TRL의 RLOOTrainer도 하나의 Prompt에서 여러 Completion을 생성한 뒤, 각 Completion을 평가할 때 자기 자신을 제외한 나머지 Completion의 평균 Reward를 Baseline으로 사용합니다. 이 Baseline은 Policy Gradient의 분산을 낮추는 데 사용됩니다.

학생 A:

“우리 반 평균이 몇 점이에요?”


RLOO:

“잠깐.

네 점수는 빼고 계산해야지.”


학생 A:

“왜 저만 빼요?”


RLOO:

“B도 B 계산할 때 빠지고,
C도 C 계산할 때 빠집니다.”


학생 A:

“공평하네요.”

오늘은 강화학습 교실에서 본인 제외 평균제를 시행해 보겠습니다. 🤖📊

1. RLOO란?

RLOO는 다음 표현의 약자입니다.

REINFORCE Leave-One-Out

이름을 두 부분으로 나눠보겠습니다.

REINFORCE

+

Leave-One-Out

즉 REINFORCE Policy Gradient를 기반으로 하면서, 여러 생성 결과 중 현재 Sample 하나를 제외한 나머지 Reward 평균을 Baseline으로 사용하는 방법입니다.

RLOO는 `Back to Basics: Revisiting REINFORCE Style Optimization for Learning from Human Feedback in LLMs` 연구에서 LLM RLHF에 적합한 단순한 REINFORCE 계열 방법으로 연구되었습니다. 해당 연구에서는 복잡한 PPO 구성 요소 중 상당 부분을 줄인 REINFORCE 계열 방법들이 자신들의 실험에서 PPO와 DPO 등 강한 Baseline과 경쟁하거나 능가할 수 있음을 보고했습니다.

전체 구조:

Prompt

↓

Policy Model

↓

여러 Completion 생성

├─ A
├─ B
├─ C
└─ D

↓

각 Completion Reward 계산

↓

A 평가
→ B, C, D 평균

B 평가
→ A, C, D 평균

C 평가
→ A, B, D 평균

D 평가
→ A, B, C 평균

↓

Advantage 계산

↓

Policy 업데이트

2. REINFORCE란?

REINFORCE는 가장 기본적인 Policy Gradient 알고리즘 중 하나입니다.

핵심 아이디어는 놀라울 정도로 단순합니다.

좋은 결과를 만든 행동

→ 확률 증가


나쁜 결과를 만든 행동

→ 확률 감소

LLM이라면 행동은 생성된 Completion입니다.

Prompt

↓

Completion

↓

Reward

예:

Prompt:

Python 리스트란?


Completion A:

리스트는 여러 값을
순서대로 저장하는 자료형입니다.

Reward:
3.0

Reward가 좋다면:

A 같은 Completion이 나올 확률

↑

반대로:

Completion B:

리스트는 CPU입니다.

Reward:
-2.0

이라면:

B 같은 Completion이 나올 확률

↓

RLOO는 이 기본적인 REINFORCE 아이디어에 좋은 Baseline을 추가합니다.

3. RLOO가 등장한 이유

PPO를 다시 떠올려 보겠습니다.

PPO에는 일반적으로 다음 구성 요소가 필요했습니다.

Policy Model

Reference Model

Reward Model

Value Model

특히 Value Model은:

앞으로 받을 Reward를
얼마나 기대할 수 있는가?

를 추정했습니다.

하지만 LLM은 모델 하나만 커도 GPU가 뜨끈해집니다.

여기에 모델을 여러 개 추가하면:

Policy

+

Reference

+

Reward

+

Value

GPU:

“혹시 저한테
개인적인 감정 있으세요?”

RLOO 연구는 PPO의 Critic, 즉 Value Function에 의존하지 않는 훨씬 단순한 REINFORCE 계열 RLHF 방법을 탐구했습니다.

RLOO에서는 Value Model 대신 여러 Online Sample의 Reward를 Baseline 계산에 활용합니다.

Value Model:

없음


다른 Completion들의 Reward:

Baseline 역할

4. PPO와 RLOO 차이

PPO

Prompt

↓

Response

↓

Reward Model

+

Value Model

↓

Advantage

↓

PPO 업데이트

RLOO

Prompt

↓

Response 여러 개

↓

각 Response Reward

↓

나머지 Response 평균

↓

Advantage

↓

REINFORCE 업데이트

가장 큰 차이:

PPO

→ Value Model 필요


RLOO

→ Value Model 없음

또한 RLOO의 현재 TRL 구현은 Completion 전체의 Sequence Probability를 이용하는 REINFORCE 형태의 목적함수를 사용합니다.

5. GRPO와 RLOO 차이

지난 편의 GRPO와 매우 비슷해 보입니다.

둘 다:

하나의 Prompt

↓

여러 Completion 생성

↓

Reward 비교

↓

Value Model 없이 Advantage 계산

합니다.

하지만 Baseline 계산 아이디어가 다릅니다.

GRPO

대표적인 GRPO 구성에서는 그룹 Reward를 중심화하거나 표준화합니다.

현재 답변 Reward

-

그룹 평균

여기서 그룹 평균에는 보통 자기 자신도 포함됩니다.

RLOO

현재 답변 Reward

-

나를 제외한
다른 Completion 평균

입니다.

GRPO:

“우리 모두의 평균을 봅시다.”


RLOO:

“본인 점수는 평균에서 빼세요.”

이름 그대로 Leave One Out입니다.

6. Leave-One-Out이란?

4개의 Reward가 있다고 하겠습니다.

A = 10

B = 8

C = 4

D = 2

A의 Baseline은:

B + C + D

─────────

3

입니다.

A 자신은 들어가지 않습니다.

B의 Baseline은:

A + C + D

─────────

3

입니다.

모든 Sample이 자신의 Baseline 계산에서 제외됩니다.

현재 TRL 공식 문서는 RLOO Baseline을 다음 개념으로 정의합니다.

현재 Completion을 제외한
나머지 G-1개의 평균 Reward

그리고 현재 Completion Reward에서 이 Baseline을 빼 Advantage를 계산합니다.

7. RLOO 전체 학습 흐름

현재 TRL 문서는 RLOO를 크게 다음 과정으로 설명합니다.

Completion 생성

↓

Reward 계산

↓

Advantage 계산

↓

KL 추정

↓

RLOO Loss 계산

RLOO는 현재 Policy가 생성한 데이터를 이용해 반복 학습하는 Online Learning 알고리즘입니다.

조금 더 자세히 보면:

① Prompt 선택

② 같은 Prompt에서 G개 Completion 생성

③ 각 Completion Reward 계산

④ KL Penalty 계산

⑤ 최종 Reward 구성

⑥ Leave-One-Out Baseline 계산

⑦ Advantage 계산

⑧ Policy Probability 계산

⑨ 필요하면 Importance Ratio 계산

⑩ Clipping

⑪ REINFORCE Loss

⑫ Policy 업데이트

8. 여러 Completion 생성

RLOO도 GRPO와 마찬가지로 한 Prompt에서 여러 답변을 생성합니다.

Prompt:

12 × 8은?

생성:

A:
96

B:
94

C:
96

D:
108

현재 TRL의 설정은:

num_generations=4

처럼 지정합니다.

현재 `RLOOConfig`의 기본값은:

num_generations=2

입니다.

9. Reward 계산

각 Completion에 Reward를 부여합니다.

정답:

96

Reward Function:

정답:

1


오답:

0

따라서:

A:
1

B:
0

C:
1

D:
0

RLOOTrainer는 현재 다음 형태의 Reward Source를 지원합니다.

Python Reward Function

Async Reward Function

Sequence Classification Reward Model

Hub의 Reward Model

여러 Reward의 조합

Custom Reward Function은 Prompt와 Completion뿐 아니라 Dataset에 존재하는 추가 열도 받을 수 있습니다.

10. Baseline이 필요한 이유

가장 단순한 REINFORCE에서는 Reward를 그대로 사용할 수 있습니다.

Reward 10

→ 강하게 증가


Reward 1

→ 약하게 증가

하지만 문제마다 Reward Scale과 난이도가 다르면 Gradient의 변동이 매우 커질 수 있습니다.

예를 들어:

문제 A:

Reward 100


문제 B:

Reward 2

100점이 무조건 훌륭한 행동이라는 의미일까요?

그 문제에서는 모두가 150점을 받을 수도 있습니다.

RLOO는 다음 질문을 합니다.

“같은 상황에서 나온
다른 Sample들과 비교하면
얼마나 잘한 것인가?”

Baseline을 빼면 Reward의 절대적인 크기보다 상대적으로 잘했는지를 볼 수 있습니다.

11. Leave-One-Out Baseline

Completion이 G개 있다면 Sample i의 Baseline은:

나를 제외한

G - 1개 Reward 평균

입니다.

TRL 공식 정의:

b_i

=

나머지 Completion Reward의 평균

그리고 Advantage는:

A_i

=

r_i - b_i

입니다.

쉽게 표현하면:

내 점수

-

나 빼고 반 평균

입니다.

12. 실제 숫자로 Baseline 계산

다음 Reward를 사용하겠습니다.

A = 10

B = 8

C = 4

D = 2

A의 Baseline

A를 빼고:

8 + 4 + 2

= 14

평균:

14 / 3

≈ 4.67

A Advantage:

10 - 4.67

≈ +5.33

B의 Baseline

B를 제외합니다.

10 + 4 + 2

= 16

평균:

16 / 3

≈ 5.33

Advantage:

8 - 5.33

≈ +2.67

C의 Baseline

10 + 8 + 2

= 20

평균:

20 / 3

≈ 6.67

Advantage:

4 - 6.67

≈ -2.67

D의 Baseline

10 + 8 + 4

= 22

평균:

22 / 3

≈ 7.33

Advantage:

2 - 7.33

≈ -5.33

정리하면:

A:
+5.33

B:
+2.67

C:
-2.67

D:
-5.33

13. Advantage 계산

이 Advantage가 Policy 학습 방향을 결정합니다.

Advantage > 0

→ 해당 Completion 확률 증가


Advantage < 0

→ 해당 Completion 확률 감소

위 예에서는:

A:

강하게 강화


B:

약하게 강화


C:

약하게 억제


D:

강하게 억제

됩니다.

RLOO:

“A는 잘했습니다.

B도 평균 이상입니다.

C는 조금 아쉽고,

D는 상담실로 오세요.”

14. RLOO Advantage의 특징

재미있는 특성이 하나 있습니다.

앞 예제의 Advantage를 모두 더해보겠습니다.

+5.33

+2.67

-2.67

-5.33

결과:

0

RLOO의 Leave-One-Out Baseline은 그룹 내부의 상대적 차이를 중심으로 학습 신호를 만듭니다.

즉 그룹 전체가 무조건 긍정적인 Gradient만 만들거나 부정적인 Gradient만 만들지 않고 상대적인 우열을 반영할 수 있습니다.

15. GRPO 평균과 RLOO 평균 비교

앞선 Reward:

10
8
4
2

전체 평균은:

6

단순 Group Mean을 사용한다면:

A:

10 - 6 = 4


B:

8 - 6 = 2


C:

4 - 6 = -2


D:

2 - 6 = -4

RLOO:

A:
+5.33

B:
+2.67

C:
-2.67

D:
-5.33

흥미로운 관계가 있습니다.

Completion 수가 G일 때, 단순 Group Mean을 사용한 Centered Reward와 RLOO Advantage는:

RLOO Advantage

=

G
─────
G - 1

×

Centered Group Reward

관계가 됩니다.

G=4라면:

4 / 3

배입니다.

다만 현재 TRL의 GRPO 기본 구성은 Group Standard Deviation Scaling 등 추가 처리가 적용될 수 있으므로 실제 GRPOTrainer와 RLOOTrainer의 Advantage가 단순히 상수배 관계라고 보면 안 됩니다.

알고리즘의 Baseline 차이를 이해하기 위한 수학적 비교입니다.

16. 모든 Reward가 같으면?

Reward:

1
1
1
1

A의 Baseline:

1

Advantage:

1 - 1

= 0

다른 Completion도 마찬가지입니다.

A:
0

B:
0

C:
0

D:
0

모두 틀렸을 때도:

0
0
0
0

Advantage가 모두 0입니다.

즉:

비교할 차이가 없다

→ 학습 신호가 없다

는 뜻입니다.

현재 RLOOTrainer도 이 상황을 진단하기 위해 `frac_reward_zero_std`를 기록합니다. 이 값은 동일 Prompt에서 생성된 답변들의 Reward 분산이 0인 비율을 나타냅니다.

17. num_generations가 중요한 이유

Generation 수를 늘리면 Baseline을 계산할 Sample이 많아집니다.

num_generations=2

라면:

A의 Baseline:

B 하나


B의 Baseline:

A 하나

입니다.

반면:

num_generations=8

이면 각 Completion의 Baseline에 다른 7개 Reward가 사용됩니다.

장점:

Baseline 안정성 증가 가능

다양한 답변 탐색

더 많은 비교 정보

단점:

Generation 비용 증가

GPU 메모리 증가

학습 시간 증가

18. RLOO 최소 Generation 수

Leave-One-Out을 하려면 최소한 다른 Sample 하나가 필요합니다.

따라서:

num_generations=1

은 불가능합니다.

현재 TRL 구현에서도 `num_generations`가 2보다 작으면 오류를 발생시킵니다.

학생 수:

1명


RLOO:

“본인을 제외한
반 평균을 계산하세요.”


학생:

“저밖에 없는데요.”


RLOO:

“수업 종료.”

19. Reward Function

RLOOTrainer에서는 Custom Reward Function을 매우 쉽게 작성할 수 있습니다.

def reward_func(
    completions,
    **kwargs
):
    return [
        1.0
        for completion
        in completions
    ]

반환값:

Completion 하나당

float Reward 하나

입니다.

현재 Reward Function에는 다음 값도 전달할 수 있습니다.

prompts

completions

completion_ids

trainer_state

Dataset 추가 열

그리고 동기 함수뿐 아니라 `async def` 형태의 비동기 Reward Function도 지원합니다. 여러 Async Reward Function이 있으면 동시에 Await해 I/O 지연을 겹쳐 처리할 수 있습니다.

20. Accuracy Reward

수학 문제를 예로 들어보겠습니다.

import re


def extract_answer(
    text: str
) -> str | None:
    match = re.search(
        r"<answer>\s*(.*?)\s*</answer>",
        text,
        flags=re.DOTALL
    )

    if match is None:
        return None

    return (
        match
        .group(1)
        .strip()
    )

Reward Function:

def accuracy_reward(
    completions,
    ground_truth,
    **kwargs
):
    rewards = []

    for completion, answer in zip(
        completions,
        ground_truth
    ):
        predicted = extract_answer(
            completion
        )

        rewards.append(
            1.0
            if predicted == str(answer)
            else 0.0
        )

    return rewards

21. Format Reward

정답뿐 아니라 형식도 가르칠 수 있습니다.

원하는 형식:

<answer>84</answer>

Reward:

def format_reward(
    completions,
    **kwargs
):
    pattern = re.compile(
        r".*<answer>\s*"
        r"[-+]?[0-9]+(?:\.[0-9]+)?"
        r"\s*</answer>\s*$",
        flags=re.DOTALL
    )

    return [
        1.0
        if pattern.fullmatch(
            completion.strip()
        )
        else 0.0
        for completion
        in completions
    ]

22. 여러 Reward 조합

현재 RLOOTrainer는 여러 Reward Function을 함께 사용할 수 있습니다.

reward_funcs=[
    accuracy_reward,
    format_reward
]

Reward:

Accuracy:

1


Format:

1

단순 합산하면:

Total Reward:

2

현재 여러 Reward Function을 지정하면 결과를 합산하며 `reward_weights`로 중요도를 조정할 수 있습니다.

23. Reward Weight

정답이 형식보다 중요하다면:

reward_weights=[
    1.0,
    0.2
]

로 지정할 수 있습니다.

예:

정답 맞음:
1 × 1.0

Format 맞음:
1 × 0.2


Total:
1.2

정답이 틀리고 형식만 맞으면:

0 + 0.2

= 0.2
모델:

“정답은 모르겠지만
태그는 예쁘게 썼습니다.”


RLOO:

“0.2점.”

24. KL Penalty

RLOO는 단순히 Reward만 최대화하지 않습니다.

Policy가 기존 모델에서 지나치게 멀리 움직이지 않도록 Reference Policy와의 KL Divergence를 Reward에 반영할 수 있습니다.

현재 RLOO의 Reward는 개념적으로:

Reward Model Score

-

beta × KL

구조로 설명됩니다.

즉:

Reward는 높지만

기존 모델에서 지나치게 멀어짐

→ Penalty

입니다.

25. beta의 의미

현재 RLOOConfig 기본값:

beta=0.05

입니다.

Beta가 커지면:

Reference에서
멀리 움직이기 어려움

Beta가 작아지면:

Reward 방향으로
더 자유롭게 이동

합니다.

특별히:

beta=0.0

으로 설정하면 Reference Model 자체를 로드하지 않아 메모리 사용량과 연산량을 줄일 수 있습니다.

26. Reference Model

이전 RLOO API에서는 Reference Policy를 직접 전달하던 시절이 있었습니다.

현재 API에서는 다릅니다.

현재 RLOOTrainer에서는:

ref_policy

직접 전달

방식이 제거되고 Policy Model을 바탕으로 Reference Model을 자동 생성합니다.

즉 현재 기본 Trainer:

trainer = RLOOTrainer(
    model=MODEL_ID,
    reward_funcs=reward_func,
    train_dataset=dataset
)

만으로도 됩니다.

개발자:

“Reference Model 인자는 어디 있죠?”


현재 RLOOTrainer:

“제가 준비합니다.”

27. 현재 RLOO와 GRPO의 KL 차이

흥미로운 차이가 있습니다.

현재 RLOO 구현에서는 KL이 Reward Shaping에 사용됩니다.

즉 KL을 계산한 후 Scalar Reward에 포함하고, 그 Reward를 이용해 Advantage를 계산합니다.

KL 항 자체를 직접 Gradient 경로로 Backpropagation하지 않습니다.

반면 현재 GRPO는 KL 항을 Objective에 직접 포함할 수 있어 Gradient가 KL 항을 통해서도 흐릅니다. TRL 공식 RLOO 문서는 이 차이를 명시하고 있습니다.

쉽게 표현하면:

RLOO:

KL 점수를 계산해서
성적표에서 감점


GRPO:

KL 항 자체도
Loss 계산에 직접 참여 가능

28. RLOO Loss

RLOO의 기본 REINFORCE Loss를 직관적으로 표현하면:

- Advantage

×

Completion Log Probability

입니다.

TRL 공식 문서에서는 Completion 전체의 확률과 Leave-One-Out Advantage를 이용하는 REINFORCE Loss로 정의합니다.

Positive Advantage:

좋은 Completion

→ Log Probability 증가 방향

Negative Advantage:

나쁜 Completion

→ Log Probability 감소 방향

29. Importance Sampling Ratio

현재 기본값은:

num_iterations=1

입니다.

이 경우 데이터를 현재 Policy로 생성하고 바로 한 번 업데이트하므로 기본적인 REINFORCE와 매우 가깝습니다.

하지만 같은 Completion Batch로 여러 번 업데이트한다면:

num_iterations=2

또는:

num_iterations=4

처럼 사용할 수 있습니다.

그러면 Generation 시점의 Policy와 현재 업데이트된 Policy가 달라집니다.

그래서:

현재 Policy 확률

──────────────

Sampling Policy 확률

인 Importance Sampling Ratio가 필요합니다.

TRL 공식 문서도 여러 Gradient Step을 수행할 경우 Ratio와 Clipping을 적용하고, 기본 `num_iterations=1`에서는 Ratio가 1이 되어 표준 REINFORCE 형태로 축소된다고 설명합니다.

30. Policy Clipping

RLOOTrainer도 Policy Update가 지나치게 커지는 것을 막기 위한 Clipping을 제공합니다.

현재 기본값:

epsilon=0.2

입니다.

대칭 Clip이라면:

0.8 ~ 1.2

정도의 Ratio 범위를 사용하게 됩니다.

또한 현재:

epsilon_high=0.28

처럼 상단 Clip을 별도로 설정하는 것도 가능합니다.

31. num_iterations

현재 기본값:

num_iterations=1

입니다.

1

현재 Policy가 생성

↓

한 번 학습

가장 Online한 형태입니다.

2 이상

한 번 생성

↓

같은 Rollout으로
여러 번 Gradient Update

Generation 비용은 줄일 수 있지만 점점 Off-policy가 되므로 Importance Ratio와 Clipping이 중요해집니다.

32. normalize_advantages

현재 기본값:

normalize_advantages=False

입니다.

활성화하면:

normalize_advantages=True

Generation Batch의 Advantage를:

평균:

0


표준편차:

1

이 되도록 정규화합니다.

Reward Scale이 불안정한 환경에서 실험할 수 있는 옵션입니다.

33. Reward Clipping

Reward가 매우 큰 값을 가지는 경우:

+5000

-3000

+12000

Gradient가 불안정해질 수 있습니다.

현재 RLOOConfig에는:

reward_clip_range=(
    -5.0,
    5.0
)

처럼 Reward 자체를 제한하는 기능이 있습니다. 기본값은 `None`이라 별도 Clipping을 하지 않습니다.

34. 현재 TRL 버전

2026년 8월 17일 현재 PyPI의 최신 TRL 안정 버전은 1.10.0이며, 2026년 8월 13일 릴리스로 표시됩니다.

설치 버전 확인:

python -m pip show trl

Python:

import trl


print(
    trl.__version__
)

현재 RLOOTrainer는 일반 TRL API에서 가져옵니다.

from trl import (
    RLOOConfig,
    RLOOTrainer
)

현재 공식 예제 목록에도 RLOOTrainer 기반 수학 Fine-tuning Script가 포함되어 있습니다.

35. RLOOTrainer 기본 사용법

현재 공식 Quick Start는 매우 간단합니다.

from datasets import load_dataset

from trl import RLOOTrainer

from trl.rewards import (
    accuracy_reward
)


dataset = load_dataset(
    "trl-lib/DeepMath-103K",
    split="train"
)


trainer = RLOOTrainer(
    model=(
        "Qwen/"
        "Qwen2.5-0.5B-Instruct"
    ),

    reward_funcs=accuracy_reward,

    train_dataset=dataset
)


trainer.train()

현재 공식 문서도 Qwen2.5-0.5B-Instruct와 DeepMath-103K 형태의 예제로 RLOOTrainer를 설명합니다.

실행:

accelerate launch train_rloo.py

36. RLOOConfig 핵심 설정

예:

from trl import RLOOConfig


training_args = RLOOConfig(
    output_dir=(
        "outputs/"
        "math-rloo"
    ),

    learning_rate=1e-6,

    per_device_train_batch_size=1,

    gradient_accumulation_steps=4,

    num_train_epochs=3,

    num_generations=4,

    max_completion_length=128,

    temperature=0.8,

    top_p=0.95,

    beta=0.05,

    num_iterations=1,

    epsilon=0.2,

    normalize_advantages=False,

    reward_weights=[
        1.0,
        0.2
    ],

    logging_steps=1,

    log_completions=True,

    report_to="none"
)

현재 RLOOConfig의 주요 기본값은 다음과 같습니다.

learning_rate:
1e-6

num_generations:
2

max_completion_length:
512

temperature:
1.0

beta:
0.05

num_iterations:
1

epsilon:
0.2

normalize_advantages:
False

37. Dataset 구성

RLOO Dataset의 핵심 열은:

prompt

입니다.

현재 Trainer는 Standard Format과 Conversational Format을 모두 지원하며 추가 Dataset 열은 Custom Reward Function에서 사용할 수 있습니다.

예:

{
    "prompt": (
        "17 × 8을 계산하세요. "
        "최종 답은 "
        "<answer>숫자</answer> "
        "형식으로 출력하세요."
    ),

    "ground_truth": "136"
}

`ground_truth`는 Reward Function에서 사용합니다.

38. LoRA와 RLOO

RLOOTrainer는 현재 `peft_config`를 직접 지원합니다.

from peft import (
    LoraConfig,
    TaskType
)


lora_config = LoraConfig(
    task_type=(
        TaskType.CAUSAL_LM
    ),

    r=16,

    lora_alpha=32,

    lora_dropout=0.05,

    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj"
    ],

    bias="none"
)

Trainer:

trainer = RLOOTrainer(
    model=MODEL_ID,

    args=training_args,

    reward_funcs=[
        accuracy_reward,
        format_reward
    ],

    train_dataset=train_dataset,

    peft_config=lora_config
)

Policy 전체가 아니라 LoRA Adapter만 Online RL로 업데이트할 수 있습니다.

39. QLoRA와 RLOO

현재 RLOOTrainer에는 `quantization_config`와 `peft_config`가 모두 존재하며, 모델 ID 문자열과 함께 사용하면 QLoRA 형태로 구성할 수 있습니다. 이미 Model Object를 직접 생성해 전달한 경우 Trainer의 `quantization_config`는 적용되지 않습니다.

예:

import torch

from transformers import (
    BitsAndBytesConfig
)


quantization_config = (
    BitsAndBytesConfig(
        load_in_4bit=True,

        bnb_4bit_quant_type="nf4",

        bnb_4bit_use_double_quant=True,

        bnb_4bit_compute_dtype=(
            torch.bfloat16
        )
    )
)

Trainer:

trainer = RLOOTrainer(
    model=MODEL_ID,

    args=training_args,

    reward_funcs=[
        accuracy_reward
    ],

    train_dataset=train_dataset,

    peft_config=lora_config,

    quantization_config=(
        quantization_config
    )
)

BF16은 실제 GPU 지원 여부를 확인해야 합니다.

40. vLLM 가속

RLOO는 Online RL입니다.

즉 계속 새로운 Completion을 생성해야 합니다.

생성

↓

학습

↓

생성

↓

학습

↓

생성

Generation이 병목이 되기 쉽습니다.

현재 RLOOTrainer는 vLLM 기반 Generation 가속을 지원합니다.

설치:

python -m pip install "trl[vllm]"

활성화:

training_args = RLOOConfig(
    use_vllm=True
)

현재 기본 Mode는:

vllm_mode="colocate"

입니다. Training Model과 같은 GPU를 공유합니다.

별도 GPU를 사용한다면:

vllm_mode="server"

를 사용할 수 있습니다. 현재 RLOOConfig의 vLLM Colocate 기본 GPU Memory Utilization은 0.3입니다.

41. RLOO 로그 읽기

현재 RLOOTrainer는 다양한 Metric을 기록합니다.

중요한 항목:

reward

reward_std

frac_reward_zero_std

rewards/<reward_name>/mean

rewards/<reward_name>/std

completions/mean_length

completions/clipped_ratio

entropy

kl

clip_ratio/region_mean

reward

전체 Reward Function을 합산한 평균 Reward입니다.

reward_std

전체 Batch Reward의 표준편차입니다.

entropy

생성 Token 분포가 얼마나 다양한지 볼 수 있습니다.

kl

`beta`가 0이 아닐 때 Reference와의 평균 KL을 기록합니다.

42. Reward Std와 Zero Std

특히 중요합니다.

frac_reward_zero_std

가 높다면:

같은 Prompt의 답변들이
모두 같은 점수를 받고 있다.

는 뜻일 수 있습니다.

예:

Prompt A

Completion 1:
0

Completion 2:
0

Completion 3:
0

Completion 4:
0

RLOO:

“나머지 평균도 0.

내 Reward도 0.

Advantage도 0.”

학습할 것이 없습니다.

원인:

문제가 너무 어려움

문제가 너무 쉬움

Reward Function이 지나치게 Binary

Temperature가 너무 낮음

모델이 같은 답만 생성

현재 Trainer가 이 비율을 직접 기록하는 이유도 이런 Online RL 학습 신호를 진단하기 위해서입니다.

43. 실전 수학 강화학습 프로젝트

이제 직접 만들어보겠습니다.

목표:

산술 문제

↓

한 Prompt에서
4개 답변 생성

↓

Accuracy Reward

+

Format Reward

↓

Leave-One-Out Advantage

↓

LoRA Policy 학습
이 예제는 RLOO Pipeline을 이해하기 위한 소규모 실습입니다. 실제 추론 성능 향상을 검증하려면 훨씬 많은 Train·Validation·Test 데이터가 필요합니다.

`train_rloo.py`:

from pathlib import Path
import re

from datasets import Dataset

from peft import (
    LoraConfig,
    TaskType
)

from transformers import (
    AutoTokenizer
)

from trl import (
    RLOOConfig,
    RLOOTrainer
)


MODEL_ID = (
    "Qwen/"
    "Qwen2.5-0.5B-Instruct"
)

OUTPUT_DIR = Path(
    "outputs/"
    "math-rloo"
)

SEED = 2026


RAW_DATA = [
    {
        "question": "12 × 7",
        "ground_truth": "84"
    },
    {
        "question": "25 + 38",
        "ground_truth": "63"
    },
    {
        "question": "144 ÷ 12",
        "ground_truth": "12"
    },
    {
        "question": "17 × 8",
        "ground_truth": "136"
    },
    {
        "question": "93 - 47",
        "ground_truth": "46"
    },
    {
        "question": "16 × 9",
        "ground_truth": "144"
    },
    {
        "question": "225 ÷ 15",
        "ground_truth": "15"
    },
    {
        "question": "128 + 79",
        "ground_truth": "207"
    },
    {
        "question": "300 - 128",
        "ground_truth": "172"
    },
    {
        "question": "19 × 11",
        "ground_truth": "209"
    },
    {
        "question": "256 ÷ 16",
        "ground_truth": "16"
    },
    {
        "question": "67 + 89",
        "ground_truth": "156"
    },
    {
        "question": "13 × 14",
        "ground_truth": "182"
    },
    {
        "question": "500 - 273",
        "ground_truth": "227"
    },
    {
        "question": "18 × 12",
        "ground_truth": "216"
    },
    {
        "question": "324 ÷ 18",
        "ground_truth": "18"
    },
    {
        "question": "45 + 76 - 21",
        "ground_truth": "100"
    },
    {
        "question": "8 × 7 + 4",
        "ground_truth": "60"
    },
    {
        "question": "(15 + 5) × 3",
        "ground_truth": "60"
    },
    {
        "question": "100 ÷ 4 + 17",
        "ground_truth": "42"
    }
]


SYSTEM_MESSAGE = (
    "당신은 정확한 산술 문제 풀이 AI입니다. "
    "문제를 계산한 뒤 최종 결과를 반드시 "
    "<answer>숫자</answer> 형식으로 출력하세요."
)


def build_prompt(
    tokenizer,
    question: str
) -> str:
    messages = [
        {
            "role": "system",
            "content": SYSTEM_MESSAGE
        },
        {
            "role": "user",
            "content": (
                f"다음 값을 계산하세요: "
                f"{question}"
            )
        }
    ]

    return tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )


def extract_answer(
    text: str
) -> str | None:
    match = re.search(
        r"<answer>\s*(.*?)\s*</answer>",
        text,
        flags=re.DOTALL
    )

    if match is None:
        return None

    return (
        match
        .group(1)
        .strip()
    )


def accuracy_reward(
    completions,
    ground_truth,
    **kwargs
):
    rewards = []

    for completion, answer in zip(
        completions,
        ground_truth
    ):
        predicted = extract_answer(
            completion
        )

        rewards.append(
            1.0
            if predicted == str(answer)
            else 0.0
        )

    return rewards


def format_reward(
    completions,
    **kwargs
):
    pattern = re.compile(
        r".*<answer>\s*"
        r"[-+]?[0-9]+(?:\.[0-9]+)?"
        r"\s*</answer>\s*$",
        flags=re.DOTALL
    )

    rewards = []

    for completion in completions:
        is_valid = bool(
            pattern.fullmatch(
                completion.strip()
            )
        )

        rewards.append(
            1.0
            if is_valid
            else 0.0
        )

    return rewards


def main():
    tokenizer = (
        AutoTokenizer
        .from_pretrained(
            MODEL_ID,
            padding_side="left"
        )
    )

    if tokenizer.pad_token is None:
        tokenizer.pad_token = (
            tokenizer.eos_token
        )

    prepared_data = []

    for row in RAW_DATA:
        prepared_data.append({
            "prompt": build_prompt(
                tokenizer,
                row["question"]
            ),

            "ground_truth": (
                row["ground_truth"]
            )
        })

    dataset = Dataset.from_list(
        prepared_data
    )

    split_dataset = (
        dataset.train_test_split(
            test_size=0.2,
            seed=SEED,
            shuffle=True
        )
    )

    train_dataset = (
        split_dataset["train"]
    )

    eval_dataset = (
        split_dataset["test"]
    )

    lora_config = LoraConfig(
        task_type=(
            TaskType.CAUSAL_LM
        ),

        r=16,

        lora_alpha=32,

        lora_dropout=0.05,

        target_modules=[
            "q_proj",
            "k_proj",
            "v_proj",
            "o_proj"
        ],

        bias="none"
    )

    training_args = RLOOConfig(
        output_dir=str(
            OUTPUT_DIR
        ),

        learning_rate=1e-6,

        lr_scheduler_type="linear",

        warmup_ratio=0.1,

        per_device_train_batch_size=1,

        per_device_eval_batch_size=2,

        gradient_accumulation_steps=4,

        num_train_epochs=3,

        num_generations=4,

        num_generations_eval=2,

        max_completion_length=128,

        temperature=0.8,

        top_p=0.95,

        beta=0.05,

        num_iterations=1,

        epsilon=0.2,

        normalize_advantages=False,

        reward_weights=[
            1.0,
            0.2
        ],

        remove_unused_columns=False,

        mask_truncated_completions=True,

        eval_strategy="epoch",

        save_strategy="epoch",

        logging_strategy="steps",

        logging_steps=1,

        logging_first_step=True,

        save_total_limit=2,

        gradient_checkpointing=True,

        log_completions=True,

        num_completions_to_print=2,

        report_to="none",

        seed=SEED,

        data_seed=SEED,

        run_name=(
            "math-rloo-lora"
        )
    )

    trainer = RLOOTrainer(
        model=MODEL_ID,

        reward_funcs=[
            accuracy_reward,
            format_reward
        ],

        args=training_args,

        train_dataset=train_dataset,

        eval_dataset=eval_dataset,

        processing_class=tokenizer,

        peft_config=lora_config
    )

    print(
        "[학습 가능한 파라미터]"
    )

    trainer.model.print_trainable_parameters()

    print(
        "[RLOO 강화학습 시작]"
    )

    train_result = (
        trainer.train()
    )

    trainer.log_metrics(
        "train",
        train_result.metrics
    )

    trainer.save_metrics(
        "train",
        train_result.metrics
    )

    trainer.save_state()

    print(
        "[Validation 평가]"
    )

    eval_metrics = (
        trainer.evaluate()
    )

    trainer.log_metrics(
        "eval",
        eval_metrics
    )

    trainer.save_metrics(
        "eval",
        eval_metrics
    )

    print(
        "[모델 저장]"
    )

    trainer.save_model(
        str(
            OUTPUT_DIR
        )
    )

    tokenizer.save_pretrained(
        OUTPUT_DIR
    )

    print(
        f"저장 완료: "
        f"{OUTPUT_DIR.resolve()}"
    )


if __name__ == "__main__":
    main()

현재 RLOOTrainer는 `prompt` 열을 갖는 Dataset을 요구하며 Custom Reward Function이 추가 열을 사용한다면 `remove_unused_columns=False`를 유지해야 합니다. `num_generations`는 Effective Training Batch Size를 정확히 나눌 수 있어야 하며 최소 2 이상이어야 합니다.

44. 학습 결과 저장

trainer.save_model(
    "outputs/math-rloo"
)

Tokenizer:

tokenizer.save_pretrained(
    "outputs/math-rloo"
)

Checkpoint에서 다시 시작하려면:

trainer.train(
    resume_from_checkpoint=True
)

또는:

trainer.train(
    resume_from_checkpoint=(
        "outputs/"
        "math-rloo/"
        "checkpoint-100"
    )
)

현재 RLOOTrainer의 `train()`은 Trainer Checkpoint에서 모델·Optimizer·Scheduler 상태를 복원해 학습을 이어갈 수 있습니다.

45. 학습 전후 평가

다음 Test 문제는 Training에 넣지 않습니다.

23 × 7

31 + 48

400 ÷ 25

18 × 14

500 - 168

(25 + 5) × 4

비교:

항목BaseRLOO
정답률측정측정
Format 준수율측정측정
평균 Reward측정측정
평균 답변 길이측정측정
EOS 비율측정측정

RLOO Training Reward만 보고 성공 여부를 판단해서는 안 됩니다.

46. Reward Hacking

RLOO도 Reward Function이 잘못되어 있으면 그 허점을 찾아냅니다.

잘못된 Reward:

def bad_reward(
    completions,
    **kwargs
):
    return [
        1.0
        if "<answer>" in completion
        else 0.0
        for completion
        in completions
    ]

모델:

<answer>고양이</answer>

Reward:

1

다음:

<answer>777777777</answer>

Reward:

1

모델:

“정답은 중요하지 않고
태그만 있으면 되는군요.”

개발자:

“아니 그게 아닌데...”

강화학습 알고리즘은 개발자의 마음을 읽지 않습니다.

코드로 정의된 Reward

=

실제 목표

로 받아들입니다.

47. RLOO 안정화 방법

학습이 불안정하다면 다음 순서로 확인합니다.

Reward Function

가장 먼저 봅니다.

실제 정답과 Reward가 일치하는가?

Reward Scale이 지나치게 큰가?

길이나 특정 표현에 편향되어 있지 않은가?

Learning Rate

현재 기본:

learning_rate=1e-6

보다 너무 크게 올리지 않았는지 확인합니다.

Beta

KL이 너무 커지면:

beta=0.1

처럼 Reference 제약을 강화하는 실험을 할 수 있습니다.

num_iterations

불안정하다면:

num_iterations=1

로 돌아갑니다.

Generation Diversity

답변이 모두 같다면:

temperature=1.0

등을 검토합니다.

Reward Zero Std

frac_reward_zero_std

가 지나치게 높은지 확인합니다.

Truncated Completion

현재 RLOOConfig에는 잘린 Completion을 Loss에서 제외하는:

mask_truncated_completions=True

옵션이 있습니다. 현재 문서는 학습 안정성을 위한 실험 옵션으로 이를 제공합니다.

48. PPO·GRPO·RLOO 비교

세 가지 Online RL 방법을 정리해 보겠습니다.

항목PPOGRPORLOO
Online GenerationOOO
Value ModelOXX
여러 Completion가능핵심핵심
BaselineValue ModelGroup RewardLeave-One-Out
자기 Reward가 Baseline에 포함해당 없음방식에 따라 포함X
Reference Model일반적으로 사용beta에 따라beta에 따라
KL 기본값사용현재 0현재 0.05
Policy ClippingOOO
구조 복잡도높음중간중간
Custom Reward가능OO

현재 TRL RLOO의 `beta` 기본값은 0.05인 반면, 앞서 살펴본 현재 GRPO 기본 `beta`는 0.0이므로 기본 Reference Model 사용 여부도 차이가 있습니다. RLOO는 `beta=0`으로 설정하면 Reference Model을 생략할 수 있습니다.

한 문장으로 정리하면:

PPO:

“Value Model에게
예상 점수를 물어봅시다.”


GRPO:

“그룹 성적을
상대 비교합시다.”


RLOO:

“내 점수는 빼고
친구들 평균과 비교합시다.”

49. 자주 발생하는 오류

오류 1. RLOOTrainer Import 실패

현재 방식:

from trl import (
    RLOOConfig,
    RLOOTrainer
)

TRL 업데이트:

python -m pip install --upgrade trl

오류 2. 오래된 rloo_k 예제를 사용함

오래된 코드:

rloo_k=4

현재:

num_generations=4

로 이름이 변경되었습니다.

오류 3. kl_coef를 사용함

오래된 이름:

kl_coef=0.05

현재:

beta=0.05

입니다.

오류 4. cliprange 사용

오래된 이름:

cliprange=0.2

현재:

epsilon=0.2

입니다.

오류 5. num_ppo_epochs 사용

오래된 설정:

num_ppo_epochs=4

현재 RLOOConfig에서는:

num_iterations=4

로 변경되었습니다.

오류 6. Reference Model을 직접 전달하려 함

과거 API에는 `ref_policy`가 있었지만 현재 Trainer에서는 제거되었습니다.

Reference Model은 Trainer가 자동 생성합니다.

오류 7. num_generations=1

ValueError

가 발생합니다.

RLOO는 Leave-One-Out Baseline을 계산해야 하므로 최소 2개의 Generation이 필요합니다.

오류 8. Batch Size가 num_generations로 나누어지지 않음

예:

Effective Batch:

4


num_generations:

3

현재 RLOOConfig는 Effective Batch가 `num_generations`로 정확히 나누어져야 합니다.

해결:

num_generations=4

오류 9. Eval Batch Size 오류

Evaluation에서도 Global Eval Batch Size와 `num_generations_eval` 관계를 확인해야 합니다.

예:

per_device_eval_batch_size=2
num_generations_eval=2

1 GPU라면 나누어집니다.

오류 10. Reward가 모두 같음

1
1
1
1

또는:

0
0
0
0

확인:

frac_reward_zero_std

Reward Function과 문제 난이도를 조정합니다.

오류 11. Reward는 오르는데 정답률은 그대로

Format Reward만 최적화하고 있을 수 있습니다.

개별 Metric:

rewards/accuracy_reward/mean

rewards/format_reward/mean

을 함께 봅니다. RLOOTrainer는 Reward Function별 평균과 표준편차를 기록합니다.

오류 12. KL이 너무 커짐

kl

Metric을 확인합니다.

대응 후보:

Learning Rate 감소

beta 증가

num_iterations 감소

오류 13. Clip Ratio가 너무 높음

확인:

clip_ratio/region_mean

높은 값은 많은 Sequence Probability Ratio가 Trust Region 밖으로 벗어나 Clip되고 있다는 의미입니다.

오류 14. Completion이 계속 잘림

확인:

completions/clipped_ratio

대응:

max_completion_length=256

또는 Prompt와 답변 형식을 간결하게 만듭니다.

오류 15. CUDA Out of Memory

우선:

Batch Size 감소

num_generations 감소

max_completion_length 감소

LoRA

QLoRA

Gradient Checkpointing

을 검토합니다.

오류 16. beta=0인데 KL Metric이 없음

정상입니다.

beta=0.0

이면 Reference Model을 로드하지 않으며 KL Metric도 사용되지 않습니다.

오류 17. Reward Model 대신 Custom Function을 쓰고 싶음

가능합니다.

trainer = RLOOTrainer(
    model=MODEL_ID,

    reward_funcs=[
        my_reward_func
    ],

    train_dataset=dataset
)

현재 RLOOTrainer는 Dense Reward Model 없이 Custom Reward만으로도 학습할 수 있습니다.

오류 18. 비동기 API를 Reward로 사용하고 싶음

현재 Custom Reward Function은:

async def reward_func(
    completions,
    **kwargs
):
    ...

형태도 지원합니다. 여러 Async Reward Function은 동시에 처리할 수 있습니다.

오류 19. vLLM OOM

Colocate Mode에서는 Training Model과 vLLM이 GPU를 공유합니다.

다음 값을 조정합니다.

vllm_gpu_memory_utilization=0.2

현재 기본값은 0.3입니다.

오류 20. vLLM Server와 Trainer GPU가 같음

Server Mode에서는 별도 GPU 사용을 권장합니다.

같은 GPU를 잘못 사용하면 NCCL 관련 충돌이 발생할 수 있다고 현재 공식 문서가 경고합니다.

50. 연습 문제

문제 1

RLOO의 전체 이름을 작성하세요.

REINFORCE Leave-One-Out

문제 2

Reward가 다음과 같습니다.

A = 8

B = 6

C = 4

D = 2

A의 Leave-One-Out Baseline을 계산하세요.

문제 3

위 데이터에서 B의 Baseline과 Advantage를 계산하세요.

문제 4

모든 Reward가 다음과 같다면 Advantage는 어떻게 될까요?

1

1

1

1

문제 5

RLOO에서 Value Model이 필요하지 않은 이유를 설명하세요.

문제 6

다음 오래된 설정을 현재 API 이름으로 바꾸세요.

rloo_k

cliprange

kl_coef

num_ppo_epochs

정답:

num_generations

epsilon

beta

num_iterations

문제 7

다음 Reward Function을 작성하세요.

정답:

+1


오답:

0

문제 8

JSON Parsing에 성공하면 0.2 Reward를 추가하세요.

문제 9

Accuracy와 Format Reward Weight를 다음처럼 지정하세요.

Accuracy:
1.0

Format:
0.2

문제 10

다음 Beta를 비교하세요.

0

0.01

0.05

0.1

그리고:

Reward

KL

Test Accuracy

를 비교하세요.

문제 11

`num_generations`를 비교하세요.

2

4

8

문제 12

각 설정에서 다음을 기록하세요.

reward_std

frac_reward_zero_std

학습 시간

GPU 메모리

문제 13

다음 Temperature를 비교하세요.

0.3

0.8

1.2

문제 14

`num_iterations`를 비교하세요.

1

2

4

문제 15

`normalize_advantages=True`와 `False`를 비교하세요.

문제 16

Reward Clip을 적용하세요.

reward_clip_range=(
    -2.0,
    2.0
)

문제 17

LoRA Rank를 비교하세요.

8

16

32

문제 18

QLoRA를 적용해 4비트 RLOO를 구성하세요.

문제 19

vLLM Colocate와 Server Mode의 장단점을 정리하세요.

문제 20

다음 프로젝트로 확장하세요.

산술 문제 5,000개

Train / Validation / Test

Accuracy Reward

Format Reward

Difficulty Reward

RLOO + LoRA

Reward Curve

KL Curve

Zero Std 분석

오답 CSV 저장

Base vs RLOO Accuracy 비교

51. 핵심 요약

Import

from trl import (
    RLOOConfig,
    RLOOTrainer
)

Dataset

{
    "prompt": (
        "12 × 7을 계산하세요."
    ),

    "ground_truth": "84"
}

Reward Function

def accuracy_reward(
    completions,
    ground_truth,
    **kwargs
):
    return [
        1.0
        if extract_answer(
            completion
        ) == answer
        else 0.0

        for completion, answer
        in zip(
            completions,
            ground_truth
        )
    ]

LoRA

lora_config = LoraConfig(
    task_type=(
        TaskType.CAUSAL_LM
    ),

    r=16,

    lora_alpha=32,

    target_modules=[
        "q_proj",
        "k_proj",
        "v_proj",
        "o_proj"
    ]
)

RLOOConfig

training_args = RLOOConfig(
    output_dir="outputs/rloo",

    learning_rate=1e-6,

    per_device_train_batch_size=1,

    gradient_accumulation_steps=4,

    num_generations=4,

    max_completion_length=128,

    temperature=0.8,

    beta=0.05,

    num_iterations=1,

    epsilon=0.2,

    reward_weights=[
        1.0,
        0.2
    ],

    log_completions=True,

    report_to="none"
)

Trainer

trainer = RLOOTrainer(
    model=MODEL_ID,

    args=training_args,

    reward_funcs=[
        accuracy_reward,
        format_reward
    ],

    train_dataset=train_dataset,

    peft_config=lora_config
)

학습

trainer.train()

저장

trainer.save_model(
    "models/rloo-model"
)

52. 마무리

이번 시간에는 RLOOTrainer를 이용해 Value Model 없이 생성형 AI를 Online Reinforcement Learning하는 방법을 알아보았습니다.

RLOO의 핵심을 한 장으로 정리하면 다음과 같습니다.

Prompt

↓

여러 Completion 생성

├─ A
├─ B
├─ C
└─ D

↓

Reward 계산

↓

A 평가:
B + C + D 평균

B 평가:
A + C + D 평균

C 평가:
A + B + D 평균

D 평가:
A + B + C 평균

↓

내 Reward
-
나를 제외한 평균

↓

Advantage

↓

REINFORCE Policy Update

GRPO와 RLOO는 형제처럼 닮았습니다.

둘 다:

Value Model 없음

여러 Completion 생성

Group Reward 활용

Online RL

이라는 공통점이 있습니다.

하지만 질문하는 방식이 다릅니다.

GRPO:

“우리 그룹에서
나는 어느 정도인가?”

RLOO:

“나를 빼고 계산한
다른 사람들의 평균보다
나는 얼마나 잘했는가?”

RLOO에서 이 Leave-One-Out Baseline은 Policy Gradient의 분산을 낮추기 위한 핵심 장치입니다. 현재 TRL 구현도 자기 자신을 제외한 나머지 Reward 평균을 Baseline으로 사용합니다.

PPO와 비교하면 구조도 단순해집니다.

PPO:

Policy
Reference
Reward
Value


RLOO:

Policy
Reference
Reward

그리고:

beta=0.0

으로 KL Penalty까지 사용하지 않는다면 현재 TRL은 Reference Model도 로드하지 않습니다.

Policy
+
Reward

수준으로 더욱 단순한 Online RL 실험도 가능합니다.

하지만 모델 수가 줄었다고 강화학습 자체가 쉬워지는 것은 아닙니다.

여전히 가장 중요한 질문은 이것입니다.

Reward를
무엇으로 정의했는가?

정답 Reward가 잘못되어 있다면:

틀린 정답

→ 높은 Reward

를 학습합니다.

Format Reward 비중이 너무 크다면:

답은 틀림

하지만 JSON은 예쁨

같은 모델이 탄생할 수 있습니다.

Reward가 모두 같다면:

0
0
0
0

RLOO는 비교할 것이 없습니다.

RLOO:

“나를 제외한 평균을
계산했습니다.”


모델:

“그래서요?”


RLOO:

“모두 0점입니다.”


모델:

“누가 잘했는데요?”


RLOO:

“...아무도요.”

그래서 Online RL에서는 다음 지표가 정말 중요합니다.

reward

reward_std

frac_reward_zero_std

kl

completion length

clip ratio

학습 Loss 하나만 보고 강화학습의 건강 상태를 판단하면 안 됩니다. 현재 RLOOTrainer 역시 Reward, Completion Length, Entropy, KL, Clip Ratio 등을 별도 Metric으로 기록합니다.

지금까지 우리는 생성형 AI Post-training의 중요한 도구들을 상당히 많이 모았습니다.

SFT

→ 모범 답안 학습


DPO

→ Preference Pair 학습


Reward Model

→ 답변 채점


PPO

→ Value Model 기반 Online RL


GRPO

→ Group Relative Online RL


RLOO

→ Leave-One-Out Online RL

처음에는 단순했던 AI 훈련장이 이제 꽤 그럴듯한 연구소가 되었습니다. 🧪🤖

다음 편 예고

[Python 완전정복 시리즈 #45] KTOTrainer 완벽 이해하기 | Chosen·Rejected 쌍 없이 좋아요와 싫어요 데이터로 AI의 선호도를 학습하는 방법

다음 시간에는 다시 Preference Optimization으로 이동합니다.

DPO에는 항상 다음 두 답변이 한 쌍으로 필요했습니다.

Chosen

vs

Rejected

그런데 실제 서비스에서는 사용자에게 이렇게 받는 경우가 훨씬 많습니다.

👍 좋아요

또는:

👎 싫어요

즉 비교 Pair가 없습니다.

질문 A + 답변 A

👍


질문 B + 답변 B

👎

이런 Unpaired Preference Data를 이용해 모델을 학습하는 방법이 KTO입니다.

DPO:

“답변 A와 B 중
어느 쪽이 더 좋습니까?”


KTO:

“이 답변은
좋았습니까, 싫었습니까?”

실제 서비스의 좋아요·싫어요 로그를 AI 학습 데이터로 연결하는 방법을 다음 편에서 알아보겠습니다. 👍👎🤖

#Python #파이썬 #Python강좌 #HuggingFace #TRL #RLOO #RLOOTrainer #REINFORCE #LeaveOneOut #강화학습 #ReinforcementLearning #RLHF #OnlineRL #RewardFunction #RewardModel #Advantage #Baseline #KLDivergence #KLPenalty #PolicyGradient #LoRA #QLoRA #PEFT #vLLM #GRPO #PPO #DPO #LLM #생성형AI #AI정렬 #PostTraining #FineTuning #PyTorch #코딩공부 #프로그래밍

댓글

0

댓글을 불러오는 중입니다.