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")
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"
tok.truncation_side = "left"
try:
import flash_attn
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,
)
base.config.pad_token_id = tok.pad_token_id
model = PeftModel.from_pretrained(base, ADAPTER)
model.eval().cuda()
def build_input(query: str, doc: str) -> str:
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
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])