sidechat / masking.py
lsb's picture
Port steganacrostics to a Gradio app; retarget to MiniCPM5-1B
08b2dd2 verified
Raw
History Blame
1.9 kB
"""Shared grammar-legality computation + a per-state cache.
The set of grammar-legal next tokens is a pure function of the grammar state, so
we cache the boolean legal mask by state. This is what makes the crossing search
affordable: its many short rollouts all start from the same handful of
line-start states and reuse one (expensive) full-vocab scan.
"""
from __future__ import annotations
import numpy as np
import torch
class LegalCache:
def __init__(self, grammar, token_text, eos_token_ids=()):
self.grammar = grammar
self.token_text = token_text
self.eos_token_ids = [int(x) for x in eos_token_ids]
# Special tokens decode to '' and are always illegal — never probe them.
self._scan_ids = [i for i, t in enumerate(token_text) if t]
self._legal_cache = {} # state-key -> np.bool_ array
self._illegal_cache = {} # state-key -> torch.BoolTensor
@staticmethod
def _key(state):
return state if isinstance(state, int) else tuple(state)
def legal_np(self, state):
key = self._key(state)
cached = self._legal_cache.get(key)
if cached is not None:
return cached
advance = self.grammar.advance
token_text = self.token_text
at_accept = self.grammar.accepts(state)
legal = np.zeros(len(token_text), dtype=bool)
for i in self._scan_ids:
if advance(state, token_text[i]) != -1:
legal[i] = True
for eid in self.eos_token_ids:
legal[eid] = at_accept
self._legal_cache[key] = legal
return legal
def illegal_tensor(self, state):
key = self._key(state)
cached = self._illegal_cache.get(key)
if cached is not None:
return cached
t = torch.from_numpy(~self.legal_np(state))
self._illegal_cache[key] = t
return t