LoRA
LoRA - Tinh chỉnh hạng thấp
Mô hình Llama 3 có 8 tỷ tham số. Bạn muốn fine-tune nó trên laptop GPU 8GB. Cần tối thiểu bao nhiêu tham số phải huấn luyện để đạt chất lượng gần full fine-tune?
Hình minh họa
Trực quan hóa adapter LoRA
Kéo các thanh trượt để thấy rank r, chiều d và chế độ QLoRA ảnh hưởng đến số tham số, bộ nhớ và cấu trúc ma trận.
Chế độ QLoRA (base 4-bit NF4)
Lượng tử hóa W gốc xuống 4-bit để fit model 65B trên 1 GPU.
1.07B
Tham số full FT
4.19M
Tham số LoRA (A+B)
0.3906%
Tỷ lệ trainable
99.61%
Tiết kiệm
Ước lượng VRAM cần thiết (rough)
2.15 GB
Base (fp16)
8.4 MB
Adapter fp16
33.6 MB
Optimizer Adam
2.20 GB
Tổng cộng
Chưa tính activations và gradient checkpointing. thực tế có thể tiết kiệm thêm 30-50%.
Ma trận gốc W có kích thước 4096 x 4096 (16.7M tham số). Với LoRA rank r = 8, tổng tham số của A và B là bao nhiêu?
Giải thích
LoRA (Low-Rank Adaptation) dựa trên giả thuyết rằng sự thay đổi trọng số khi fine-tune có intrinsic rank thấp. Thay vì cập nhật toàn bộ ma trận W, LoRA phân tích delta thành tích của hai ma trận mỏng:
Trong đó đóng băng, và là hai ma trận huấn luyện được, với . Hệ số kiểm soát độ lớn của đóng góp adapter. chia cho r để tổng tác động không phụ thuộc nhiều vào r.
Quy trình LoRA gồm ba bước cốt lõi:
- Đóng băng W gốc: Ma trận pre-train không bị thay đổi. bảo toàn toàn bộ kiến thức nền đã học được.
- Thêm adapter: Khởi tạo A ngẫu nhiên (Gaussian), B = 0. Tại bước 0, ΔW = BA = 0. mô hình vẫn hoạt động hệt như cũ. Khi gradient chảy vào B, nó học hướng cần thiết.
- Gộp khi triển khai:Sau huấn luyện, tính W' = W + (α/r)·BA một lần và lưu. Không tăng chi phí inference so với mô hình gốc.
from peft import LoraConfig, get_peft_model, TaskType
from transformers import AutoModelForCausalLM
# 1. Load base model (fp16 trên GPU)
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3-8B",
torch_dtype="float16",
device_map="auto",
)
# 2. Cấu hình LoRA
config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=8, # Rank. trade-off giữa chất lượng/chi phí
lora_alpha=16, # Scaling: W' = W + (16/8)·BA = W + 2·BA
target_modules=[ # Áp dụng cho attention projections
"q_proj", "v_proj", # Tối thiểu: Q và V
# "k_proj", "o_proj" # Tùy chọn: K và O cho biểu diễn mạnh hơn
],
lora_dropout=0.05, # Regularization
bias="none", # Không huấn luyện bias
modules_to_save=None, # Không có module full-trainable
)
# 3. Gắn adapter
model = get_peft_model(base_model, config)
model.print_trainable_parameters()
# → trainable params: 4,194,304 || all params: 8,034,078,720
# → trainable%: 0.0522
# 4. Huấn luyện như bình thường
# trainer = Trainer(model=model, ...)
# trainer.train()
# 5. Lưu adapter (chỉ vài MB, không phải 14GB mô hình gốc!)
model.save_pretrained("./my-lora-adapter")
# 6. Nạp lại khi inference
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3-8B")
model = PeftModel.from_pretrained(base, "./my-lora-adapter")
# 7. (Tùy chọn) Gộp để tăng tốc inference
model = model.merge_and_unload() # W' = W + BA, xoá adapterLoRA vs QLoRA: khi nào dùng cái nào?
Nếu LoRA đã tiết kiệm 99% tham số huấn luyện, tại sao vẫn cần QLoRA? Câu trả lời: LoRA vẫn phải giữ W gốc ở fp16 trên GPU. Với mô hình 65B, bạn cần 130GB VRAM chỉ để load W₀. vẫn quá tải cho consumer GPU. QLoRA = LoRA + quantization 4-bit để cắt 4× footprint của base model.
| Tiêu chí | LoRA | QLoRA |
|---|---|---|
| Base model | fp16 / bf16 (2 bytes/param) | 4-bit NF4 (0.5 bytes/param) |
| Adapter A, B | fp16 | fp16 (không quantize) |
| Optimizer state | Adam fp32 trên GPU | Paged Adam (offload CPU) |
| VRAM cho Llama 65B | ~130GB (8× A100) | ~48GB (1× A100) |
| Tốc độ | Nhanh nhất | Chậm hơn ~25% (dequantize on-the-fly) |
| Chất lượng | Gần full FT | 99%+ so với LoRA (NF4 hầu như không mất) |
- 4-bit NormalFloat (NF4): kiểu lượng tử hóa phù hợp phân phối chuẩn của trọng số pre-train. Hiệu quả hơn int4 đều.
- Double quantization: lượng tử hóa chính scale factor của từng block, tiết kiệm thêm ~0.37 bit/param.
- Paged optimizer: khi VRAM đầy, tự động offload Adam state sang RAM CPU qua NVIDIA unified memory. tránh OOM khi gặp peak activation.
Dưới đây là cấu hình QLoRA đầy đủ, sẵn sàng copy-paste. Lưu ý 3 chỗ khác biệt so với LoRA thuần: BitsAndBytesConfig, prepare_model_for_kbit_training, và optimizer paged_adamw_32bit.
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
Trainer,
)
from peft import (
LoraConfig,
get_peft_model,
prepare_model_for_kbit_training,
TaskType,
)
# ===== 1. 4-bit quantization config =====
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat 4-bit
bnb_4bit_compute_dtype=torch.bfloat16, # Tính toán ở bf16
bnb_4bit_use_double_quant=True, # Double quantization
)
# ===== 2. Load model 4-bit =====
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3-70B",
quantization_config=bnb_config,
device_map="auto",
trust_remote_code=True,
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3-70B")
# ===== 3. Chuẩn bị cho k-bit training =====
# Bật gradient checkpointing, cast layer norm về fp32,
# đóng băng mọi tham số non-LoRA
model = prepare_model_for_kbit_training(
model,
use_gradient_checkpointing=True,
)
# ===== 4. Gắn LoRA adapter =====
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # Rank cao hơn LoRA thường
lora_alpha=32, # α = 2r
target_modules=[ # Tất cả linear layer trong attention
"q_proj", "k_proj",
"v_proj", "o_proj",
# "gate_proj", "up_proj", # Cũng có thể thêm MLP
# "down_proj",
],
lora_dropout=0.05,
bias="none",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# → trainable: 40M / 70B = 0.057%
# ===== 5. Training với paged optimizer =====
args = TrainingArguments(
output_dir="./qlora-llama3-70b",
num_train_epochs=3,
per_device_train_batch_size=1,
gradient_accumulation_steps=16,
optim="paged_adamw_32bit", # Offload optimizer state
learning_rate=2e-4,
bf16=True,
logging_steps=10,
save_strategy="epoch",
warmup_ratio=0.03,
lr_scheduler_type="cosine",
)
# trainer = Trainer(model=model, args=args, ...)
# trainer.train()
# ===== 6. Lưu adapter (~100MB thay vì 140GB!) =====
model.save_pretrained("./qlora-llama3-adapter")Bạn có GPU 24GB VRAM. Muốn fine-tune Llama-3 13B (base fp16 ≈ 26GB). Cách nào hợp lý nhất?
- LoRA đóng băng W gốc, chỉ học hai ma trận hạng thấp A (r × d_in) và B (d_out × r). giảm >99% tham số huấn luyện.
- Rank r là tham số quan trọng nhất: r = 4-16 đủ cho hầu hết tác vụ; r = 32-64 khi cần biểu diễn mạnh hơn.
- Khởi tạo B = 0, A ~ Gaussian → mô hình ban đầu hoạt động như cũ; scale α/r giúp ổn định khi đổi r.
- Sau huấn luyện gộp W' = W + (α/r)·BA. không tăng latency khi triển khai.
- QLoRA = LoRA + 4-bit NF4 base + double quant + paged optimizer → fit được model 65B trên 1 GPU 48GB.
- Cho phép multi-tenant serving: 1 base model + N adapter (vài MB/cái) thay cho N bản sao model 14GB.
Kiểm tra hiểu biết
LoRA tiết kiệm bộ nhớ bằng cách nào?