Choosing the Method: LoRA, QLoRA, Full, Preference Tuning
By Syncfusion®, Inc. · Last updated
Full fine-tuning, LoRA, QLoRA, DPO, GRPO-style training and distillation: what each changes, the data and GPU memory it needs, and sensible starting settings.
Choosing the Method: LoRA, QLoRA, Full, Preference Tuning
The JSON ticket team has clean data and a plan. They open a fine-tuning tool, and it asks: "Full fine-tune, LoRA, or QLoRA? SFT, DPO, or something else?"
Each option changes different things, needs different data, and needs a different amount of GPU memory. Start with the memory, because it decides what you can run at all.
A full fine-tune of an 8B model needs about 136 GB. LoRA needs about 26 GB. QLoRA needs about 10 GB, small enough for one consumer GPU.
Simple lesson: pick the lightest method that can do the job, then prove it with the eval.
A Real-World Example
Think about changing how a shop looks.
A full fine-tune is rebuilding the shop. A LoRA adapter is new signs and shelves on top of the same building. QLoRA is the same new signs, but you packed the building's furniture more tightly to fit a smaller van.
Preference tuning is asking customers "which of these two window displays do you like more?" many times, and learning from their choices. Distillation is training a new, smaller shop assistant by having them watch your best one.
Simple lesson: most of the time, new signs and shelves are enough.
What You Will Learn
In this lesson, you will learn:
- What full fine-tuning, LoRA and QLoRA change, and what they need.
- How to estimate GPU memory for each one.
- When preference tuning (DPO) and reinforcement with a checker (GRPO-style) help.
- When distillation is the right method.
- Sensible starting settings in the common tools.
Before you start
- This is the first lesson of Train and Operate. It assumes the data from the Decide and Prepare lessons.
- Memory numbers here are rules of thumb, not exact. Real use depends on sequence length, batch size and the tool.
- Tool names and settings come from each tool's docs. Check them before you run anything; they change often.
TL;DR
SFT (supervised fine-tuning) teaches from input and answer pairs. It is the first step for almost every fine-tune.
LoRA trains a small adapter on a frozen base. QLoRA does the same with the base stored in 4-bit, using much less memory.
DPO learns from pairs of better and worse answers, after SFT. GRPO-style training learns from a program that checks answers.
Distillation trains a small model on a big model's answers, to cut cost and speed things up.
Memory: the First Filter
Bits, bytes and 4-bit models
- Each weight is a number stored in memory. In 16-bit form each weight takes 2 bytes. In 4-bit form it takes half a byte.
- A model with 8 billion weights at 2 bytes is about 16 GB, just to hold it.
- Training needs more than holding: the optimiser keeps extra numbers for every weight it changes.
Rough GPU memory per method
// Rough GPU memory to fine-tune a model, in GB.
// bytes per weight: full fine-tune ~16 (weights, gradients, optimiser state),
// LoRA ~2 (16-bit frozen base), QLoRA ~0.5 (4-bit frozen base).
const methods = [
{ name: "Full fine-tune", bytesPerWeight: 16, extraGb: 8 },
{ name: "LoRA", bytesPerWeight: 2, extraGb: 10 },
{ name: "QLoRA", bytesPerWeight: 0.5, extraGb: 6 },
];
for (const billions of [1, 8]) {
for (const m of methods) {
const gb = Math.round(billions * m.bytesPerWeight + m.extraGb);
const fits = gb <= 24 ? "fits a 24 GB GPU" : gb <= 80 ? "needs an 80 GB GPU" : "needs several GPUs";
console.log(`${billions}B ${m.name.padEnd(14)} ~${gb} GB ${fits}`);
}
}
Rough GPU memory per method
// Rough GPU memory to fine-tune a model, in GB.
// bytes per weight: full fine-tune ~16 (weights, gradients, optimiser state),
// LoRA ~2 (16-bit frozen base), QLoRA ~0.5 (4-bit frozen base).
var methods = new[]
{
new { name = "Full fine-tune", bytesPerWeight = 16.0, extraGb = 8 },
new { name = "LoRA", bytesPerWeight = 2.0, extraGb = 10 },
new { name = "QLoRA", bytesPerWeight = 0.5, extraGb = 6 },
};
foreach (var billions in new[] { 1, 8 })
{
foreach (var m in methods)
{
// AwayFromZero rounds 6.5 up to 7, like JavaScript's Math.round.
var gb = Math.Round(billions * m.bytesPerWeight + m.extraGb, MidpointRounding.AwayFromZero);
var fits = gb <= 24 ? "fits a 24 GB GPU" : gb <= 80 ? "needs an 80 GB GPU" : "needs several GPUs";
Console.WriteLine($"{billions}B {m.name.PadRight(14)} ~{gb} GB {fits}");
}
}
Expected output:
1B Full fine-tune ~24 GB fits a 24 GB GPU
1B LoRA ~12 GB fits a 24 GB GPU
1B QLoRA ~7 GB fits a 24 GB GPU
8B Full fine-tune ~136 GB needs several GPUs
8B LoRA ~26 GB needs an 80 GB GPU
8B QLoRA ~10 GB fits a 24 GB GPU
For a 1B model every method fits on one consumer GPU. For an 8B model only QLoRA does.
Which Method for Which Job?
| Method | What it changes | Data it needs | Use it when |
|---|---|---|---|
| Full fine-tune | Every weight | Input and answer pairs | Rarely: LoRA was measured and fell short |
| LoRA | A small adapter | Input and answer pairs | The default for a behaviour gap |
| QLoRA | A small adapter, 4-bit base | Input and answer pairs | LoRA on a smaller GPU |
| DPO | Usually an adapter, after SFT | Better and worse answer pairs | Tone or style that is easier to compare than to write |
| GRPO-style | Usually an adapter, after SFT | Inputs and a checker program | Answers a program can verify |
| Distillation | A whole small model | Inputs labelled by a big teacher | Quality is fine, cost or speed is not |
Preference pair = two answers to the same input, marked "better" and "worse".
Checker = code that scores an answer, like "the JSON parses and the order number exists".
A first-pass method picker
type Need = { gap: "format" | "taste" | "checkable" | "volume"; gpuGb: number };
// A first-pass method picker. Always confirm with an eval.
function pick(n: Need): string {
if (n.gap === "volume") return "distillation (small student, big teacher)";
if (n.gap === "taste") return "SFT first, then DPO on better/worse pairs";
if (n.gap === "checkable") return "SFT first, then GRPO-style with a checker";
return n.gpuGb >= 32 ? "LoRA" : "QLoRA";
}
const cases: Need[] = [
{ gap: "format", gpuGb: 24 },
{ gap: "format", gpuGb: 80 },
{ gap: "taste", gpuGb: 80 },
{ gap: "checkable", gpuGb: 80 },
{ gap: "volume", gpuGb: 80 },
];
for (const c of cases) console.log(`${c.gap.padEnd(9)} ${String(c.gpuGb).padStart(2)} GB -> ${pick(c)}`);
A first-pass method picker
// A first-pass method picker. Always confirm with an eval.
string Pick(Need n)
{
if (n.Gap == "volume") return "distillation (small student, big teacher)";
if (n.Gap == "taste") return "SFT first, then DPO on better/worse pairs";
if (n.Gap == "checkable") return "SFT first, then GRPO-style with a checker";
return n.GpuGb >= 32 ? "LoRA" : "QLoRA";
}
var cases = new List<Need>
{
new("format", 24),
new("format", 80),
new("taste", 80),
new("checkable", 80),
new("volume", 80),
};
foreach (var c in cases) Console.WriteLine($"{c.Gap.PadRight(9)} {c.GpuGb,2} GB -> {Pick(c)}");
// Gap is one of: "format", "taste", "checkable", "volume"
record Need(string Gap, int GpuGb);
Expected output:
format 24 GB -> QLoRA
format 80 GB -> LoRA
taste 80 GB -> SFT first, then DPO on better/worse pairs
checkable 80 GB -> SFT first, then GRPO-style with a checker
volume 80 GB -> distillation (small student, big teacher)
SFT first, then the fancy methods.
DPO and GRPO-style training improve a model that already does the task. They start from an SFT adapter. If SFT is not working, they will not rescue it: fix the data first.
Choosing the Base Model
The method is only half the choice. You also pick the base model the adapter sits on.
| Check | Why it matters |
|---|---|
| Licence | Some licences limit commercial use, user numbers or training on outputs |
| Size | Bigger is usually better and always slower and dearer. Start small: 1-3B for format tasks, 7-8B if the eval says you need it |
| Context length | Your longest prompt plus answer must fit |
| Tokenizer and languages | A tokenizer that splits your language into many tokens is slower and weaker |
| Instruct or base | An instruction-tuned model already follows a chat template; usually the right start |
| Where it can run | Your GPUs, a serving tool you use, or a provider that hosts fine-tunes |
| How long it will be supported | A retired base forces a re-train (Serving and Maintaining lesson) |
Then run the same eval on two or three candidates with your best prompt, before any training. The smallest one that is close is often the right base.
Memory also grows with sequence length and batch size, not only model size:
Memory with sequence length and batch size
// Memory also grows with sequence length and batch size (activations).
// Rough rule for a 7-8B model with gradient checkpointing: about
// 0.5 MB per token in the batch, on top of the weights.
const weightsGb = { lora: 16, qlora: 4 }; // 8B model: 16-bit vs 4-bit base
const mbPerToken = 0.5;
for (const seq of [512, 2048, 8192]) {
for (const batch of [1, 4]) {
const act = (seq * batch * mbPerToken) / 1024;
console.log(`seq ${String(seq).padStart(4)} batch ${batch}: LoRA ~${Math.round(weightsGb.lora + 2 + act)} GB, QLoRA ~${Math.round(weightsGb.qlora + 2 + act)} GB`);
}
}
Memory with sequence length and batch size
// Memory also grows with sequence length and batch size (activations).
// Rough rule for a 7-8B model with gradient checkpointing: about
// 0.5 MB per token in the batch, on top of the weights.
var weightsGb = new { lora = 16, qlora = 4 }; // 8B model: 16-bit vs 4-bit base
var mbPerToken = 0.5;
double JsRound(double x) => Math.Round(x, MidpointRounding.AwayFromZero);
foreach (var seq in new[] { 512, 2048, 8192 })
{
foreach (var batch in new[] { 1, 4 })
{
var act = (seq * batch * mbPerToken) / 1024;
Console.WriteLine($"seq {seq,4} batch {batch}: LoRA ~{JsRound(weightsGb.lora + 2 + act)} GB, QLoRA ~{JsRound(weightsGb.qlora + 2 + act)} GB");
}
}
Expected output:
seq 512 batch 1: LoRA ~18 GB, QLoRA ~6 GB
seq 512 batch 4: LoRA ~19 GB, QLoRA ~7 GB
seq 2048 batch 1: LoRA ~19 GB, QLoRA ~7 GB
seq 2048 batch 4: LoRA ~22 GB, QLoRA ~10 GB
seq 8192 batch 1: LoRA ~22 GB, QLoRA ~10 GB
seq 8192 batch 4: LoRA ~34 GB, QLoRA ~22 GB
Starting Settings
The same starting point works in most tools:
- LoRA rank 16, alpha 32 (alpha is a scaling number, often twice the rank).
- Learning rate 2e-4, which is 0.0002.
- 3 epochs, with a validation check every 50 steps.
- Keep the best checkpoint, not the last one.
Here are those settings in four common tools.
Simple lesson: start from the defaults, change one thing at a time, and let the validation line decide.
Out of scope for this course
- Pre-training a model from scratch: months of compute on huge datasets. Fine-tuning starts from a model someone else pre-trained.
- Large-scale RLHF with human raters and a reward model: DPO covers the common product case more simply.
- Multi-node training across many GPU machines: needed for full fine-tunes of big models, rarely for LoRA.
What to ask about the method
- Why this method? "LoRA, because the gap is behaviour" is a good answer. "Full, because it is best" is not.
- What hardware, for how long? A QLoRA run on one GPU for an hour is cheap. A full fine-tune on many GPUs is not.
- Are we allowed to fine-tune this base model? Check its licence before any work starts.
- Which base models were compared, on the same eval, before choosing?
- For DPO: where do the preference pairs come from, and who checks them?
Do's and Don'ts
- Do start with SFT and LoRA or QLoRA.
- Do estimate memory before you rent hardware.
- Do use the tool's defaults first, then change one setting at a time.
- Do check the base model's licence.
- Don't start with a full fine-tune.
- Don't reach for DPO or GRPO before SFT works.
- Don't copy settings from a blog without your own validation line.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Full fine-tune by default | "More weights changed, better result." | Start with LoRA. Go full only with evidence. | Weeks of GPU time for the same score. |
| Ignoring memory | "We will find a GPU." | Estimate memory per method first. | The run crashes with out-of-memory, or the bill surprises you. |
| DPO without SFT | "Preference tuning will teach the format." | SFT first. DPO polishes a model that already works. | The model never learns the basic task. |
| Copying settings blindly | "This learning rate worked for someone." | Use defaults, watch validation, adjust. | A run that looks fine on paper overfits or explodes. |
| No licence check | "It is open, so we can use it." | Read the base model's licence. | A shipped product you are not allowed to sell. |
Check Your Understanding
Exercises
What to Remember
- SFT first, with LoRA, or QLoRA on a smaller GPU.
- Estimate memory before you rent anything.
- DPO learns from better/worse pairs; GRPO-style from a checker; both after SFT.
- Distillation trades a little quality for a lot of cost and speed.
- Start with defaults, keep the best checkpoint, and check the licence.
Next: Chat Templates, Masking and Stop Tokens, the formatting bugs that break real fine-tunes without showing up in the loss.