The single most under-appreciated detail
An instruct-tuned model was fine-tuned on conversations wrapped in a specific format — special tokens marking turn boundaries, special tokens marking system / user / assistant roles, special tokens marking end-of-turn. If you send the model a raw prompt without that wrapping, it has no idea you want a chat response. It might continue your text. It might output the wrapping itself. It might never stop generating. Most "the model is broken" complaints I see are actually "the chat template is missing."
The fix is one helper — tokenizer.apply_chat_template. The tokenizer that came back from load() already knows the right template for the model. You give it a list of role-keyed messages; it gives you back a properly-wrapped prompt string.
The shape
Input is a list of dicts with role and content:
messages = [
{"role": "system", "content": "You are a terse assistant."},
{"role": "user", "content": "Capital of France?"},
]
Output (when called with tokenize=False, add_generation_prompt=True) is a single string ready to feed to 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|>
Send that to generate and the model knows it's the assistant's turn, knows the conversation context, and knows where to stop.
Multi-turn — just keep appending
For follow-up turns, append the assistant's previous response to the messages list and re-render the template. mlx-lm doesn't track conversation state for you — you maintain the message list, render fresh on each turn. That sounds like work; it's actually a feature, because it gives you full control over context window management.
What goes wrong when you skip this
- Model answers your prompt but then keeps going, generating fake user / assistant turns.
- Model outputs literal
<|eot_id|>as text instead of stopping. - Model gives confused, non-instruction-following responses (because it's in completion mode, not chat mode).
- Model never stops until
max_tokenskicks in.
All four of these are the same bug — missing or wrong chat template. Always render the template before generate on instruct models.