ADM-1 / inference.py
Aurley's picture
Upload NF4 decision scorer
85dad47 verified
Raw History Blame Contribute Delete
4.01 kB
from __future__ import annotations
from typing import Any, Sequence
import torch
from transformers import AutoModel, BitsAndBytesConfig, MistralCommonBackend
class MinistralDecisionInference:
def __init__(self, model, tokenizer):
self.model = model.eval()
self.tokenizer = tokenizer
self.device = model.get_input_embeddings().weight.device
self.temperature = float(model.decision_temperature)
@classmethod
def from_pretrained(cls, model_id: str = ".", *, quantized: bool = True):
quantization = None
if quantized:
quantization = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_use_double_quant=True,
bnb_4bit_compute_dtype=torch.float16,
)
model = AutoModel.from_pretrained(
model_id,
trust_remote_code=True,
quantization_config=quantization,
device_map={"": 0} if torch.cuda.is_available() else "cpu",
low_cpu_mem_usage=True,
)
tokenizer = MistralCommonBackend.from_pretrained(model_id)
return cls(model, tokenizer)
@staticmethod
def _pair_text(state: str, question: str, option: dict[str, str]) -> str:
criteria = option.get("criteria", "").strip()
criterion_text = f"\nCandidate criteria: {criteria}" if criteria else ""
return (
"Choose the best answer for this decision.\n"
f"Task: {question}\n"
f"Context: {state}\n"
f"Candidate answer: {option['label']}{criterion_text}\n"
"Does this candidate correctly answer the task?"
)
def score(self, state: str, question: str, options: Sequence[dict[str, str]], *, abstain_below: float | None = None) -> dict[str, Any]:
if len(options) < 2:
raise ValueError("Provide at least two options")
ids = [option["id"] for option in options]
if len(ids) != len(set(ids)):
raise ValueError("Option ids must be unique")
rows = []
for option in options:
encoded = self.tokenizer.encode(self._pair_text(state, question, option), return_tensors="pt")
input_ids = encoded.input_ids if hasattr(encoded, "input_ids") else encoded
rows.append(input_ids[0] if input_ids.ndim == 2 else input_ids)
pad_id = getattr(self.tokenizer, "pad_token_id", None)
if pad_id is None:
pad_id = getattr(self.tokenizer, "eos_token_id", 0) or 0
scores = []
for start in range(0, len(rows), 4):
batch = rows[start:start + 4]
lengths = torch.tensor([row.numel() for row in batch], dtype=torch.long)
width = int(lengths.max().item())
input_ids = torch.full((len(batch), width), int(pad_id), dtype=torch.long)
mask = torch.zeros((len(batch), width), dtype=torch.long)
for index, row in enumerate(batch):
input_ids[index, :row.numel()] = row
mask[index, :row.numel()] = 1
input_ids, mask = input_ids.to(self.device), mask.to(self.device)
with torch.inference_mode():
scores.extend(self.model.score_candidate_pairs(input_ids, mask).float().cpu().tolist())
probabilities = torch.softmax(torch.tensor(scores) / self.temperature, dim=0).tolist()
best = max(range(len(options)), key=probabilities.__getitem__)
confidence = probabilities[best]
abstain = abstain_below is not None and confidence < abstain_below
return {
"choice": None if abstain else ids[best],
"confidence": confidence,
"probabilities": dict(zip(ids, probabilities)),
"scores": dict(zip(ids, scores)),
"abstain": abstain,
"calibrated_temperature": self.temperature,
}