7장. SFT 데이터 구성과 Chat Template
모델은 messages 를 직접 읽지 못한다: Chat Template
연극 대본을 떠올려 봅니다. 대본에는 "철수: 안녕?", "영희: 응, 안녕." 처럼 말한 사람의 이름표가 줄마다 붙어 있습니다. 배우는 이 이름표로 누가 말할 차례인지 압니다. 파이썬의 [{"role": "user", "content": "..."}] 리스트는 사람이 보기 편한 모양일 뿐이고, 모델에 들어가는 것은 이름표가 붙은 긴 대본 한 편을 토큰 숫자로 바꾼 목록입니다. 이 대본 양식이 Chat Template 입니다.
그림처럼 messages 리스트는 템플릿을 거쳐 머리표가 붙은 문자열 하나가 되고, 토크나이저를 거쳐 숫자 목록이 됩니다. 모델마다 머리표 모양이 다르고, 그 규칙은 토크나이저 설정 파일(tokenizer_config.json)에 함께 저장되어 있습니다. 그래서 문자열을 손으로 조립하지 않고 tokenizer.apply_chat_template() 을 씁니다. Qwen 의 양식은 ChatML 이라고 부릅니다.
Qwen(ChatML)의 머리표
| 표시 | 토큰 ID | 뜻 |
|---|---|---|
<|im_start|>system | 151644 + 낱말 | 모델의 역할과 응답 지침 시작. 따로 주지 않으면 기본 문구가 들어간다 |
<|im_start|>user | 151644 + 낱말 | 사용자 발화 시작 |
<|im_start|>assistant | 151644 + 낱말 | 모델 답변 시작 |
<|im_end|> | 151645 | 한 턴의 끝. 모델이 이것을 생성하면 답변이 끝난다 |
<|endoftext|> | 151643 | 이 토크나이저가 패딩에 쓰는 토큰 |
짧은 예시
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
messages = [{"role": "user", "content": "환불은 언제까지 신청할 수 있나요?"},
{"role": "assistant", "content": "수령 후 7일 이내에 신청하세요."}]
# apply_chat_template = 대본 양식 입히기. tokenize=False 면 문자열로, True 면 토큰 ID 목록으로 돌려준다
text = tok.apply_chat_template(messages, tokenize=False)
ids = tok.apply_chat_template(messages, tokenize=True)
print(text.count("<|im_start|>"), "개의 턴") # 결과: 3 개의 턴 (system 기본 문구, user, assistant)
print("토큰 수:", len(ids)) # 결과: 토큰 수: 57
print("앞 세 토큰:", ids[:3]) # 결과: [151644, 8948, 198] → <|im_start|>, system, 줄바꿈
# convert_ids_to_tokens: ID 를 토큰 글자로 되돌린다
print(tok.convert_ids_to_tokens(ids[:2])) # 결과: ['<|im_start|>', 'system']이 코드가 뜻하는 것
두 줄짜리 대사를 대본 양식에 옮겨 적었더니 이름표가 셋 붙었습니다. 우리가 주지 않은 system 턴을 Qwen 템플릿이 "You are Qwen, created by Alibaba Cloud. ..." 기본 문구로 채웠기 때문입니다. 대본 전체는 57 토큰이고, 맨 앞은 <|im_start|>(151644), system(8948), 줄바꿈(198)입니다.
| 줄 | 하는 일 | 쉽게 말하면 |
|---|---|---|
apply_chat_template(messages, tokenize=False) | 머리표를 붙인 문자열을 만든다 | 대본 양식에 옮겨 적기 |
apply_chat_template(messages, tokenize=True) | 같은 문자열을 토큰 ID 로 바꾼다 | 대본을 상품 번호 열로 |
text.count("<|im_start|>") | 턴 수를 센다 | 이름표가 몇 개인가 |
convert_ids_to_tokens(ids[:2]) | 번호를 토큰 글자로 되돌린다 | 번호표 읽어 보기 |
주의할 점
- 템플릿은 모델마다 다릅니다. Qwen 데이터를 Llama 템플릿으로 학습하거나, 학습 때와 다른 템플릿으로 추론하면 답이 이상해집니다. 언제나 그 모델의
apply_chat_template을 씁니다. - Qwen 은 system 을 주지 않으면 기본 문구를 넣습니다. 학습 때와 쓸 때 system 을 같게 맞춰야 학습한 말투가 잘 나옵니다.