Distillation troubleshooting¶
Each gmlx distill step prints progress lines and can write a report. This
page says which figures matter and what to do when a step fails or a result
is poor. The steps themselves are in the walkthrough.
The figures¶
- A pass rate is the share of held-out questions the checker accepts. It is the one figure that says whether the task works. Served twice, the same adapter's pass rate moves by three or four in a hundred, so smaller differences between adapters mean nothing.
- A loss is what training drives down. Only its trend matters.
- Nats per token is the student's average surprise at the tokens the teacher wrote. A token the student gave probability 0.37 costs one nat. Lower is better, and zero means the student would have written the same.
- Bits per byte,
bpbin the tables, is the same surprise per byte of text, so slices with different tokenizers compare.
Training lines¶
train prints a line every 10 steps, and a validation line every 200 steps
and at the last step:
[train] it 200 loss 0.0465 dk 0.0332 alm 0.0134 ce 0.1682 floored 0 lr 1.43e-05 260 tok/s step 8700 ms load 100 ms peak 49.7 GB active 13.5 cache 8.0
[train] it 200 val 0.0806
lossshould fall through the first third of the run, then flatten.valis the loss on the rowsalignheld back. Later validation lines add the best earlier value in parentheses.peakis the most memory the run has used, in GB.
val none means no held-back row had a position to score. The other
fields are described in Distillation internals.
The eval report¶
eval writes one Markdown table per kind of measurement. Most tables have
one row per slice. This is the walkthrough's reply table:
reply slice |
bpb after |
bpb before |
nats/token after |
nats/token before |
se |
rows |
|---|---|---|---|---|---|---|
| heldout | 0.2225 | 1.4131 | 0.6946 | 4.4116 | 0.0056 | 173 |
after columns score the student with the adapter, and before columns
without it, filled in by --before. se is the standard error of
bpb after. Two adapters whose bpb differs by less than twice the se
cannot be told apart. Compare nats/token after with the teacher's own
figure, the with value on the census row
teacher nats per token on high-delta positions.
| Table | What it shows |
|---|---|
slice |
Plain-text slices. gate reads void when over 1% of the slice is in the training corpus, so its score does not count. |
chat slice |
Conversations, scored on their assistant turns. |
task |
Accuracy on the local benchmark task files. |
chat sanity |
How the student behaves on the --chat-sanity prompts, one row per figure, listed below. |
kld vs cache |
The distance from the teacher's stored choices, and top-1, how often both pick the same token. |
The chat sanity table has these rows:
compliance: the share of replies that kept the turn structure.truncated_rate: the share of replies that--chat-max-tokenscut.refusal_rate: the share ofrefuseprompts the student refused.task_refusal_rate: the share oftaskprompts the student refused. A rise after training means the adapter makes the student refuse ordinary requests.ref_nll_nats: the student's surprise, in nats per token, at earlier replies to the same prompts. Under--beforethese are the adapter-off replies, otherwise the replies in the--chat-refsreport. The higher it is, the more the adapter changed ordinary answers.
A reply table that shows None scored no row. Every row was longer than
--chat-max-len or had nothing to score.
The filter dropped most rows¶
The rejects file gives one reason per dropped row:
| Reason | Meaning | Fix |
|---|---|---|
length |
The reply did not reach its end of turn. | Raise --max-tokens on gen. With --thinking and no budget, the reasoning shares it. |
budget |
The reasoning hit --thinking-budget. |
Raise the budget, or drop --thinking and --thinking-budget. |
empty |
The answer has fewer than --min-words words. |
Use --min-words 1 for short answers. |
marker |
A chat template marker leaked into the reply. | None, the reply is unusable. |
repeat |
Lines or phrases repeat in the reply or its reasoning. | None, the reply is unusable. |
ascii |
Too many non-ASCII characters. Only with --max-non-ascii. |
Raise --max-non-ascii, or leave it off. |
tokens |
The reply is over --max-reply-tokens. |
Size it as the walkthrough shows. |
verify |
Your checker rejected the reply, with its word under detail. |
See below. |
Many verify rejections mean the teacher gets the task wrong even with the
document in view. A teacher that fails most of a task cannot teach it. Try a
larger teacher, or make the questions and the document clearer.
The checker fails¶
filter exits 2 when the --verify command fails. The command must exit 0
and print exactly one line for each row it reads. Run it by hand on a few
rows: head -3 r1-replies.jsonl | ./check-sql.py freight.sqlite.
align warns about the tokenizers¶
align prints [align] warn: when the own-group fraction a is under
0.90 or the singleton fraction s is under 0.50. The two tokenizers split
text differently, and part of the teacher's output has no direct target in
the student. The run still works, with less to learn from.
With a under 0.70, align refuses, writes no view and exits 3. Pick a
student from the teacher's family, or pass --force and expect a weak
result.
align drops rows¶
The failed to render or pair line counts rows that the student's chat
template renders differently from the teacher's, and names the first one.
A template that refuses a turn type, such as a tool turn, drops every row
that has one. Pick a student whose template renders the same turns, or
leave those rows out of the corpus.
The census effect is small¶
The document changes little of what the teacher says on these prompts.
Check that gen ran with --context, that the questions need the
document, and that the second cache used --messages-key student_messages.
The loss does not fall¶
If the loss has not fallen by step 40, stop the run. Check that filter
kept the rows you expected and that align printed a=1.000, or a
warning you accepted.
The validation loss rises¶
A val that rises while loss keeps falling means the adapter memorizes
the training rows. Run again with fewer steps or a lower --lora-rank.
The adapter file holds the last step. The best checkpoint, the one with
the lowest val, stays under --ckpt-dir, but no action turns it into an
adapter.
The pass rate is low¶
A pass rate near zero after training usually has one of three causes:
- The student was served without
--adapter, or with thinking off. - The checker cannot parse the student's replies. Read a few by hand.
- The task is beyond the student. The untouched student with the document pasted in then scores low too, and no adapter fixes that.
When the pass rate is low but not zero, check the census effect first. A small effect caps what any adapter can learn. Then add prompts and phrasings for the kinds that fail, including combined questions, and train with more steps. If the rate stays low, use a larger student.
A step runs out of memory¶
For train, use --batch-size 1 or --grad-checkpoint. Both are slower.
A shorter --max-len on cache makes shorter rows, but drops the replies
that no longer fit.
For cache and gen, try a smaller quantization of the teacher. cache
refuses a dense teacher larger than the memory it can wire. It streams the
experts of a mixture-of-experts teacher from disk when they do not fit, and
gmlx validate says whether a file is one.
The document is in the teacher's prompt on every row. A long document needs
a larger --max-len, and memory grows with it. To split one, give each
prompt row the section it needs in its own context field.
gen cannot start its server¶
gen exits 2 with one of these lines, which do not say why:
[gen] error: server exited with code N before becoming ready
[gen] error: server not ready after 900s
The server writes its own log beside the --out file, named
<out>.server.log, such as r1-replies.jsonl.server.log. The cause is at
its end.
port 8093 already has a listener¶
gen refuses to start while another server holds its port. A gen that
was killed can leave its own server running there, and the message names
the command that stops it.
train or eval refuses its input¶
- A refused view means its cache changed, or the student is not the one
the view was aligned for. Run
alignagain. - A refusal that names fewer train rows than
--batch-sizemeans the corpus is too small for that batch. evalrefuses--reply-positionswhen the census JSON names none of the reply rows. Runcensus --corpuswith the same file the reply slice reads.
Every action's exit codes are in the distillation reference. Troubleshooting covers problems outside this pipeline.