Fine-tune LLMs with Unsloth on a Massed Compute L40.

Fine-Tune LLMs Faster with Unsloth on GPU Cloud (2026 Guide)

Unsloth is a Python stack for LoRA and QLoRA fine-tuning. This guide launches a Massed Compute GPU VM, installs Unsloth in a venv, trains an ungated instruct model on a public Hugging Face dataset, and writes a LoRA adapter you can reload later.

GPUNVIDIAL40UnslothLoRAQLoRAFine-TuningUbuntuLLM

The walkthrough uses 4-bit QLoRA (load_in_4bit=True) 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 run. Swap the model name once the adapter path is green. This was validated on an NVIDIA L40 (48 GB) on August 25, 2026.

If you already follow Fine-Tune LLMs with QLoRA on a Cloud GPU, that post is PEFT + bitsandbytes. This post is Unsloth’s FastLanguageModel path. Do not copy that recipe’s torch and TRL pins. Axolotl and LLaMA-Factory are separate later guides — not this workflow.

Technology Stack
Component Version Purpose
Ubuntu Server 24.04 LTS Image 184: NVIDIA driver 580.126.16
Unsloth 2026.8.20 FastLanguageModel + Unsloth gradient checkpointing
PyTorch 2.11.0+cu130 CUDA 13.0 wheels pulled by pip install unsloth
TRL 0.24.0 SFTTrainer / SFTConfig (processing_class=tokenizer)
PEFT 0.20.0 LoRA adapter (Unsloth wraps get_peft_model)
Transformers 5.5.0 Qwen2 tokenizer + generate
bitsandbytes 0.50.1 4-bit NF4 base weights
Model (validated) Qwen/Qwen2.5-0.5B-Instruct Ungated instruct model, load_in_4bit=True
Dataset yahma/alpaca-cleaned 51,760 rows; this run used 1,024
System Requirements
Resource This walkthrough Notes
GPU L40 48 GB (gpu_1x_l40) Validated. 0.5B QLoRA used ~1.2 GB; 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 unsloth pulls torch (~0.5 GB) plus CUDA wheels

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 25, 2026.

SKU Description vCPU RAM Storage Price Capacity
gpu_1x_l40_spot 1x L40 (48GB) [Spot] 14 72 GiB 625 GB $0.78/hr 22
gpu_1x_6000_ada 1x RTX 6000 ADA (48GB) 12 72 GiB 350 GB $0.79/hr 4
gpu_1x_l40 1x L40 (48GB) 14 72 GiB 625 GB $0.86/hr 22
gpu_1x_A100_SXM4 1x A100 SXM4 (80GB) 14 100 GiB 625 GB $1.38/hr 12
gpu_1x_DGX_A100 1x DGX A100 (80GB) 16 120 GiB 1000 GB $1.38/hr 2
Spot pricing available: Spot instances can be interrupted. This walkthrough used on-demand 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.

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.

1

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: unsloth-finetune

Wait until the VM is running and copy the SSH details. This run landed in us-central-3 (Des Moines).

2

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, 46068 MiB, 580.126.16.

3

Install Unsloth

Pin whatever pip actually installs. This VM printed Unsloth 2026.8.20, torch 2.11.0+cu130, TRL 0.24.0. Import unsloth before trl / transformers / peft or Unsloth warns that patches may be skipped.

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
python3 -m venv ~/unsloth_env
~/unsloth_env/bin/pip install -U pip
~/unsloth_env/bin/pip install unsloth
~/unsloth_env/bin/python - <<'PY'
import unsloth, torch, trl, peft, transformers, bitsandbytes
print("unsloth", unsloth.__version__)
print("torch", torch.__version__, "cuda", torch.version.cuda)
print("trl", trl.__version__)
print("peft", peft.__version__)
print("transformers", transformers.__version__)
print("bnb", bitsandbytes.__version__)
print("gpu", torch.cuda.get_device_name(0))
PY
EOF

Unsloth’s current install page is Install Unsloth via pip. The older docs.unsloth.ai/get-started/installing-+-updating URL 404s.

4

Dataset format

Alpaca-style rows are instruction, input, output. Unsloth’s notebooks format them into a single text field and append the tokenizer EOS. Qwen2.5 Instruct’s EOS is <|im_end|> (id 151645 on this run). TRL 0.24 rejects a fake <EOS_TOKEN> that is not in the vocab.

This run loaded yahma/alpaca-cleaned (51,760 train rows) and kept the first 1,024. Full-epoch training is num_train_epochs=1 with max_steps unset — not this smoke.

5

LoRA / QLoRA config

load_in_4bit=True is QLoRA (4-bit base + LoRA adapters). r=16, lora_alpha=16, lora_dropout=0, bias="none", use_gradient_checkpointing="unsloth". Target modules: q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj.

Unsloth printed 8,798,208 trainable parameters of 502,830,976 (1.75%). max_seq_length=2048. Padding-free training auto-enabled.

6

Train

TRL 0.24’s SFTTrainer takes processing_class=tokenizer, not tokenizer=. Batch 2 × grad accum 4 = effective 8. max_steps=60, optim="adamw_8bit", learning_rate=2e-4, report_to="none".

ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
  '~/unsloth_env/bin/python ~/unsloth_train.py'

Put unsloth_train.py on the VM (same logic as below). First step was slower (~6.5 s); later steps ~1.05 s.

import unsloth  # before trl / transformers / peft
from datasets import load_dataset
from trl import SFTConfig, SFTTrainer
from unsloth import FastLanguageModel

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name="Qwen/Qwen2.5-0.5B-Instruct",
    max_seq_length=2048,
    load_in_4bit=True,
)
model = FastLanguageModel.get_peft_model(
    model,
    r=16,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                    "gate_proj", "up_proj", "down_proj"],
    lora_alpha=16,
    lora_dropout=0,
    bias="none",
    use_gradient_checkpointing="unsloth",
    random_state=3407,
)

alpaca = (
    "Below is an instruction that describes a task, paired with an input that "
    "provides further context. Write a response that appropriately completes "
    "the request.\n\n### Instruction:\n{}\n\n### Input:\n{}\n\n### Response:\n{}"
)
eos = tokenizer.eos_token  # <|im_end|> on Qwen2.5 Instruct

def fmt(batch):
    texts = [
        alpaca.format(i, x, o) + eos
        for i, x, o in zip(batch["instruction"], batch["input"], batch["output"])
    ]
    return {"text": texts}

ds = load_dataset("yahma/alpaca-cleaned", split="train").select(range(1024))
ds = ds.map(fmt, batched=True)

trainer = SFTTrainer(
    model=model,
    processing_class=tokenizer,
    train_dataset=ds,
    args=SFTConfig(
        dataset_text_field="text",
        per_device_train_batch_size=2,
        gradient_accumulation_steps=4,
        warmup_steps=5,
        max_steps=60,
        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",
        eos_token=eos,
    ),
)
trainer.train()
model.save_pretrained("lora_model")
tokenizer.save_pretrained("lora_model")
7

Save the adapter

model.save_pretrained("lora_model") wrote:

File Size
adapter_config.json 1,313 B
adapter_model.safetensors 35,237,104 B (~33.6 MiB)
tokenizer.json / tokenizer_config.json tokenizer copy

That is the adapter, not a merged 16-bit model. Merge is optional (FAQ). Checkpoint-60 also landed under outputs/.

8

Generate from the adapter

FastLanguageModel.for_inference(model)
prompt = alpaca.format("Explain LoRA fine-tuning in one sentence.", "", "")
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
out = model.generate(**inputs, max_new_tokens=64, use_cache=True)
print(tokenizer.decode(out[0], skip_special_tokens=True))

This smoke returned a completion. Quality after 60 steps on 0.5B is not a product claim — it only proves generate runs on the adapter.

9

GPU util

nvidia-smi memory.used:

When Used (MiB) / total
Idle 541 / 46068
After 4-bit load 1105 / 46068
After tokenize, before train 1139 / 46068
After 60 steps + generate 1213 / 46068

Used rose during the job. 0.5B QLoRA barely taxes a 48 GB L40. Size the SKU for the 7B–8B job you actually want, not this smoke.

Unsloth vs PEFT QLoRA vs Axolotl / LLaMA-Factory

Path What it is Use this post?
Unsloth FastLanguageModel, Unsloth checkpointing, TRL SFT Yes — this guide
PEFT + bitsandbytes Generic Hugging Face QLoRA Existing QLoRA guide
Axolotl YAML training launcher Later cluster post (MAR-45) — do not copy that workflow here
LLaMA-Factory Web UI / config training Later cluster post (MAR-47)

Unsloth’s own docs claim faster kernels and lower VRAM than stock PEFT. This VM did not run the PEFT recipe side-by-side. Do not quote a 2× number from this article.

VRAM / GPU tier

Model (QLoRA 4-bit) This article SKU
0.5B Instruct Measured: ~1.2 GB used on L40 24 GB is enough; we used 48 GB
3B-class Not run. Typical QLoRA fits 24 GB A30 / A5000 when in stock
7B–8B Instruct Not run. Unsloth lists ~16 GB class as a floor L40 48 GB is the comfortable card
70B QLoRA Not run 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, Unsloth 2026.8.20, Qwen2.5-0.5B-Instruct 4-bit, yahma/alpaca-cleaned (1,024 rows), 60 steps, August 25, 2026.

Metric Value
Trainer wall clock 71.24 s (train_runtime 69.83 s)
Steps 60 (epoch 0.47 of the 1,024-row slice)
Train loss 1.323 (step 1: 1.926)
Throughput 6.874 samples/s · 0.859 steps/s
Peak nvidia-smi used 1213 MiB of 46068 MiB
Trainable params 8,798,208 / 502,830,976 (1.75%)
Adapter adapter_model.safetensors 35.2 MB
Generate One completion from the adapter (smoke quality)

This is a 0.5B smoke, not an 8B overnight job and not a vs-PEFT 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.

Unsloth should be imported before [trl, transformers, peft]. Put import unsloth at the top of the train script. This run still trained after a first attempt that imported TRL first; import order is the supported path.

eos_token ('<EOS_TOKEN>') is not found in the vocabulary. TRL 0.24. Pass Qwen’s real EOS (<|im_end|>) in SFTConfig(eos_token=...) and append that string to each text example. Do not append a placeholder that is not in the tokenizer.

SFTTrainer() got an unexpected keyword argument 'tokenizer'. TRL 0.24 uses processing_class=tokenizer. Follow Unsloth’s current notebook, not an older PEFT gist.

Gated Llama 401. This guide uses ungated Qwen2.5 Instruct. Llama 3.x needs HF_TOKEN in the environment before from_pretrained.

Out of memory on 7B–8B. Lower per_device_train_batch_size, raise gradient_accumulation_steps, shorten max_seq_length, keep load_in_4bit=True, or move to 48 GB / 80 GB. This 0.5B run is not an OOM test.

Xformers = None in the Unsloth banner. This L40 trained without xformers. If a later Unsloth/torch pair demands it, install the xformers wheel Unsloth’s installer selects for that torch — do not mix random versions.

Loss goes to 0. Overfit on a tiny slice. This smoke’s mean loss was 1.32 after 60 steps. Use a larger dataset and 1–3 epochs for a real adapter.

Fine-Tune LLMs Faster with Unsloth

Launch NVIDIA L40 instances for Unsloth QLoRA fine-tuning. Get 48GB VRAM, NVMe storage, 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 + Unsloth
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP 'bash -s' <<'EOF'
sudo apt-get update -qq && sudo apt-get install -y python3.12-venv
python3 -m venv ~/unsloth_env
~/unsloth_env/bin/pip install -U pip unsloth
EOF

# 4. Train + save adapter (script from step 6)
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
  '~/unsloth_env/bin/python ~/unsloth_train.py'

# 5. Confirm adapter files
ssh -o IdentitiesOnly=yes Ubuntu@YOUR_VM_IP \
  'ls -l ~/unsloth-run/lora_model/adapter_*.json ~/unsloth-run/lora_model/*.safetensors'

Frequently Asked Questions

01What did this guide actually run?

gpu_1x_l40, image 184, Unsloth 2026.8.20, 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.24 s. Peak used 1213 MiB.

02Is this faster than the PEFT QLoRA post?

Not measured here. Use Fine-Tune LLMs with QLoRA on a Cloud GPU for PEFT. This post is Unsloth.

03Should I merge the adapter into the base model?

Not for the smoke. Keep adapter_config.json + adapter_model.safetensors and load with Unsloth or PEFT. Merge when you need a single Hugging Face folder for a server that does not load adapters.

04What about 7B–8B?

Same install. Change model_name to an ungated instruct checkpoint (or an unsloth/*-bnb-4bit repo). Expect more VRAM and wall-clock. This VM did not train 8B.

05Can I QLoRA a 70B on this L40?

Not this walkthrough. 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 25, 2026.