글 목록으로

chat template - alpaca & chatml

LLM 모델들의 chat-template 총정리: Alpaca부터 ChatML까지

chat template이란?

LLM 모델 중 chat (instruct) 모델의 경우에는 /v1/completions를 통한 텍스트 문자열 대신 /v1/chat/completions를 통한 role과 content로 이루어진 messages 배열을 입력으로 받습니다.

[
  { "role": "user", "content": "안녕?" },
  { "role": "assistant", "content": "안녕하세요, 저는 친절한 챗봇입니다" },
  { "role": "user", "content": "하늘이 파란 이유가 무엇인가요?" }
]

이런 메세지 배열을 실제로 모델에 입력하기 앞서 텍스트 문자열 형식으로 변환하여야 하는데 이때 chat template이 사용됩니다. chat template은 위에 messages 배열을 어떤 포맷의 텍스트 문자열로 변환할지를 결정하는 jinja template를 의미합니다.

아래는 많은 chat template format 중 하나인 chatml format의 간소화된 예시입니다.

{% if not add_generation_prompt is defined %}
  {% set add_generation_prompt = false %}
{% endif %}
 
{%- for message in messages %}
    {{- '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n' }}
{%- endfor %}
 

{%- if add_generation_prompt %}
    {{- '<|im_start|>assistant\n' }}
{%- endif %}

위에 messages 배열을 chatml format을 통해 변환하면 다음과 같은 텍스트 문자열이 생성됩니다.

<|im_start|>user
안녕?<|im_end|>
<|im_start|>assistant
안녕하세요, 저는 친절한 챗봇입니다<|im_end|>
<|im_start|>user
하늘이 파란 이유가 무엇인가요?<|im_end|>

<|im_start|>assistant

기본적으로 <|im_start|><|im_end|>는 각각 턴의 시작과 끝을 나타내며, 마지막 하이라이팅된 부분은 model에게 지침을 제공하기 위해 add_generation_prompt 옵션을 True로 하여 앞으로 생성해야할 부분의 시작을 (header를) 랜더링 한 결과이다.

이렇게 랜더링된 텍스트를 모델에게 입력하면 모델은 다음과 같이 응답을 생성합니다.


하늘이 파란 이유는 빛의 산란이라는 현상 때문입니다.<|im_end|>

여기서 실제로 모델은 <맥락과 턴에 맞게 생성된 응답><|im_end|>를 생성하였고, stop token (eos token)으로 <|im_end|>이 인식되어 생성이 중단되었습니다.
여기까지 완료되면 /v1/chat/completions에 대한 응답으로 모델의 응답이 반환됩니다.

여기까지가 chatml format을 예시로 한 chat template과 /v1/chat/completions의 내부 동작 방식에 대한 설명이였습니다. 다만 Base model이 아닌 Instruct model의 경우 학습단계에서 특정한 chat template을 사용하므로, 해당 모델의 chat template을 사용하여야 합니다. 모델별로 매우 다양한 chat template이 존재하며 아래는 그 정리입니다.

chat template 포맷 종류

ChatML

가장 광범위하게 채택된 포맷이며 준수한 성능을 보여줍니다. ChatML과 그 파생형을 사용하는 대표적인 모델은 다음과 같습니다.

Qwen, Hermes, SmolLM, Dolphin, InternLM (파생형)....

턴의 시작을 알리는 <|im_start|>{{role}}\n과 턴의 끝을 알리는 <|im_end|>를 사용합니다.

BOS: <|begin_of_text|> (Depends on model)
EOS: <|im_end|>

NousResearch/Hermes-3-Llama-3.1-8B


<|begin_of_text|><|im_start|>system
넌 사용자를 항상 텐션 업 시켜주는 에너제틱한 친구야.<|im_end|>
<|im_start|>user
졸리다.<|im_end|>
<|im_start|>assistant
커피 마셔!<|im_end|>
<|im_start|>user
근데 커피 별로 안 땡겨.<|im_end|>
<|im_start|>assistant
그럼 시원한 아이스 초코 가자!<|im_end|>

Alpaca

스탠포드에서 개발된 Alpaca 7B 모델에서 적용된 포맷입니다. 싱글턴을 전제하에 개발되었으며 prompt_no_input, prompt_input 2가지 버전이 존재한다.



### Instruction:
점심 뭐 먹지?

### Response:
국밥 어때?

Mistral (Latest)

mistral 계열은 특징으로 렌더링 결과가 개행 없이 한 줄로 나온다는 점이 있는데, 아래에서는 시각적 편의를 위해 줄바꿈을 추가하였습니다. 또한 mistralai는 chat template를 가장 자주 업데이트하고 개선하는데 버전 별로 공백처리, 개행처리 등의 개선을 보는 것도 재미있습니다.

BOS: <s>
EOS: </s>

mistralai/Mistral-Small-24B-Instruct-2501 (V7-Tekken)






<s>[SYSTEM_PROMPT]넌 사용자에게 빠르게 날씨를 알려주는 친구야.[/SYSTEM_PROMPT]
[INST]오늘 날씨 어때?[/INST]흐리고 쌀쌀해.</s>
[INST]우산 필요할까?[/INST]응, 비 올 수도 있어!</s>
mistralai/Ministral-8B-Instruct-2410 (V3-Tekken)




<s>[INST]넌 사용자에게 빠르게 날씨를 알려주는 친구야.

오늘 날씨 어때?[/INST]흐리고 쌀쌀해.</s>
[INST]우산 필요할까?[/INST]응, 비 올 수도 있어!</s>

For more information about the tokenizer please refer to mistral-common

Llama 3.x

llama 계열 Instruct 모델에서 사용되는 포맷이다.
EOS와 다음 지침을 의미하는 스페셜 토큰 간에 개행이 없는 것이 특징이다.

BOS: <|begin_of_text|>
EOS: <|eot_id|>

meta-llama/Llama-3.3-70B-Instruct




<|begin_of_text|><|start_header_id|>system<|end_header_id|>

넌 사용자의 결정을 도와주는 현실적인 친구야.<|eot_id|><|start_header_id|>user<|end_header_id|>

운동 갈까 말까?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

가라, 후회 안 한다.<|eot_id|><|start_header_id|>user<|end_header_id|>

근데 너무 귀찮은데...<|eot_id|><|start_header_id|>assistant<|end_header_id|>

5분만 가볍게라도 해봐, 그럼 몸이 풀릴 걸!<|eot_id|>

모델별 chat template 찾는 법

모델 별로 학습할 때 사용된 탬플릿을 찾아서 올바르게 사용하는 것이 모델 성능에 큰 영향을 미치는데, 이를 찾는 방법은 어렵지 않다.

  1. huggingface files에서 tokenizer_config.json 파일 찾기
    모델이 instrcut 모델이라면 높은 확율로 tokenizer_config.json 파일에 chat template 필드가 존재한다. 보통 한줄로 포맷팅되어 있으며 여기에 탬플릿이 존재한다면 대부분 그대로 사용하면 된다.

    hf.co/NousResearch/Hermes-3-Llama-3.2-3B/blob/main/tokenizer_config.json
    ...
    "bos_token": "<|begin_of_text|>",
    
    "chat_template": "{% if not add_generation_prompt is defined %}{% set add_generation_prompt = false %}{% endif %}{% for message in messages %}{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}}{% endfor %}{% if add_generation_prompt %}{{ '<|im_start|>assistant\n' }}{% endif %}",
    "clean_up_tokenization_spaces": true,
    "eos_token": "<|im_end|>",
    ...
  2. model card에 명시된 "Prompting Template" 찾기
    모델 제작자가 친절한 경우 어떤 탬플릿이 작동작하는지에 대하여 서술해두는 경우가 있습니다. 어떤 방법으로 학습했는지에 따라 완전히 다른 탬플릿이 사용되기도 하니 이를 참고하여 사용하면 됩니다.
    아래의 경우 Mistral [INST] 탬플릿과 ChatML의 혼합된 형태를 사용하고 있습니다.
    e.g. Sao10K/MN-12B-Lyra-v3

AutoTokenizer로 chat template 렌더링

from transformers import AutoTokenizer
import os
 
messages = [
    {"role": "system", "content": "넌 사용자의 하루를 재미있게 만들어주는 장난기 많은 친구야."},
    {"role": "user", "content": "심심해."},
    {"role": "assistant", "content": "두더지 농담 하나 할게! 왜 두더지는 인터넷을 좋아할까?"},
    {"role": "user", "content": "왜?"},
    {"role": "assistant", "content": "왜냐면, '터널'을 통해 항상 새로운 정보를 찾거든!"},
]
 
tokenizer = AutoTokenizer.from_pretrained(
    "HuggingFaceTB/SmolLM2-1.7B-Instruct",
    token=os.environ.get("HF_TOKEN"),
    legacy=False,
)
 
print(tokenizer.apply_chat_template(messages, tokenize=False))

Reference

작성일:
수정일:

이전글 / 다음글