Hugging Face Chat Templates
Hugging Face, 성능 저하를 방지하기 위한 채팅 템플릿 도입
Hugging Face는 "조용한 성능 저하(silent performance killers)"—채팅 모델이 학습 시 사용된 형식과 다른 형식으로 프롬프트될 때 발생하는 심각한 모델 성능 저하 문제를 해결하기 위해 토크나이저를 위한 chat_template 속성을 도입했습니다. 포맷팅 로직을 토크나이저 내부에 Jinja 템플릿으로 저장함으로써, 개발자는 대화 기록이 모델이 기대하는 정확한 문자열 형식으로 변환되도록 보장할 수 있으며, 잘못된 포맷팅으로 인한 분포 변화(distribution shifts)를 제거할 수 있습니다.
채팅 포맷팅에서의 분포 변화 문제
표준 언어 모델의 경우, 동일한 체크포인트에서 토크나이저와 모델을 함께 로드하면 일반적으로 분포 변화를 방지할 수 있습니다. 하지만 채팅 모델은 "user", "assistant", "system"과 같은 역할을 포함하는 메시지 시퀀스를 하나의 토크나이징 가능한 문자열로 변환해야 합니다. 이러한 변환에 대한 업계 표준이 없기 때문에, 모델마다 다음과 같이 매우 다른 형식을 사용합니다:
- 단순 레이블:
User: [text] \n Bot: [text] - 특수 토큰:
[USER] [text] [/USER] \n [ASST] [text] [/ASST] - 경계 토큰:
<|im_start|>user \n [text]<|im_end|>
잘못된 형식을 사용하는 것은 Python 예외를 발생시키거나 명시적인 오류를 일으키지 않기 때문에 "조용한 오류(silent error)"입니다. 대신 모델의 성능이 현저히 떨어지게 되어 문제를 디버깅하기 어렵게 만듭니다.
Jinja 템플릿을 통한 기술적 구현
채팅 템플릿은 토크나이저와 함께 저장되고 로드되는 Jinja 템플릿 문자열로 구현됩니다. 이러한 방식이 단순한 시스템(역할별 접두사 및 접미사 사용 등)보다 선택된 이유는 템플릿 방식이 모든 알려진 메시지 형식을 지원할 수 있을 만큼 유연하며, 로직과 검사 기능을 템플릿에 직접 인코딩할 수 있기 때문입니다.
템플릿 로직 예시
경계 토큰을 사용하는 형식의 경우, Jinja 템플릿은 다음과 같을 수 있습니다:
{% for message in messages %}
{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}
{% endfor %}
Transformers 라이브러리와의 통합
채팅 템플릿은 전처리 정보가 모델의 토크나이징 로직과 함께 유지되어야 한다는 원칙을 지키기 위해 토크나이저에 직접 통합되었습니다.
클래스 수준 포맷팅에서의 전환
이전에는 채팅 포맷팅이 클래스 수준에서 처리되었습니다(예: 모든 LLaMA 체크포인트는 transformers 라이브러리의 동일한 하드코딩된 로직을 사용함). 하위 호환성을 유지하기 위해 이제 모델 클래스에는 **기본 채팅 템플릿(default chat templates)**이 포함되어 있습니다. 하지만 Hugging Face는 취약성을 피하고 향후 기본 템플릿의 변경이 모델 성능을 저하시키지 않도록 모든 채팅 모델에 chat_template을 명시적으로 설정할 것을 강력히 권장합니다.
사용 및 배포
개발자는 tokenizer.apply_chat_template() 메서드를 사용하여 이러한 템플릿을 적용할 수 있습니다. 토크나이저에 chat_template 속성이 없는 경우 클래스 기본값으로 폴백(fallback)되며, 이는 위에서 언급한 조용한 버그로 이어질 수 있습니다. 사용자들은 모델 카드에서 올바른 형식을 확인하고, Hugging Face Hub의 체크포인트에 chat_template 속성을 추가하기 위한 풀 리퀘스트(pull request)를 제출할 것을 권장합니다.
새로운 모델을 위한 권장 사항
Hugging Face는 단일 표준 형식이 이상적이라는 점을 인정하지만, 기존에 학습된 모델의 다양성 때문에 하드코딩된 표준을 만드는 것은 불가능합니다. 새로운 채팅 모델을 학습시키는 경우, Hugging Face는 <|im_start|> 및 <|im_end|> 토큰을 사용하는 OpenAI의 ChatML 형식을 권장합니다. 이 형식은 역할(role)에 대해 유연하며 다음과 같이 한 줄의 템플릿 할당으로 구현할 수 있습니다:
tokenizer.chat_template = "{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}"