logo

채팅 템플릿

대화 메시지를 모형의 채팅 형식으로 변환해 응답을 생성한다.

채팅 템플릿의 필요성

  • 채팅 모형: 대화 형식의 토큰열에서 다음 토큰을 예측하도록 훈련된 언어 모형
  • 대화 정보: 각 메시지의 발화자, 내용, 차례로 구성
  • 질문 문자열만 입력할 때의 문제: 모형은 그 문자열이 누구의 말인지, 다음이 누구의 차례인지 알 수 없음
  • 채팅 템플릿(chat template): 대화 정보를 학습에 사용한 모형 고유의 토큰열로 변환
    • 발화자와 차례의 경계에 특수 토큰 추가
    • 모형마다 다른 특수 토큰과 배치 순서를 토크나이저에 저장

실습 준비

!pip install -q "transformers==4.55.4" accelerate jinja2
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "LGAI-EXAONE/EXAONE-4.0-1.2B"  # 허브 저장소 식별자
tokenizer = AutoTokenizer.from_pretrained(model_name)  # 토크나이저 로드
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype=torch.float16,  # T4 GPU용 16비트 자료형
    device_map="auto",         # 사용 가능한 GPU에 자동 배치
)

메시지 작성

  • 메시지(message): 발화자의 역할과 내용을 묶은 자료
  • role: 메시지를 작성한 주체
    • user: 사용자, assistant: 모형의 응답, system: 모형의 행동 지침
    • 사용 가능한 역할은 모형에 따라 다름
  • content: 메시지의 내용
prompt = "한국의 봄을 한 문장으로 설명해 줘."
messages = [
    {"role": "user", "content": prompt}
]

템플릿 없이 생성

  • 비교 실험: 질문 문자열을 채팅 템플릿 없이 그대로 토큰화해 생성
raw_inputs = tokenizer(prompt, return_tensors="pt").to(model.device)  # 질문만 토큰화

with torch.inference_mode():
    raw_output_ids = model.generate(
        **raw_inputs,
        max_new_tokens=40,  # 새로 생성할 토큰 수의 상한
        do_sample=False,    # 무작위 추출을 사용하지 않음
    )

raw_length = raw_inputs["input_ids"].shape[1]
tokenizer.decode(raw_output_ids[0, raw_length:])  # 특수 토큰을 포함해 복원
실행 결과
'[|endofturn|]'
  • 생성 결과: 답 대신 차례의 끝을 뜻하는 특수 토큰 [|endofturn|] 하나만 생성하고 종료
  • 이유: 발화자와 차례를 표시하는 특수 토큰이 없어 모형이 응답을 시작할 차례라는 신호를 받지 못함
    • 채팅 형식으로 학습한 모형은 학습 때 본 형식이 아니면 제대로 응답하지 못함

채팅 템플릿의 기능

  • apply_chat_template(): 메시지를 토크나이저에 저장된 대화 형식으로 변환
  • tokenize=False: 변환된 형식을 문자열로 반환
  • add_generation_prompt=True: 다음 차례가 모형의 응답임을 나타내는 표지를 추가
  • enable_thinking=False: EXAONE의 추론 과정을 생략한 일반 응답 형식 사용
    • 기본값이 일반 응답이므로 생략해도 결과는 같음. True로 바꾸면 추론 모드
formatted_prompt = tokenizer.apply_chat_template(
    messages,
    tokenize=False,              # 변환 결과를 문자열로 반환
    add_generation_prompt=True,  # 어시스턴트 응답 차례 추가
    enable_thinking=False,       # 일반 응답 형식 사용
)
formatted_prompt
실행 결과
'[|user|]\n한국의 봄을 한 문장으로 설명해 줘.[|endofturn|]\n[|assistant|]\n<think>\n\n</think>\n\n'
  • [|user|], [|assistant|], [|endofturn|]: 발화자와 차례의 경계를 표시
  • 일반 응답 모드: <think> 블록이 비어 있음

채팅 형식으로 생성

  • tokenize=True: 채팅 템플릿을 적용한 결과를 토큰 ID로 변환
  • return_dict=True: input_idsattention_mask를 포함한 입력을 사전 형태로 반환
  • return_tensors="pt": 사전의 값을 파이토치 텐서로 반환
  • .to(model.device): 입력을 모형과 같은 장치로 이동
inputs = tokenizer.apply_chat_template(
    messages,
    tokenize=True,               # 변환 결과를 토큰 ID로 반환
    add_generation_prompt=True,  # 어시스턴트 응답 차례 추가
    enable_thinking=False,       # 일반 응답 형식 사용
    return_dict=True,            # input_ids와 attention_mask를 사전으로 반환
    return_tensors="pt",         # 사전의 값을 파이토치 텐서로 변환
).to(model.device)               # 입력을 모형과 같은 장치로 이동

with torch.inference_mode():
    output_ids = model.generate(
        **inputs,
        max_new_tokens=40,  # 새로 생성할 토큰 수의 상한
        do_sample=False,    # 무작위 추출을 사용하지 않음
        use_cache=True,     # 이전 토큰의 Key·Value를 KV 캐시에 저장해 재사용
    )
  • 생성 부분 분리: output_ids에서 입력 길이 이후의 토큰만 선택
input_length = inputs["input_ids"].shape[1]
generated_ids = output_ids[0, input_length:]
tokenizer.decode(generated_ids, skip_special_tokens=True)
실행 결과
'"봄은 따뜻한 바람과 함께 꽃들이 피며 새로운 시작을 맞이하는 계절입니다."'
  • 생성 결과: 사용자의 요청에 맞는 어시스턴트 응답을 생성
    • 템플릿 없이 생성했을 때와 같은 질문, 같은 모형이지만 형식을 맞추자 응답이 나옴
  • 전체 과정: 메시지 작성 → 채팅 템플릿 적용 → 토큰화 → 생성 → 문자열 복원
Previous
확률적 디코딩