mirror of
https://github.com/mcloh/decision-models-benchmark.git
synced 2026-11-05 22:04:12 +00:00
setup models
This commit is contained in:
14
adapters/__init__.py
Normal file
14
adapters/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
"""Adaptadores JEV locais. Cada candidato roda num ambiente próprio (dependências incompatíveis)."""
|
||||
from importlib import import_module
|
||||
|
||||
REGISTRY = {
|
||||
"laya": "adapters.laya.adapter:LayaAdapter",
|
||||
"semif": "adapters.semif.adapter:SemIfAdapter",
|
||||
"gliner": "adapters.gliner.adapter:GlinerAdapter",
|
||||
"rizzo_flow": "adapters.rizzo_flow.adapter:RizzoFlowAdapter",
|
||||
}
|
||||
|
||||
|
||||
def adapter_class(name: str):
|
||||
module, cls = REGISTRY[name].split(":")
|
||||
return getattr(import_module(module), cls)
|
||||
55
adapters/gliner/adapter.py
Normal file
55
adapters/gliner/adapter.py
Normal file
@@ -0,0 +1,55 @@
|
||||
"""Adaptador do GLiNER2.5-multi-Decide via classify_text (tarefa 44).
|
||||
|
||||
O GLiNER devolve só o rótulo vencedor no modo de rótulo único. Para obter a
|
||||
distribuição completa, cada pergunta é enviada com `multi_label=True`,
|
||||
`class_act="softmax"` e `cls_threshold=0`: o modelo calcula o mesmo softmax do modo
|
||||
de rótulo único e devolve todas as opções com sua probabilidade.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dmb.adapter import Adapter
|
||||
from dmb.jev import ContextLimit, answer_from_distribution, option_descriptions
|
||||
|
||||
|
||||
class GlinerAdapter(Adapter):
|
||||
name = "gliner"
|
||||
probabilities_source = "native"
|
||||
|
||||
def load(self) -> None:
|
||||
from gliner2 import AutoExtractor
|
||||
from tokenizers import Tokenizer
|
||||
|
||||
self.model = AutoExtractor.from_pretrained(str(self.model_dir))
|
||||
if self.device not in ("auto", "cpu"):
|
||||
self.model.to(self.device)
|
||||
elif self.device == "auto":
|
||||
import torch
|
||||
if torch.cuda.is_available():
|
||||
self.model.to("cuda")
|
||||
self.model.eval()
|
||||
self.tokenizer = Tokenizer.from_file(str(self.model_dir / "tokenizer.json"))
|
||||
|
||||
def native_context_limit(self) -> int:
|
||||
return int(getattr(self.model.config, "max_len", 512) or 512)
|
||||
|
||||
def count_tokens(self, text: str) -> int:
|
||||
return len(self.tokenizer.encode(text, add_special_tokens=False).ids)
|
||||
|
||||
def decide(self, state: str, questions: dict) -> tuple[dict, int]:
|
||||
tokens = self.count_tokens(state)
|
||||
if tokens > self.context_limit:
|
||||
raise ContextLimit(f"{tokens} tokens > limite {self.context_limit}")
|
||||
# As instruções entram como prefixo do texto: o classify_text não tem campo de pergunta.
|
||||
answers = {}
|
||||
for name, question in questions.items():
|
||||
labels = option_descriptions(question)
|
||||
text = f"Pergunta: {question['instructions']}\n{state}"
|
||||
raw = self.model.classify_text(
|
||||
text,
|
||||
{name: {"labels": labels, "multi_label": True, "class_act": "softmax",
|
||||
"cls_threshold": 0.0}},
|
||||
include_confidence=True,
|
||||
)[name]
|
||||
dist = {item["label"]: item["confidence"] for item in raw}
|
||||
answers[name] = answer_from_distribution(question, dist)
|
||||
return answers, tokens
|
||||
51
adapters/laya/adapter.py
Normal file
51
adapters/laya/adapter.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Adaptador do Laya multilingual (tarefa 42)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from dmb.adapter import Adapter
|
||||
from dmb.jev import ContextLimit, answer_from_distribution, option_keys
|
||||
|
||||
|
||||
class LayaAdapter(Adapter):
|
||||
name = "laya"
|
||||
# Laya arredonda as probabilidades em 4 casas; o piso comum de dmb.jev cobre os zeros.
|
||||
probabilities_source = "native"
|
||||
|
||||
def load(self) -> None:
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import laya
|
||||
from tokenizers import Tokenizer
|
||||
|
||||
# laya.load() regrava tokenizer/tokenizer_config.json no próprio diretório
|
||||
# (_fix_tokenizer_config). Carregamos de uma cópia para não alterar os artefatos verificados.
|
||||
self._workdir = Path(tempfile.mkdtemp(prefix="dmb-laya-")) / "model"
|
||||
shutil.copytree(self.model_dir, self._workdir)
|
||||
self.agent = laya.load(str(self._workdir))
|
||||
self.tokenizer = Tokenizer.from_file(str(self.model_dir / "tokenizer" / "tokenizer.json"))
|
||||
|
||||
def native_context_limit(self) -> int:
|
||||
return 1024 # padrão do checkpoint; 256 desses tokens vão para pergunta + opções
|
||||
|
||||
def count_tokens(self, text: str) -> int:
|
||||
return len(self.tokenizer.encode(text, add_special_tokens=False).ids)
|
||||
|
||||
def decide(self, state: str, questions: dict) -> tuple[dict, int]:
|
||||
result = self.agent.predict(state, questions, max_len=self.context_limit)
|
||||
usage = result.get("usage", {})
|
||||
if usage.get("truncated") or usage.get("state_tokens_dropped"):
|
||||
raise ContextLimit(f"Laya truncou o estado: {usage}")
|
||||
answers = {}
|
||||
for name, question in questions.items():
|
||||
raw = result["answers"][name]
|
||||
if question["type"] == "noul":
|
||||
p = float(raw["noul"])
|
||||
dist = {"true": p, "false": 1 - p}
|
||||
else:
|
||||
dist = raw["probabilities"]
|
||||
if question["type"] == "score":
|
||||
dist = {k: dist[k] for k in option_keys(question)}
|
||||
answers[name] = answer_from_distribution(question, dist, raw.get("confidence"))
|
||||
return answers, int(usage.get("input_tokens", 0))
|
||||
59
adapters/rizzo_flow/adapter.py
Normal file
59
adapters/rizzo_flow/adapter.py
Normal file
@@ -0,0 +1,59 @@
|
||||
"""Adaptador do Rizzo Flow 4B (GGUF Q8_0 sobre llama.cpp) com prompts spark-decisions-v3 (tarefa 45)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from dmb.adapter import Adapter
|
||||
from dmb.jev import ContextLimit, answer_from_distribution, option_keys
|
||||
|
||||
GGUF_FILE = "spark-x2.5-4b-rizzo-flow-lora-q8_0.gguf"
|
||||
|
||||
|
||||
class RizzoFlowAdapter(Adapter):
|
||||
name = "rizzo_flow"
|
||||
probabilities_source = "native"
|
||||
|
||||
def __init__(self, *args, runtime_dir: str | Path | None = None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.runtime_dir = runtime_dir
|
||||
|
||||
def load(self) -> None:
|
||||
from rizzo_flow.backend_llama import LlamaBackend
|
||||
from rizzo_flow.compat import model_name
|
||||
from rizzo_flow.engine import Engine
|
||||
|
||||
device = "cuda" if self.device == "cuda" else ("cpu" if self.device == "cpu" else "auto")
|
||||
backend = LlamaBackend.load(self.model_dir / GGUF_FILE, device=device, ctx=self.context_limit,
|
||||
runtime_dir=self.runtime_dir)
|
||||
self.engine = Engine(backend, ctx=self.context_limit)
|
||||
self.served = model_name(backend.metadata)
|
||||
|
||||
def native_context_limit(self) -> int:
|
||||
return 2048 # comprimento máximo dos prompts de treino do LoRA
|
||||
|
||||
def count_tokens(self, text: str) -> int:
|
||||
return len(self.engine.backend.tokenizer.encode(text))
|
||||
|
||||
def decide(self, state: str, questions: dict) -> tuple[dict, int]:
|
||||
from rizzo_flow.compat import SystemOneRequest, from_native, to_native
|
||||
|
||||
request = SystemOneRequest.model_validate(
|
||||
{"model": self.served, "state": state, "questions": questions})
|
||||
native, options = to_native(request)
|
||||
try:
|
||||
raw = from_native(request, self.engine.decide(native), options, self.served)
|
||||
except ValueError as error:
|
||||
if "context" in str(error).lower() or "tokens" in str(error).lower():
|
||||
raise ContextLimit(str(error)) from error
|
||||
raise
|
||||
raw = raw if isinstance(raw, dict) else raw.model_dump()
|
||||
answers = {}
|
||||
for name, question in questions.items():
|
||||
a = raw["answers"][name]
|
||||
if question["type"] == "noul":
|
||||
p = float(a["noul"])
|
||||
dist = {"true": p, "false": 1 - p}
|
||||
else:
|
||||
dist = {k: a["probabilities"][k] for k in option_keys(question)}
|
||||
answers[name] = answer_from_distribution(question, dist, a.get("confidence"))
|
||||
return answers, int(raw.get("usage", {}).get("input_tokens", 0))
|
||||
45
adapters/semif/adapter.py
Normal file
45
adapters/semif/adapter.py
Normal file
@@ -0,0 +1,45 @@
|
||||
"""Adaptador do SemIf (modo direct) com Qwen3.5-4B fixado (tarefa 43)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dmb.adapter import Adapter
|
||||
from dmb.jev import ContextLimit, answer_from_distribution, option_descriptions
|
||||
|
||||
|
||||
class SemIfAdapter(Adapter):
|
||||
name = "semif"
|
||||
probabilities_source = "native"
|
||||
|
||||
def load(self) -> None:
|
||||
from semif_phase1 import core, direct
|
||||
|
||||
dev = self.device
|
||||
if dev == "auto":
|
||||
import torch
|
||||
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
||||
self._direct = direct
|
||||
self.model, self.tokenizer, self.metadata = core.load_causal_model(
|
||||
str(self.model_dir), self.revision, device=dev, dtype="bfloat16")
|
||||
|
||||
def native_context_limit(self) -> int:
|
||||
return 8192
|
||||
|
||||
def count_tokens(self, text: str) -> int:
|
||||
return len(self.tokenizer.encode(text, add_special_tokens=False))
|
||||
|
||||
def decide(self, state: str, questions: dict) -> tuple[dict, int]:
|
||||
answers, total_tokens = {}, 0
|
||||
for name, question in questions.items():
|
||||
options = option_descriptions(question)
|
||||
row = {"id": name, "state": state, "question": question["instructions"],
|
||||
"options": [{"id": k, "description": v} for k, v in options.items()]}
|
||||
try:
|
||||
out = self._direct.score(self.model, self.tokenizer, row, self.metadata,
|
||||
max_tokens=self.context_limit)
|
||||
except ValueError as error:
|
||||
if "exceed limit" in str(error):
|
||||
raise ContextLimit(str(error)) from error
|
||||
raise
|
||||
dist = dict(zip(out["option_ids"], out["probabilities"]))
|
||||
answers[name] = answer_from_distribution(question, dist)
|
||||
total_tokens += out["input_tokens"]
|
||||
return answers, total_tokens
|
||||
Reference in New Issue
Block a user