CoolFace
Modelpublic

hoailebads/Qwen3-Embedding-0.6B-Zalo-Legal-Retrieval-LoRA

sourceHugging Faceapache-2.0updated 1mo agoView on Hugging Face
0likes
Model Card

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.

KRecall@KPrecision@KHit@K
165.7066.4166.41
383.7528.4184.62
587.8317.9388.70
1092.399.4593.09
10097.251.0097.65

So với các checkpoint Zalo khác (cùng tập 637 câu)

RunR@1R@10R@100
`zalo_synthetic_hoi_dap/checkpoint-12` ← model này65.7092.3997.25
train_zalo_dual (checkpoint cha, dual-adapter)64.6891.3797.25
train_zalo_single (single-adapter)58.3288.3096.39

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 đó:

python
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:

python
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ật

Quy ướ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

Base modelQwen/Qwen3-Embedding-0.6B (đóng băng)
LoRAr=16, alpha=32, dropout=0.05, target = q,k,v,o,gate,up,down_proj
Trainable20,185,088 / 615,961,600 params (3.28%), ×2 adapter
Lossfast_cached_mnrl_dual — cached multiple-negatives ranking, temperature=0.05
Hard negatives1/query (mine từ chính checkpoint cha)
Batch size256 (cached mini-batch 16)
Checkpoint chọnstep 12 = epoch 2/5 (train chạy đủ 30 step, epoch 2 là điểm eval tốt nhất)
Precisionbf16 + Flash Attention 2, 2×GPU torchrun
Resume từoutput/train_zalo_dual
Train lossepoch 1: 1.2065 → epoch 2: 1.0016 → epoch 5: 0.6327
Dataset2,666 sample — Zalo gốc + câu hỏi synthetic biến thể hỏi–đáp

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

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.