채팅 템플릿
대화 메시지를 모형의 채팅 형식으로 변환해 응답을 생성한다.
채팅 템플릿의 필요성
- 채팅 모형: 대화 형식의 토큰열에서 다음 토큰을 예측하도록 훈련된 언어 모형
- 대화 정보: 각 메시지의 발화자, 내용, 차례로 구성
- 질문 문자열만 입력할 때의 문제: 모형은 그 문자열이 누구의 말인지, 다음이 누구의 차례인지 알 수 없음
- 채팅 템플릿(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_ids와attention_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)
실행 결과
'"봄은 따뜻한 바람과 함께 꽃들이 피며 새로운 시작을 맞이하는 계절입니다."'
- 생성 결과: 사용자의 요청에 맞는 어시스턴트 응답을 생성
- 템플릿 없이 생성했을 때와 같은 질문, 같은 모형이지만 형식을 맞추자 응답이 나옴
- 전체 과정: 메시지 작성 → 채팅 템플릿 적용 → 토큰화 → 생성 → 문자열 복원