Track A โ A Real Fine-Tune with Unsloth
Lesson 5: Track A โ A Real Fine-Tune with Unsloth
Unsloth is an open-source framework for training and running open-weight models. Its selling point is efficiency: it rewrites the attention and MLP kernels so that LoRA and QLoRA training runs substantially faster and in dramatically less VRAM than the standard Hugging Face stack, with no change to your data or your model choice. It also handles the fiddly parts โ chat templates, gradient checkpointing, quantisation โ with sane defaults.
Three ways to use it
| Product | What it is | Pick it ifโฆ |
|---|---|---|
| Unsloth Core | The original Python library. pip install unsloth, then write the training script yourself (or paste a notebook). | You want full control, scriptability, and reproducibility. This is the path this lesson teaches. |
| Unsloth Studio | A local web UI: upload a dataset, pick a model, train, chat, export โ no code. | You want a first result without writing Python, or you want their Data Recipes feature to generate a dataset. |
| Unsloth Desktop | A packaged desktop app for Mac, Windows and Linux. | You want the whole thing as an installed application. |
Hardware reality check, straight from their docs: training works on NVIDIA, AMD, Intel and Apple Silicon (MLX); CUDA compute capability 7.0+ (V100, T4, RTX 20-series and newer) is the floor, and anything from 2018 onward generally works. CPU-only still works for chat and dataset generation. Python must be 3.11โ3.13, and CUDA 12.4+ is recommended (12.8+ for Blackwell).
Install
# Recommended: uv with an explicit Python and automatic torch backend selection
uv venv unsloth_env --python 3.13
source unsloth_env/bin/activate
uv pip install unsloth --torch-backend=auto
# Or plain pip (unsloth pulls in matching torch / transformers / trl versions)
pip install unsloth
# Unsloth and vLLM together (for serving what you train)
uv pip install unsloth vllm --torch-backend=auto
# Linux, from scratch, if you need a venv first
apt install python3.13-venv -y
python -m venv unsloth_env
source unsloth_env/bin/activate
pip install --upgrade pip
pip install uv
uv pip install unsloth --torch-backend=auto
There is also an official Docker image (unsloth/unsloth on Docker Hub) which sidesteps every CUDA/driver version problem in one step.
The six steps of every fine-tune
Everything below is the same shape whether you are on a free Colab T4 or a rented H100. The numbers change; the pipeline does not.
Load a base model in 4-bit
from unsloth import FastLanguageModel
import torch
max_seq_length = 2048 # context length; 2048 is the recommended test value
dtype = None # None = auto; torch.bfloat16 on newer GPUs
load_in_4bit = True # QLoRA. 4x less memory, ~1-2% accuracy cost.
model, tokenizer = FastLanguageModel.from_pretrained(
model_name = "unsloth/llama-3.1-8b-unsloth-bnb-4bit",
max_seq_length = max_seq_length,
dtype = dtype,
load_in_4bit = load_in_4bit,
)
Note the model name. Models ending in unsloth-bnb-4bit are Unsloth's dynamic 4-bit quants โ they use a little more VRAM than standard BitsAndBytes 4-bit but recover meaningfully more accuracy. Names ending in plain bnb-4bit are the ordinary variety, and names with no suffix are the full 16-bit originals. Use the Unsloth one when it exists. There is a full model catalogue and a "which model should I use" guide.
Attach the LoRA adapters
model = FastLanguageModel.get_peft_model(
model,
r = 16, # rank: 8, 16, 32, 64 or 128
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_alpha = 16, # set equal to r, or 2x r
lora_dropout = 0, # 0 is the optimised path
bias = "none", # optimised path
use_gradient_checkpointing = "unsloth", # extra ~30% memory saving
random_state = 3407, # reproducible runs
use_rslora = False, # rank-stabilised LoRA (alpha/sqrt(r))
loftq_config = None,
)
Unsloth's docs are unusually firm about target_modules: adapt all major linear layers, both attention and MLP. Removing modules to save memory costs quality for very little saving โ the RoUGE scores in their guide show all-layers winning over attention-only or MLP-only.
Load your dataset
from datasets import load_dataset
dataset = load_dataset("json", data_files="train.jsonl", split="train")
# Each row: {"messages": [{"role": "user", ...}, {"role": "assistant", ...}]}
Apply the model's chat template
from unsloth.chat_templates import get_chat_template
tokenizer = get_chat_template(tokenizer, chat_template="llama-3.1")
def formatting_prompts_func(examples):
convos = examples["messages"]
texts = [tokenizer.apply_chat_template(c, tokenize=False,
add_generation_prompt=False)
for c in convos]
return {"text": texts}
dataset = dataset.map(formatting_prompts_func, batched=True)
This is the step that silently ruins runs when it is skipped or mismatched (Lesson 4). get_chat_template loads the correct special tokens for the family you chose โ llama-3.1, qwen-2.5, gemma-3, and so on. The chat templates guide lists the supported names.
Train
from trl import SFTTrainer, SFTConfig
trainer = SFTTrainer(
model = model,
tokenizer = tokenizer, # newer TRL builds call this processing_class
train_dataset = dataset,
args = SFTConfig(
dataset_text_field = "text",
max_seq_length = max_seq_length,
per_device_train_batch_size = 2,
gradient_accumulation_steps = 4, # effective batch size = 8
warmup_steps = 5,
max_steps = 60, # quick smoke test; use num_train_epochs for real runs
learning_rate = 2e-4,
logging_steps = 1,
optim = "adamw_8bit",
weight_decay = 0.01,
lr_scheduler_type = "linear",
seed = 3407,
output_dir = "outputs",
report_to = "none",
),
)
trainer.train()
from unsloth.chat_templates import train_on_responses_onlytrainer = train_on_responses_only(trainer, instruction_part="<|start_header_id|>user<|end_header_id|>\n\n", response_part="<|start_header_id|>assistant<|end_header_id|>\n\n")For Gemma models the parts are
"<start_of_turn>user\n" and "<start_of_turn>model\n". Get these strings wrong and you get a confusingly untrained model.
Test, then export
FastLanguageModel.for_inference(model) # Unsloth's 2x faster inference path
inputs = tokenizer([tokenizer.apply_chat_template(
[{"role": "user", "content": "Claim: rear-ended at a stoplight."}],
tokenize=False, add_generation_prompt=True)], return_tensors="pt").to("cuda")
out = model.generate(**inputs, max_new_tokens=256)
print(tokenizer.batch_decode(out)[0])
# Save the adapter (~tens to hundreds of MB)
model.save_pretrained("lora_adapter")
tokenizer.save_pretrained("lora_adapter")
# Or merge into a standalone model / export for local runtimes
model.save_pretrained_merged("merged_model", tokenizer, save_method="merged_16bit")
model.save_pretrained_gguf("gguf_model", tokenizer, quantization_method="q4_k_m")
The Unsloth-specific gotchas worth knowing
| Gotcha | Detail |
|---|---|
tokenizer= vs processing_class= | Recent TRL releases renamed the SFTTrainer argument. If you get an unexpected-keyword or deprecation error, swap to processing_class=tokenizer. |
use_gradient_checkpointing="unsloth" | Not a boolean โ the string is a Unsloth-specific implementation that cuts memory a further ~30% and enables very long context. True works but gives up the extra saving. |
| Training loss below ~0.2 | Per Unsloth's guide, that's the overfitting signal. Their cheap remedy: halve lora_alpha, or equivalently average the base model and the fine-tune's weights 50/50 โ a softer, less pronounced fine-tune. |
| Dynamic vs standard 4-bit | Dynamic (unsloth-bnb-4bit) costs slightly more VRAM and is worth it for accuracy. |
| Unsloth fixes gradient accumulation | They corrected the long-standing bug where b2/g8 and b8/g2 produced different loss curves despite equal effective batch size. In Unsloth, those configurations are now genuinely equivalent. |
| Verify the adapter actually changed | Do not use np.allclose() to check that LoRA weights updated โ it misses the tiny Gaussian values in matrix A. Use a hash/checksum, the sum of absolute differences, or np.array_equal(). |
๐บ Watch:
- How to Fine-tune LLMs with Unsloth: Complete Guide โ pookie (86k views). The full pipeline as written above, run live.
- How to Fine-Tune any AI Model Locally (FULL Tutorial) โ Tech With Tim. Good on installation and environment problems.
- Get Started with Unsloth Studio โ NVIDIA Developer. The no-code path, including dataset generation.
๐ง Knowledge Check
1. What does load_in_4bit = True give you, and what does it cost?
2. Why does Unsloth insist you adapt all seven linear modules rather than just attention?
3. Your LoRA run produced a model that behaves almost like the base model. What's the least likely explanation?