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:

  1. What full fine-tuning, LoRA and QLoRA change, and what they need.
  2. How to estimate GPU memory for each one.
  3. When preference tuning (DPO) and reinforcement with a checker (GRPO-style) help.
  4. When distillation is the right method.
  5. 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?

MethodWhat it changesData it needsUse it when
Full fine-tuneEvery weightInput and answer pairsRarely: LoRA was measured and fell short
LoRAA small adapterInput and answer pairsThe default for a behaviour gap
QLoRAA small adapter, 4-bit baseInput and answer pairsLoRA on a smaller GPU
DPOUsually an adapter, after SFTBetter and worse answer pairsTone or style that is easier to compare than to write
GRPO-styleUsually an adapter, after SFTInputs and a checker programAnswers a program can verify
DistillationA whole small modelInputs labelled by a big teacherQuality 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.

CheckWhy it matters
LicenceSome licences limit commercial use, user numbers or training on outputs
SizeBigger 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 lengthYour longest prompt plus answer must fit
Tokenizer and languagesA tokenizer that splits your language into many tokens is slower and weaker
Instruct or baseAn instruction-tuned model already follows a chat template; usually the right start
Where it can runYour GPUs, a serving tool you use, or a provider that hosts fine-tunes
How long it will be supportedA 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

MistakeWrong ideaCorrect ideaWhat 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.