ActionCodec2 / runtime /backends /actioncodec2 /unified_set_bpe.py
ZibinDong's picture
Publish audited ActionCodec2 release files
1bfc3f0 verified
Raw History Blame Contribute Delete
8.95 kB
"""Dense single-stage spatial/temporal Set-BPE using AC2Sparse's merge trainer."""
from __future__ import annotations
import json
from collections.abc import Sequence
from dataclasses import dataclass
from numbers import Integral
from pathlib import Path
import numpy as np
from ._validation import active_dimensions
from .bpe import BPE_NAME, _dense_cells, _read_payload, _rules
from .cache import BoundedCache
from .grammar import _PrefixGrammar
from .sparse.training import fit_sparse_vocabulary
from .sparse.vocabulary import SparseVocabulary
@dataclass(frozen=True)
class DenseSetLayout:
'Keep every active physical cell and append an end-of-frame atom.'
bins: tuple[int, ...]
dense = True
@property
def dims(self):
return len(self.bins)
@property
def e_dim(self):
return self.dims
@property
def zero(self):
return (-2,) * self.dims
@property
def native_zero(self):
return [-2] * self.dims
@property
def sparse_bins(self):
return self.bins + (1,)
def sparsify(self, cells):
cells = np.asarray(cells, dtype=np.int64)
if cells.ndim != 2 or cells.shape[1] != self.dims or not len(cells):
raise ValueError("cells must be a nonempty dense trajectory")
if np.any(cells < -1) or np.any(cells >= np.asarray(self.bins)):
raise ValueError("cell outside the primitive range")
if np.any((cells >= 0) != (cells[0] >= 0)) or not np.any(cells[0] >= 0):
raise ValueError("active slots must remain fixed and nonempty")
out = np.full((len(cells), self.dims + 1), -1, dtype=np.int64)
out[:, : self.dims] = cells
out[:-1, self.dims] = 0
return out
def densify(self, frames, active):
cells = np.asarray(frames, dtype=np.int64)[:, : self.dims].copy()
flags = np.asarray(active, dtype=bool)
if np.any(cells[:, flags] < 0) or np.any(cells[:, ~flags] >= 0):
raise ValueError("dense stream is missing an active cell")
return cells
class UnifiedSetBPESerializer:
'Single-stage Set-BPE over complete physical cells and frame markers.'
scheme = "unified_set_bpe"
def __init__(self, *, bins: Sequence[int], lookahead: int, merges):
self.bins = tuple(int(v) for v in bins)
self.lookahead = int(lookahead)
self.merges = _rules(merges, 3, "merges")
self.layout = DenseSetLayout(self.bins)
self.vocabulary = SparseVocabulary(self.layout, (), self.merges, self.lookahead)
self.vocab_size = self.vocabulary.vocab_size
physical_mask = (1 << len(self.bins)) - 1
self.token_cell_counts = np.asarray(
[
sum((int(mask) & physical_mask).bit_count() for mask in masks)
for masks in self.vocabulary.tables.masks
],
dtype=np.int64,
)
def configure_encoding(self, *, spatial_search=False, temporal_search=False, **kwargs):
if spatial_search or temporal_search:
raise ValueError("packing search is not available for unified Set-BPE")
return self
def encode_batch(self, cells, *, active_dims=None):
dense, _ = _dense_cells(np.asarray(cells), self.bins, active_dims, ndim=3)
return self.vocabulary.encode_batch(dense)
def encode(self, cells, *, active_dims=None):
dense, _ = _dense_cells(np.asarray(cells), self.bins, active_dims)
return self.vocabulary.encode_batch(dense[None])[0]
def decode_batch(self, streams, *, active_dims=None):
dims = active_dimensions(active_dims, len(self.bins))
rows, horizons = [], []
for row in streams:
checked = []
for token in row:
if (
isinstance(token, bool)
or not isinstance(token, Integral)
or not 0 <= int(token) < self.vocab_size
):
raise ValueError("token outside vocabulary")
checked.append(int(token))
count = sum(int(self.token_cell_counts[token]) for token in checked)
horizon, remainder = divmod(count, len(dims))
if not checked or not horizon or remainder:
raise ValueError("incomplete unified Set-BPE stream")
rows.append(checked)
horizons.append(horizon)
if not rows or len(set(horizons)) != 1:
raise ValueError("streams must have the same nonempty horizon")
flags = [d in dims for d in range(len(self.bins))]
return self.vocabulary.decode_batch(rows, horizons[0], flags)
def decode(self, tokens, *, active_dims=None):
return self.decode_batch([tokens], active_dims=active_dims)[0]
def to_dict(self):
return {"merges": [list(rule) for rule in self.merges]}
@classmethod
def from_pretrained(cls, directory: str | Path, *, bins, lookahead):
payload = _read_payload(directory)
if set(payload) != {"merges"}:
raise ValueError("unified bpe.json requires exactly merges")
return cls(bins=bins, lookahead=lookahead, merges=payload["merges"])
def save_pretrained(self, directory: str | Path):
path = Path(directory) / BPE_NAME
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(self.to_dict(), indent=2) + "\n", encoding="utf-8")
return path
class UnifiedSetBPEGrammar(_PrefixGrammar):
'Dense frame-coverage grammar for unified Set-BPE.'
def __init__(self, serializer, *, horizon, active_dims=None):
if isinstance(horizon, bool) or not isinstance(horizon, int) or horizon < 1:
raise ValueError("horizon must be positive")
self.horizon, self.vocab_size = horizon, serializer.vocab_size
dims = active_dimensions(active_dims, len(serializer.bins))
self._full = sum(1 << d for d in dims)
self._e_bit = 1 << len(serializer.bins)
self._e_atom = sum(serializer.bins)
self._patterns = serializer.vocabulary.tables.masks
self._lookahead = serializer.lookahead
self._state_cache, self._mask_cache = BoundedCache(256), BoundedCache(64)
self._empty_state = self._initial_state()
self._native = None
def _compiled(self):
return None
def _initial_state(self):
return (0,) * self.horizon
def _covered_steps(self, state):
for index, mask in enumerate(state):
needed = self._full | (self._e_bit if index < self.horizon - 1 else 0)
if mask != needed:
return index
return self.horizon
def _state_is_complete(self, state):
return self._covered_steps(state) == self.horizon
def _advance(self, state, token):
if not 0 <= token < self.vocab_size:
return None
front = self._covered_steps(state)
if front == self.horizon:
return None
masks = self._patterns[token]
if front + len(masks) > self.horizon:
return None
updated = list(state)
allowed = self._full | self._e_bit
for offset, mask in enumerate(masks):
mask = int(mask)
if not mask:
continue
index = front + offset
if mask & ~allowed or mask & updated[index] or updated[index] & self._e_bit:
return None
if index == self.horizon - 1 and mask & self._e_bit:
return None
updated[index] |= mask
if mask & self._e_bit and updated[index] & self._full != self._full:
return None
return tuple(updated)
def _python_mask(self, state):
return np.asarray(
[self._advance(state, token) is not None for token in range(self.vocab_size)]
)
def completion_token(self, state, zero_cells, bins):
front = self._covered_steps(state)
missing = self._full & ~state[front]
if not missing:
return self._e_atom
dim = (missing & -missing).bit_length() - 1
return sum(bins[:dim]) + int(zero_cells[dim])
def fit_unified_set_bpe(config, cells, *, threads, backend="native", progress=True):
"""Fit AC2Sparse's one-stage Set-BPE on complete dense-cell chunks."""
from .training import build_primitives
primitives = build_primitives(config)
layout = DenseSetLayout(tuple(primitives.bins))
vocab, report = fit_sparse_vocabulary(
layout,
cells,
spatial_vocab_size=sum(layout.sparse_bins),
vocab_size=config.train.bpe.vocab_size,
lookahead=config.set_bpe.lookahead,
backend=backend,
threads=threads,
progress=progress,
)
serializer = UnifiedSetBPESerializer(
bins=primitives.bins, lookahead=config.set_bpe.lookahead, merges=vocab.temporal_merges
)
return serializer, report