LoRA & QLoRA — Lý thuyết và thực hành
Cơ chế low-rank decomposition, rank/alpha/target_modules, lượng tử hoá 4-bit trong QLoRA, code thực hành đầy đủ.
LoRA và biến thể QLoRA của nó là kỹ thuật fine-tuning được dùng nhiều nhất trong thực tế ngoài các phòng thí nghiệm lớn — nó là lý do một người với 1 GPU tiêu dùng (hoặc thuê GPU theo giờ với vài USD) có thể fine-tune một model 7B-13B ngay tại nhà. Đây là module thực hành trọng tâm của cả khoá học.
🎯 Mục tiêu học tập
- Giải thích được cơ chế low-rank decomposition đằng sau LoRA
- Cấu hình đúng các siêu tham số quan trọng: rank (r), alpha, target_modules, dropout
- Hiểu QLoRA lượng tử hoá model gốc xuống 4-bit để tiết kiệm bộ nhớ ra sao mà vẫn giữ chất lượng huấn luyện
- Viết được code fine-tune một model nhỏ bằng LoRA/QLoRA từ đầu đến cuối
Cơ chế LoRA: Low-Rank Decomposition
Với một weight matrix gốc W (kích thước d×k, đã đóng băng), LoRA không sửa trực tiếp
W mà học thêm một "phần điều chỉnh" ΔW = B·A, trong đó A có kích
thước r×k và B có kích thước d×r, với r (rank) rất nhỏ (thường 8-64) so với
d và k (thường hàng nghìn). Output của layer trở thành:
h = W·x + (B·A)·x * (alpha / r)
Vì r nhỏ, số tham số cần huấn luyện trong A và B nhỏ hơn W gốc
hàng trăm đến hàng nghìn lần. Trực giác: giả định rằng "phần điều chỉnh cần thiết" để thích ứng model với
tác vụ mới có hạng nội tại (intrinsic rank) thấp — tức là không cần thay đổi model theo
mọi hướng có thể, chỉ cần thay đổi theo một số ít "hướng" quan trọng nhất — giả định này được nghiên cứu
gốc của LoRA chứng minh thực nghiệm là hợp lý cho hầu hết tác vụ thích ứng (adaptation) thực tế.
Siêu tham số quan trọng
| Tham số | Ý nghĩa | Giá trị thường dùng |
|---|---|---|
r (rank) | Kích thước "cổ chai" — r càng lớn, model càng có khả năng học pattern phức tạp nhưng càng nhiều tham số cần train | 8-64 (bắt đầu từ 16, tăng nếu underfitting) |
alpha | Hệ số scale cho ΔW (xem công thức trên) — thường đặt alpha = 2×r làm điểm khởi đầu | 16-32 |
target_modules | Layer nào được gắn LoRA — thường là các ma trận attention (q_proj, v_proj, đôi khi cả k_proj, o_proj), có thể mở rộng sang MLP để tăng khả năng học | Tối thiểu q_proj + v_proj |
lora_dropout | Dropout riêng cho nhánh LoRA, chống overfitting khi dữ liệu ít | 0.05-0.1 |
QLoRA: Lượng tử hoá 4-bit + LoRA
QLoRA kết hợp LoRA với việc lượng tử hoá model gốc xuống 4-bit (dùng định dạng NF4 — NormalFloat 4-bit, tối ưu cho phân phối trọng số của model đã huấn luyện) trước khi gắn adapter LoRA lên trên. Đóng góp kỹ thuật quan trọng giúp không mất nhiều chất lượng: double quantization (lượng tử hoá cả hằng số lượng tử hoá để tiết kiệm thêm bộ nhớ) và paged optimizer (dùng cơ chế bộ nhớ CPU/GPU linh động của NVIDIA để tránh out-of-memory khi có đột biến bộ nhớ). Kết quả: một model 7B có thể fine-tune trên GPU chỉ 12-16GB VRAM (ví dụ RTX 4060 Ti/4070) thay vì cần 24GB+ như LoRA thường (không lượng tử hoá).
Code thực hành: QLoRA với Hugging Face
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
model_name = "Qwen/Qwen2.5-7B-Instruct"
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(model_name, quantization_config=bnb_config, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = prepare_model_for_kbit_training(model)
lora_config = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# In ra: ví dụ "trainable params: 20M || all params: 7.6B || trainable%: 0.26%"
Thư viện Unsloth viết lại các phép tính lõi bằng kernel tối ưu, cho tốc độ huấn luyện QLoRA nhanh hơn 2x và dùng ít VRAM hơn khoảng 60% so với cách viết thuần Hugging Face ở trên, với cùng chất lượng kết quả — đáng cân nhắc thay thế khi tài nguyên GPU hạn chế.
Sau khi train: Merge hay giữ tách rời?
Sau khi huấn luyện xong, bạn có 2 lựa chọn: merge (gộp B·A trực tiếp vào
W gốc, tạo ra 1 model độc lập, đơn giản khi serving nhưng mất khả năng "tháo rời") hoặc
giữ tách rời (giữ adapter LoRA như 1 file nhỏ riêng, load động cùng base model lúc chạy —
cho phép chạy nhiều adapter khác nhau trên cùng 1 base model, tiết kiệm bộ nhớ đáng kể khi cần phục vụ
nhiều "phiên bản chuyên biệt" — xem thêm ở Module 13).
🏋️ Bài tập thực hành
Trên 1 GPU thuê theo giờ (RunPod/Colab/Lambda) hoặc GPU cá nhân nếu đủ VRAM, fine-tune 1 model nhỏ (Qwen2.5-1.5B hoặc tương đương) bằng QLoRA trên 1 dataset instruction công khai nhỏ (vài trăm mẫu). Thử nghiệm với 2 giá trị rank khác nhau (r=8 và r=32), so sánh training loss cuối cùng và chất lượng output chủ quan trên vài câu hỏi test. Ghi lại VRAM thực tế đã dùng.
📚 Tài nguyên học tập
-
Hu et al. — LoRA: Low-Rank Adaptation of LLMs (paper gốc)Bài báo gốc giới thiệu LoRAPaper
-
Dettmers et al. — QLoRA (paper gốc)Bài báo gốc giới thiệu QLoRAPaper
-
Hugging Face — PEFT LoRA guideHướng dẫn thực hành LoRA với thư viện PEFTDocs
-
Unsloth — DocumentationThư viện tăng tốc fine-tuning LoRA/QLoRADocs
✅ Tự đánh giá hoàn thành
- Giải thích được công thức B·A và ý nghĩa của rank
- Cấu hình đúng target_modules, r, alpha cho một bài toán cụ thể
- Giải thích được QLoRA tiết kiệm bộ nhớ bằng cơ chế nào (NF4, double quantization)
- Đã tự fine-tune thành công ít nhất 1 model bằng QLoRA và đo được VRAM sử dụng thật