목차
메타 설명
대화형 AI 모델은 어떻게 사용자의 지시를 이해하고 답변하는 방법을 배울까요? Hugging Face TRL과 SFTTrainer를 이용해 Instruction Dataset, Conversational Dataset, Chat Template, Completion Only Loss, Assistant Only Loss, Packing, LoRA·QLoRA, Checkpoint, 모델 평가, 추론과 Gradio 연결 방법까지 실습합니다.
지난 시간에는 PEFT와 LoRA를 이용해 거대한 모델 전체를 학습하지 않고 작은 Adapter만 학습하는 방법을 알아보았습니다.
기본 모델
→ 가중치 동결
→ LoRA Adapter 추가
→ Adapter만 학습
→ 작은 파일로 저장덕분에 GPU 메모리와 저장 공간을 크게 줄일 수 있었습니다.
하지만 아직 중요한 질문이 남아 있습니다.
LoRA를 이용해
무엇을 가르칠 것인가?모델에게 질문을 이해하고 답변하는 방법을 가르치고 싶다면 다음과 같은 데이터가 필요합니다.
사용자:
Python의 리스트와 튜플 차이를 설명해 주세요.
AI:
리스트는 값을 변경할 수 있지만
튜플은 생성 후 값을 변경할 수 없습니다.또는 다음과 같은 지시 데이터가 필요합니다.
지시:
입력된 문장을 세 줄로 요약하세요.
입력:
Python은 다양한 분야에서 사용되는 프로그래밍 언어입니다...
정답:
Python은 읽기 쉬운 범용 프로그래밍 언어입니다.
데이터 분석과 웹 개발 등에 활용됩니다.
풍부한 라이브러리 생태계를 제공합니다.이런 데이터를 모델에 반복적으로 보여주면 모델은 다음 패턴을 학습합니다.
사용자 지시를 확인한다.
→ 필요한 작업을 파악한다.
→ 적절한 답변 형식을 선택한다.
→ 모범 답안과 비슷한 응답을 생성한다.이 과정을 Supervised Fine-Tuning, 줄여서 SFT라고 부릅니다.
이번 시간의 주인공은 Hugging Face TRL의 `SFTTrainer`입니다.
Transformers Trainer
→ 범용 모델 학습 교관
TRL SFTTrainer
→ 생성형 AI와 대화형 모델을 위한
전문 교육 교관AI 훈련소에 질문지와 모범 답안이 도착했습니다.
이제 모델에게 “말은 할 줄 알지만, 대답하는 법은 아직 서툰 상태”에서 벗어나는 방법을 가르쳐 보겠습니다. 🎓🤖
1. TRL이란?
TRL은 Hugging Face가 제공하는 Foundation Model Post-training 라이브러리입니다.
이름은 다음 표현에서 출발했습니다.
Transformers Reinforcement Learning초기에는 강화학습 중심 도구였지만 현재는 SFT, DPO, GRPO, Reward Modeling 등 Foundation Model의 다양한 후처리 학습 방법을 제공하는 종합 Post-training 라이브러리로 확장되었습니다. Transformers, Accelerate, PEFT와 통합되어 단일 GPU부터 분산 학습 환경까지 연결할 수 있습니다.
대표 Trainer는 다음과 같습니다.
SFTTrainer
→ 모범 답안을 이용한 지도 학습
DPOTrainer
→ 선호 답변과 비선호 답변 비교 학습
GRPOTrainer
→ 보상 함수 기반 강화학습
RewardTrainer
→ 답변 품질을 평가하는 보상 모델 학습
KTOTrainer
→ 선호·비선호 단일 피드백 기반 학습이번 편에서는 Post-training의 출발점인 `SFTTrainer`를 사용합니다.
2. SFT란?
SFT는 다음 표현의 약자입니다.
Supervised Fine-Tuning한국어로는 다음처럼 표현할 수 있습니다.
지도 미세조정
또는
지도 학습 기반 Fine-tuningSFT에서는 모델에게 입력과 정답 출력을 함께 제공합니다.
입력:
REST API란 무엇인가요?
정답:
REST API는 HTTP를 이용해
자원을 주고받는 인터페이스 설계 방식입니다.모델은 정답 문장의 다음 Token을 예측하도록 학습합니다.
REST
→ REST API
→ REST API는
→ REST API는 HTTP를
→ REST API는 HTTP를 이용해SFTTrainer의 기본 학습 목표는 이전 Token들을 바탕으로 다음 정답 Token의 확률을 높이는 Token 단위 음의 로그 우도 손실입니다. Padding이나 학습에서 제외한 Token에는 `-100`과 같은 Ignore Index가 적용되어 Loss 계산에서 빠집니다.
3. Pre-training과 SFT의 차이
Pre-training
방대한 일반 문서를 이용해 언어 패턴을 학습합니다.
책
웹 문서
소스코드
뉴스
백과사전
논문학습 목표는 대체로 다음 Token을 예측하는 것입니다.
Python은 배우기
→ 쉬운
→ 프로그래밍
→ 언어입니다.Pre-training을 마친 모델을 Base Model이라고 부릅니다.
Base Model은 언어를 이어 쓰는 능력은 있지만 사용자의 지시를 안정적으로 따르지 못할 수 있습니다.
SFT
질문과 모범 답안을 이용해 원하는 행동을 추가로 가르칩니다.
질문에 답하기
문서 요약하기
코드 설명하기
표 형식으로 출력하기
특정 말투 유지하기
도구 호출 형식 생성하기전체 관계는 다음과 같습니다.
Pre-training
→ 언어와 지식의 기본 패턴 학습
→ Base Model
SFT
→ 지시와 대화 방식 학습
→ Instruct·Chat Model4. Instruction Tuning이란?
Instruction Tuning은 다양한 자연어 지시와 정답을 이용해 모델이 새로운 지시도 이해하고 수행하도록 학습하는 과정입니다.
지시:
이 문장을 영어로 번역하세요.
지시:
다음 내용을 세 줄로 요약하세요.
지시:
오류가 있는 Python 코드를 수정하세요.
지시:
초보자가 이해하도록 설명하세요.단순한 업무 하나만 학습하는 것이 아니라 다양한 지시 형식을 함께 학습하면 모델은 새로운 요청에 대응하는 일반화 능력을 얻을 수 있습니다.
모델:
“이 문장을 학습한 적은 없지만
요청의 형태는 이해했습니다.”다만 데이터가 지나치게 작거나 지시 유형이 편중되면 모델은 특정 문장 패턴만 외울 수 있습니다.
5. Chat Model이 학습되는 과정
대화형 모델도 내부에서는 Token Sequence를 이어 쓰는 Causal Language Model입니다.
개발자가 보는 대화는 다음과 같습니다.
messages = [
{
"role": "system",
"content": "당신은 친절한 Python 교관입니다."
},
{
"role": "user",
"content": "리스트란 무엇인가요?"
},
{
"role": "assistant",
"content": "리스트는 여러 값을 순서대로 저장하는 자료형입니다."
}
]하지만 모델이 보는 것은 역할과 특수 Token이 포함된 하나의 Token Sequence입니다.
<system>
당신은 친절한 Python 교관입니다.
</system>
<user>
리스트란 무엇인가요?
</user>
<assistant>
리스트는 여러 값을 순서대로 저장하는 자료형입니다.
</assistant>모델마다 역할을 표시하는 Control Token과 형식이 다릅니다. Chat Template은 `role`과 `content`로 구성된 메시지 목록을 모델이 학습한 Token 형식으로 변환합니다. 같은 Base Model에서 출발한 Chat Model도 서로 다른 Control Token을 사용할 수 있어 올바른 Template 적용이 중요합니다.
6. SFTTrainer란?
`SFTTrainer`는 생성형 언어 모델의 지도 Fine-tuning을 쉽게 구성하도록 만든 TRL의 Trainer입니다.
기본 사용법은 매우 간단합니다.
from datasets import load_dataset
from trl import SFTTrainer
dataset = load_dataset(
"trl-lib/Capybara",
split="train"
)
trainer = SFTTrainer(
model="Qwen/Qwen2.5-0.5B",
train_dataset=dataset
)
trainer.train()SFTTrainer는 Transformers의 `Trainer`를 기반으로 하며 데이터 형식 변환, Chat Template 적용, Tokenization, Packing, Completion Masking, PEFT와 Quantization 연결 등 생성형 모델 학습에 필요한 기능을 추가로 제공합니다.
7. Trainer와 SFTTrainer의 차이
| 구분 | Trainer | SFTTrainer |
|---|---|---|
| 주요 목적 | 범용 Transformers 학습 | 생성형 AI SFT |
| 원본 대화 데이터 | 직접 전처리 필요 | 자동 처리 가능 |
| Chat Template | 직접 적용 | Conversational Dataset에 자동 적용 |
| Prompt·Completion | 직접 결합·Masking | 기본 지원 |
| Completion Only Loss | 직접 구성 필요 | 설정 지원 |
| Assistant Only Loss | 직접 Mask 생성 필요 | 설정 지원 |
| Packing | 별도 구현 | 내장 지원 |
| PEFT | 연결 가능 | `peft_config` 직접 지원 |
| QLoRA | 직접 모델 구성 가능 | Quantization과 PEFT 동시 지원 |
| 일반 분류 모델 | 적합 | 주로 Causal LM SFT 중심 |
Trainer가 종합 운동장이라면 SFTTrainer는 대화형 AI 전용 훈련관입니다.
8. 현재 TRL 버전
2026년 8월 5일 기준 PyPI의 최신 TRL 안정 버전은 1.9.2이며 2026년 7월 28일 공개되었습니다. 현재 패키지는 Python 3.10 이상을 요구합니다.
설치 버전 확인:
python -m pip show trlPython에서 확인:
import trl
print(
trl.__version__
)TRL 1.x는 과거 0.x 버전과 API가 크게 달라질 수 있습니다.
오래된 블로그:
trl 0.7
현재 환경:
trl 1.9
개발자:
“같은 SFTTrainer인데
왜 인수가 다르죠?”코드를 사용할 때 작성 시점과 설치 버전을 함께 확인해야 합니다.
9. 개발 환경 설치
Windows
python -m venv venv
venv\Scripts\activate
python -m pip install --upgrade pip
python -m pip install \
"transformers[torch]" \
datasets \
accelerate \
peft \
trlWindows 명령 프롬프트에서는 한 줄로 입력합니다.
python -m pip install "transformers[torch]" datasets accelerate peft trlmacOS·Linux
python3 -m venv venv
source venv/bin/activate
python -m pip install --upgrade pip
python -m pip install \
"transformers[torch]" \
datasets \
accelerate \
peft \
trlQLoRA 추가 패키지
python -m pip install bitsandbytesTRL은 PEFT와 Quantization을 통합해 LoRA·QLoRA 학습을 구성할 수 있습니다. 현재 SFTTrainer는 모델 ID, `peft_config`, `quantization_config`를 함께 받아 모델 로딩과 Adapter 구성을 처리할 수 있습니다.
10. 전체 SFT 과정
이번 프로젝트의 학습 흐름입니다.
1. 대화형 학습 데이터 준비
2. Prompt와 Completion 분리
3. Train·Validation 분리
4. Tokenizer와 Chat Template 확인
5. LoRA 설정
6. SFTConfig 작성
7. SFTTrainer 생성
8. Chat Template 자동 적용
9. Tokenization
10. Completion Token만 Loss 계산
11. LoRA Adapter 학습
12. Validation Loss 확인
13. Adapter 저장
14. 대화형 추론11. Base Model과 Instruct Model
생성형 모델은 크게 두 종류로 구분할 수 있습니다.
Base Model
일반적인 다음 Token 예측을 학습한 모델입니다.
Qwen Base
Llama Base
Mistral BaseBase Model은 자유로운 텍스트 생성을 할 수 있지만 지시를 따르는 능력은 약할 수 있습니다.
Base Model을 사용해 Instruction Model을 만들려면 대화 데이터와 Chat Template을 함께 학습해야 합니다.
Instruct Model
이미 Instruction Tuning을 거친 모델입니다.
Qwen Instruct
Llama Instruct
Mistral Instruct기본적인 지시 수행 능력이 있으며, 특정 조직·업무·말투에 맞게 추가 SFT를 진행할 수 있습니다.
이번 프로젝트에서는 실행 부담이 비교적 작은 다음 모델을 사용합니다.
Qwen/Qwen2.5-0.5B-Instruct해당 모델은 Transformers에서 `apply_chat_template()`를 이용한 대화형 생성 예제를 제공하는 Instruct Model입니다.
이미 대화할 줄 아는 신입사원
+ 우리 조직 업무 교육
= 업무 특화 AI12. SFT 데이터 형식
SFTTrainer가 지원하는 주요 데이터 유형은 다음 두 가지입니다.
Language Modeling
Prompt-Completion각 유형은 다시 두 형식으로 작성할 수 있습니다.
Standard Format
Conversational FormatSFTTrainer는 네 가지 조합을 지원합니다.
Standard Language Modeling
Conversational Language Modeling
Standard Prompt-Completion
Conversational Prompt-CompletionConversational Dataset이 전달되면 SFTTrainer가 Chat Template을 자동으로 적용합니다.
13. Standard Format
Standard Format은 일반 문자열로 데이터를 작성합니다.
Language Modeling
example = {
"text": (
"Python의 리스트는 여러 값을 "
"순서대로 저장하는 자료형입니다."
)
}Prompt-Completion
example = {
"prompt": (
"Python 리스트란 무엇인가요?"
),
"completion": (
"리스트는 여러 값을 순서대로 "
"저장하는 변경 가능한 자료형입니다."
)
}Standard Format은 단순하지만 역할 정보를 직접 표현하기 어렵습니다.
14. Conversational Format
Conversational Format은 각 메시지를 `role`과 `content`로 작성합니다.
example = {
"messages": [
{
"role": "system",
"content": (
"당신은 친절한 "
"Python 교관입니다."
)
},
{
"role": "user",
"content": (
"리스트란 무엇인가요?"
)
},
{
"role": "assistant",
"content": (
"리스트는 여러 값을 "
"순서대로 저장하는 자료형입니다."
)
}
]
}Conversational Format에서 일반적으로 사용하는 역할은 다음과 같습니다.
system
→ 모델의 행동과 역할 정의
user
→ 사용자의 질문이나 지시
assistant
→ 모델의 정답 응답
tool
→ 도구 실행 결과TRL의 Conversational Dataset은 각 메시지가 `role`과 `content`를 갖는 메시지 목록으로 구성됩니다.
15. Language Modeling Dataset
대화 전체를 `messages` 열에 저장할 수 있습니다.
example = {
"messages": [
{
"role": "user",
"content": (
"Python의 딕셔너리는 "
"무엇인가요?"
)
},
{
"role": "assistant",
"content": (
"딕셔너리는 Key와 Value를 "
"한 쌍으로 저장하는 자료형입니다."
)
}
]
}이 형식에서는 설정에 따라 전체 대화 Token이 Loss 계산에 포함될 수 있습니다.
사용자 질문 Token
+
AI 응답 Token
→ 모두 학습모델이 대화 형식 전체를 학습하도록 만들 때 사용할 수 있습니다.
16. Prompt-Completion Dataset
Prompt와 Completion을 명확하게 분리합니다.
example = {
"prompt": [
{
"role": "system",
"content": (
"당신은 초보자를 위한 "
"Python 교관입니다."
)
},
{
"role": "user",
"content": (
"for 반복문을 설명해 주세요."
)
}
],
"completion": [
{
"role": "assistant",
"content": (
"for 반복문은 여러 데이터를 "
"하나씩 꺼내 같은 작업을 "
"반복할 때 사용합니다."
)
}
]
}이 구조는 다음을 명확하게 구분합니다.
Prompt
→ 모델에게 제공할 입력
Completion
→ 모델이 학습해야 할 정답이번 프로젝트에서는 Conversational Prompt-Completion Format을 사용합니다.
17. System·User·Assistant 역할
System
모델의 기본 행동을 정합니다.
{
"role": "system",
"content": (
"당신은 친절하고 정확한 "
"Python 입문 교관입니다. "
"답변에는 짧은 예제를 포함하세요."
)
}User
사용자의 요청입니다.
{
"role": "user",
"content": (
"while 반복문은 언제 사용하나요?"
)
}Assistant
학습할 모범 답안입니다.
{
"role": "assistant",
"content": (
"while 반복문은 특정 조건이 "
"참인 동안 작업을 반복할 때 사용합니다."
)
}System Message가 일관되지 않으면 모델이 여러 정체성을 동시에 학습할 수 있습니다.
데이터 1:
당신은 간결한 교관입니다.
데이터 2:
당신은 매우 장황한 소설가입니다.
데이터 3:
답변하지 말고 질문만 하세요.AI 신입사원이 첫날부터 세 부서의 업무 지시를 동시에 받은 상황입니다.
18. Chat Template이란?
Chat Template은 메시지 목록을 모델이 이해하는 하나의 문자열 또는 Token Sequence로 변환하는 규칙입니다.
messages = [
{
"role": "user",
"content": "리스트란 무엇인가요?"
},
{
"role": "assistant",
"content": "여러 값을 저장하는 자료형입니다."
}
]Template 적용 결과는 모델에 따라 다음처럼 달라질 수 있습니다.
<|im_start|>user
리스트란 무엇인가요?
<|im_end|>
<|im_start|>assistant
여러 값을 저장하는 자료형입니다.
<|im_end|>또는:
[INST]
리스트란 무엇인가요?
[/INST]
여러 값을 저장하는 자료형입니다.Chat Template은 Tokenizer에 저장되며 `apply_chat_template()`로 사용할 수 있습니다.
19. apply_chat_template 사용하기
from transformers import (
AutoTokenizer
)
MODEL_ID = (
"Qwen/"
"Qwen2.5-0.5B-Instruct"
)
tokenizer = (
AutoTokenizer
.from_pretrained(
MODEL_ID
)
)
messages = [
{
"role": "system",
"content": (
"당신은 친절한 "
"Python 교관입니다."
)
},
{
"role": "user",
"content": (
"변수란 무엇인가요?"
)
}
]
formatted_text = (
tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True
)
)
print(
formatted_text
)tokenize=False
결과를 문자열로 반환합니다.
tokenize=True
결과를 Token ID로 반환합니다.
add_generation_prompt=True
모델이 다음 Assistant Message를 생성하도록 Assistant 시작 Token을 추가합니다.
20. Chat Template이 중요한 이유
두 Chat Model이 같은 Base Model에서 출발했더라도 서로 다른 역할 Token을 사용했을 수 있습니다.
모델 A:
[INST] 질문 [/INST]
모델 B:
<|user|>질문</|user|>
<|assistant|>잘못된 Template을 적용하면 모델은 역할 경계를 정확히 인식하지 못할 수 있습니다.
사용자 질문이 어디서 끝나는가?
Assistant 답변은 어디서 시작하는가?
한 대화 Turn은 어디서 끝나는가?공식 Transformers 문서는 잘못된 Control Token을 사용하면 모델 성능이 크게 저하될 수 있다고 설명합니다.
대본은 맞는데
배역 이름표가 모두 바뀐 연극모델은 대사를 가지고 있어도 누가 말해야 하는지 혼란스러워질 수 있습니다.
21. 학습용과 추론용 Template 차이
학습
이미 Assistant 정답까지 포함되어 있습니다.
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=False
)추론
Assistant 답변이 아직 없습니다.
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True
)차이:
학습:
System
→ User
→ Assistant 정답
→ 대화 종료
추론:
System
→ User
→ Assistant 시작
→ 모델이 이어서 생성SFTTrainer는 Conversational Dataset을 받으면 학습용 Chat Template 적용을 자동으로 처리합니다.
22. EOS Token의 역할
EOS는 다음 표현의 약자입니다.
End Of Sequence모델에게 문장이나 대화 Turn이 끝났음을 알려주는 Token입니다.
답변 생성
→ EOS Token 출력
→ 생성 종료EOS Token이 Chat Template과 맞지 않으면 다음 문제가 생길 수 있습니다.
답변이 끝없이 이어짐
다음 User 역할까지 모델이 생성
불필요한 반복
최대 Token까지 멈추지 않음Qwen2.5 계열의 Chat Template에서는 다음 Token을 대화 종료에 사용합니다.
<|im_end|>TRL 공식 SFTTrainer 문서도 Qwen2.5 계열처럼 기존 Chat Template이 있는 모델에서는 Template의 종료 Token과 SFTConfig의 `eos_token`을 맞추도록 안내합니다.
eos_token="<|im_end|>"23. Completion Only Loss
Prompt-Completion Dataset에서는 Prompt는 모델 입력이고 Completion은 정답입니다.
Prompt:
Python 함수란 무엇인가요?
Completion:
함수는 특정 작업을 묶어
재사용할 수 있게 만든 코드 블록입니다.Completion Only Loss는 Prompt Token을 Loss 계산에서 제외하고 Completion Token만 학습합니다.
Prompt Token:
Loss 제외
Completion Token:
Loss 계산현재 SFTTrainer는 Prompt-Completion Dataset에서 기본적으로 Completion Token에만 Loss를 계산합니다. 전체 Sequence를 학습하려면 `completion_only_loss=False`로 변경할 수 있습니다.
명시적으로 설정:
completion_only_loss=True24. Assistant Only Loss
Conversational Language Modeling Dataset에는 여러 역할의 메시지가 함께 들어 있습니다.
System
User
Assistant
User
Assistant`assistant_only_loss=True`를 사용하면 Assistant 답변 Token에만 Loss를 계산합니다.
assistant_only_loss=True이 기능을 사용하려면 Chat Template이 Assistant 생성 영역을 표시하는 `{% generation %}`와 `{% endgeneration %}` 구문을 지원해야 합니다. 일부 알려진 모델 계열은 TRL이 Template을 보완하지만 모든 사용자 정의 Template에서 자동으로 작동하는 것은 아닙니다.
따라서 이번 실습에서는 더 명확한 다음 구조를 사용합니다.
Conversational Prompt-Completion
+
completion_only_loss=True25. Loss Masking 원리
학습할 Token과 제외할 Token은 Label 값으로 구분할 수 있습니다.
예:
입력 Token:
[101, 2054, 2003, 1037, 3231, 102]Label:
[-100, -100, -100, 1037, 3231, 102]`-100`인 위치는 Cross Entropy Loss 계산에서 제외됩니다.
Prompt Token
→ labels = -100
→ 학습 제외
Completion Token
→ labels = 실제 Token ID
→ 학습 대상현재 SFTTrainer는 Pre-tokenized Dataset의 `labels`, `assistant_masks`, `completion_mask`를 인식해 Loss Mask를 구성할 수 있습니다.
26. Packing이란?
대화 데이터는 길이가 짧은 경우가 많습니다.
Example 1:
80 Token
Example 2:
120 Token
Example 3:
60 Token`max_length=512`로 Batch를 만들면 나머지가 Padding으로 채워질 수 있습니다.
실제 데이터:
80 Token
Padding:
432 TokenPacking은 여러 짧은 Example을 하나의 고정 길이 Sequence에 채웁니다.
512 Token 공간
├─ Example 1: 80
├─ Example 2: 120
├─ Example 3: 60
├─ Example 4: 170
└─ 나머지 공간활성화:
packing=TrueSFTTrainer는 여러 Example을 고정 길이 블록에 배치해 Padding 낭비를 줄이는 Packing을 기본 기능으로 제공합니다.
27. Packing의 장점과 주의사항
장점
Padding 감소
GPU 연산 활용률 향상
짧은 데이터 처리량 증가
학습 시간 감소 가능주의사항
Example 경계가 복잡해짐
디버깅이 어려워질 수 있음
아주 긴 Example은 잘릴 수 있음
평가 데이터 Packing 여부 검토 필요현재 SFTConfig의 주요 Packing 전략은 다음과 같습니다.
bfd
→ Best-Fit Decreasing
bfd_split
→ 긴 Sequence 분할 허용
wrapped
→ Sequence 중간 분할을 적극 허용`packing=True`일 때 `max_length`가 Packing Sequence의 크기를 결정합니다. 현재 기본 전략은 `bfd`입니다.
첫 실습에서는 데이터 구조 확인을 쉽게 하기 위해 다음처럼 설정하겠습니다.
packing=False기능이 정상 작동한 뒤 Packing을 활성화해 성능을 비교합니다.
28. SFTConfig 이해하기
`SFTConfig`는 SFTTrainer의 학습과 데이터 처리 설정을 관리합니다.
from trl import SFTConfig
training_args = SFTConfig(
output_dir="outputs/python-tutor",
learning_rate=1e-4,
num_train_epochs=3,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=8,
max_length=512,
packing=False,
completion_only_loss=True,
eval_strategy="epoch",
save_strategy="epoch"
)SFTConfig는 Transformers TrainingArguments의 설정에 더해 Chat Template, Dataset 열, EOS, Max Length, Packing, Completion Only Loss, Assistant Only Loss 같은 SFT 전용 설정을 제공합니다.
29. 학습 길이와 max_length
max_length=512`max_length`는 학습 Sequence의 최대 Token 수입니다.
현재 SFTConfig의 기본값은 1,024입니다. 길이를 초과한 Sequence는 설정된 Truncation 방식에 따라 잘릴 수 있습니다. Packing을 사용할 때는 Packing Block 크기로도 사용됩니다.
너무 작게 설정
System Message
+ 긴 User 질문
+ Assistant 답변
→ 뒷부분 잘림정답 답변이 잘리면 모델은 문장을 끝맺는 방법을 충분히 학습하지 못할 수 있습니다.
너무 크게 설정
GPU 메모리 증가
연산량 증가
Padding 낭비 증가 가능실제 데이터의 Token 길이 분포를 먼저 확인하는 것이 좋습니다.
30. 학습 데이터 만들기
이번 프로젝트에서는 Python 입문 교관용 대화 데이터를 직접 만듭니다.
````pythonTRAINING_EXAMPLES = [{"prompt": [{"role": "system","content": ("당신은 Python 입문자를 위한 ""친절한 교관입니다. ""핵심 개념을 설명하고 ""짧은 코드 예제를 제공합니다.")},{"role": "user","content": ("Python 변수란 무엇인가요?")}],"completion": [{"role": "assistant","content": ("변수는 프로그램에서 값을 ""저장하기 위해 붙이는 이름입니다.\n\n""예를 들어 다음 코드는 age라는 ""변수에 20을 저장합니다.\n\n""```python\n""age = 20\n""print(age)\n""```")}]},{"prompt": [{"role": "system","content": ("당신은 Python 입문자를 위한 ""친절한 교관입니다. ""핵심 개념을 설명하고 ""짧은 코드 예제를 제공합니다.")},{"role": "user","content": ("리스트와 튜플의 차이를 ""설명해 주세요.")}],"completion": [{"role": "assistant","content": ("리스트와 튜플은 여러 값을 ""순서대로 저장합니다.\n\n""리스트는 생성 후 값을 변경할 수 있고, ""튜플은 일반적으로 변경할 수 없습니다.\n\n""```python\n""numbers = [1, 2, 3]\n""point = (10, 20)\n""```")}]}]````
실제 프로젝트에서는 수십 개가 아니라 업무 범위를 충분히 대표하는 고품질 데이터가 필요합니다.
두 개의 예제:
코드 실행 테스트용
수천 개의 검증된 예제:
실제 학습 검토용31. 데이터 품질 검증
SFT 데이터에는 최소한 다음 검사가 필요합니다.
Prompt가 비어 있지 않은가?
Completion이 비어 있지 않은가?
역할 순서가 올바른가?
Assistant 정답이 정확한가?
답변 스타일이 일관적인가?
개인정보가 포함되어 있지 않은가?
서로 모순되는 답변이 없는가?검증 함수:
def validate_example(
example: dict
) -> None:
prompt = example.get(
"prompt"
)
completion = example.get(
"completion"
)
if not prompt:
raise ValueError(
"prompt가 비어 있습니다."
)
if not completion:
raise ValueError(
"completion이 비어 있습니다."
)
if completion[-1].get(
"role"
) != "assistant":
raise ValueError(
"completion의 마지막 역할은 "
"assistant여야 합니다."
)
for message in (
prompt + completion
):
if message.get(
"role"
) not in {
"system",
"user",
"assistant"
}:
raise ValueError(
"지원하지 않는 역할입니다."
)
if not str(
message.get(
"content",
""
)
).strip():
raise ValueError(
"메시지 내용이 비어 있습니다."
)32. Train·Validation 분리
from datasets import Dataset
dataset = Dataset.from_list(
TRAINING_EXAMPLES
)
dataset = dataset.train_test_split(
test_size=0.2,
seed=2026,
shuffle=True
)
train_dataset = dataset[
"train"
]
validation_dataset = dataset[
"test"
]실제 프로젝트에서는 Test Dataset을 별도로 유지하는 것이 좋습니다.
Train
→ Adapter 학습
Validation
→ 학습 설정과 Checkpoint 선택
Test
→ 최종 품질 평가33. LoRA 설정
Qwen2.5의 Attention Projection Layer에 LoRA를 적용합니다.
from peft import (
LoraConfig,
TaskType
)
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
inference_mode=False,
r=16,
lora_alpha=32,
lora_dropout=0.05,
target_modules=[
"q_proj",
"k_proj",
"v_proj",
"o_proj"
],
bias="none"
)구성:
r=16
→ LoRA 내부 Rank
lora_alpha=32
→ Adapter Scaling
lora_dropout=0.05
→ 과적합 완화
target_modules
→ Attention Projection에 Adapter 추가34. SFTTrainer 생성
from trl import (
SFTConfig,
SFTTrainer
)
training_args = SFTConfig(
output_dir=(
"outputs/"
"python-tutor-sft"
),
learning_rate=1e-4,
per_device_train_batch_size=2,
per_device_eval_batch_size=2,
gradient_accumulation_steps=8,
num_train_epochs=3,
eval_strategy="epoch",
save_strategy="epoch",
logging_strategy="steps",
logging_steps=5,
logging_first_step=True,
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
save_total_limit=2,
max_length=512,
packing=False,
completion_only_loss=True,
eos_token="<|im_end|>",
report_to="none",
seed=2026,
data_seed=2026
)
trainer = SFTTrainer(
model=MODEL_ID,
args=training_args,
train_dataset=train_dataset,
eval_dataset=validation_dataset,
processing_class=tokenizer,
peft_config=lora_config
)SFTTrainer에 `peft_config`를 전달하면 모델을 직접 `get_peft_model()`로 감싸지 않아도 Trainer가 PEFT 모델로 구성합니다. Adapter 학습에서는 새 파라미터만 학습하므로 Full Fine-tuning보다 높은 `1e-4` 안팎의 Learning Rate가 자주 사용됩니다.
35. SFT Fine-tuning 시작
train_result = trainer.train()SFTTrainer 내부에서는 다음 작업이 수행됩니다.
대화 데이터 읽기
→ Chat Template 적용
→ Prompt와 Completion 결합
→ Tokenization
→ Prompt Token Masking
→ Completion Token Loss 계산
→ LoRA Gradient 계산
→ Adapter 업데이트
→ Validation Loss 측정
→ Checkpoint 저장학습 결과:
print(
train_result.metrics
)저장:
trainer.log_metrics(
"train",
train_result.metrics
)
trainer.save_metrics(
"train",
train_result.metrics
)
trainer.save_state()36. 학습 로그 해석
SFTTrainer는 학습 중 다음과 같은 지표를 기록합니다.
global_step
epoch
num_tokens
loss
entropy
mean_token_accuracy
learning_rate
grad_norm현재 SFTTrainer는 기본적으로 Mask되지 않은 Token에 대한 Loss와 평균 Token Accuracy, 예측 분포의 Entropy, Gradient Norm 등을 기록합니다.
로그 예:
{
"loss": 2.31,
"entropy": 4.82,
"mean_token_accuracy": 0.41,
"learning_rate": 0.00008,
"epoch": 0.5
}37. Token Accuracy와 Loss
Loss
정답 Token에 높은 확률을 부여할수록 감소합니다.
Loss 감소
→ 학습 데이터의 답변 Token을
더 잘 예측하고 있음Mean Token Accuracy
각 학습 위치에서 확률이 가장 높은 Token이 정답 Token과 일치한 비율입니다.
정답:
리스트는 여러 값을 저장합니다.
예측:
리스트는 여러 데이터를 저장합니다.문장 의미는 비슷하지만 `값을` 대신 `데이터를` 예측하면 해당 Token은 오답으로 계산될 수 있습니다.
따라서 Token Accuracy만으로 실제 답변 품질을 판단해서는 안 됩니다.
낮은 Loss
≠
정확한 사실
높은 Token Accuracy
≠
좋은 대화 품질생성 결과를 실제 질문으로 평가해야 합니다.
38. Adapter 저장
from pathlib import Path
ADAPTER_DIR = Path(
"models/"
"python-tutor-sft-lora"
)
trainer.save_model(
str(
ADAPTER_DIR
)
)
tokenizer.save_pretrained(
ADAPTER_DIR
)PEFT를 사용한 SFTTrainer에서 저장되는 주요 모델 파일은 전체 Base Model이 아니라 LoRA Adapter입니다.
adapter_config.json
adapter_model.safetensors
tokenizer.json
tokenizer_config.json39. 저장된 모델 불러오기
from peft import (
AutoPeftModelForCausalLM
)
model = (
AutoPeftModelForCausalLM
.from_pretrained(
ADAPTER_DIR
)
)
model.eval()장치 이동:
import torch
if torch.cuda.is_available():
device = torch.device(
"cuda"
)
elif torch.backends.mps.is_available():
device = torch.device(
"mps"
)
else:
device = torch.device(
"cpu"
)
model = model.to(
device
)40. Chat Template 기반 추론
messages = [
{
"role": "system",
"content": (
"당신은 Python 입문자를 위한 "
"친절한 교관입니다. "
"핵심 개념과 짧은 예제를 제공합니다."
)
},
{
"role": "user",
"content": (
"Python 함수의 return은 "
"무슨 역할을 하나요?"
)
}
]Token 변환:
input_ids = (
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
)
.to(
device
)
)생성:
with torch.inference_mode():
output_ids = model.generate(
input_ids=input_ids,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
eos_token_id=(
tokenizer.eos_token_id
),
pad_token_id=(
tokenizer.pad_token_id
)
)새로 생성된 부분만 추출:
generated_ids = output_ids[
0,
input_ids.shape[-1]:
]문자열 변환:
response = tokenizer.decode(
generated_ids,
skip_special_tokens=True
)
print(
response
)41. Base Model과 SFT 결과 비교
같은 질문을 Base Model과 SFT Adapter Model에 각각 입력합니다.
질문:
Python 함수의 return은
무슨 역할을 하나요?비교 항목:
질문을 정확히 이해하는가?
한국어로 답변하는가?
설명이 초보자에게 적합한가?
코드 예제가 포함되는가?
불필요한 내용을 생성하지 않는가?
System Message를 따르는가?비교표:
| 항목 | Base Model | SFT Model |
|---|---|---|
| 지시 준수 | 측정 | 측정 |
| 답변 형식 | 측정 | 측정 |
| 정확성 | 측정 | 측정 |
| 코드 예제 | 측정 | 측정 |
| 장황함 | 측정 | 측정 |
| 일관성 | 측정 | 측정 |
하나의 예제만 보고 성능을 판단하지 않습니다.
42. 여러 질문 일괄 평가
EVALUATION_QUESTIONS = [
"Python 변수란 무엇인가요?",
"리스트와 튜플의 차이는 무엇인가요?",
"for 반복문을 예제로 설명해 주세요.",
"함수의 매개변수란 무엇인가요?",
"try-except는 언제 사용하나요?"
]반복 평가:
def generate_answer(
question: str
) -> str:
messages = [
{
"role": "system",
"content": (
"당신은 Python 입문자를 위한 "
"친절하고 정확한 교관입니다."
)
},
{
"role": "user",
"content": question
}
]
input_ids = (
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
)
.to(
device
)
)
with torch.inference_mode():
output_ids = model.generate(
input_ids=input_ids,
max_new_tokens=200,
do_sample=False,
eos_token_id=(
tokenizer.eos_token_id
),
pad_token_id=(
tokenizer.pad_token_id
)
)
generated_ids = output_ids[
0,
input_ids.shape[-1]:
]
return tokenizer.decode(
generated_ids,
skip_special_tokens=True
)
for question in (
EVALUATION_QUESTIONS
):
answer = generate_answer(
question
)
print(
f"[질문]\n{question}"
)
print(
f"\n[답변]\n{answer}"
)
print(
"\n"
+ "=" * 60
+ "\n"
)43. 답변 품질 평가 방법
생성형 AI는 단순 Accuracy만으로 평가하기 어렵습니다.
사실 정확성
설명에 기술적 오류가 없는가?지시 준수
사용자가 요구한 형식을 따르는가?관련성
질문과 직접 관련된 내용을 답하는가?완전성
필요한 핵심 정보를 빠뜨리지 않았는가?간결성
불필요한 반복이 없는가?안전성
위험하거나 부적절한 내용을 생성하지 않는가?형식 정확성
JSON 요청 시 유효한 JSON인가?
표 요청 시 표로 출력하는가?
코드 요청 시 실행 가능한가?평가표 예:
| 질문 | 정확성 | 지시 준수 | 관련성 | 형식 | 종합 |
|---|---|---|---|---|---|
| ------ | --: | ----: | --: | -: | --: |
| 변수 설명 | 5 | 5 | 5 | 4 | 4.8 |
| 반복문 예제 | 4 | 5 | 5 | 5 | 4.8 |
| 예외 처리 | 4 | 4 | 5 | 4 | 4.3 |
자동 지표와 사람 평가를 함께 사용하는 것이 좋습니다.
44. QLoRA와 SFTTrainer
더 큰 모델을 SFT하려면 QLoRA를 사용할 수 있습니다.
Base Model:
4비트 양자화
+
LoRA Adapter:
BF16·FP16 학습필요 패키지:
python -m pip install bitsandbytes양자화 설정:
import torch
from transformers import (
BitsAndBytesConfig
)
compute_dtype = (
torch.bfloat16
if (
torch.cuda.is_available()
and torch.cuda.is_bf16_supported()
)
else torch.float16
)
quantization_config = (
BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=(
compute_dtype
)
)
)45. SFTTrainer의 Quantization 통합
현재 SFTTrainer는 `quantization_config`와 `peft_config`를 함께 받을 수 있습니다.
qlora_trainer = SFTTrainer(
model=MODEL_ID,
args=training_args,
train_dataset=train_dataset,
eval_dataset=validation_dataset,
processing_class=tokenizer,
peft_config=lora_config,
quantization_config=(
quantization_config
)
)SFTTrainer에 모델 ID를 문자열로 전달하면 Quantization 설정으로 모델을 불러오고 PEFT 설정으로 Adapter를 연결할 수 있습니다. 이미 생성된 모델 객체를 전달한 경우 `quantization_config` 인수는 사용되지 않습니다.
QLoRA는 주로 CUDA GPU 환경에서 사용하며 `bitsandbytes`의 운영체제와 Hardware Backend 지원 상태를 먼저 확인해야 합니다.
46. Checkpoint와 학습 재개
SFTTrainer도 Transformers Trainer의 Checkpoint 기능을 상속합니다.
가장 최근 Checkpoint에서 재개:
trainer.train(
resume_from_checkpoint=True
)특정 Checkpoint:
trainer.train(
resume_from_checkpoint=(
"outputs/"
"python-tutor-sft/"
"checkpoint-100"
)
)`resume_from_checkpoint=True`를 사용하면 Output Directory의 마지막 Checkpoint에서 모델, Optimizer, Scheduler 상태를 복원합니다.
Checkpoint와 최종 Adapter 폴더는 역할이 다릅니다.
Checkpoint:
학습 재개용
최종 Adapter:
추론·배포용47. Adapter 병합
학습된 Adapter를 Base Model에 병합할 수 있습니다.
merged_model = (
model.merge_and_unload(
safe_merge=True
)
)저장:
MERGED_DIR = Path(
"models/"
"python-tutor-sft-merged"
)
merged_model.save_pretrained(
MERGED_DIR
)
tokenizer.save_pretrained(
MERGED_DIR
)병합 전
Base Model
+
LoRA Adapter병합 후
독립된 전체 모델병합 모델은 Adapter보다 훨씬 크지만 PEFT 없이 일반 모델처럼 배포하기 편리합니다.
48. Hugging Face Hub 업로드
로그인:
hf auth loginSFTConfig:
training_args = SFTConfig(
push_to_hub=True,
hub_model_id=(
"사용자명/"
"python-tutor-sft-lora"
),
hub_private_repo=True,
hub_strategy="end"
)학습 후:
trainer.push_to_hub(
commit_message=(
"Fine-tune Python tutor "
"with TRL SFTTrainer"
)
)SFTTrainer의 `push_to_hub()`는 모델과 Processing Class를 설정된 Hub Repository에 업로드합니다.
업로드 전 확인:
Base Model 라이선스
학습 데이터 라이선스
개인정보 포함 여부
회사 내부 정보
모델의 알려진 한계
상업 이용 조건49. TRL CLI 사용하기
TRL은 Python 코드를 직접 작성하지 않고 설정 파일을 이용해 SFT를 실행할 수 있는 CLI를 제공합니다.
실행:
trl sft \
--config sft_config.yaml설정 파일 예:
model_name_or_path: Qwen/Qwen2.5-0.5B-Instruct
dataset_name: trl-lib/Capybara
output_dir: outputs/qwen-sft
learning_rate: 0.0001
num_train_epochs: 3
per_device_train_batch_size: 2
gradient_accumulation_steps: 8
max_length: 512
packing: true
use_peft: true
lora_r: 16
lora_alpha: 32
lora_dropout: 0.05
report_to: noneCLI는 여러 실험 설정을 YAML 파일로 관리할 때 편리합니다.
실험 A:
config_a.yaml
실험 B:
config_b.yaml
실험 C:
config_c.yaml50. Gradio 챗봇 연결
import gradio as gr
def chat(
message: str,
history: list[dict]
):
messages = [
{
"role": "system",
"content": (
"당신은 Python 입문자를 위한 "
"친절하고 정확한 교관입니다."
)
}
]
for item in history:
role = item.get(
"role"
)
content = item.get(
"content"
)
if role in {
"user",
"assistant"
}:
messages.append({
"role": role,
"content": content
})
messages.append({
"role": "user",
"content": message
})
input_ids = (
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
)
.to(
device
)
)
with torch.inference_mode():
output_ids = model.generate(
input_ids=input_ids,
max_new_tokens=256,
do_sample=True,
temperature=0.7,
top_p=0.9,
eos_token_id=(
tokenizer.eos_token_id
),
pad_token_id=(
tokenizer.pad_token_id
)
)
generated_ids = output_ids[
0,
input_ids.shape[-1]:
]
yield tokenizer.decode(
generated_ids,
skip_special_tokens=True
)
demo = gr.ChatInterface(
fn=chat,
title="🐍 Python AI 교관",
description=(
"TRL과 LoRA로 SFT한 "
"Python 입문 교관입니다."
)
)
demo.queue()
demo.launch()51. 실전 프로젝트 전체 코드
다음 내용을 `train_sft.py`로 저장합니다.
````pythonfrom pathlib import Pathimport jsonimport time
import torch
from datasets import Dataset
from peft import (AutoPeftModelForCausalLM,LoraConfig,TaskType)
from transformers import (AutoTokenizer,set_seed)
from trl import (SFTConfig,SFTTrainer)
MODEL_ID = ("Qwen/""Qwen2.5-0.5B-Instruct")
OUTPUT_DIR = Path("outputs/""python-tutor-sft")
ADAPTER_DIR = Path("models/""python-tutor-sft-lora")
SEED = 2026
SYSTEM_MESSAGE = ("당신은 Python 입문자를 위한 ""친절하고 정확한 교관입니다. ""먼저 핵심 개념을 설명하고 ""필요하면 짧은 코드 예제를 제공합니다. ""확실하지 않은 내용은 추측하지 않습니다.")
TRAINING_EXAMPLES = [{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("Python 변수란 무엇인가요?")}],"completion": [{"role": "assistant","content": ("변수는 프로그램에서 값을 ""저장하기 위해 붙이는 이름입니다.\n\n""예를 들어 다음 코드는 age라는 ""변수에 숫자 20을 저장합니다.\n\n""```python\n""age = 20\n""print(age)\n""```\n\n""변수를 사용하면 같은 값을 ""여러 곳에서 쉽게 재사용할 수 있습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("리스트와 튜플의 차이를 ""설명해 주세요.")}],"completion": [{"role": "assistant","content": ("리스트와 튜플은 여러 값을 ""순서대로 저장합니다.\n\n""리스트는 생성 후 값을 변경할 수 있지만 ""튜플은 일반적으로 변경할 수 없습니다.\n\n""```python\n""numbers = [1, 2, 3]\n""numbers[0] = 10\n\n""point = (10, 20)\n""```\n\n""값을 변경해야 한다면 리스트, ""고정된 값을 보관한다면 튜플을 ""고려할 수 있습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("for 반복문은 언제 사용하나요?")}],"completion": [{"role": "assistant","content": ("for 반복문은 여러 데이터를 ""하나씩 꺼내 같은 작업을 ""반복할 때 사용합니다.\n\n""```python\n""fruits = ['사과', '바나나', '포도']\n\n""for fruit in fruits:\n"" print(fruit)\n""```\n\n""이 코드는 리스트의 과일 이름을 ""차례대로 출력합니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("while 반복문과 for 반복문의 ""차이는 무엇인가요?")}],"completion": [{"role": "assistant","content": ("for 반복문은 반복할 데이터나 횟수가 ""비교적 명확할 때 사용합니다.\n\n""while 반복문은 특정 조건이 참인 동안 ""계속 반복해야 할 때 사용합니다.\n\n""```python\n""count = 0\n\n""while count < 3:\n"" print(count)\n"" count += 1\n""```\n\n""while문에서는 조건이 언젠가 ""거짓이 되도록 값을 변경해야 ""무한 반복을 방지할 수 있습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("Python 함수는 왜 사용하나요?")}],"completion": [{"role": "assistant","content": ("함수는 특정 작업을 하나의 이름으로 ""묶어 재사용하기 위해 사용합니다.\n\n""```python\n""def greet(name):\n"" return f'{name}님, 안녕하세요.'\n\n""message = greet('파이썬')\n""print(message)\n""```\n\n""함수를 사용하면 중복 코드를 줄이고 ""프로그램의 구조를 이해하기 쉬워집니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("함수의 return은 ""무슨 역할을 하나요?")}],"completion": [{"role": "assistant","content": ("return은 함수가 만든 결과를 ""함수 바깥으로 전달하고 ""함수 실행을 종료합니다.\n\n""```python\n""def add(a, b):\n"" return a + b\n\n""result = add(3, 5)\n""print(result)\n""```\n\n""이 예제에서 add 함수는 8을 반환하고 ""그 값이 result 변수에 저장됩니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("딕셔너리는 어떤 자료형인가요?")}],"completion": [{"role": "assistant","content": ("딕셔너리는 Key와 Value를 ""한 쌍으로 저장하는 자료형입니다.\n\n""```python\n""user = {\n"" 'name': '홍길동',\n"" 'age': 20\n""}\n\n""print(user['name'])\n""```\n\n""순번 대신 의미 있는 Key로 ""값에 접근할 수 있다는 장점이 있습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("try-except는 언제 사용하나요?")}],"completion": [{"role": "assistant","content": ("try-except는 프로그램 실행 중 ""발생할 수 있는 예외를 처리할 때 ""사용합니다.\n\n""```python\n""try:\n"" number = int(input('숫자: '))\n""except ValueError:\n"" print('올바른 숫자를 입력하세요.')\n""```\n\n""예외 처리를 사용하면 오류가 발생해도 ""프로그램이 갑자기 종료되는 상황을 ""줄일 수 있습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("클래스와 객체의 관계를 ""쉽게 설명해 주세요.")}],"completion": [{"role": "assistant","content": ("클래스는 객체를 만들기 위한 설계도이고, ""객체는 그 설계도로 만든 실제 결과물입니다.\n\n""```python\n""class Dog:\n"" def __init__(self, name):\n"" self.name = name\n\n""dog = Dog('초코')\n""print(dog.name)\n""```\n\n""Dog는 클래스이고 dog는 ""Dog 클래스로 만든 객체입니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("모듈을 사용하는 이유는 무엇인가요?")}],"completion": [{"role": "assistant","content": ("모듈은 관련된 함수와 클래스, 변수를 ""하나의 Python 파일에 정리한 것입니다.\n\n""```python\n""import math\n\n""print(math.sqrt(16))\n""```\n\n""모듈을 사용하면 코드를 기능별로 나누고 ""다른 프로그램에서 재사용할 수 있습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("리스트 컴프리헨션을 ""초보자에게 설명해 주세요.")}],"completion": [{"role": "assistant","content": ("리스트 컴프리헨션은 반복문을 이용한 ""리스트 생성을 짧게 표현하는 문법입니다.\n\n""```python\n""squares = [number ** 2 ""for number in range(5)]\n""print(squares)\n""```\n\n""복잡한 조건이 많아 읽기 어려워진다면 ""일반 for 반복문을 사용하는 편이 좋습니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("가상환경은 왜 필요한가요?")}],"completion": [{"role": "assistant","content": ("가상환경은 프로젝트마다 Python 패키지와 ""버전을 분리하기 위해 사용합니다.\n\n""```bash\n""python -m venv venv\n""```\n\n""프로젝트 A와 프로젝트 B가 서로 다른 ""라이브러리 버전을 사용하더라도 ""충돌하지 않도록 도와줍니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("패키지 설치에 pip를 ""어떻게 사용하나요?")}],"completion": [{"role": "assistant","content": ("pip는 Python 패키지를 설치하고 ""관리하는 명령줄 도구입니다.\n\n""```bash\n""python -m pip install requests\n""```\n\n""설치된 패키지를 확인하려면 ""다음 명령을 사용할 수 있습니다.\n\n""```bash\n""python -m pip list\n""```")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("None은 무엇을 의미하나요?")}],"completion": [{"role": "assistant","content": ("None은 값이 없거나 아직 값이 ""정해지지 않았음을 나타내는 ""Python의 특별한 객체입니다.\n\n""```python\n""result = None\n\n""if result is None:\n"" print('결과가 없습니다.')\n""```\n\n""None을 비교할 때는 일반적으로 ""==보다 is를 사용합니다.")}]},{"prompt": [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": ("Boolean 자료형을 ""예제로 설명해 주세요.")}],"completion": [{"role": "assistant","content": ("Boolean은 참과 거짓을 나타내는 ""자료형이며 값은 True와 False입니다.\n\n""```python\n""is_logged_in = True\n\n""if is_logged_in:\n"" print('로그인 상태입니다.')\n""```\n\n""조건문에서 프로그램의 실행 방향을 ""결정할 때 자주 사용합니다.")}]}]
def validate_example(example: dict,index: int) -> None:"""대화 학습 데이터 구조를 검사합니다."""prompt = example.get("prompt")
completion = example.get("completion")
if not isinstance(prompt,list) or not prompt:raise ValueError(f"{index}번 데이터의 "f"prompt가 올바르지 않습니다.")
if not isinstance(completion,list) or not completion:raise ValueError(f"{index}번 데이터의 "f"completion이 올바르지 않습니다.")
allowed_roles = {"system","user","assistant"}
for message in (prompt + completion):role = message.get("role")
content = str(message.get("content","")).strip()
if role not in allowed_roles:raise ValueError(f"{index}번 데이터에 "f"지원하지 않는 역할이 있습니다: "f"{role}")
if not content:raise ValueError(f"{index}번 데이터에 "f"빈 메시지가 있습니다.")
if completion[-1].get("role") != "assistant":raise ValueError(f"{index}번 데이터의 "f"completion은 assistant "f"역할이어야 합니다.")
def select_precision() -> tuple[bool,bool,torch.dtype]:"""실행 환경에 맞는 학습 정밀도를 선택합니다."""if not torch.cuda.is_available():return (False,False,torch.float32)
use_bf16 = (torch.cuda.is_bf16_supported())
if use_bf16:return (True,False,torch.bfloat16)
return (False,True,torch.float16)
def select_device() -> torch.device:"""추론에 사용할 장치를 선택합니다."""if torch.cuda.is_available():return torch.device("cuda")
if torch.backends.mps.is_available():return torch.device("mps")
return torch.device("cpu")
def generate_answer(model,tokenizer,device: torch.device,question: str) -> str:"""학습된 모델로 Python 질문에 답합니다."""messages = [{"role": "system","content": SYSTEM_MESSAGE},{"role": "user","content": question}]
input_ids = (tokenizer.apply_chat_template(messages,tokenize=True,add_generation_prompt=True,return_tensors="pt").to(device))
with torch.inference_mode():output_ids = model.generate(input_ids=input_ids,
max_new_tokens=256,
do_sample=False,
eos_token_id=(tokenizer.eos_token_id),
pad_token_id=(tokenizer.pad_token_id))
generated_ids = output_ids[0,input_ids.shape[-1]:]
return tokenizer.decode(generated_ids,skip_special_tokens=True).strip()
def main() -> None:set_seed(SEED)
OUTPUT_DIR.mkdir(parents=True,exist_ok=True)
ADAPTER_DIR.mkdir(parents=True,exist_ok=True)
print("[1/10] 학습 데이터 검증")
for index, example in enumerate(TRAINING_EXAMPLES):validate_example(example,index)
print(f"검증된 데이터: "f"{len(TRAINING_EXAMPLES)}개")
print("[2/10] Dataset 생성")
dataset = Dataset.from_list(TRAINING_EXAMPLES)
split_dataset = (dataset.train_test_split(test_size=0.2,seed=SEED,shuffle=True))
train_dataset = (split_dataset["train"])
validation_dataset = (split_dataset["test"])
print(f"Train: "f"{len(train_dataset)}개")
print(f"Validation: "f"{len(validation_dataset)}개")
print("[3/10] Tokenizer 로딩")
tokenizer = (AutoTokenizer.from_pretrained(MODEL_ID))
if tokenizer.pad_token is None:tokenizer.pad_token = (tokenizer.eos_token)
print("[4/10] Chat Template 확인")
sample_text = (tokenizer.apply_chat_template(TRAINING_EXAMPLES[0]["prompt"],tokenize=False,add_generation_prompt=True))
print(sample_text)
print("[5/10] LoRA 설정")
lora_config = LoraConfig(task_type=TaskType.CAUSAL_LM,inference_mode=False,
r=16,lora_alpha=32,lora_dropout=0.05,
target_modules=["q_proj","k_proj","v_proj","o_proj"],
bias="none")
(use_bf16,use_fp16,model_dtype) = select_precision()
print(f"CUDA: "f"{torch.cuda.is_available()}")
print(f"BF16: {use_bf16}")
print(f"FP16: {use_fp16}")
print("[6/10] SFTConfig 생성")
training_args = SFTConfig(output_dir=str(OUTPUT_DIR),
model_init_kwargs={"dtype": model_dtype},
learning_rate=1e-4,lr_scheduler_type="linear",warmup_ratio=0.1,
per_device_train_batch_size=2,per_device_eval_batch_size=2,
gradient_accumulation_steps=8,
num_train_epochs=3,
eval_strategy="epoch",save_strategy="epoch",
logging_strategy="steps",logging_steps=5,logging_first_step=True,
load_best_model_at_end=True,metric_for_best_model=("eval_loss"),greater_is_better=False,
save_total_limit=2,
max_length=512,packing=False,
completion_only_loss=True,
eos_token="<|im_end|>",
gradient_checkpointing=True,
bf16=use_bf16,fp16=use_fp16,
report_to="none",
seed=SEED,data_seed=SEED,
run_name=("python-tutor-sft-lora"))
print("[7/10] SFTTrainer 생성")
trainer = SFTTrainer(model=MODEL_ID,args=training_args,
train_dataset=train_dataset,eval_dataset=(validation_dataset),
processing_class=tokenizer,
peft_config=lora_config)
print("\n[학습 가능한 파라미터]")
trainer.model.print_trainable_parameters()
print("[8/10] SFT 학습 시작")
start_time = (time.perf_counter())
train_result = trainer.train()
elapsed_seconds = (time.perf_counter()- start_time)
train_result.metrics["elapsed_seconds"] = elapsed_seconds
trainer.log_metrics("train",train_result.metrics)
trainer.save_metrics("train",train_result.metrics)
trainer.save_state()
print("[9/10] Validation 평가")
validation_metrics = (trainer.evaluate())
trainer.log_metrics("validation",validation_metrics)
trainer.save_metrics("validation",validation_metrics)
print("[10/10] Adapter 저장과 추론")
trainer.save_model(str(ADAPTER_DIR))
tokenizer.save_pretrained(ADAPTER_DIR)
training_summary = {"model_id": MODEL_ID,"train_examples": len(train_dataset),"validation_examples": len(validation_dataset),"elapsed_seconds": (elapsed_seconds),"best_checkpoint": (trainer.state.best_model_checkpoint),"best_metric": (trainer.state.best_metric)}
(OUTPUT_DIR/ "training_summary.json").write_text(json.dumps(training_summary,ensure_ascii=False,indent=2),encoding="utf-8")
print(f"Adapter 저장 위치: "f"{ADAPTER_DIR.resolve()}")
del trainer
if torch.cuda.is_available():torch.cuda.empty_cache()
inference_model = (AutoPeftModelForCausalLM.from_pretrained(ADAPTER_DIR))
device = select_device()
inference_model = (inference_model.to(device))
inference_model.eval()
questions = [("Python 함수의 return은 ""무슨 역할을 하나요?"),("set과 list의 차이는 무엇인가요?"),("파일을 안전하게 닫으려면 ""어떤 문법을 사용해야 하나요?")]
print("\n[생성 결과]")
for question in questions:answer = generate_answer(inference_model,tokenizer,device,question)
print(f"\n질문:\n{question}")
print(f"\n답변:\n{answer}")
print("\n"+ "=" * 70)
if __name__ == "__main__":main()````
52. 프로젝트 실행 방법
1단계: 폴더 생성
mkdir trl_sft_project
cd trl_sft_project2단계: 가상환경 생성
Windows
python -m venv venv
venv\Scripts\activatemacOS·Linux
python3 -m venv venv
source venv/bin/activate3단계: 패키지 설치
python -m pip install \
"transformers[torch]" \
datasets \
accelerate \
peft \
trl4단계: 코드 저장
train_sft.py5단계: 실행
python train_sft.py주의사항
예제 데이터는 학습 파이프라인을 확인하기 위한 소규모 데이터입니다.
15개 데이터
→ 코드와 구조 검증
실제 AI 교관 개발
→ 충분한 업무 데이터
→ 전문가 검수
→ 독립 Test Dataset
→ 안전성 평가소량 데이터만으로 모델의 지식이나 정확도가 크게 향상되지는 않습니다.
53. 결과 파일 구조
trl_sft_project/
├─ train_sft.py
│
├─ outputs/
│ └─ python-tutor-sft/
│ ├─ checkpoint-...
│ ├─ train_results.json
│ ├─ validation_results.json
│ ├─ trainer_state.json
│ └─ training_summary.json
│
└─ models/
└─ python-tutor-sft-lora/
├─ adapter_config.json
├─ adapter_model.safetensors
├─ tokenizer.json
├─ tokenizer_config.json
└─ special_tokens_map.jsonAdapter 폴더는 추론과 배포에 사용합니다.
Checkpoint 폴더는 학습 재개에 사용합니다.
54. 데이터 보안과 라이선스
Instruction Dataset은 다음 정보를 포함할 수 있습니다.
회사 업무 절차
고객 상담 기록
내부 매뉴얼
보안 정책
소스코드
장애 보고서
개인정보SFT 데이터 업로드 전 확인:
학습에 사용할 권리가 있는가?
개인정보를 제거했는가?
외부 Hub에 업로드해도 되는가?
모델이 답변으로 기밀을 재현할 수 있는가?
데이터 보관 기간은 적절한가?
접근 권한이 제한되어 있는가?모델에 데이터를 학습했다고 원본 정보가 안전하게 삭제되는 것은 아닙니다.
원본 파일 삭제
≠
모델이 정보를 기억하지 않음민감정보가 포함된 데이터는 조직 보안 정책과 개인정보 보호 절차를 따라야 합니다.
55. SFT의 한계
SFT는 모범 답안을 따라 하는 방법을 가르칩니다.
하지만 다음 문제를 자동으로 해결하지는 않습니다.
사실 정확성
학습 데이터가 틀리면 모델도 틀린 답변을 배웁니다.
최신 정보
학습 후 새로 발생한 정보는 알 수 없습니다.
선호도 정렬
여러 개의 가능한 답변 중 사용자가 더 좋아하는 답변을 선택하는 능력은 별도의 Preference 학습이 필요할 수 있습니다.
안전성
위험한 요청을 거절하는 능력도 적절한 안전 데이터와 평가가 필요합니다.
환각
SFT 후에도 모델은 존재하지 않는 정보를 생성할 수 있습니다.
지식 주입의 한계
소량 SFT 데이터로 대량의 정확한 업무 지식을 안정적으로 저장하기는 어렵습니다.
업무 행동과 말투
→ SFT
실시간·정확한 문서 지식
→ RAG·검색·도구 연결 검토56. 자주 발생하는 오류
오류 1. trl을 찾을 수 없음
ModuleNotFoundError:
No module named 'trl'설치:
python -m pip install --upgrade trl확인:
python -m pip show trl오류 2. Python 버전이 낮음
현재 TRL 1.9.2는 Python 3.10 이상을 요구합니다.
확인:
python --version오류 3. SFTConfig 인수를 인식하지 못함
unexpected keyword argument
'completion_only_loss'TRL 버전을 확인합니다.
python -m pip show trl오래된 TRL 예제와 현재 1.x API를 혼합하지 않았는지 확인합니다.
오류 4. dataset_text_field 오류
Standard Language Modeling Dataset의 문자열 열 이름이 `text`가 아니라면 지정합니다.
training_args = SFTConfig(
dataset_text_field=(
"instruction_text"
)
)현재 기본 열 이름은 `text`입니다.
오류 5. Chat Template이 없음
tokenizer.chat_template is not set해결 방법:
Chat Template이 있는 Instruct Model 사용
또는
chat_template_path 지정training_args = SFTConfig(
chat_template_path=(
"Chat Template이 있는 "
"Tokenizer ID"
)
)SFTConfig의 `chat_template_path`에는 Hub Tokenizer, 로컬 Tokenizer 폴더 또는 Jinja Template 파일을 지정할 수 있습니다.
오류 6. EOS Token이 맞지 않음
증상:
답변이 끝나지 않음
User 역할까지 생성
같은 문장 반복Chat Template이 사용하는 종료 Token을 확인합니다.
print(
tokenizer.eos_token
)Qwen2.5 예:
eos_token="<|im_end|>"오류 7. Padding Token이 없음
Asking to pad but the tokenizer
does not have a padding token설정:
if tokenizer.pad_token is None:
tokenizer.pad_token = (
tokenizer.eos_token
)SFTTrainer는 Processing Class에 Padding Token이 없으면 EOS Token을 기본 Padding Token으로 사용할 수 있습니다.
오류 8. Completion Only Loss를 사용할 수 없음
`completion_only_loss=True`는 Prompt-Completion Dataset에서 사용합니다.
잘못된 데이터:
{
"messages": [...]
}권장 구조:
{
"prompt": [...],
"completion": [...]
}오류 9. Assistant Only Loss가 작동하지 않음
Chat Template에 Assistant 생성 영역을 표시하는 구문이 없을 수 있습니다.
{% generation %}
{% endgeneration %}Prompt-Completion 구조와 `completion_only_loss=True`를 사용하는 방법도 검토합니다.
오류 10. Target Module을 찾지 못함
Target modules not found모델의 Linear Module 이름을 확인합니다.
import torch
from transformers import (
AutoModelForCausalLM
)
model = (
AutoModelForCausalLM
.from_pretrained(
MODEL_ID
)
)
for name, module in (
model.named_modules()
):
if isinstance(
module,
torch.nn.Linear
):
print(name)오류 11. 학습 가능한 파라미터가 0개
trainer.model.print_trainable_parameters()확인:
peft_config가 전달되었는가?
Target Module이 존재하는가?
inference_mode=False인가?
Adapter가 활성화되어 있는가?오류 12. CUDA 메모리 부족
CUDA out of memory대응:
Batch Size 감소
Gradient Accumulation 증가
max_length 감소
Rank 감소
Target Module 감소
Gradient Checkpointing
FP16·BF16
QLoRA오류 13. Loss가 NaN이 됨
확인:
Learning Rate가 너무 높지 않은가?
FP16이 불안정하지 않은가?
빈 Completion이 있는가?
데이터가 모두 Mask되지 않았는가?
Gradient가 폭발하지 않는가?Learning Rate 감소:
learning_rate=5e-5오류 14. Training Loss가 0임
Completion Token이 모두 Mask되었거나 데이터 형식이 올바르지 않을 수 있습니다.
Prompt 존재
Completion 비어 있음
→ 학습할 Token 없음실제 Template 적용 결과와 Label Mask를 확인합니다.
오류 15. Packing 활성화 후 오류 발생
먼저 다음처럼 비활성화해 데이터 문제를 확인합니다.
packing=False정상 학습을 확인한 뒤 다시 활성화합니다.
packing=True오류 16. Validation Loss가 계속 증가함
과적합 가능성이 있습니다.
Epoch 감소
LoRA Dropout 증가
Rank 감소
데이터 추가
중복 데이터 제거
Early Stopping 적용오류 17. 학습 후 답변이 그대로임
가능한 원인:
데이터가 너무 적음
Learning Rate가 너무 낮음
LoRA Target이 부족함
학습 Epoch가 부족함
질문이 학습 도메인 밖임
Adapter가 로드되지 않음오류 18. 모델이 학습 답변을 그대로 반복함
데이터를 외운 과적합 상태일 수 있습니다.
유사한 질문만 반복
동일한 문장 구조
데이터 수 부족
Epoch 과다질문 표현과 난이도를 다양화해야 합니다.
오류 19. 모델이 System Message를 무시함
확인:
System Message가 데이터마다 일관적인가?
사용 모델이 system 역할을 지원하는가?
올바른 Chat Template을 적용했는가?
학습 데이터에 상충하는 지시가 없는가?오류 20. Gradio Chat History 형식 오류
현재 Gradio ChatInterface의 대화 기록 구조와 모델의 Chat Template 입력 구조가 다를 수 있습니다.
History를 다음 형태로 변환해야 합니다.
{
"role": "user",
"content": "질문"
}{
"role": "assistant",
"content": "답변"
}57. 연습 문제
문제 1
Pre-training과 SFT의 차이를 설명하세요.
문제 2
다음 내용을 Conversational Language Modeling 형식으로 작성하세요.
System:
친절한 SQL 교관
User:
SELECT문이란 무엇인가요?
Assistant:
데이터베이스에서 데이터를 조회하는 명령입니다.문제 3
같은 데이터를 Conversational Prompt-Completion 형식으로 변환하세요.
문제 4
Tokenizer의 Chat Template을 문자열로 출력하세요.
문제 5
다음 두 설정의 결과 차이를 설명하세요.
add_generation_prompt=Falseadd_generation_prompt=True문제 6
Prompt Token에는 Loss를 계산하지 않도록 설정하세요.
completion_only_loss=True문제 7
Assistant Message에만 Loss를 계산하도록 설정하세요.
assistant_only_loss=True필요한 Chat Template 조건도 확인하세요.
문제 8
Packing을 활성화하고 다음 설정을 적용하세요.
max_length:
512
packing_strategy:
bfd문제 9
다음 LoRA 설정을 작성하세요.
Rank:
8
Alpha:
16
Dropout:
0.05
Target:
q_proj, v_proj문제 10
SFTTrainer에서 LoRA Adapter만 학습되는지 확인하세요.
trainer.model.print_trainable_parameters()문제 11
Learning Rate를 다음 세 값으로 비교하세요.
5e-5
1e-4
2e-4문제 12
Packing 사용 전후의 학습 시간을 비교하세요.
문제 13
Completion Only Loss 사용 전후의 생성 결과를 비교하세요.
문제 14
학습된 Adapter를 저장하고 다시 불러오세요.
문제 15
다섯 개 질문에 대해 Base Model과 SFT Model 결과를 비교하세요.
문제 16
4비트 QLoRA 설정을 SFTTrainer에 연결하세요.
문제 17
Checkpoint에서 중단된 학습을 재개하세요.
문제 18
학습된 Adapter를 Base Model에 병합하세요.
문제 19
Gradio ChatInterface에 학습된 모델을 연결하세요.
문제 20
실전 프로젝트에 다음 기능을 추가하세요.
CSV·JSONL 학습 데이터 불러오기
데이터 중복 제거
Token 길이 통계
Packing 전후 속도 비교
Base·SFT 답변 비교표
사람 평가 결과 CSV 저장
Adapter Hub 비공개 업로드
Gradio 스트리밍 응답58. 핵심 요약
설치
python -m pip install \
trl \
transformers \
datasets \
accelerate \
peftConversational Dataset
example = {
"messages": [
{
"role": "user",
"content": "질문"
},
{
"role": "assistant",
"content": "정답"
}
]
}Prompt-Completion Dataset
example = {
"prompt": [
{
"role": "user",
"content": "질문"
}
],
"completion": [
{
"role": "assistant",
"content": "정답"
}
]
}Chat Template
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True
)LoRA 설정
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"
]
)SFTConfig
training_args = SFTConfig(
output_dir="outputs/sft",
learning_rate=1e-4,
per_device_train_batch_size=2,
gradient_accumulation_steps=8,
num_train_epochs=3,
max_length=512,
packing=False,
completion_only_loss=True,
eval_strategy="epoch",
save_strategy="epoch"
)SFTTrainer
trainer = SFTTrainer(
model=MODEL_ID,
args=training_args,
train_dataset=train_dataset,
eval_dataset=validation_dataset,
processing_class=tokenizer,
peft_config=lora_config
)학습
trainer.train()Adapter 저장
trainer.save_model(
"saved_adapter"
)모델 불러오기
model = (
AutoPeftModelForCausalLM
.from_pretrained(
"saved_adapter"
)
)추론
input_ids = (
tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt"
)
)QLoRA
trainer = SFTTrainer(
model=MODEL_ID,
peft_config=lora_config,
quantization_config=(
quantization_config
)
)59. 마무리
이번 시간에는 Hugging Face TRL과 SFTTrainer를 이용해 대화형 AI 모델을 Instruction Dataset으로 Fine-tuning하는 방법을 알아보았습니다.
전체 흐름을 다시 정리해 보겠습니다.
질문과 모범 답안 준비
→ Conversational Dataset 생성
→ Prompt와 Completion 분리
→ Chat Template 적용
→ Tokenization
→ Prompt Token Masking
→ Completion Token Loss 계산
→ LoRA Adapter 학습
→ Validation 평가
→ Adapter 저장
→ 대화형 추론SFT의 핵심은 단순히 많은 문장을 모델에게 보여주는 것이 아닙니다.
어떤 질문을 받았을 때
어떤 방식으로 생각하고
어떤 형식과 말투로
어떤 답변을 생성해야 하는가이 행동 패턴을 모범 답안으로 가르치는 과정입니다.
Chat Template은 대화의 역할과 경계를 알려줍니다.
누가 지시하는가?
누가 답변하는가?
답변은 어디서 시작하는가?
한 대화는 어디서 끝나는가?Completion Only Loss는 모델이 사용자의 질문을 따라 쓰는 대신 정답 응답을 생성하는 데 집중하게 합니다.
Packing은 짧은 대화 여러 개를 하나의 Sequence에 담아 Padding으로 낭비되는 공간을 줄입니다.
LoRA와 QLoRA는 거대한 모델 전체를 학습하지 않고 작은 Adapter만 업데이트할 수 있도록 도와줍니다.
Chat Template
→ 대화 형식
SFTTrainer
→ 지도 학습
Completion Only Loss
→ 정답에 집중
Packing
→ 연산 효율
LoRA·QLoRA
→ 메모리 효율모든 부품이 연결되면 Base Model은 질문을 단순히 이어 쓰는 모델에서 사용자의 지시를 이해하고 정해진 방식으로 답하는 Assistant로 성장할 수 있습니다.
하지만 SFT 데이터가 곧 모델의 행동 교과서라는 사실을 잊어서는 안 됩니다.
무례한 답변 데이터
→ 무례한 모델
장황한 답변 데이터
→ 장황한 모델
틀린 코드 데이터
→ 자신감 있게 틀린 코드 생성
일관된 고품질 데이터
→ 일관된 답변 가능성 증가AI는 모범 답안의 품질을 판단하고 골라서 배우지 않습니다.
주어진 교재를 매우 성실하게 따라 합니다.
개발자:
“왜 모델이 항상 다섯 문단으로 답하죠?”
데이터:
“모든 정답이 다섯 문단이었습니다.”
모델:
“학습한 대로 했습니다.”SFT에서 가장 비싼 자원은 GPU만이 아닙니다.
정확하고 일관되며 안전한 고품질 모범 답안입니다. 🎓🤖
다음 편 예고
[Python 완전정복 시리즈 #40] DPOTrainer 완벽 이해하기 | 좋은 답변과 나쁜 답변을 비교해 AI의 선호도를 학습하는 방법
다음 시간에는 SFT를 마친 모델에 `chosen`과 `rejected` 답변을 제공해 더 유용하고 정확한 응답을 선호하도록 학습하는 DPO를 알아봅니다.
Preference Dataset, Reference Model, Beta, DPO Loss, Conversational Preference Format, LoRA·QLoRA 연결, Reward Margin, 평가, Adapter 저장과 Gradio 비교 화면까지 실습합니다.
#Python #파이썬 #Python강좌 #파이썬기초 #HuggingFace #TRL #SFTTrainer #SFT #SupervisedFineTuning #InstructionTuning #대화형AI #ChatModel #ChatTemplate #InstructionDataset #ConversationalDataset #PromptCompletion #CompletionOnlyLoss #AssistantOnlyLoss #Packing #LoRA #QLoRA #PEFT #Transformers #생성형AI #LLM #파인튜닝 #AI교관 #Gradio #PyTorch #코딩공부 #프로그래밍
