지시 학습 모델은 아무 대화문이나 보고 배운 게 아니야. 턴 경계, system·user·assistant 역할, 턴의 끝을 나타내는 특수 토큰으로 감싼 형식에 맞춰 파인튜닝됐어. 이 포장 없이 원문 프롬프트만 보내면 채팅 답을 원한다는 사실조차 모를 수 있어. 입력을 이어 쓰거나 포장 문자를 그대로 출력하거나 영원히 멈추지 않기도 해. "모델이 고장 났어"라는 불평 상당수는 사실 채팅 템플릿을 빼먹은 경우야.
해결은 tokenizer.apply_chat_template 하나면 돼. load()가 돌려준 토크나이저는 모델에 맞는 템플릿을 이미 알고 있어. 역할이 붙은 메시지 목록을 주면 올바르게 감싼 프롬프트 문자열을 만들어줘.
입력과 출력의 모양
입력은 role과 content를 가진 사전 목록이야.
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.
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])
여러 턴 코드 블록을 실행해 두 턴 모두 정확히 답하는지 확인해. 다음에는 같은 대화에서 apply_chat_template을 완전히 빼고 사용자의 원문 문자열을 generate에 직접 넘겨. 입력을 이어 쓰거나 system 프롬프트를 출력하거나 멈추지 않는지 살펴봐. 관찰을 두 문장으로 적어. 한 번 보면 평생 알아볼 버그 모양이야.
Progress
Progress is local-only — sign in to sync across devices.