본문 바로가기
C.W.K.
Stream
Lesson 05 of 07 · published

채팅 템플릿 — 모델은 포장 방식까지 배웠어

~14 min · chat, templates, tokenizer

Level 0호기심
0 XP0/51 lessons0/15 achievements
0/100 XP to next level100 XP to go0% complete

가장 자주 과소평가하는 한 가지

지시 학습 모델은 아무 대화문이나 보고 배운 게 아니야. 턴 경계, system·user·assistant 역할, 턴의 끝을 나타내는 특수 토큰으로 감싼 형식에 맞춰 파인튜닝됐어. 이 포장 없이 원문 프롬프트만 보내면 채팅 답을 원한다는 사실조차 모를 수 있어. 입력을 이어 쓰거나 포장 문자를 그대로 출력하거나 영원히 멈추지 않기도 해. "모델이 고장 났어"라는 불평 상당수는 사실 채팅 템플릿을 빼먹은 경우야.

해결은 tokenizer.apply_chat_template 하나면 돼. load()가 돌려준 토크나이저는 모델에 맞는 템플릿을 이미 알고 있어. 역할이 붙은 메시지 목록을 주면 올바르게 감싼 프롬프트 문자열을 만들어줘.

입력과 출력의 모양

입력은 rolecontent를 가진 사전 목록이야.

messages = [
    {"role": "system", "content": "You are a terse assistant."},
    {"role": "user",   "content": "Capital of France?"},
]

tokenize=False, add_generation_prompt=True로 호출하면 generate에 바로 넣을 문자열을 돌려줘.

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

You are a terse assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>

Capital of France?<|eot_id|><|start_header_id|>assistant<|end_header_id|>

이 문자열을 generate에 보내면 모델은 지금이 assistant 턴이라는 것, 앞의 대화 맥락, 멈출 지점을 모두 알아.

여러 턴은 메시지를 계속 붙이면 돼

다음 턴에서는 assistant의 이전 답을 메시지 목록에 추가하고 템플릿을 다시 적용해. mlx-lm은 대화 상태를 대신 기억하지 않아. 메시지 목록을 직접 유지하고 매 턴 새로 그려야 해. 번거롭게 들리지만 장점이야. 문맥 창에 무엇을 남기고 버릴지 완전히 통제할 수 있거든.

빼먹었을 때 나타나는 증상

  • 프롬프트에 답한 뒤 가짜 user와 assistant 턴까지 계속 만들어.
  • 멈추는 대신 <|eot_id|>를 글자 그대로 출력해.
  • 채팅이 아니라 문장 완성 모드에 있어 지시를 따르지 않는 혼란스러운 답을 줘.
  • max_tokens를 채울 때까지 멈추지 않아.

넷 모두 채팅 템플릿이 없거나 틀렸을 때 생기는 같은 버그야. 지시 학습 모델에는 언제나 generate 전에 템플릿을 적용해.

Code

apply_chat_template — 표준 방식·python
from mlx_lm import load, generate

model, tok = load("mlx-community/Llama-3.2-1B-Instruct-4bit")

messages = [
    {"role": "system", "content": "You are a terse assistant."},
    {"role": "user",   "content": "Capital of France?"},
]

prompt = tok.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)

print("--- rendered prompt ---")
print(prompt)
print("--- generation ---")
print(generate(model, tok, prompt=prompt, max_tokens=20, verbose=False))

# Verified output (2026-05-03):
#   --- rendered prompt ---
#   <|begin_of_text|><|start_header_id|>system<|end_header_id|>
#
#   Cutting Knowledge Date: December 2023
#   Today Date: 03 May 2026
#
#   You are a terse assistant.<|eot_id|><|start_header_id|>user<|end_header_id|>
#
#   Capital of France?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
#
#   --- generation ---
#   Paris.
여러 턴 — assistant 답을 붙이고 다시 적용하기·python
from mlx_lm import load, generate

model, tok = load("mlx-community/Llama-3.2-1B-Instruct-4bit")

messages = [
    {"role": "system", "content": "You are a terse assistant."},
    {"role": "user",   "content": "Capital of France?"},
]

# Turn 1
prompt = tok.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
reply  = generate(model, tok, prompt=prompt, max_tokens=20, verbose=False)
print("Turn 1:", reply)
messages.append({"role": "assistant", "content": reply})

# Turn 2 — follow-up question, with prior turns in context
messages.append({"role": "user", "content": "And of Germany?"})
prompt = tok.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
reply  = generate(model, tok, prompt=prompt, max_tokens=20, verbose=False)
print("Turn 2:", reply)
가공 전 템플릿 살펴보기 — 디버깅 도구·python
from mlx_lm import load
model, tok = load("mlx-community/Llama-3.2-1B-Instruct-4bit")

# The Jinja template the tokenizer will use to render messages.
# Useful when you're debugging "why does my prompt look weird".
print("Chat template (first 600 chars):")
print(tok.chat_template[:600])

External links

Exercise

여러 턴 코드 블록을 실행해 두 턴 모두 정확히 답하는지 확인해. 다음에는 같은 대화에서 apply_chat_template을 완전히 빼고 사용자의 원문 문자열을 generate에 직접 넘겨. 입력을 이어 쓰거나 system 프롬프트를 출력하거나 멈추지 않는지 살펴봐. 관찰을 두 문장으로 적어. 한 번 보면 평생 알아볼 버그 모양이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.