Instructions to use hoailebads/Qwen3-Reranker-8B-VLSP-Legal-LoRA with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use hoailebads/Qwen3-Reranker-8B-VLSP-Legal-LoRA with PEFT:
from peft import PeftModel from transformers import AutoModelForSequenceClassification base_model = AutoModelForSequenceClassification.from_pretrained("Qwen/Qwen3-Reranker-8B") model = PeftModel.from_pretrained(base_model, "hoailebads/Qwen3-Reranker-8B-VLSP-Legal-LoRA") - Notebooks
- Google Colab
- Kaggle
Qwen3-Reranker-8B — VLSP Vietnamese Legal Reranker (LoRA)
LoRA adapter cho Qwen/Qwen3-Reranker-8B, fine-tune để xếp hạng lại (rerank) điều luật
tiếng Việt cho bài toán truy hồi văn bản pháp luật VLSP: cho một câu hỏi pháp lý và một danh
sách điều luật ứng viên, model chấm điểm mức liên quan của từng cặp (câu hỏi, điều luật).
Đây là checkpoint tốt nhất (checkpoint-1100) sau khi quét toàn bộ 12 checkpoint của lần
train v6.
Kết quả trên private leaderboard VLSP: F2MACRO 0.7360 / R 0.7459 / P 0.6989 — vượt SOTA
VLSP (0.7261) +0.0099.
- 📦 Code, pipeline, script tune & tạo submission: https://github.com/hoaileba/Qwen-Retrieval-Tuning
- 📄 Tài liệu kỹ thuật chi tiết:
docs/PIPELINE.md
Điểm quan trọng: đây là đường SEQ_CLS, KHÔNG phải yes/no
Qwen3-Reranker-8B gốc chấm điểm theo đường generative (logit("yes") − logit("no")).
Adapter này không dùng đường đó. Model được nạp qua
AutoModelForSequenceClassification(num_labels=1), điểm là logit vô hướng ở token cuối, và
score head tuyến tính (Linear(hidden → 1, bias=False)) được huấn luyện cùng LoRA và lưu
trong file adapter dưới key base_model.model.score.weight (PEFT modules_to_save).
Hệ quả — muốn điểm đúng thì bắt buộc:
- Nạp base bằng
AutoModelForSequenceClassification(num_labels=1, ignore_mismatched_sizes=True)→ tạoscorehead mới (random), rồiPeftModel.from_pretrained(...)sẽ ghi đèscorehead bằng trọng số đã train (vì nó nằm trongmodules_to_save). Nạp lênAutoModelForCausalLMhoặc dùng prompt yes/no sẽ ra điểm vô nghĩa. - Đặt
pad_token_idvàpadding_side="left": SEQ_CLS pool hidden state ở token cuối cùng không phải pad. Thiếu bước này (pad sai / pad bên phải) → pool nhầm token → điểm sai hoàn toàn.
Code dưới đây khớp đúng với script inference trong repo
(reranker/eval_rerank_ckpt_nofaiss.py).
Kết quả
Tập eval: 219 câu hỏi VLSP (ground-truth dedup theo aid), rerank top-100 ứng viên do
bi-encoder Qwen3-Embedding-0.6B + dual-LoRA sinh ra. Đơn vị: %.
| Cấu hình | R@1 | R@3 | R@5 | R@10 |
|---|---|---|---|---|
| Retrieval thuần (không rerank) | 45.36 | 63.51 | 69.06 | 81.13 |
| Base Qwen3-Reranker-8B (chưa fine-tune, đường yes/no) | 55.25 | 72.75 | 79.76 | 83.90 |
| + LoRA này (checkpoint-1100) | 61.72 | 79.07 | 85.73 | 89.57 |
- Rerank thêm +16.4 R@1 so với retrieval thuần.
- Fine-tune LoRA thêm ~+6 điểm ở mọi k so với base 8B.
- Từ
checkpoint-1000trở đi model bão hoà (chênh < 0.5 điểm) → chọncheckpoint-1100. Bảng quét đầy đủ 12 checkpoint:eval_checkpoint_sweep.csv.
Leaderboard private VLSP — so sánh với SOTA
Số đo thật trên tập private của giải, điểm hoạt động @1-3 (chọn động 1–3 điều luật/query).
Bảng đầy đủ: private_leaderboard_vlsp.csv.
| Hệ thống | Precision | Recall | F2MACRO |
|---|---|---|---|
| SOTA (VLSP) | 0.6773 | 0.7394 | 0.7261 |
| Retrieval + reranker pretrained | 0.6580 | 0.5593 | 0.5767 |
| Retrieval + reranker 0.6B fine-tune | 0.5990 | 0.6937 | 0.6158 |
| Retrieval + adapter này (8B fine-tune) | 0.6989 | 0.7459 | 0.7360 |
| +0.0216 | +0.0065 | +0.0099 vs SOTA |
Vượt SOTA ở cả precision và recall cùng lúc. Đổi reranker 0.6B → 8B đáng giá +0.1202 F2, gấp ~3 lần lợi ích của việc fine-tune bản 0.6B (+0.0391 so với pretrained).
Điểm cuối phụ thuộc cả luật chọn số đáp án áp lên điểm rerank (xem submission/ trong repo
GitHub) — ground truth trung bình chỉ ~1.35 điều luật/câu hỏi, nên top-k cố định làm sụp precision:
cùng adapter này, F2 @1-3 = 0.7360 nhưng @10 chỉ 0.3707.
| Luật chọn top-k | Precision | Recall | F2MACRO |
|---|---|---|---|
softmax t=20, Pc=0.58, k≤4 |
0.6989 | 0.7459 | 0.7360 ✅ |
margin T=8, k≤3 |
0.7376 | 0.7196 | 0.7231 |
Cách dùng
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
BASE = "Qwen/Qwen3-Reranker-8B"
ADAPTER = "hoailebads/Qwen3-Reranker-8B-VLSP-Legal-LoRA"
MAX_DOC_LEN = 1024
INSTRUCTION = ("Given a Vietnamese legal question, retrieve the most relevant "
"legal article that directly answers the question")
# ── Tokenizer: BẮT BUỘC left-pad + có pad_token (SEQ_CLS pool token cuối) ──
tok = AutoTokenizer.from_pretrained(BASE, trust_remote_code=True)
if tok.pad_token is None:
tok.pad_token = tok.eos_token
tok.padding_side = "left" # pooling lấy token cuối
tok.truncation_side = "left" # giữ <eos> ở cuối chuỗi
# ── Model: SEQ_CLS(num_labels=1) → PeftModel nạp LoRA + score head ──
try:
import flash_attn # noqa
attn = "flash_attention_2"
except ImportError:
attn = "sdpa"
base = AutoModelForSequenceClassification.from_pretrained(
BASE, num_labels=1, torch_dtype=torch.bfloat16, trust_remote_code=True,
attn_implementation=attn,
ignore_mismatched_sizes=True, # score head mới ≠ lm_head gốc
)
base.config.pad_token_id = tok.pad_token_id # BẮT BUỘC — pool đúng token cuối
model = PeftModel.from_pretrained(base, ADAPTER) # ghi đè score head bằng trọng số đã train
model.eval().cuda()
# (tuỳ chọn) kiểm tra score head đã nạp — không phải random:
# from safetensors.torch import load_file
# sd = load_file(hf_hub_download(ADAPTER, "adapter_model.safetensors"))
# assert "base_model.model.score.weight" in sd # trọng số score head có trong adapter
def build_input(query: str, doc: str) -> str:
# Document pre-truncate TRƯỚC khi ghép chuỗi (khớp đúng lúc train / eval)
ids = tok(doc, add_special_tokens=False)["input_ids"]
if len(ids) > MAX_DOC_LEN:
doc = tok.decode(ids[:MAX_DOC_LEN], skip_special_tokens=True)
return (f"Instruct: {INSTRUCTION}\n"
f"query: {query}\n"
f"document: {doc}") + tok.eos_token
@torch.no_grad()
def score(query: str, docs: list[str]) -> list[float]:
texts = [build_input(query, d) for d in docs]
enc = tok(texts, add_special_tokens=False, padding=True, truncation=True,
max_length=MAX_DOC_LEN + 200, return_tensors="pt").to(model.device)
with torch.autocast(device_type="cuda", dtype=torch.bfloat16):
logits = model(input_ids=enc["input_ids"],
attention_mask=enc["attention_mask"]).logits # [B, 1]
return logits.squeeze(-1).float().cpu().tolist()
query = "Phạm nhân không biết chữ có được tạo điều kiện học văn hóa để xóa mù chữ không?"
docs = ["Điều 31. Chế độ học tập, học nghề của phạm nhân ...", "Điều 5. Nguyên tắc ..."]
ranked = sorted(zip(docs, score(query, docs)), key=lambda x: -x[1])
⚠️ Điểm là logit THÔ — chỉ so được TRONG cùng một query
score head xuất logit chưa hiệu chuẩn (dải quan sát khoảng −77 … 96.5). So sánh điểm giữa
các query khác nhau là sai. Vì vậy đừng dùng ngưỡng tuyệt đối để quyết định "có liên quan
hay không" — hãy dùng hình dạng điểm trong từng query:
margin: giữ vị tríikhiscore[0] − score[i] <= T(T=8), dừng ở vi phạm đầu tiên, capkmax=3.softmax(tốt hơn cho F2):p = softmax(score[:kmax]/temp), lấyknhỏ nhất sao chocumsum(p) >= Pcvớitemp=20, Pc=0.58, kmax=4.
Bằng chứng: 84.9% query có score@2 > 0 nhưng chỉ 28.3% thực sự có ≥2 đáp án đúng — ngưỡng tuyệt
đối over-predict rất nặng. Code sẵn: submission/make_dynamic_submission.py và
submission/make_softmax_submission.py trong repo GitHub.
Chi tiết huấn luyện
| Base model | Qwen/Qwen3-Reranker-8B (decoder-only, 36 layer) |
| Kiểu | AutoModelForSequenceClassification, num_labels=1, task_type=SEQ_CLS |
| LoRA | r=32, alpha=64, dropout=0.1 |
| Target modules | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
modules_to_save |
score (đầu điểm tuyến tính — huấn luyện đầy đủ) |
| Tham số huấn luyện | 87.3M (505 tensor; LoRA fp32, score head bf16) |
| Loss | listwise cross-entropy, positive ở index 0 (InfoNCE, KHÔNG in-batch negative) |
| Hard negative | 23/query → 24 passage/query, mine bằng dense retriever |
| Max length | doc pre-truncate 1024 token, tổng 1024 + 200 |
| Batch | batch_size=3 × grad_accum=5 |
| Optimizer | lr 1e-4, weight decay 0.05, warmup ratio 0.06, max_grad_norm=0.5 |
| Precision | bf16 + gradient checkpointing + flash-attention-2 (fallback sdpa) |
| Step | 1100 / epoch ≈ 1.65 (train loss 18.03 → 0.0003) |
| Seed | 42 |
Config đầy đủ: train_config.json.
Nội dung repo này
| File | |
|---|---|
adapter_model.safetensors |
LoRA weights + score head (349 MB) |
adapter_config.json |
config PEFT (base_model_name_or_path đã trỏ về Qwen/Qwen3-Reranker-8B) |
train_config.json |
toàn bộ hyperparameter của lần train |
eval_checkpoint_sweep.csv |
R@k / P@k của cả 12 checkpoint + baseline |
private_leaderboard_vlsp.csv |
số đo private VLSP: 4 cấu hình + SOTA, F2/R/P × @1-3…@500 |
Trạng thái optimizer/scheduler (optimizer.pt, scheduler.pt, rng_state.pth) không được
upload — chỉ cần cho việc resume training, không cần cho inference.
Giới hạn
- Chỉ dành cho truy hồi điều luật tiếng Việt; miền khác cần re-tune.
- Trần recall của pha retrieval là R@100 = 95.21 trên eval (0.9710 trên private) → ~5% câu hỏi không có đáp án đúng trong 100 ứng viên, reranker không thể cứu. (Trần này vẫn cao hơn SOTA VLSP: 0.9710 vs 0.9655 — khoảng cách với SOTA nằm ở reranker, không ở retrieval.)
- F2 gần bão hoà: chỉnh ngưỡng chỉ dịch P ↔ R, muốn tăng thật phải cải thiện retrieval hoặc reranker.
- Instruction và format input phải khớp chính xác như trên, sai lệch sẽ giảm điểm.
License
Adapter phát hành theo Apache-2.0. Trọng số base Qwen/Qwen3-Reranker-8B theo license riêng
của Qwen.
- Downloads last month
- 15