| """Backbone -> slot hidden states -> restricted logits over option letters.""" |
| import torch, torch.nn as nn, torch.nn.functional as F |
| from transformers import AutoModelForCausalLM, AutoTokenizer |
| from decider.prompt import letter_ids, MAX_OPTIONS |
|
|
|
|
| class DecisionModel(nn.Module): |
| def __init__(self, name, dtype=torch.bfloat16, grad_ckpt=True): |
| super().__init__() |
| if hasattr(torch.backends.cuda, "enable_cudnn_sdp"): |
| torch.backends.cuda.enable_cudnn_sdp(False) |
| self.tok = AutoTokenizer.from_pretrained(name) |
| self.lm = AutoModelForCausalLM.from_pretrained(name, dtype=dtype) |
| if grad_ckpt: |
| self.lm.gradient_checkpointing_enable() |
| self.register_buffer("letters", torch.tensor(letter_ids(self.tok)), persistent=False) |
|
|
| def slot_logits(self, input_ids, attention_mask, slot_idx, slot_batch, nopts): |
| """input_ids [B,T]; slot_idx/slot_batch [N] flat slot positions; nopts [N]. |
| Returns [N, MAX_OPTIONS] logits with invalid options masked to -inf.""" |
| h = self.lm.model(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state |
| hs = h[slot_batch, slot_idx] |
| W = self.lm.lm_head.weight[self.letters] |
| logits = F.linear(hs, W).float() |
| ar = torch.arange(MAX_OPTIONS, device=logits.device)[None, :] |
| logits = logits.masked_fill(ar >= nopts[:, None], float("-inf")) |
| return logits |
|
|
| def forward(self, batch): |
| return self.slot_logits(batch["input_ids"], batch["attention_mask"], batch["slot_idx"], batch["slot_batch"], batch["nopts"]) |
|
|
|
|
| def collate(items, pad_id): |
| """items: list of dicts from prompt.build (+ 'task', 'ex_id'). Right-pad.""" |
| T = max(len(it["ids"]) for it in items) |
| T = ((T + 63) // 64) * 64 |
| B = len(items) |
| input_ids = torch.full((B, T), pad_id, dtype=torch.long) |
| attn = torch.zeros((B, T), dtype=torch.long) |
| slot_idx, slot_batch, golds, nopts, tasks, qidx = [], [], [], [], [], [] |
| for b, it in enumerate(items): |
| n = len(it["ids"]) |
| input_ids[b, :n] = torch.tensor(it["ids"]) |
| attn[b, :n] = 1 |
| for k, s in enumerate(it["slots"]): |
| slot_idx.append(s); slot_batch.append(b); golds.append(it["golds"][k]); nopts.append(it["nopts"][k]) |
| tasks.append(it.get("task", "")); qidx.append(k) |
| return dict(input_ids=input_ids, attention_mask=attn, slot_idx=torch.tensor(slot_idx), slot_batch=torch.tensor(slot_batch), |
| golds=torch.tensor(golds), nopts=torch.tensor(nopts), tasks=tasks, qidx=qidx) |
|
|