LoRA là gì trong AI? Tự fine-tune model nhỏ bằng Python trên CPU (2026)

AI

Trong bài này
  1. Fine-tune là gì, LoRA là gì?
  2. Cần chuẩn bị
  3. Bước 1: Cài thư viện
  4. Bước 2: Chọn model nền và xem nó có những lớp nào
  5. Bước 3: Thử model gốc trước khi fine-tune
  6. Bước 4: Chuẩn bị dữ liệu huấn luyện
  7. Bước 5: Cấu hình LoRA và huấn luyện
  8. Bước 6: Hỏi lại model đã gắn LoRA
  9. Bước 7: Adapter nằm ở đâu, dùng lại thế nào
  10. Lỗi thường gặp
  11. Target modules not found in the base model
  12. Chỉ chọn q_proj, v_proj nên LoRA gần như không tác động
  13. Cảnh báo "falling back to its reference PyTorch implementation"
  14. Mong fine-tune sửa được kiến thức sai
  15. Câu hỏi thường gặp
  16. Nên dùng fine-tune hay RAG?
  17. Cần bao nhiêu ví dụ?
  18. Có gộp adapter vào model được không?
  19. Không có GPU thì fine-tune được model lớn không?
  20. Nguồn tham khảo

Khi tìm "LoRA là gì", các bạn sẽ gặp hai thứ khác nhau: LoRa (sóng radio trong IoT) và LoRA trong AI (Low-Rank Adaptation), một cách fine-tune model ngôn ngữ mà chỉ cần huấn luyện một phần rất nhỏ tham số. Bài này nói về cái thứ hai. Mình sẽ giải thích fine-tune và LoRA là gì, rồi cùng các bạn tự fine-tune model Qwen3.5-0.8B để nó giải thích lỗi lập trình bằng tiếng Việt theo đúng mẫu 3 dòng: Lỗi / Nguyên nhân / Cách sửa.

Toàn bộ chạy bằng CPU, không cần GPU, không cần API key. Phần huấn luyện mất khoảng 9 phút rưỡi trên 4 nhân CPU. Bài dùng peft 0.21.3 và trl 1.18.0 (cả hai lên PyPI ngày 13/11/2026), thử nghiệm ngày 15/11/2026.

Video demo trên kênh Huy Nguyen - Android A - Z.

Fine-tune là gì, LoRA là gì?

Fine-tune (tinh chỉnh) là huấn luyện thêm một model đã có sẵn bằng dữ liệu của mình để nó làm tốt một việc cụ thể. Fine-tune toàn bộ nghĩa là cập nhật mọi trọng số, rất tốn bộ nhớ. LoRA giữ nguyên (đóng băng) trọng số gốc và chỉ học thêm hai ma trận nhỏ A và B gắn vào một số lớp. Tài liệu PEFT mô tả đúng như vậy: các cập nhật trọng số được biểu diễn bằng hai ma trận nhỏ, còn ma trận gốc "remains frozen".

Sơ đồ LoRA: ma trận W gốc đóng băng cộng với tích hai ma trận nhỏ B và A được huấn luyện, kèm ví dụ số tham số
LoRA: W giữ nguyên, chỉ học B và A. Số liệu bên phải lấy từ thử nghiệm trong bài.

Cần chuẩn bị

  • Python 3.10 trở lên (mình dùng 3.12), RAM trống khoảng 6 GB, ổ cứng trống khoảng 5 GB (model 1,75 GB và thư viện).
  • Biết chạy model bằng transformers. Nếu chưa, các bạn đọc trước bài Hugging Face là gì.
  • Khoảng 30–40 phút, phần lớn là chờ tải model và chờ huấn luyện.

Bước 1: Cài thư viện

Tạo môi trường ảo, cài PyTorch bản CPU, rồi cài transformers (chạy model), peft (LoRA), trl (trình huấn luyện SFTTrainer) và datasets.

python -m venv .venv && source .venv/bin/activate
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install transformers peft trl accelerate datasets jinja2
Terminal cài môi trường, pip list hiện peft 0.21.3, trl 1.18.0, transformers 5.23.0, torch 2.15.0+cpu
Bước 1: các phiên bản dùng trong bài.

Bước 2: Chọn model nền và xem nó có những lớp nào

Mình chọn Qwen/Qwen3.5-0.8B: nhỏ, giấy phép Apache 2.0, tải không cần đăng nhập. Chính model card ghi model này dành cho "prototyping, task-specific fine-tuning", tức là hợp để tập fine-tune.

Trang Qwen3.5-0.8B trên Hugging Face, đoạn intended use cases gồm task-specific fine-tuning được khoanh đỏ
Bước 2: model card Qwen3.5-0.8B nói rõ mục đích dùng để fine-tune.

LoRA được gắn vào các lớp Linear, nên trước khi cấu hình, mình liệt kê tên các lớp này bằng một script nhỏ (inspect_model.py). Kết quả cho thấy model có 752.393.024 tham số, 24 khối. Đáng chú ý: q_proj, v_proj chỉ có ở 6 khối, còn 18 khối kia dùng linear attention với tên khác (in_proj_qkv, out_proj…). Các lớp MLP gate_proj, up_proj, down_proj thì có ở cả 24 khối.

Terminal liệt kê các lớp Linear của Qwen3.5-0.8B: gate_proj, up_proj, down_proj 24 lớp, q_proj và v_proj chỉ 6 lớp
Bước 2: tên và số lượng các lớp Linear trong Qwen3.5-0.8B.

Bước 3: Thử model gốc trước khi fine-tune

Luôn đo trước để biết fine-tune có đáng không. Mình hỏi model gốc 4 thông báo lỗi và 1 câu hỏi thường (không có trong dữ liệu huấn luyện). Model gốc trả lời bằng tiếng Anh, dài, kiểu Markdown, câu nào cũng chạm giới hạn 160 token mà chưa xong.

Terminal: model gốc trả lời lỗi ValueError bằng tiếng Anh dài, câu hỏi Kotlin trả lời dài, cả hai đều 160 token
Bước 3: model gốc trả lời dài, lẫn tiếng Anh.

Cách rẻ nhất là thêm system prompt yêu cầu đúng 3 dòng. Model làm theo mẫu, nhưng nội dung yếu (lỗi int('abc') lại khuyên "dùng str()"), và nó ép cả câu hỏi về Kotlin vào mẫu lỗi: "Nguyên nhân: Tôi không có kiến thức về Kotlin."

Terminal: model gốc có system prompt trả lời đúng mẫu 3 dòng nhưng ép cả câu hỏi Kotlin vào mẫu Lỗi, Nguyên nhân, Cách sửa
Bước 3: chỉ dùng prompt thì model áp mẫu cho mọi câu hỏi.

Bước 4: Chuẩn bị dữ liệu huấn luyện

Mình tự viết 40 cặp "thông báo lỗi → giải thích 3 dòng", gồm lỗi Android/Kotlin, PHP/Laravel, Python, Node.js, SQL và Git. Dữ liệu ở dạng prompt-completion. Theo tài liệu TRL, với dạng này SFTTrainer mặc định chỉ tính loss trên phần completion (câu trả lời), không học lại câu hỏi.

Trích file data.py: danh sách TRAIN gồm cặp thông báo lỗi và câu trả lời 3 dòng, hàm to_rows tạo prompt và completion
Bước 4: dữ liệu là danh sách cặp (lỗi, giải thích), đổi sang dạng prompt-completion.

Chất lượng dữ liệu quyết định kết quả: câu trả lời mẫu phải đúng và cùng một định dạng. 4 lỗi dùng để kiểm tra mình để riêng trong TEST, model không được thấy khi huấn luyện.

Bước 5: Cấu hình LoRA và huấn luyện

Mình gắn LoRA vào cả lớp attention và MLP, r=8, lora_alpha=16, học 3 epoch với learning rate 2e-4, batch 4. Phần chính của train_lora.py:

lora = LoraConfig(r=8, lora_alpha=16, lora_dropout=0.05, task_type="CAUSAL_LM",
                  target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
                                  "gate_proj", "up_proj", "down_proj"])
args = SFTConfig(output_dir="lora-loi-3-dong-mlp", num_train_epochs=3,
                 per_device_train_batch_size=4, learning_rate=2e-4,
                 logging_steps=5, save_strategy="no", use_cpu=True, max_length=256)
trainer = SFTTrainer(model=model, args=args, train_dataset=dataset,
                     processing_class=tok, peft_config=lora)
trainer.train()
trainer.save_model(args.output_dir)
Trích file train_lora.py với LoraConfig, SFTConfig và SFTTrainer
Bước 5: script huấn luyện đầy đủ phần cấu hình.

Ý nghĩa từng tham số các bạn xem trong tài liệu PEFT: r là hạng của hai ma trận (càng nhỏ càng ít tham số), lora_alpha là hệ số nhân, target_modules là các lớp được gắn LoRA.

Tài liệu PEFT, danh sách tham số LoraConfig gồm r, target_modules, lora_alpha được khoanh đỏ
Bước 5: các tham số LoRA trong tài liệu chính thức của PEFT.

Chạy python train_lora.py 3 mlp. Dòng đầu cho biết chỉ có 3.194.880 tham số được huấn luyện (0,42%). Loss giảm từ 2,366 xuống khoảng 0,98, toàn bộ 30 bước mất 563 giây.

Log huấn luyện: trainable params 3,194,880, loss giảm từ 2.366 xuống 0.9848 sau 3 epoch, train 563 giây
Bước 5: log huấn luyện 3 epoch trên CPU.

Bước 6: Hỏi lại model đã gắn LoRA

Script ask.py nạp model gốc rồi gắn adapter bằng PeftModel.from_pretrained(model, "lora-loi-3-dong-mlp"). Với 4 lỗi chưa từng thấy, model trả lời bằng tiếng Việt, đúng 3 dòng, chỉ 34–41 token. Điểm quan trọng: câu hỏi về Kotlin không bị ép vào mẫu lỗi như khi dùng system prompt.

Terminal: model gắn LoRA trả lời 4 lỗi bằng tiếng Việt đúng mẫu 3 dòng, câu hỏi Kotlin trả lời bình thường
Bước 6: sau fine-tune, model trả lời đúng định dạng với lỗi mới.

Nhưng các bạn đọc kỹ nội dung: với lỗi ERESOLVE, nguyên nhân thật thường là xung đột phiên bản giữa các gói (peer dependency), không phải "thư viện không có trong dự án". memory_limit là một thiết lập trong php.ini, không phải "hàm". Cả model gốc lẫn model đã fine-tune đều nói Kotlin "do Google phát triển", trong khi Kotlin do JetBrains phát triển. 40 ví dụ đủ dạy model cách trả lời, nhưng không làm nó biết thêm.

Bước 7: Adapter nằm ở đâu, dùng lại thế nào

Kết quả huấn luyện là thư mục lora-loi-3-dong-mlp. File quan trọng là adapter_model.safetensors (12,8 MB) và adapter_config.json (ghi model gốc, r, lora_alpha), so với file model gốc 1,75 GB. Có thể giữ nhiều adapter cho nhiều việc khác nhau trên cùng một model gốc.

Thư mục adapter gồm adapter_model.safetensors 13M, adapter_config.json ghi base model Qwen3.5-0.8B, r 8, lora_alpha 16
Bước 7: adapter chỉ khoảng 13 MB và ghi rõ model gốc đi kèm.

Lỗi thường gặp

Target modules not found in the base model

Nhiều hướng dẫn cũ viết target_modules=["query", "value"]. Đó là tên lớp của BERT, Qwen không có, nên PEFT báo NoMatchingPeftModuleError. Cách sửa: in tên lớp như Bước 2 rồi chọn đúng tên, hoặc dùng target_modules="all-linear".

Lỗi NoMatchingPeftModuleError: Target modules query, value not found in the base model
Lỗi 1: tên target_modules không có trong model.

Chỉ chọn q_proj, v_proj nên LoRA gần như không tác động

Công thức ["q_proj", "v_proj"] không báo lỗi, nhưng với Qwen3.5-0.8B nó chỉ gắn vào 6/24 khối và chỉ có 319.488 tham số để học, ít hơn 10 lần so với cấu hình trong bài. Với model kiến trúc lai như Qwen3.5, nên thêm các lớp MLP.

So sánh: q_proj, v_proj gắn LoRA vào 6 trên 24 lớp với 319,488 tham số; cấu hình đầy đủ gắn vào 24 lớp với 3,194,880 tham số
Lỗi 2: q_proj, v_proj chỉ phủ 6 khối của Qwen3.5-0.8B.

Cảnh báo "falling back to its reference PyTorch implementation"

Khi chạy Qwen3.5 trên CPU, transformers báo thiếu causal_conv1d và flash-linear-attention. Chính cảnh báo nói là kết quả vẫn đúng, chỉ chậm hơn. Mọi số liệu trong bài đều chạy ở chế độ này.

Mong fine-tune sửa được kiến thức sai

Như Bước 6 cho thấy, model vẫn nói sai vài chỗ. Muốn model trả lời đúng theo tài liệu thì dùng RAG, còn fine-tune dùng để cố định giọng văn, định dạng, ngôn ngữ trả lời.

Câu hỏi thường gặp

Nên dùng fine-tune hay RAG?

Muốn model trả lời theo tài liệu hay dữ liệu luôn thay đổi (quy chế, giá, lịch học) thì dùng RAG. Muốn model luôn trả lời theo một định dạng, giọng văn hoặc ngôn ngữ cố định thì fine-tune. Hai cách có thể dùng chung.

Cần bao nhiêu ví dụ?

Trong bài, 40 ví dụ viết tay đã đủ để model theo đúng mẫu với lỗi mới. Để nội dung đáng tin hơn thì cần nhiều ví dụ hơn và phải kiểm tra từng câu trả lời mẫu, vì model sẽ học theo cả chỗ sai trong dữ liệu.

Có gộp adapter vào model được không?

Được. PEFT có hàm merge_and_unload() để gộp adapter vào model gốc thành một model độc lập. Tài liệu PEFT ghi rằng sau khi gộp, LoRA không làm chậm lúc chạy.

Không có GPU thì fine-tune được model lớn không?

Với CPU, các bạn nên dừng ở model khoảng 1 tỷ tham số và dữ liệu nhỏ như bài này: 30 bước đã mất hơn 9 phút. Model lớn hơn nên dùng GPU, kết hợp lượng tử hóa (QLoRA).

Nguồn tham khảo

Ảnh chụp và thử nghiệm ngày 15/11/2026.