트랜스포머·PEFT 2일 과정7장 · SFT 데이터 구성과 Chat Template10 / 18← 이전목차다음 →MetaContents

7장. SFT 데이터 구성과 Chat Template

input_ids, attention_mask, labels

학교 시험지를 채점하는 선생님을 떠올려 봅니다. 시험지에는 지문(질문)과 답란이 함께 있지만, 점수는 답란에만 매깁니다. 지문 부분은 "채점 제외"로 표시해 둡니다. 모델 학습에서 이 표시가 labels 의 -100 입니다. 지문은 답을 쓰려면 읽어야 하므로 입력에는 그대로 남고, 채점에서만 빠집니다.

input_ids, attention_mask, labels 세 줄이 system, user, assistant 머리표, 답변, 턴 끝, 패딩 구간마다 어떤 값을 갖는지 보여 주는 강사자료 그림

그림의 첫 줄 input_ids 는 대화 전체입니다. 둘째 줄 attention_mask 는 실제 토큰이면 1, 맨 오른쪽 패딩이면 0 입니다. 셋째 줄 labels 는 system·user·assistant 머리표 구간과 패딩이 -100(빨간 칸)이고, 답변 내용과 <|im_end|> 만 원래 토큰 ID(초록 칸)입니다. 모델 안에서 labels 를 한 칸 밀어 "각 위치의 다음 토큰"과 비교하므로, 우리가 직접 한 칸 밀 필요는 없습니다.

세 목록의 뜻

이름내용예
input_ids템플릿을 적용한 대화 전체의 토큰 ID[151644, 8948, 198, ...]
attention_mask실제 토큰은 1, 패딩은 0[1, 1, 1, ..., 0, 0]
labels각 위치에서 맞혀야 할 정답. -100 이면 손실 계산에서 제외[-100, -100, ..., 151645, 198]

짧은 예시

Python
from transformers import AutoTokenizer

tok = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B-Instruct")
question = [{"role": "user", "content": "환불은 언제까지 신청할 수 있나요?"}]
answer = [{"role": "assistant", "content": "수령 후 7일 이내에 신청하세요."}]

prompt_len = len(tok.apply_chat_template(question, tokenize=True, add_generation_prompt=True))  # 42
input_ids = tok.apply_chat_template(question + answer, tokenize=True)                           # 57개
attention_mask = [1] * len(input_ids)          # 패딩이 없으므로 모두 1
# labels: input_ids 를 복사한 뒤 질문 구간(앞 42개)을 -100 으로 가린다
labels = [-100] * prompt_len + input_ids[prompt_len:]

print("가린 칸:", labels.count(-100))                 # 결과: 가린 칸: 42
print("채점하는 칸:", len(labels) - labels.count(-100))  # 결과: 채점하는 칸: 15
print("labels 끝:", labels[-3:])                      # 결과: labels 끝: [13, 151645, 198]

이 코드가 뜻하는 것

57칸짜리 시험지에서 앞 42칸(system 기본 문구, 질문, assistant 머리표)에 "채점 제외" 도장을 찍고, 뒤 15칸(답변 13칸과 <|im_end|>, 줄바꿈)만 채점하는 일입니다. SFTTrainer 에 prompt-completion 데이터를 주면 이 일을 알아서 해 줍니다. 여기서는 속을 들여다보려고 손으로 만들어 봤습니다.

줄하는 일쉽게 말하면
prompt_len = len(...)질문 구간의 길이를 잰다지문이 몇 칸인가
attention_mask = [1] * len(input_ids)모든 칸을 본다고 표시빈칸이 없는 시험지
[-100] * prompt_len + input_ids[prompt_len:]앞을 -100 으로 가린 정답 목록지문에 채점 제외 도장
labels.count(-100)가린 칸을 센다도장 찍은 칸 수

주의할 점

  • 질문 구간을 가리지 않으면, 앞 토큰을 보고 질문의 다음 토큰을 맞히는 데에도 손실을 계산합니다. 학습이 답변 만들기에 덜 집중하게 됩니다. 질문은 labels 에서만 빠지고 입력에서는 사라지지 않습니다.
  • -100 은 PyTorch 의 손실 함수가 "무시할 칸"으로 약속한 숫자입니다. 다른 숫자(예: 0)를 넣으면 0번 토큰을 맞히도록 학습되어 버립니다.
← 이전 절7장 · 학습 입력과 추론 입력다음 절 →7장 · 패딩과 배치