devto 2026-08-08 원문 보기 ↗
The dangerous fine-tuning failures are not the ones that raise an exception. They are the ones where the run completes, the loss curve looks plausible and the model is quietly wrong — and the way to catch those is to know what each failure mode looks like from the outside.
A run that survives a thousand steps and then raises CUDA out of memory did not gradually leak. It met the longest sequence in the dataset. Memory for activations and for the logits tensor scales with sequence length, and the tail of a real length distribution is long.
Diagnostic: plot your token-length histogram and check whether the crash step corresponds to a batch containing the maximum. Sort the dataset by length and run the longest batch first — if it fails immediately, that was it. Fix: cap max_seq_length, use length-grouped batching, reduce the micro-batch and raise gradient accumulation, or use a chunked cross-entropy that never materialises the full fp32 logits. The arithmetic is in the QLoRA memory budget.
A RuntimeError familiar to anyone who has combined gradient checkpointing with a frozen quantised base. The base weights require no gradient, so the checkpointed segment’s inputs do not either, and the recomputation has nothing to build a graph from.
Fix: the standard remedy documented in the PEFT library is to call model.enable_input_require_grads() before training, which makes the embedding outputs require gradients so the checkpointed blocks have a graph. Utility functions that prepare a quantised model for training generally do this for you, which is why the error appears mostly in hand-rolled setups.
Overwhelmingly this is fp16 on a model pretrained in bf16. The two formats have the same width and very different dynamic range; bf16 keeps fp32’s exponent range, fp16 does not, and activations that were unremarkable in training overflow.
Diagnostic: check the base model’s stated training precision, then check what your config set. Fix: bf16. If the hardware cannot do bf16, lower the learning rate, enable gradient clipping, and inspect the batch at the step before the NaN for a degenerate example.
These are the expensive ones. The run completes, the numbers look normal, and nothing is learned.
| Failure | Description |
|---|---|
| Zero trainable parameters | Target module names do not match the architecture, so no adapters were attached. The trainer prints a trainable-parameter count at startup; read it. If it is zero or implausibly small, that is the bug. Loss is flat from step 0 rather than falling and plateauing. |
| Loss exactly 0.0 | Every label is masked to -100. The completion-only masking did not find the assistant turn, usually because the chat template rendered differently than the masking code expected. Decode one label sequence and look at it. |
| Chat template mismatch | Trained with one template, served with another. The adapter has learned to respond to a prompt format that never arrives in production. Symptom: excellent offline evaluation, no effect in the product. Diagnostic: print the fully rendered prompt with special tokens visible in both paths and diff them. |
| Adapter not loaded at inference | Some serving paths accept an adapter path, log nothing when it is missing or misnamed, and serve the base. Diagnostic: pick a prompt where base and fine-tune are known to differ and make it a startup health check. |
| Model never stops generating | The end-of-sequence token was masked out of the loss, or the pad token was set equal to the EOS token and then masked along with the padding. The model was never trained to stop. Diagnostic: check whether the EOS token id appears in the unmasked labels of a rendered example. |
| Evaluating the wrong artefact | Evaluated unmerged, shipped merged into a quantised base, or the reverse. Merging into a quantised base is lossy. Always evaluate the exact file you deploy. |
When a fine-tune disappoints, check in this order. It is ordered by how cheap the check is divided by how often it is the answer, and the first three account for most of it.
The ordering is the point. Almost every disappointing fine-tune is resolved somewhere in the first three items, and almost every team starts at the last one.