Dev-4B / dmm /cache.py
suhaas-teja's picture
Dev-4B v0.1.0
a69969c verified
Raw History Blame Contribute Delete
2.94 kB
"""Shared prefill: encode a state once with the base, then run questions or generation on it.
The state is prefilled with the adapter off, which is exactly what the masked adapter computes
on state tokens. Each question suffix then runs with the adapter on against that cache, and the
cache is cropped back to the state afterwards, so any number of questions, and an escalation to
generation, reuse one prefill.
"""
import numpy as np
import torch
from dmm import lora
from dmm.head import option_logits
from dmm.template import Template, decision_text
class StateCache:
def __init__(self, model, ids: list[int]):
self.model, self.ids = model, list(ids)
with torch.no_grad(), lora.disabled(model):
out = model.model(input_ids=torch.tensor([ids], device=model.device), use_cache=True)
self.kv = out.past_key_values
def __len__(self) -> int:
return len(self.ids)
@torch.no_grad()
def run_suffix(self, suffix: list[int], adapter: bool = True) -> torch.Tensor:
"""Final hidden state at the suffix's last token; the cache is cropped back after."""
try:
ctx = lora.disabled(self.model) if not adapter else _null()
with ctx:
out = self.model.model(
input_ids=torch.tensor([suffix], device=self.model.device),
past_key_values=self.kv,
use_cache=True,
)
return out.last_hidden_state[0, -1]
finally:
self.kv.crop(len(self.ids))
class _null:
def __enter__(self):
return None
def __exit__(self, *exc):
return False
class CachedDecisionModel:
"""decide(items) through the shared-prefill path: one state prefill per distinct state."""
def __init__(self, model, tokenizer, head, temperatures=None):
self.model, self.head, self.tpl = model, head, Template(tokenizer)
self.temperatures = temperatures or {}
self.prefills = 0
@torch.no_grad()
def option_logits(self, items: list[dict]) -> list[torch.Tensor]:
out: list = [None] * len(items)
by_state: dict[str, list[int]] = {}
for i, it in enumerate(items):
by_state.setdefault(it["state"], []).append(i)
for _, idx in by_state.items():
cache = StateCache(self.model, self.tpl.state_ids(items[idx[0]]))
self.prefills += 1
for i in idx:
h = cache.run_suffix(self.tpl.encode(decision_text(items[i])))
out[i] = option_logits(self.head(h[None]), [items[i]])[0].float().cpu()
return out
def decide(self, items: list[dict]) -> list[np.ndarray]:
out = []
for z, it in zip(self.option_logits(items), items, strict=True):
z = z / self.temperatures.get(it["kind"], 1.0)
out.append(torch.softmax(z, -1).numpy().astype(np.float64))
return out