LLaMA-Factory is a YAML-driven fine-tuning stack with a CLI and a Gradio WebUI (LLaMA Board). This guide launches a Massed Compute GPU VM, installs LLaMA-Factory from source, QLoRA-tunes an ungated instruct model on a public Hugging Face dataset from one config file, and writes a LoRA adapter you can reload later.
The walkthrough uses 4-bit QLoRA (quantization_bit: 4, quantization_method: bnb) on Qwen/Qwen2.5-0.5B-Instruct and the first 1,024 rows of yahma/alpaca-cleaned. That is a smoke you can finish in minutes, not a production 8B or 70B run. Swap the model path once adapter_model.safetensors is on disk. This was validated on an NVIDIA L40 (48 GB) on August 27, 2026. llamafactory-cli train is the reproducible path. llamafactory-cli webui was started on 127.0.0.1:7860 and returned HTTP 200 — it is not how this job trained.
If you already follow Fine-Tune LLMs Faster with Unsloth on GPU Cloud, that post is Unsloth’s FastLanguageModel path. Fine-Tune LLMs with Axolotl on Multi-GPU VMs is Axolotl YAML + torchrun. The existing Fine-Tune LLMs with QLoRA on a Cloud GPU post is PEFT + bitsandbytes. This post is llamafactory-cli train + one YAML. Do not copy those install commands. DPO, KTO, PPO, and DeepSpeed ZeRO are named here, not walked through.
| Component | Version | Purpose |
|---|---|---|
| Ubuntu Server | 24.04 LTS | Image 184: NVIDIA driver 580.126.16 |
| LLaMA-Factory | 0.9.6.dev0 (7fcf5b3) |
YAML trainer (llamafactory-cli train) + LLaMA Board |
| PyTorch | 2.13.0+cu130 | CUDA 13.0 wheels from pip install -e . |
| Transformers | 5.8.0 | Qwen2 tokenizer + causal LM |
| PEFT | 0.18.1 | LoRA adapter (peft_type: LORA, r: 16) |
| bitsandbytes | 0.50.2 | 4-bit QLoRA (quantization_method: bnb) |
| TRL | 0.24.0 | Pulled by LLaMA-Factory; SFT stage |
| Datasets | 4.0.0 | yahma/alpaca-cleaned via dataset_info.json |
| Accelerate | 1.11.0 | Trainer launcher |
| Model (validated) | Qwen/Qwen2.5-0.5B-Instruct | Ungated instruct model, QLoRA |
| Dataset | yahma/alpaca-cleaned | 51,760 rows; this run used 1,024 (max_samples: 1024) |
| Resource | This walkthrough | Notes |
|---|---|---|
| GPU | L40 48 GB (gpu_1x_l40) |
Validated. 0.5B QLoRA peaked 8759 MiB. 7B–8B QLoRA is why you want 48 GB |
| System RAM | 72 GB | SKU ships 72 GiB |
| vCPU | 14 | SKU ships 14 vCPU |
| Storage | 625 GB | Hugging Face cache plus adapter |
| Network | 1 Gbps | First pip install -e . pulls torch CUDA 13 wheels |
CLI vs WebUI
LLaMA-Factory ships both. This article trains with the CLI so every flag is in git.
| Interface | Command | Use |
|---|---|---|
| CLI (this guide) | llamafactory-cli train your.yaml |
Reproducible QLoRA. What we timed |
| WebUI (LLaMA Board) | llamafactory-cli webui |
Point-and-click. We bound 127.0.0.1:7860 and got HTTP 200. Do not open Gradio to the public internet |
| Chat / API | llamafactory-cli chat / api |
After an adapter exists |
Keep UFW at SSH 22 only. Reach the WebUI with an SSH tunnel (ssh -L 7860:127.0.0.1:7860), not a public security-group hole.
Massed Compute VM Pricing
Lead with the L40 this guide was tested on. 80 GB cards are listed for 70B QLoRA we did not run here.
Pricing fetched from the Massed Compute inventory API on August 27, 2026.
| SKU | Description | vCPU | RAM | Storage | Price | Capacity |
|---|---|---|---|---|---|---|
gpu_1x_l40_spot |
1x L40 (48GB) [Spot] | 14 | 72 GiB | 625 GB | $0.78/hr | 24 |
gpu_1x_6000_ada |
1x RTX 6000 ADA (48GB) | 12 | 72 GiB | 350 GB | $0.79/hr | 5 |
gpu_1x_l40 |
1x L40 (48GB) | 14 | 72 GiB | 625 GB | $0.86/hr | 24 |
gpu_1x_A100_SXM4 |
1x A100 SXM4 (80GB) | 14 | 100 GiB | 625 GB | $1.38/hr | 2 |
gpu_1x_DGX_A100 |
1x DGX A100 (80GB) | 16 | 120 GiB | 1000 GB | $1.38/hr | 5 |
gpu_1x_l40. Use on-demand if the job cannot restart. The L40 is the card this run used — see NVIDIA L40 GPU Best Use Cases. For 80 GB QLoRA (70B-class) see NVIDIA A100 GPU Best Use Cases. Training vs inference card sizing: The Best GPU for LLM Inference Without Overpaying. After you have an adapter, serve it with SGLang or vLLM — that is a different post. RL post-training is verl, not this SFT YAML.Step-by-Step Deployment
Image 184 logs in as Ubuntu (capital U). If SSH offers extra keys and then fails, add -o IdentitiesOnly=yes. Image 184 ships NVIDIA drivers, not a ready Python venv. Install python3.12-venv (or python3-venv) before python3 -m venv.
Launch GPU VM
# Launch via Massed Compute dashboard or API
# Product: gpu_1x_l40
# Image: 184 (Ubuntu Server 24.04 w/ Drivers)
# SSH Key: attach your public key
# Instance name: llama-factory-finetune
Wait until the VM is running and copy the SSH details.
Verify GPU Access
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
'nvidia-smi --query-gpu=name,memory.total,driver_version --format=csv,noheader'
This run printed NVIDIA L40, 49140 MiB, 580.126.16.
Install LLaMA-Factory
Pin whatever pip actually installs. This VM printed LLaMA-Factory 0.9.6.dev0 at git 7fcf5b3, torch 2.13.0+cu130, transformers 5.8.0. Official install from source is pip install -e . plus requirements/metrics.txt. Add bitsandbytes for QLoRA (quantization_method: bnb).
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'bash -s' <<'EOF'
set -euxo pipefail
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y python3.12-venv git
python3 -m venv ~/lf_env
~/lf_env/bin/pip install -U pip
git clone --depth 1 https://github.com/hiyouga/LlamaFactory.git ~/LlamaFactory
cd ~/LlamaFactory
~/lf_env/bin/pip install -e .
~/lf_env/bin/pip install -r requirements/metrics.txt
~/lf_env/bin/pip install bitsandbytes
~/lf_env/bin/python - <<'PY'
import llamafactory, torch, transformers, peft, bitsandbytes
print("llamafactory", llamafactory.__version__)
print("torch", torch.__version__, "cuda", torch.version.cuda)
print("transformers", transformers.__version__)
print("peft", peft.__version__)
print("bnb", bitsandbytes.__version__)
print("gpu", torch.cuda.get_device_name(0))
PY
EOF
Upstream install notes: LLaMA-Factory Getting Started.
Register the dataset
Bundled alpaca_en_demo is a tiny local JSON. This smoke used the same public set as the Unsloth post: yahma/alpaca-cleaned. Add it to data/dataset_info.json so YAML can name dataset: alpaca_cleaned.
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'python3 -' <<'PY'
import json
from pathlib import Path
p = Path("/home/Ubuntu/LlamaFactory/data/dataset_info.json")
d = json.loads(p.read_text())
d["alpaca_cleaned"] = {
"hf_hub_url": "yahma/alpaca-cleaned",
"columns": {"prompt": "instruction", "query": "input", "response": "output"},
}
p.write_text(json.dumps(d, indent=2) + "\n")
print("added alpaca_cleaned")
PY
max_samples: 1024 keeps the job a smoke. The Hub repo has 51,760 train rows.
QLoRA YAML
Copy of the file this VM trained: examples/train_qlora/qwen25_05b_qlora_sft.yaml. Template for Qwen2.5 Instruct is qwen, not qwen3_nothink.
### model
model_name_or_path: Qwen/Qwen2.5-0.5B-Instruct
quantization_bit: 4
quantization_method: bnb
trust_remote_code: true
### method
stage: sft
do_train: true
finetuning_type: lora
lora_rank: 16
lora_target: all
### dataset
dataset: alpaca_cleaned
template: qwen
cutoff_len: 2048
max_samples: 1024
preprocessing_num_workers: 4
dataloader_num_workers: 0
### output
output_dir: saves/qwen25-05b/qlora/sft
logging_steps: 5
save_steps: 60
plot_loss: false
overwrite_output_dir: true
save_only_model: false
report_to: none
### train
per_device_train_batch_size: 2
gradient_accumulation_steps: 4
learning_rate: 2.0e-4
num_train_epochs: 1.0
max_steps: 60
lr_scheduler_type: cosine
warmup_ratio: 0.1
bf16: true
Effective batch is 2 × 4 = 8. lora_rank: 16 (PEFT wrote lora_alpha: 32). dataloader_num_workers: 0 avoids extra DataLoader processes on a short job. 16-bit LoRA is the same file without quantization_bit / quantization_method. DPO is a different stage: — see examples/train_lora/qwen3_lora_dpo.yaml; not this smoke.
Train
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'bash -s' <<'EOF'
export PATH="$HOME/lf_env/bin:$PATH"
export WANDB_MODE=disabled
cd ~/LlamaFactory
llamafactory-cli train examples/train_qlora/qwen25_05b_qlora_sft.yaml
EOF
The trainer printed Num examples = 1,024, trainable params: 8,798,208 / 502,830,976 (1.7497%), Quantizing model to 4 bit with bitsandbytes. train_runtime 71.91 s. Mean loss 1.354. Process wall including the first Hugging Face download was ~102 s.
Save the adapter
output_dir wrote:
| File | Size |
|---|---|
adapter_config.json |
1,056 B |
adapter_model.safetensors |
35,237,104 B (~33.6 MiB) |
tokenizer.json / tokenizer_config.json |
tokenizer copy |
checkpoint-60/ |
step-60 snapshot |
That is the adapter, not a merged 16-bit model. Merge is llamafactory-cli export (FAQ). Do not pass quantization_bit when merging.
Generate from the adapter
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'bash -s' <<'EOF'
export PATH="$HOME/lf_env/bin:$PATH"
cd ~/LlamaFactory
python3 - <<'PY'
from llamafactory.chat.chat_model import ChatModel
chat = ChatModel({
"model_name_or_path": "Qwen/Qwen2.5-0.5B-Instruct",
"adapter_name_or_path": "saves/qwen25-05b/qlora/sft",
"template": "qwen",
"finetuning_type": "lora",
"quantization_bit": 4,
"quantization_method": "bnb",
"infer_backend": "huggingface",
"trust_remote_code": True,
})
out = chat.chat([{"role": "user", "content": "Explain LoRA in one sentence."}])[0]
print(out.response_text)
PY
EOF
This smoke returned a completion (Loaded adapter(s): saves/qwen25-05b/qlora/sft). Quality after 60 steps on 0.5B is not a product claim — it only proves generate runs on the adapter. Interactive equivalent: llamafactory-cli chat examples/inference/qwen3_lora_sft.yaml with your paths.
Optional: LLaMA Board on localhost
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
'cd ~/LlamaFactory && CUDA_VISIBLE_DEVICES="" GRADIO_SERVER_NAME=127.0.0.1 GRADIO_SERVER_PORT=7860 \
~/lf_env/bin/llamafactory-cli webui'
This VM answered HTTP 200 from curl http://127.0.0.1:7860/ (uvicorn / Gradio). Tunnel from your laptop: ssh -L 7860:127.0.0.1:7860 -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP.
GPU util
nvidia-smi sampled every 2 s during the 60-step job:
| Metric | Value |
|---|---|
Peak memory.used |
8759 MiB of 49140 MiB |
| Card | NVIDIA L40, driver 580.126.16 |
0.5B QLoRA does not fill a 48 GB L40. Size the SKU for the 7B–8B job you actually want, not this smoke.
LLaMA-Factory vs Unsloth vs Axolotl vs PEFT
| Path | What it is | Use this post? |
|---|---|---|
| LLaMA-Factory | YAML + CLI + WebUI; SFT/DPO/KTO and 100+ model templates | Yes — this guide |
| Unsloth | FastLanguageModel, Unsloth kernels |
Unsloth GPU cloud |
| Axolotl | YAML + torchrun multi-GPU | Axolotl multi-GPU |
| PEFT + bitsandbytes | Generic Hugging Face QLoRA | Existing QLoRA guide |
Same 0.5B / Alpaca / 60-step smoke on an L40: the Unsloth post measured ~1.2 GB used; this LLaMA-Factory run peaked 8759 MiB. Different trainers (Unsloth kernels vs stock bnb QLoRA). This VM did not re-run Unsloth. Do not quote a speedup from this article.
VRAM / GPU tier
LLaMA-Factory’s README estimates 4-bit QLoRA roughly 6 GB for 7B, 12 GB for 14B, 48 GB for 70B. Those are upstream estimates, not this VM.
| Model (QLoRA 4-bit) | This article | SKU |
|---|---|---|
| 0.5B Instruct | Measured: 8759 MiB used on L40 | 24 GB is enough; we used 48 GB |
| 7B–8B Instruct | Not run. Upstream ~6 GB class floor | L40 48 GB is the comfortable card |
| 14B-class | Not run. Upstream ~12 GB | L40 / 48 GB Ada |
| 70B QLoRA | Not run. Upstream ~48 GB | 80 GB A100 / H100 — see the A100 product post |
CTA matches the demonstrated 0.5B-on-L40 plus the 8B recommendation: launch gpu_1x_l40.
Measured result
Same L40, LLaMA-Factory 0.9.6.dev0, Qwen2.5-0.5B-Instruct 4-bit QLoRA, yahma/alpaca-cleaned (1,024 rows), 60 steps, August 27, 2026.
| Metric | Value |
|---|---|
| Trainer wall clock | 71.91 s (train_runtime) |
| Process wall (incl. first HF download) | 101.9 s |
| Steps | 60 (epoch 0.47 of the 1,024-row slice) |
| Train loss | 1.354 |
| Throughput | 6.675 samples/s · 0.834 steps/s |
Peak nvidia-smi used |
8759 MiB of 49140 MiB |
| Trainable params | 8,798,208 / 502,830,976 (1.75%) |
| Adapter | adapter_model.safetensors 35.2 MB |
| WebUI | HTTP 200 on 127.0.0.1:7860 |
| Generate | One completion from the adapter (smoke quality) |
This is a 0.5B smoke, not an 8B overnight job, not DPO, and not a vs-Unsloth bake-off.
Troubleshooting
SSH fails or keeps asking for a password. Image 184’s user is Ubuntu, not ubuntu. Use -o IdentitiesOnly=yes. Refresh host keys with ssh-keygen -R YOUR_VM_IP if the instance was relaunched on a reused IP.
ensurepip is not available. Image 184 needs sudo apt-get install -y python3.12-venv (or python3-venv) before python3 -m venv.
Unknown dataset name: alpaca_cleaned. The Hub id is not enough. Register it in data/dataset_info.json (step 4) or use a bundled key such as alpaca_en_demo.
Wrong chat template. Qwen2.5 Instruct is template: qwen. The upstream Qwen3 examples use qwen3_nothink. Mixing them garbles special tokens.
bitsandbytes / CUDA mismatch. This L40 used bnb 0.50.2 on torch 2.13.0+cu130. Install bnb after torch. If import fails, print torch.version.cuda and reinstall the matching wheel — do not mix random versions.
Gated Llama 401. This guide uses ungated Qwen2.5 Instruct. Llama 3.x needs HF_TOKEN in the environment before train.
Out of memory on 7B–8B. Lower per_device_train_batch_size, raise gradient_accumulation_steps, shorten cutoff_len, keep quantization_bit: 4, or move to 48 GB / 80 GB. This 0.5B run is not an OOM test.
WebUI reachable from the internet. Bind GRADIO_SERVER_NAME=127.0.0.1, keep UFW on 22, use an SSH tunnel. Gradio is not an auth gate.
Merge fails on a quantized model. Upstream: do not export with quantization_bit set. Merge 16-bit LoRA, or reload the adapter at inference.
Loss goes to 0. Overfit on a tiny slice. This smoke’s mean loss was 1.35 after 60 steps. Use a larger dataset and 1–3 epochs for a real adapter.
Fine-Tune LLMs with LLaMA-Factory on GPU Cloud
Launch NVIDIA L40 instances for LLaMA-Factory QLoRA fine-tuning. Get 48GB VRAM, YAML CLI or WebUI, and per-second billing.
Think it. Build it. Scale it.
Quick Setup Reference
# 1. Launch gpu_1x_l40, image 184
# 2. Verify GPU
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'nvidia-smi'
# 3. venv + LLaMA-Factory
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'bash -s' <<'EOF'
sudo apt-get update -qq && sudo apt-get install -y python3.12-venv git
python3 -m venv ~/lf_env
~/lf_env/bin/pip install -U pip
git clone --depth 1 https://github.com/hiyouga/LlamaFactory.git ~/LlamaFactory
cd ~/LlamaFactory && ~/lf_env/bin/pip install -e . && ~/lf_env/bin/pip install -r requirements/metrics.txt bitsandbytes
EOF
# 4. YAML QLoRA (see step 5) then train
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
'export PATH="$HOME/lf_env/bin:$PATH"; cd ~/LlamaFactory && llamafactory-cli train examples/train_qlora/qwen25_05b_qlora_sft.yaml'
# 5. Confirm adapter files
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
'ls -l ~/LlamaFactory/saves/qwen25-05b/qlora/sft/adapter_config.json ~/LlamaFactory/saves/qwen25-05b/qlora/sft/adapter_model.safetensors'
Frequently Asked Questions
01What did this guide actually run?
gpu_1x_l40, image 184, LLaMA-Factory 0.9.6.dev0, Qwen/Qwen2.5-0.5B-Instruct 4-bit QLoRA, yahma/alpaca-cleaned (1,024 of 51,760 rows), 60 SFT steps, adapter on disk. Trainer wall 71.91 s. Peak used 8759 MiB. WebUI HTTP 200 on localhost. DPO not run.
02Is this faster or smaller-VRAM than Unsloth?
Not a bake-off on this VM. The Unsloth L40 post measured ~1.2 GB on the same model family; this run peaked 8.8 GB. Use that post for Unsloth kernels. This post is LLaMA-Factory YAML.
03Should I merge the adapter into the base model?
Not for the smoke. Keep adapter_config.json + adapter_model.safetensors and load with LLaMA-Factory or PEFT. llamafactory-cli export when a server needs one Hugging Face folder.
04What about 7B–8B?
Same install. Change model_name_or_path to an ungated instruct checkpoint. Expect more VRAM and wall-clock. This VM did not train 8B.
05Can I QLoRA a 70B on this L40?
Not this walkthrough. Upstream 4-bit 70B is ~48 GB class — tight. Use 80 GB (A100) or multi-GPU. Full fine-tune of 7B+ is out of scope.
06Do I need a Hugging Face token?
Not for Qwen2.5-0.5B-Instruct or yahma/alpaca-cleaned. Gated Llama weights need HF_TOKEN.
07What’s the difference between spot and on-demand?
Spot is cheaper and can be interrupted. This run used on-demand gpu_1x_l40. Use on-demand if a long epoch cannot restart.
Recipe tested on August 27, 2026.











