Hugging Face chat template 오류, add_generation_prompt와 특수 토큰을 분리하는 법
AI를 활용하여 생성한 이미지입니다.
같은 대화 목록을 넣었는데 모델마다 답변 시작 방식이 달라지는 이유는 chat template이 서로 다르기 때문이에요. 채팅 모델도 결국 토큰의 연속을 읽지만, 사용자·어시스턴트·시스템 메시지를 어떤 제어 토큰으로 감쌀지는 모델이 학습한 형식에 달려 있습니다.[1] 템플릿을 문자열로 직접 흉내 내면 첫 응답이 잘리거나, 사용자의 문장을 이어 쓰거나, 특수 토큰이 중복되는 문제가 생길 수 있어요.
먼저 tokenizer의 템플릿을 기준으로 삼아요
Transformers는 메시지 목록을 role과 content를 가진 딕셔너리 배열로 받고, tokenizer에 저장된 템플릿을 apply_chat_template로 적용하도록 안내해요. 같은 Mistral 계열이라도 모델 저장소에 들어 있는 템플릿과 제어 토큰이 다를 수 있으므로, 모델 이름만 보고 문자열을 조립하면 안 됩니다.[1]
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("모델-경로")
messages = [
{"role": "system", "content": "간결하게 답하세요."},
{"role": "user", "content": "상태를 설명해 주세요."},
]
encoded = tokenizer.apply_chat_template(
messages,
tokenize=True,
add_generation_prompt=True,
return_tensors="pt",
)
tokenize=True로 바로 텐서까지 만들면 템플릿 안에 포함된 특수 토큰을 다시 붙이는 실수를 줄일 수 있어요. 반대로 tokenize=False로 문자열을 만든 다음 별도로 토크나이즈한다면, 템플릿이 이미 넣은 BOS·EOS 같은 토큰을 또 추가하지 않도록 add_special_tokens=False를 검토해야 합니다.[1]
add_generation_prompt가 필요한 순간
| 상황 | 사용할 값 | 주의할 점 |
|---|---|---|
| 마지막 메시지가 user이고 새 답변을 생성 | add_generation_prompt=True |
템플릿이 assistant 시작 토큰을 추가하는지 확인해요. |
| assistant 답변의 일부를 이어 생성 | continue_final_message=True |
assistant 메시지를 새로 시작하지 않고 마지막 내용을 이어요. |
| 학습 데이터 전처리 | 보통 add_generation_prompt=False |
학습 샘플에 불필요한 응답 시작 토큰을 넣지 않아요. |
add_generation_prompt=True는 모델이 이제 assistant 응답을 시작해야 한다는 토큰을 끝에 붙이는 동작이에요. 이 표식이 없으면 모델이 user 문장을 계속 쓰는 것처럼 행동할 수 있습니다. 다만 모든 모델이 같은 표식을 필요로 하는 것은 아니고, 어떤 템플릿에서는 이 옵션이 아무 효과가 없을 수도 있어요.[1]
continue_final_message와 함께 쓰지 않기
continue_final_message는 마지막 메시지의 끝 토큰을 제거하고 그 내용에서 이어 쓰게 만드는 옵션이에요. JSON을 미리 여는 답변이나 reasoning 필드의 일부를 채우는 경우에는 유용하지만, 새 assistant 응답을 시작하는 add_generation_prompt와 목적이 반대입니다. 두 옵션을 동시에 켜면 오류가 나므로, “새 답변”인지 “마지막 답변 이어 쓰기”인지 먼저 정하세요.[1]
툴 호출 템플릿은 더 엄격하게 봐요
고급 템플릿은 tools와 documents 같은 변수를 받아 함수 설명이나 검색 문서를 모델이 학습한 형식으로 펼칠 수 있어요. 하지만 도구 스키마를 넣는다고 모델이 자동으로 도구 호출을 이해하는 것은 아닙니다. 템플릿이 출력하는 토큰과 JSON 구조가 해당 모델이 학습한 형식과 맞아야 하고, 서버 쪽에서도 실제 tool call과 tool response를 같은 순서로 다시 넣어야 해요.[2]
템플릿을 수정했다면 모델 응답 품질만 보지 말고 다음 네 가지를 확인하세요. 렌더링된 문자열에 역할 토큰이 한 번만 나오는지, EOS가 중복되지 않는지, assistant 시작 위치가 마지막 user 뒤에 있는지, 도구 응답이 다음 assistant 생성 앞에 정확히 들어가는지 확인하면 원인을 빠르게 좁힐 수 있습니다.
배포 전 점검표
- 모델 tokenizer의
chat_template존재 여부와 기본 템플릿 확인하기 - 문자열 렌더링과 직접 토크나이즈 경로를 분리해 테스트하기
- 새 응답에는
add_generation_prompt, 이어 쓰기에는continue_final_message중 하나만 선택하기 - 툴 호출이 있다면 tools·tool_calls·tool response의 렌더링 결과 저장하기
- 생성 앞뒤의 토큰 ID와 실제 문자열을 작은 회귀 테스트로 비교하기
템플릿을 맞춘 뒤 긴 프롬프트의 처리량을 조정한다면 vLLM Prefix Caching과 Tensor Parallelism 조정법에서 캐시와 병렬화 조건을 이어서 확인해 보세요.
문제가 생겼을 때 비교할 로그
템플릿 문제는 모델 자체의 능력 저하처럼 보일 때가 많아요. 그래서 같은 messages를 템플릿 적용 전 문자열, 적용 후 토큰 ID, 생성에 넣은 최종 입력으로 각각 저장해 두는 것이 좋습니다. 생성 결과가 user 문장을 반복하면 assistant 시작 토큰이 빠졌는지 먼저 보고, 답변이 빈 문자열이면 EOS가 너무 일찍 들어갔는지 확인하세요.
모델을 교체할 때는 tokenizer도 함께 교체해야 합니다. 기본 모델의 tokenizer에 다른 모델의 템플릿을 얹으면 문법이 비슷해 보여도 토큰 ID와 종료 조건이 달라질 수 있어요. 회귀 테스트에는 일반 질문 하나만 넣지 말고 system·user·assistant가 섞인 대화, JSON 이어 쓰기, 도구 호출과 도구 결과가 포함된 대화를 각각 넣어야 합니다.