LLM fine-tuning / LLaMA-Factory in practice

LLaMA-Factory QLoRA Tutorial: Fine-Tuning Qwen2.5-7B on PubMedQA

Many LLaMA-Factory tutorials stop at installation, the WebUI and the training command, with no real evaluation. This page runs QLoRA end to end on one 24GB GPU: data split, configuration, measured VRAM, three random seeds, paired evaluation, and a config mistake that quietly turns QLoRA into plain LoRA.

The data on this page come from one complete Scientify run: it rented an A10G GPU, ran the baseline, three fine-tuning seeds and the evaluation in about 2 hours 40 minutes, and passed an independent review. See the run record

Short answer

To run QLoRA in LLaMA-Factory, set finetuning_type: lora, quantization_bit: 4 and quantization_method: bnb in the YAML, then start llamafactory-cli train. quantization_method must be bnb; writing bitsandbytes raises no error but skips 4-bit quantization. Here correct QLoRA on Qwen2.5-7B peaked at about 7.25 GiB VRAM, each seed trained in about 17 minutes, and PubMedQA accuracy rose from 42.2% to 76.8%.

About this page

Tutorial
What this page solves
You want to fine-tune a 7B model on one consumer or cloud GPU but are unsure how to write the QLoRA config, how much VRAM you need, and how to tell whether fine-tuning actually helped.
Who it is for
Graduate students and engineers fine-tuning an LLM for the first time. You should know the Linux command line and Python and the difference between train, validation and test sets.
What you will have
A ready-to-use QLoRA config, an evaluation method for judging fine-tuning, and complete measured results for a 7B model on PubMedQA.
Covers
  • LLaMA-Factory data format and dataset_info.json registration
  • Every QLoRA setting explained (rank, alpha, learning rate, gradient accumulation and more)
  • Measured VRAM peaks and GPU choice
  • How to confirm 4-bit quantization is really active
  • Zero-shot baseline, paired bootstrap intervals and multiple seeds
  • Per-class metrics and how to read a class that degrades after fine-tuning
Does not cover
  • Full fine-tuning and multi-GPU training
  • Preference alignment such as DPO or RLHF
  • Merging, exporting and deploying the model

Main path: step by step

Setup

Why this stepInstall LLaMA-Factory and confirm the command works.

The WebUI is good for learning the settings. For experiments you need to reproduce and record, a YAML config with the command line is easier to manage; this page uses the command line throughout.

If PyTorch is already installed on the server, create a new virtual environment that reuses it and install the other dependencies within the ranges LLaMA-Factory declares, without replacing the CUDA stack.

Install from source (official README, Python ≥ 3.11)bash
git clone --depth 1 https://github.com/hiyouga/LlamaFactory.git
cd LlamaFactory
pip install -e .
pip install -r requirements/metrics.txt
# QLoRA also needs bitsandbytes
pip install bitsandbytes
Launch the WebUI or train from YAMLbash
llamafactory-cli webui
llamafactory-cli train train.yaml

Step 1

Why this stepTraining data must be in a format LLaMA-Factory reads and registered in dataset_info.json.

The expert-labeled PubMedQA set (PQA-L) has 1,000 questions. Each gives a biomedical research question and an abstract with its conclusion removed, and the answer is yes, no or maybe. This example uses the official script to split 450 train, 50 dev and 500 test, with no overlap between sets.

Train and dev data become JSON with instruction / input / output fields: instruction holds the question and abstract, output holds only the answer. The test set is not registered in dataset_info.json, so the training pipeline cannot read it.

One training example (Alpaca format)json
{
  "instruction": "Question: ...\nCONTEXTS: ...\nAnswer with yes, no, or maybe.",
  "input": "",
  "output": "yes"
}
data/dataset_info.jsonjson
{
  "pubmedqa_train": { "file_name": "train.json" },
  "pubmedqa_dev":   { "file_name": "dev.json" }
}
SplitQuestionsyesnomaybe
Test50027616955
Classes are imbalanced: answering yes every time scores 55.2%, the minimum reference for whether a model has learned anything.

Step 2

Why this stepPick the GPU by peak VRAM: too small causes OOM, too large wastes money.

The estimate was 14 GB VRAM, 24 GB RAM, 4 CPU cores and 60 GB disk. With a 1.25 margin this selected an AWS g5.2xlarge (A10G 24 GB, 8 cores, 32 GB RAM). 14 GB × 1.25 = 17.5 GB exceeds a T4's 16 GB, and a T4 does not support bf16, so it would need fp16 instead.

The model weights are about 15 GB and downloaded in 140 seconds. The base model and Python environment stay on the GPU server; only adapters, predictions and logs are synced back.

RunMeasured VRAM peakNote
QLoRA (4-bit NF4) training7.25 GiB allocated / 8.55 GiB reserved (PyTorch)nvidia-smi sampled maximum 9,213 MiB
Zero-shot inference (4-bit)6.59 GiBbatch 4
Accidental BF16 LoRA trainingabout 22 GiB (22,587 MiB)Actual use when quantization was not active
Qwen2.5-7B-Instruct, max sequence length 2048, batch 1 with gradient accumulation 16, gradient checkpointing on.

Step 3

Why this stepWithout a baseline under the same protocol, you cannot say how much fine-tuning helped.

The baseline and the fine-tuned model use exactly the same protocol: the same Qwen chat template, the same prompt, 4-bit NF4 loading, greedy decoding and at most 8 new tokens. Output must match yes, no or maybe exactly (ignoring case and a trailing period); anything else is invalid and counts as wrong. Answers are never extracted from explanations.

Baseline inference on 500 test questions took about 3 minutes and scored 42.2%, below the 55.2% of always answering yes. The base model answers maybe too often: 117 of 276 yes questions and 136 of 169 no questions were answered maybe.

Step 4

Why this stepRecording each value and the reason for it is what makes reproduction and tuning possible.

LLaMA-Factory implements LoRA through the PEFT library: the base model is frozen and only low-rank matrices A and B on selected linear layers are trained. QLoRA additionally loads the frozen base weights in 4-bit with bitsandbytes. The main fields of the final config are below.

train.yaml (main fields of the final config)yaml
model_name_or_path: /path/to/Qwen2.5-7B-Instruct
stage: sft
do_train: true
finetuning_type: lora
lora_target: all
lora_rank: 16
lora_alpha: 32
lora_dropout: 0.05
quantization_bit: 4
quantization_method: bnb      # must be bnb
quantization_type: nf4
double_quantization: true
dataset: pubmedqa_train
eval_dataset: pubmedqa_dev
template: qwen
cutoff_len: 2048
train_on_prompt: false
per_device_train_batch_size: 1
gradient_accumulation_steps: 16
learning_rate: 1.0e-4
num_train_epochs: 3
lr_scheduler_type: cosine
warmup_ratio: 0.05
bf16: true
gradient_checkpointing: true
optim: paged_adamw_8bit
eval_strategy: epoch
save_strategy: epoch
load_best_model_at_end: true
metric_for_best_model: eval_loss
Start trainingbash
llamafactory-cli train train.yaml
FieldValuePurpose
lora_targetallAdd LoRA to every linear layer
lora_rank / lora_alpha16 / 32Low-rank dimension and scaling
quantization_bit / method4 / bnbLoad and freeze base weights in 4-bit
quantization_type / double_quantizationnf4 / trueNF4 quantization with compressed scales
batch × accumulation1 × 16Effective batch 16 with minimal VRAM
learning_rate / epochs1e-4 / 3A limited budget for a small dataset
train_on_promptfalseCompute loss only on the answer
load_best_model_at_endtrue, by eval_lossReload the checkpoint with the lowest dev loss
29 optimizer steps per epoch, 87 over 3 epochs. The three seeds change only seed, data_seed and output directory.

Step 5

Why this stepWriting 4-bit in the config does not mean the model is loaded in 4-bit.

The first config used quantization_method: bitsandbytes. The LLaMA-Factory version in use only recognized bnb, and the wrong value produced no error or warning; quantization was simply skipped. The first two training runs were ordinary BF16 LoRA at about 22 GiB.

The problem surfaced in a runtime check before the third seed: is_loaded_in_4bit was False and there were no Linear4bit layers. After changing to bnb, assertions at training start confirmed is_loaded_in_4bit is True, the quant type is NF4, double quantization is on and Linear4bit layers exist, and all three seeds were retrained from scratch. The wrong runs were archived intact and reported separately.

Quantization check at training startpython
import bitsandbytes as bnb

assert getattr(model, "is_loaded_in_4bit", False), "model is not 4-bit"
qc = model.config.quantization_config
assert qc.bnb_4bit_quant_type == "nf4" and qc.bnb_4bit_use_double_quant
n4 = sum(isinstance(m, bnb.nn.Linear4bit) for m in model.modules())
assert n4 > 0, "no Linear4bit layers"
print("Linear4bit layers:", n4)
Scientify workspace where the agent reports that the corrected run uses real QLoRA with a VRAM peak near 7.2 GiB
Confirmation after the fix (UI in Chinese): 4-bit linear layers present, NF4 and double quantization on, VRAM peak about 7.2 GiB.

Step 6

Why this stepOn a small dataset training loss drops fast and says little about generalization.

All three seeds have their lowest dev loss after epoch 1. In epochs 2 and 3 training loss keeps falling while dev loss rises, a sign of overfitting on a small sample. load_best_model_at_end reloads the step-29 checkpoint automatically.

The dev set has only 50 questions, 6 of them maybe, so checkpoint selection is itself noisy. Each seed's 87 steps took about 17 minutes.

Training and dev loss curves for three QLoRA seeds, with the selected checkpoint at step 29 marked
Training loss (solid) and dev loss (dashed) for three seeds. All three reach their lowest dev loss at the end of epoch 1 (step 29).

Step 7

Why this stepCompare before and after on the same test questions and check stability across seeds.

Paired bootstrap resamples the same test questions each time and computes the distribution of fine-tuned minus baseline. A 95% CI for the difference that excludes 0 means the gain is stable on these 500 questions. Overlap of the two models' separate intervals is not a substitute for a paired test, and 3 seeds × 500 questions are not 1,500 independent questions.

MetricZero-shot baselineQLoRA (main seed)Gain (paired 95% CI)
Accuracy42.2% (38.0–46.6)76.8% (73.0–80.4)+34.6 (+29.2 to +40.0)
Macro-F136.95% (33.0–40.8)55.28% (52.5–58.4)+18.3 (+13.7 to +23.0)
PubMedQA test set, 500 questions, 10,000 paired bootstrap resamples. The other two seeds scored 78.2% and 77.8% accuracy and 54.6% and 54.4% macro-F1.
Paired differences between fine-tuned and baseline with 95% bootstrap intervals
Paired difference (fine-tuned minus baseline) with 95% bootstrap intervals. Accuracy, macro-F1 and yes/no recall rise; maybe recall falls by about 56 points.
Accuracy and macro-F1 for three random seeds
Three prespecified seeds give similar results. Error bars are per-seed bootstrap intervals over the 500 questions, not intervals across seeds.

Step 8

Why this stepOverall accuracy can hide a class that gets worse.

The gain comes from correcting yes and no: the base model over-answered maybe and fine-tuning fixed that. But the fine-tuned model almost never answers maybe, so its ability to say the evidence is insufficient has degraded. This may relate to how rare maybe is in the data (55 of 500 test questions).

If your application needs the model to say "uncertain" when evidence is lacking, this result is not usable as is. Options to try include class weighting or oversampling for maybe, or threshold tuning as a separate experiment, all validated on new held-out data. PubMedQA is an offline benchmark; accuracy does not establish clinical safety.

ClassQuestionsBaseline recallFine-tuned recall
yes27655.8%87.7%
no16914.8%83.4%
maybe5558.2%1.8%
Zero-shot baseline confusion matrix on PubMedQA, with many yes and no questions answered maybe
Before fine-tuning: many yes and no questions are answered maybe.
QLoRA confusion matrix on PubMedQA with maybe recall of 1.8%
After fine-tuning: yes and no recall rise sharply, and only 1 of 55 maybe questions is answered correctly.

Concepts and judgment

Comparison

Why this stepPublished scores show the range, but settings differ, so they are not a ranking.

Med-PaLM 2's 81.8% uses few-shot prompting and 11-sample voting, GPT-4's 75.2% is a single zero-shot answer, and this page fine-tunes on 450 expert questions with one greedy decode. The papers do not publish per-question predictions, so no paired test is possible; treat them as reference only.

System and settingAccuracy
Always answer yes55.2%
BioBERT, multi-phase, extra supervision (Jin et al. 2019)68.1%
GPT-4, zero-shot (Nori et al. 2023)75.2%
Single human annotator (Jin et al. 2019)78.0%
Med-PaLM 2, optimized prompt + 11-sample self-consistency (Singhal et al. 2023)81.8%
This page: Qwen2.5-7B + QLoRA, 450 expert questions76.8% (three seeds 76.8–78.2%)
All use the question plus the abstract without its conclusion, but models, number of examples, sampling and training data differ.

Reference and appendix

Run record

Why this stepData source and run conditions.

The numbers come from one long research task on Scientify on 29 October 2026. The user uploaded a task brief. Scientify estimated resources and rented an A10G GPU, checked the model revision and data split, ran the baseline before training, stopped when it found quantization inactive, kept the evidence and retrained every seed, then released the GPU after evaluation.

The deliverables were an 11-page report in Chinese, 8 figures, 3 Excel files, three adapters, 2,000 per-question predictions and reproduction code, and an independent review passed. The task took about 2 hours 40 minutes, about 1.9 hours of it on the GPU.

Scientify home page with the QLoRA task brief attached and long research enabled
Uploading the task brief with long research enabled.

Common errors and fixes

In the order they occurred in this run.

Config says 4-bit, but VRAM looks like BF16

Cause: quantization_method was set to a value the framework does not recognize, such as bitsandbytes, and quantization was skipped silently.

Fix: Set quantization_method: bnb and assert at training start that is_loaded_in_4bit is True and Linear4bit layers exist.

ModuleNotFoundError: No module named 'torchaudio'

Cause: Recent LLaMA-Factory imports torchaudio unconditionally in its multimodal module, and the preinstalled PyTorch had no matching build.

Fix: For text-only tasks, make that import optional and record the patch; for audio, install a matching full environment.

CUDA out of memory

Cause: Quantization inactive, or batch or sequence length too large.

Fix: First confirm 4-bit is active; then lower per_device_train_batch_size or cutoff_len and keep the effective batch with gradient accumulation. Use the same protocol for baseline and fine-tuned runs.

transformers / peft version conflicts

Cause: Latest versions installed outside the range declared in LLaMA-Factory's pyproject.

Fix: Install within the declared ranges in a separate venv that reuses the existing PyTorch; do not replace the CUDA stack.

Model adds explanations and the evaluation marks them invalid

Cause: Generation is too long or the prompt differs.

Fix: Limit max_new_tokens and keep the prompt and parsing rule unchanged; keep raw outputs and never edit answers afterwards.

Please provide model_name_or_path

Cause: The low-level get_train_args was given a YAML path instead of a parsed dict.

Fix: Use llamafactory-cli train train.yaml, or yaml.safe_load the file first.

References

FAQ

How do I install LLaMA-Factory?

Per the official README: clone the repository, then run pip install -e . and pip install -r requirements/metrics.txt with Python 3.11 or newer; QLoRA also needs bitsandbytes. Then open the interface with llamafactory-cli webui or train with llamafactory-cli train and a YAML file.

How much VRAM does QLoRA need for a 7B model?

Correct QLoRA on Qwen2.5-7B peaked at about 7.25 GiB allocated in PyTorch and about 9 GB in nvidia-smi (batch 1, sequence 2048, gradient checkpointing). A 16 GB card fits by memory; this config uses bf16, which needs a card that supports it, such as an A10G or RTX 30 series and newer.

What should quantization_method be in LLaMA-Factory?

bnb. Writing bitsandbytes produced no error in this run but skipped 4-bit quantization, so training was BF16 LoRA at about 22 GiB.

What is the difference between LoRA and QLoRA?

LoRA freezes base weights and trains low-rank adapters; QLoRA also loads the frozen base weights in 4-bit NF4, cutting VRAM further. Here QLoRA used about 7 GiB and BF16 LoRA about 22 GiB.

How many epochs should I fine-tune?

Decide by dev loss. This run set 3 epochs; all three seeds had their lowest dev loss after epoch 1 and overfit afterwards. load_best_model_at_end selects the best checkpoint automatically.

Accuracy improved, so why look at per-class metrics?

Here accuracy rose from 42.2% to 76.8%, but maybe recall fell from 58.2% to 1.8%, so the model almost stopped expressing uncertainty. Accuracy alone hides that.

How long does one fine-tuning run take?

On an A10G each seed's 87 steps took about 17 minutes, 500-question inference about 3 minutes and the model download about 2 minutes. Three seeds plus evaluation used about 1.9 GPU hours.

Let Scientify run your fine-tuning experiment

Upload a task description. Scientify estimates resources, rents a GPU, runs the baseline and fine-tuning, completes a paired evaluation and releases the GPU when training ends.