7장. SFT 데이터 구성과 Chat Template
실습 3 — Qwen 의 Chat Template 들여다보기
messages 를 Qwen 의 대본 양식(ChatML)으로 바꿔 보고, system 메시지와 여러 턴 대화가 어떻게 들어가는지 확인합니다. 6장의 준비 코드(셀 3, 6)를 먼저 실행해 둡니다. 셀 6 이 tokenizer 를 불러옵니다. 이 실습은 토크나이저만 쓰므로 GPU 가 없어도 됩니다.
코드를 단계별로 따라가기 전에 실행 흐름을 한 장으로 봅니다. 두 갈래는 따로 실행할 수 있는 셀 묶음입니다.
| 갈래 | 단계 | 반복과 조건 | 핵심 한 줄 |
|---|---|---|---|
| A. 한 턴 대화 | 1~2 | 없음 | tokenizer.apply_chat_template(messages, tokenize=False) |
| B. system·여러 턴 | 3 | 없음 | tokenizer.apply_chat_template(multi_turn, tokenize=False, add_generation_prompt=True) |
- 갈래의 색: A 는 짙은 청록, B 는 살구입니다. 두 갈래는 서로 쓰는 변수가 없어서,
tokenizer만 있으면 3단계만 따로 실행해도 됩니다. - A 의 끝: 답변 뒤에
<|im_end|>가 붙어 끝납니다. 질문과 모범 답이 다 들어 있는 학습 입력의 모양입니다. - B 의 끝:
<|im_start|>assistant머리표로 끝납니다. 모델이 답을 이어 쓸 자리를 열어 둔 추론 입력의 모양입니다. - 마름모가 없는 까닭: 메시지를 하나씩 돌며 머리표를 붙이는 일은
apply_chat_template안에서 일어나고 코드에는 보이지 않으므로, 네모 하나로 그렸습니다.
1단계. 한 턴 대화 messages 만들기
손님 질문 한 줄과 상담원 답 한 줄짜리 짧은 대본을 준비합니다. 이 셀은 변수만 만듭니다.
# messages에 Qwen Chat Template 적용
# messages: role 과 content 를 담은 사전의 리스트. user 질문 하나와 assistant 답 하나
messages = [ # 사용자 질문과 정답 답변이 있는 한 턴 대화입니다.
{"role": "user", "content": "환불은 언제까지 신청할 수 있나요?"}, # 사용자 메시지입니다.
# assistant: 모델이 해야 할 모범 답
{"role": "assistant", "content": "수령 후 7일 이내에 마이페이지에서 신청하실 수 있습니다."},
]2단계. Chat Template 을 입혀 문자열로 보기
대본을 Qwen 의 양식에 옮겨 적어, 이름표가 어떻게 붙는지 봅니다.
print("[messages -> 문자열]")
# apply_chat_template = 대본 양식 입히기. tokenize=False: 토큰 ID 가 아니라 사람이 읽을 문자열로 돌려준다
# 결과: system 기본 문구, user 질문, assistant 답이 <|im_start|>역할 ... <|im_end|> 로 감싸져 나온다
print(tokenizer.apply_chat_template(messages, tokenize=False))[messages -> 문자열]
<|im_start|>system
You are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>
<|im_start|>user
환불은 언제까지 신청할 수 있나요?<|im_end|>
<|im_start|>assistant
수령 후 7일 이내에 마이페이지에서 신청하실 수 있습니다.<|im_end|>3단계. system 메시지와 여러 턴 대화
"너는 쇼핑몰 상담원이다"라는 배역 설명을 붙이고, 질문과 답을 두 번 주고받는 대본을 만들어, 다음 답을 생성시킬 시험지 모양으로 바꿉니다.
# system 지시와 여러 번 주고받은 대화가 채팅 템플릿에 어떻게 들어가는지 확인합니다.
multi_turn = [ # system 메시지와 두 번의 사용자 질문을 담습니다.
# system: 대화 내내 지킬 역할과 규칙을 적는 자리
{"role": "system", "content": "너는 쇼핑몰 상담원이다."},
{"role": "user", "content": "배송비는 얼마인가요?"},
{"role": "assistant", "content": "3만 원 이상 무료, 그 미만은 3,000원입니다."},
# 마지막은 user 턴이다. 모델이 이어서 답할 차례
{"role": "user", "content": "제주도는요?"},
]
# add_generation_prompt=True: 끝에 "<|im_start|>assistant" 머리표를 붙여 모델이 답을 이어 쓸 자리를 연다
# 결과: 우리가 준 system 문구가 기본 문구 대신 들어가고, 맨 끝이 <|im_start|>assistant 로 끝난다
print(tokenizer.apply_chat_template(multi_turn, tokenize=False, add_generation_prompt=True)) # 다음 답변을 생성할 문자열을 출력합니다.<|im_start|>system
너는 쇼핑몰 상담원이다.<|im_end|>
<|im_start|>user
배송비는 얼마인가요?<|im_end|>
<|im_start|>assistant
3만 원 이상 무료, 그 미만은 3,000원입니다.<|im_end|>
<|im_start|>user
제주도는요?<|im_end|>
<|im_start|>assistant이 실습이 뜻하는 것
짧은 대본(1단계)을 Qwen 의 대본 양식에 옮겨 적어 보고(2단계), 배역 설명과 여러 번 주고받는 대본을 시험지 모양으로 바꿔 본(3단계) 일입니다. 2단계는 끝이 답변과 <|im_end|> 이므로 학습 입력의 모양이고, 3단계는 끝이 <|im_start|>assistant 이므로 추론 입력의 모양입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. messages | 출력 없음 | user 와 assistant 한 턴짜리 대화를 만들었습니다 |
| 2. 템플릿 적용 | system 기본 문구가 붙은 세 턴 | system 을 안 주면 Qwen 이 기본 문구를 넣습니다 |
| 3. system·멀티턴 | 우리가 준 system, 네 턴, assistant 머리표 | 턴마다 머리표와 <|im_end|> 로 나뉘고, 끝은 답할 자리입니다 |
한 줄로 줄이면, messages 리스트는 <|im_start|>역할 과 <|im_end|> 로 감싼 문자열 하나가 되어 모델에 들어가며, 이 양식을 손으로 조립하지 않고 apply_chat_template 에 맡깁니다.
코드 설명
tokenizer는 6장 셀 6 에서AutoTokenizer.from_pretrained(MODEL_NAME)로 불러온 Qwen 토크나이저입니다. 템플릿 규칙이 토크나이저 안에 들어 있습니다.tokenize=False는 눈으로 확인하려고 문자열로 받는 것입니다. 학습과 생성에서는 보통 토큰 ID 로 받습니다.- 2단계에서 system 을 주지 않았는데도 "You are Qwen, ..." 이 들어갔습니다. 3단계처럼 system 을 주면 그 문구가 기본 문구를 대신합니다.
- 3단계 출력의 맨 끝
<|im_start|>assistant다음 줄이 비어 있습니다. 모델은 여기서부터 "제주도는요?"에 대한 답을 이어 씁니다. - 실습 2 에서 만든 prompt-completion 데이터도 8장 학습 때
SFTTrainer가 이 템플릿을 입혀 2단계 같은 문자열로 바꿉니다.