Fine-Tuning & PEFT Series — Presentation 01

The Supervised Fine-Tuning Pipeline

SFT datasets, chat templates, loss masking, sample packing, Axolotl / TRL / Unsloth / torchtune — everything between a base model and a well-behaved assistant.

SFT Chat Templates Loss Masking Sample Packing Axolotl TRL Unsloth torchtune
Dataset → Tokenise → Mask → Pack → Train → Eval → Merge
00

Topics We'll Cover

01

Why Fine-Tune — Capability vs Style; Alternatives

Supervised fine-tuning (SFT) updates a pre-trained base model's weights on a labelled dataset of (input, desired-output) pairs. It is the first training stage that turns a next-token predictor into something that behaves like an assistant. Understanding when to fine-tune versus reaching for a cheaper tool is the first decision in any LLM deployment.

Fine-Tune When…

  • The model needs a new capability it lacks entirely — e.g. structured JSON output, domain vocabulary, code in an obscure language
  • You need consistent style (tone, format, persona) that is too verbose to repeat in every prompt
  • Latency or cost constraints mean short prompts are mandatory
  • The task requires domain knowledge denser than the context window can carry

Consider Alternatives When…

  • RAG: knowledge is dynamic, large, or must stay auditable — grounding in retrieved documents is cheaper than baking facts into weights
  • Prompting + few-shot: 3–10 demonstrations often suffice for format tasks; GPT-4o and Claude 3.5 are strong enough that style can be prompted
  • Distillation: you want a smaller model to match a larger teacher — SFT from teacher-generated synthetic data, then prune or quantise
  • Prompt caching: long system prompts are expensive but cacheable; check the maths before training
Rule of thumb

If a 2 000-token system prompt plus 5 few-shot examples achieves 90 % of the target quality, ship the prompt. Fine-tune when you need the remaining 10 %, or when the prompt would need to be embedded in millions of calls per day and cost becomes prohibitive.

02

Datasets — Instruction, Conversation, Format Converters

Dataset quality dominates SFT outcomes far more than model size or training duration. The field has converged on two structural formats.

FormatShapeCanonical examplesTypical size (deduplicated)
Alpaca / instruction{"instruction":..., "input":..., "output":...}Alpaca (52 k), Dolly-15k, OpenHermes-2.5, SlimOrca10 k–1 M rows
ShareGPT / conversation{"conversations":[{"from":"human","value":...},{"from":"gpt","value":...}]}ShareGPT90k, WizardLM-Evol, Capybara50 k–500 k turns
OpenAI messages{"messages":[{"role":"system",...},{"role":"user",...},{"role":"assistant",...}]}Tulu-3-SFT, Orca-3300 k–1 M

Format Converters

Most frameworks accept multiple formats and convert internally. Axolotl's datasets: block takes a type: key (alpaca, sharegpt, completion, context_qa, …) and handles the mapping. TRL's DataCollatorForCompletionOnlyLM works on the tokenised stream after template application.

Data quality checklist

Deduplication: MinHash + LSH at character n-gram level — Slimpajama found ~30 % overlap in raw C4. Length filtering: discard <10 tokens (label noise) and >8 192 tokens unless you intend long-context training. Language filtering: fastText lid.176 at threshold 0.65. PII scrubbing: regex + NER for emails, phone numbers, SSNs — especially if dataset comes from web crawls. Perplexity-based quality: score with a small KenLM or GPT-2 model; discard top-5 % outliers.

03

Tokenisation & Chat Templates (ChatML, Llama-3, Qwen)

A chat template is the byte-level contract that maps structured conversation turns into a flat token sequence the model was trained on. Using the wrong template is silent — training completes, but quality collapses at inference because the model sees token patterns it was never optimised against.

ChatML (OpenAI, Mistral models) — literal token strings
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
What is gradient checkpointing?<|im_end|>
<|im_start|>assistant
Gradient checkpointing trades compute for memory ...<|im_end|>
Llama-3 (Meta, 2024) — BOS + header tokens
<|begin_of_text|><|start_header_id|>system<|end_header_id|>
You are a helpful assistant.<|eot_id|>
<|start_header_id|>user<|end_header_id|>
What is gradient checkpointing?<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
Gradient checkpointing trades compute for memory ...<|eot_id|>
Qwen-2.5 (Alibaba) — close to ChatML with different special token IDs
<|im_start|>system\n
You are Qwen, created by Alibaba Cloud.<|im_end|>\n
<|im_start|>user\n
What is gradient checkpointing?<|im_end|>\n
<|im_start|>assistant\n
Gradient checkpointing trades compute for memory ...<|im_end|>\n
HuggingFace tokenizer.apply_chat_template

From transformers≥4.34 every tokeniser ships a Jinja2 template in tokenizer_config.json under the chat_template key. Call tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) and you get the correctly formatted string. Never hand-format — template bugs are the single largest source of SFT regression.

04

Loss Masking — Train Only on Assistant Tokens

In a standard causal-LM training loop the cross-entropy loss is computed over every token in the sequence. For SFT you almost always want to mask out the prompt tokens so that the model only learns to predict assistant responses. Computing loss on human turns is wasteful at best and actively harmful (the model learns to generate user-side content) at worst.

<|sys|> You are helpful <|usr|> What is SFT? <|asst|> SFT is fine -tuning <|eot|> label = −100 (masked, no gradient) label = token id (loss computed here)

Implementation in TRL

trl ≥0.8 — SFTTrainer with response_template masking
from trl import SFTTrainer, DataCollatorForCompletionOnlyLM

# ChatML assistant header tokens
response_template = "<|im_start|>assistant\n"
collator = DataCollatorForCompletionOnlyLM(
    response_template=response_template,
    tokenizer=tokenizer,
    mlm=False,
)

trainer = SFTTrainer(
    model=model,
    args=training_args,
    train_dataset=dataset,
    data_collator=collator,   # ← this is all you need
)
Axolotl equivalent

In Axolotl's YAML config, set train_on_inputs: false. Axolotl detects the template automatically from the model's tokeniser config and masks prompt tokens before packing. Setting this to true trains on all tokens — useful only for completion-style datasets with no user/assistant role distinction.

05

Sample Packing — More Tokens Per Step

Naively, each batch entry is one conversation padded to the maximum sequence length. On typical instruction datasets where most examples are 256–512 tokens, padding a 2 048-token context wastes 75–87 % of the compute budget on <pad> tokens that produce zero gradient signal.

Sample packing (also called bin packing) concatenates multiple short samples end-to-end within a single sequence until the context length is filled, with attention masking to prevent cross-contamination between packed items.

Ex 1 (320 tok)
+
Ex 2 (410 tok)
+
Ex 3 (280 tok)
+
Ex 4 (380 tok)
→
1 390 / 2 048 tokens (68 %)

Attention Masking for Packing

Without correct masking, token N in example 2 attends to tokens from example 1, causing cross-contamination. The fix is a block-diagonal attention mask: each packed sub-sequence attends only to itself. Flash Attention 2 (flash_attn≥2.1) supports variable-length sequences natively via cu_seqlens — this is the most compute-efficient path.

Config keyAxolotlTRL SFTTrainertorchtune
Enable packingsample_packing: truepacking=Truepacked: true in dataset config
Cross-attn preventionauto via Flash Attn 2manual DataCollatorForSeq2Seqbuilt-in cu_seqlens path
Typical throughput gain2–4× on short datasets2–3×2–4×
When not to pack

If your dataset has very long examples (>3 000 tokens on average) packing offers diminishing returns and the bookkeeping overhead can cost more than the padding it saves. Evaluate effective tokens-per-second with and without packing on your actual dataset before committing.

06

Frameworks — Axolotl, TRL, Unsloth, torchtune

Four frameworks dominate open-source SFT. Each makes different tradeoffs between configurability, speed, and ease of use.

Axolotl OpenAccess-AI-Collective

Config-file driven. One config.yaml specifies everything: base model, dataset type, template, LoRA rank, scheduler, sample packing, Flash Attn, DeepSpeed/FSDP. Preferred for production pipelines and multi-dataset merging. Supports deepspeed_zero3, gradient checkpointing, 4-bit QLoRA out of the box. axolotl train config.yaml → HF Hub push.

TRL SFTTrainer HuggingFace

Python-API driven. Extends transformers.Trainer. Integrates with peft for LoRA, bitsandbytes for QLoRA, and the full HF ecosystem. SFTTrainer + DataCollatorForCompletionOnlyLM covers 90 % of SFT tasks. TRL ≥0.10 added GRPOTrainer, DPOTrainer, PPOTrainer — all in one package.

Unsloth unslothai

Speed-optimised. Hand-written Triton kernels for attention, RoPE, and cross-entropy. Claims 2× throughput vs native HF on single-GPU consumer hardware (A100 40 GB, 3090, 4090). Drop-in replacement: from unsloth import FastLanguageModel. Supports Llama 3.x, Mistral, Gemma, Phi-3, Qwen-2.5. Free tier covers most consumer GPUs; Pro tier for multi-GPU.

torchtune PyTorch

Framework-native. Pure PyTorch, no HF Trainer. Ships ready-made recipes: full_finetune_single_device, lora_finetune_distributed. Config via YAML. Designed for reproducibility and easy recipe modification. Excellent for research baselines where you want full control over the training loop. Added Llama-3.1 405B support and QAT (Quantisation-Aware Training) in torchtune 0.4.

Which to choose

For quick experiments on a single GPU: Unsloth (fastest iteration). For production pipelines with complex dataset mixes: Axolotl. For HF-ecosystem integration (PEFT, evaluate, trl reward training): TRL. For research with custom training loops: torchtune.

07

Evaluating SFT Runs — Perplexity, Eval Suites

Training loss falling does not mean the model is getting better at the task you care about. A comprehensive evaluation strategy triangulates across three axes: intrinsic metrics, downstream benchmarks, and human / model preference ratings.

MetricWhat it measuresToolingCaveats
Eval loss / PPLGeneralisation to held-out splits of the training distributionHF Trainer eval_datasetOnly valid if eval set is drawn from same distribution; doesn't detect mode collapse
MT-BenchMulti-turn instruction following, scored by GPT-4 on 8 categoriesfastchat eval harnessLLM-as-judge bias; GPT-4 scores can drift between API versions
AlpacaEval 2.0Length-controlled win rate vs GPT-4 Turbo on 805 promptsalpaca_eval libraryMeasures verbosity preference; reward-hacked by longer outputs
IFEvalStrict instruction-following on programmatically verifiable constraints (word count, JSON, format)lm-evaluation-harnessCovers only verifiable format constraints, not semantic quality
MMLU / BBHKnowledge retention after fine-tuninglm-evaluation-harnessWatch for catastrophic forgetting of base capabilities
The forgetting trap

SFT on a narrow domain dataset commonly degrades performance on unrelated benchmarks. Run MMLU and BBH before and after every SFT run. If accuracy drops >2 % absolute, consider reducing learning rate, shortening training, or mixing in general-domain data. The typical culprit is a too-high learning rate (3e-5 for 7B models; 1e-5 for 70B+ models) or too many epochs on a small dataset.

08

Hardware Reality — VRAM Math, Throughput, Multi-GPU

VRAM is the hard constraint. Before writing a single config line, compute the memory budget:

VRAM formula for full-precision SFT (bytes)
# Per-parameter costs for bf16 + full-precision optimiser state (AdamW)
# 2 bytes (bf16 param) + 4 bytes (fp32 copy) + 8 bytes (Adam m + v) = 14 bytes/param
# Activations = batch_size * seq_len * hidden_dim * num_layers * ~2 bytes

params_7b = 7e9
base_vram  = params_7b * 14   # ≈ 98 GB — needs 2×A100 80 GB minimum

# With gradient checkpointing (store only layer boundaries):
# activation VRAM drops ~8x, adds ~30% compute overhead

# QLoRA (4-bit base + bf16 LoRA adapters):
qlora_vram = params_7b * 0.5 + 0.01 * params_7b * 14  # ≈ 4.5 GB + 1 GB adapters
Model sizeFull SFT bf16QLoRA 4-bitPractical GPU
7B~98 GB~10 GB1× A100 40 GB (QLoRA) or 1× 3090/4090 (QLoRA)
13B~182 GB~16 GB1× A100 40 GB (QLoRA) or 2× A100 (full)
70B~980 GB~48 GB1× A100 80 GB (QLoRA) or 4× H100 (full)
405B~5.7 TB~200 GB8× H100 minimum (full); 4× H100 (QLoRA + pipeline)
Multi-GPU strategies

FSDP (FullyShardedDataParallel): shards parameters, gradients, and optimiser states across GPUs — linear VRAM scaling, requires fast NVLink. DeepSpeed ZeRO-3: similar sharding but with optional CPU offload for optimiser states. Tensor parallelism: splits individual layers across GPUs — needed for 70B+ full SFT. Axolotl supports all three via config; torchtune ships FSDP2 recipes natively.

09

What to Take Away

Where to next

Deck 02 covers LoRA and its variants — the adapter techniques that make SFT practical on consumer hardware without touching the base weights. Deck 03 shows what SFT alone can't fix and why RLHF and PPO were invented.