hoailebads/Qwen3-Embedding-0.6B-Zalo-Legal-Retrieval-LoRA
Qwen3-Embedding-0.6B — Zalo Legal Retrieval (Dual-LoRA)
Bi-encoder truy hồi điều luật tiếng Việt trên corpus Zalo Legal Text Retrieval. Kiến trúc dual-LoRA: hai adapter LoRA riêng biệt dùng chung một base model đóng băng — query_adapter mã hoá câu hỏi, passage_adapter mã hoá điều luật.
Đây là checkpoint Zalo tốt nhất đã đo (zalo_synthetic_hoi_dap/checkpoint-12), thuộc cùng pipeline với hoaileba/Qwen-Retrieval-Tuning.
Kết quả
Tập eval: 637 câu hỏi (zalo_valset.json). Corpus: 61,031 điều luật (gộp từ 972,327 dòng legal_corpus_splitted.csv, group theo law_title|article_id). FAISS inner-product, dedup theo aid.
So với các checkpoint Zalo khác (cùng tập 637 câu)
Dual-adapter hơn single-adapter +7.4 R@1 — khoảng cách lớn hơn nhiều so với trên VLSP (+4.8), và là kết quả rõ ràng nhất trong toàn bộ thí nghiệm. Augment synthetic dạng hỏi–đáp cộng thêm +1.0 R@1 lên trên checkpoint cha.
Số đo đầy đủ: `eval_results.json`.
Cách dùng
Model gồm hai adapter nên không load thẳng bằng AutoModel được — phải nạp cả hai và chuyển adapter theo loại text. File `modeling_dual_lora.py` trong repo này làm sẵn việc đó:
from modeling_dual_lora import DualLoRAEncoder
import numpy as np
enc = DualLoRAEncoder.from_pretrained("hoailebads/Qwen3-Embedding-0.6B-Zalo-Legal-Retrieval-LoRA")
questions = ["Người điều khiển xe máy không đội mũ bảo hiểm bị phạt bao nhiêu tiền?"]
articles = ["Điều 6. Xử phạt người điều khiển xe mô tô, xe gắn máy ... "]
q = enc.encode(questions, is_query=True) # (1, 1024), đã L2-normalize
d = enc.encode(articles, is_query=False) # (N, 1024)
scores = q @ d.T # inner product == cosine
top = np.argsort(-scores[0])[:10]Hoặc tự nạp bằng PEFT:
from transformers import AutoModel, AutoTokenizer
from peft import PeftModel
import torch
base = AutoModel.from_pretrained("Qwen/Qwen3-Embedding-0.6B",
torch_dtype=torch.bfloat16, trust_remote_code=True)
model = PeftModel.from_pretrained(base, "<repo>/query_adapter", adapter_name="query_adapter")
model.load_adapter("<repo>/passage_adapter", adapter_name="passage_adapter")
model.set_adapter("query_adapter") # trước khi encode câu hỏi
model.set_adapter("passage_adapter") # trước khi encode điều luậtQuy ước bắt buộc (phải khớp lúc train)
- Không có instruction prefix. Encode raw text — thêm
"Instruct: ..."sẽ lệch phân bố. - Pooling = hidden state của token thật cuối cùng, sau đó L2-normalize.
max_length = 1024, truncation bên phải (mặc định của tokenizer).- Passage text =
article_title + "\n" + content(content = nối các khoản/điểm của cùng điều). - Encode query bằng `passage_adapter` (hoặc ngược lại) làm hỏng kết quả — đây là lỗi dễ mắc nhất khi dùng model này.
Huấn luyện
Loss vẫn giảm mạnh sau epoch 2 (1.00 → 0.63) nhưng recall không cải thiện — train loss ở đây không phải tín hiệu chọn checkpoint; các epoch sau là overfit trên synthetic question. Đó là lý do checkpoint được chọn là step 12 chứ không phải step 30.
Model liên quan
- `hoailebads/Qwen3-Embedding-0.6B-VLSP-Legal-Retrieval-LoRA` — bản retrieval cho corpus VLSP
- `hoailebads/Qwen3-Reranker-8B-VLSP-Legal-LoRA` — reranker
- `hoailebads/Qwen3-Reranker-0.6B-VLSP-Legal-LoRA` — reranker bản nhẹ
Code + số đo đầy đủ: hoaileba/Qwen-Retrieval-Tuning
Giới hạn
- Corpus Zalo và corpus VLSP khác nhau (định dạng
aid, cách chunk). Model này train và đo trên Zalo; dùng chéo sang VLSP chưa được đo. - Số ở đây đo trên
zalo_valset.json(637 câu). Một tập nhỏ hơn (zalo_eval.json, 198 câu) cho số cao hơn — hai tập không so sánh trực tiếp với nhau được. - Retrieval thuần, chưa có rerank.
License
Apache-2.0, theo base model Qwen/Qwen3-Embedding-0.6B.
