Reasoning models
A reasoning student writes out its reasoning in a thinking block, then gives the answer. For
Qwen3 models the block is <think>…</think> at the start of the reply. The student learns to do
this from reasoning written by the teacher during synthetic data generation, or from reasoning you
put in your own training data.
Use a reasoning student when getting to the answer takes several steps: a calculation, a rule applied to the facts, a choice between close options. The cost is more output tokens on every request, so replies are slower than from the same student without thinking. Only the answer is scored, so train a normal student first and compare.
Turning it on
Section titled “Turning it on”One setting controls it:
base:
task: question-answering
student_model_name: Qwen3-1.7B
teacher_model_name: openai.gpt-oss-120b
enable_thinking: true
base.enable_thinking defaults to false. When it is true, synthetic data generation writes
reasoning for every training example, and the student is trained, evaluated and served with its
chat template’s thinking mode on. It works with every task type.
Set it on the seed dataset’s config, before you generate training data. The training dataset and the model inherit it from there.
Supported students
Section titled “Supported students”| Model | Value |
|---|---|
| Qwen3 0.6B | Qwen3-0.6B |
| Qwen3 1.7B | Qwen3-1.7B |
| Qwen3 8B | Qwen3-8B |
| Qwen3.5 0.8B | Qwen3.5-0.8B |
| Qwen3.5 2B | Qwen3.5-2B |
| Qwen3.5 4B | Qwen3.5-4B |
| Qwen3.5 9B | Qwen3.5-9B |
| Qwen3.6 35B A3B | Qwen3.6-35B-A3B |
| Nemotron 3.5 Lightning 30B A3B | Nemotron-3.5-Lightning-30B-A3B |
Any other student fails config validation. This includes Qwen3-4B-Instruct-2507, which has no
thinking mode:
base.enable_thinking=true is not available for student model 'Qwen3-4B-Instruct-2507'. Its chat
template either has no thinking mode, or its response template cannot parse the thinking block
back out of the generated text. Supported: Qwen3-0.6B, Qwen3-1.7B, ...
The teacher does not need to be a reasoning model. -thinking teachers are a separate setting,
covered on Supported models.
Where the reasoning comes from
Section titled “Where the reasoning comes from”You don’t have to write reasoning. With base.enable_thinking: true, synthetic data generation
produces it in two places:
- During generation. The teacher writes the reasoning next to each assistant message it generates.
- Backfill. A final pass goes over every training example, your seed rows included. When the final assistant message has no reasoning, the teacher writes it. The backfill changes only the reasoning, never the answer or the tool calls. Rows where the teacher still writes none after a retry are dropped.
The teacher is told to write the reasoning in the first person, to work the answer out rather
than explain it, to rule out the closest wrong option, and to use only facts that are in the
conversation. Put instructions on the style and length of the reasoning in
synthetic_data_generation_instructions in the
job description, for example “Keep the reasoning under 250
tokens: one short line per check.”
The backfill costs one teacher call per example that has no reasoning. Check the train row count after generation: the dropped rows are not replaced.
Reasoning in your data
Section titled “Reasoning in your data”To supply reasoning yourself, put it in reasoning_content on the assistant message:
{"messages": [
{"role": "user", "content": "A pack holds 12 pens. How many packs for 50 pens?"},
{"role": "assistant", "content": "5", "reasoning_content": "50 / 12 is 4.17. Four packs hold 48, which is 2 short, so it takes 5."}
]}
For tool calls, the reasoning goes on the assistant message that carries tool_calls:
{"messages": [
{"role": "user", "content": "What's the weather in Berlin?"},
{"role": "assistant", "content": "", "reasoning_content": "The user asks about current weather in one city, so get_weather with city Berlin.", "tool_calls": [
{"type": "function", "function": {"name": "get_weather", "arguments": {"city": "Berlin"}}}
]}
]}
These rules apply:
- The key is
reasoning_content, on the assistant message. Areasoningkey, or a key at the row’s top level, is ignored without an error. - Only the final assistant message keeps its reasoning. Multi-turn conversations are split into one example per assistant turn, and reasoning on earlier turns is removed. A deployed model never sees the reasoning of earlier turns, so it does not train on it either.
- Reasoning is optional per row. The backfill writes it for rows that have none.
- With
enable_thinking: false, reasoning is removed when the data is read. The same data works for both kinds of student. - The legacy format uses a top-level
reasoningfield next toquestionandanswer. Legacy multi-turn tool calling has no reasoning field.
test.jsonl needs no reasoning. Evaluation scores only the answer.
Starting from traces
Section titled “Starting from traces”Trace processing does not accept enable_thinking: true. Production traces have no reasoning,
and this stage writes none. Process the traces with thinking off, then turn it on for
generation:
# 1. Process the traces with base.enable_thinking: false (the default)
distil seed-dataset create-from-traces <traces-id>
# 2. Read back the config the seed dataset carries
distil seed-dataset download-metadata -d ./reasoning <seed-dataset-id>
# in ./reasoning/config.yaml, under base:
# enable_thinking: true
# 3. Generate the training data with thinking on
distil training-dataset create-from-seed-dataset --output json \
--config ./reasoning/config.yaml <seed-dataset-id> | jq -r .id
The override replaces config.yaml whole, so edit the file you read back and send all of it.
See How the platform works.
Adding reasoning without new examples
Section titled “Adding reasoning without new examples”To keep your training set as it is and only add reasoning, set synthgen.generation_target: 0.
Generation then makes no new examples, but the backfill still runs over your seed rows. This
does not work for question-answering-closed-book, which computes its own target.
Don’t turn on enable_thinking for a training run on a training dataset that was generated
without it. Its rows have no reasoning, nothing adds it at training time, and the student learns
to write an empty thinking block. Generate a new training dataset with thinking on instead.
Length limits
Section titled “Length limits”The reasoning makes each example longer, and two settings limit the length:
tuning.max_completion_length(default2048tokens) is the most the student can generate during evaluation and RLVR, reasoning included. If the longest training completion is longer, training logs a warning, and completions that are cut off score near zero. Raise it when the reasoning is long.synthgen.validation_max_total_length(default30000characters) applies to the whole example. Examples that the backfill makes longer than this are dropped.
Evaluation
Section titled “Evaluation”The metrics score only the answer: the content and the tool calls. The reasoning is not
scored.
The student is evaluated with its thinking mode on, at the sampling settings its vendor
recommends for thinking. For Qwen3 that is temperature 0.6, top_p 0.95 and top_k 20. For
Qwen3.5 and Qwen3.6 it is temperature 1.0, top_p 0.95 and top_k 20.
Deployment
Section titled “Deployment”The generated model_client.py sends chat_template_kwargs: {"enable_thinking": true} for a
reasoning student, with temperature=0. Use it rather than a hand-built request, as for any
other model. Hosted inference supports reasoning students, so a
model trained with enable_thinking: true needs no extra setup there.
When you serve the model yourself, start vLLM with the reasoning parser for the student’s family, so the reasoning is returned separately from the answer:
| Student | vLLM flags |
|---|---|
| Qwen3 | --enable-auto-tool-choice --tool-call-parser hermes --reasoning-parser qwen3 |
| Qwen3.5, Qwen3.6 | --enable-auto-tool-choice --tool-call-parser qwen3_xml --reasoning-parser qwen3 |
| Nemotron | --enable-auto-tool-choice --tool-call-parser qwen3_xml --reasoning-parser nemotron_v3 |
vllm serve model --reasoning-parser qwen3 --api-key EMPTY
With the parser, the returned message holds the reasoning in a separate field
(reasoning_content, or reasoning in newer vLLM versions) and only the answer in content.
Without it, the thinking block stays at the start of content. See
Local deployment.
Every other parameter: Config file. The generation stage that writes the reasoning: Synthetic data generation.