tiny-cube-value / code /cube_tokenizer.py
briscoooe's picture
Value function training and state search
223c8ed verified
Raw
History Blame Contribute Delete
2.48 kB
"""
Tokenizer for the cube task.
Deliberately not a text tokenizer. The task has a tiny closed vocabulary -- six
sticker colours in, eighteen moves out -- so every token is one symbol with one
meaning, and a sequence is a fixed 54-token state followed by a short solution.
A BPE tokenizer would only introduce ambiguity (multi-character merges, casing,
whitespace) into an input that has none.
Sequence layout:
[BOS] s0 s1 ... s53 [SEP] m0 m1 ... mk [EOS]
Loss is taken only on the move span and [EOS]. The state is the conditioning
prompt -- training the model to predict the scrambled state would spend capacity
learning the distribution of cube states, which is not the task and which the
model gets for free as input anyway.
Both the trainer and the serving shim import this module, so their vocabularies
cannot drift apart. A silent mismatch there would look exactly like a model that
failed to learn.
"""
from typing import List
COLORS = list("URFDLB")
MOVES = [f + s for f in "URFDLB" for s in ("", "'", "2")]
SPECIALS = ["<pad>", "<bos>", "<sep>", "<eos>"]
VOCAB = SPECIALS + COLORS + MOVES
STOI = {t: i for i, t in enumerate(VOCAB)}
ITOS = {i: t for t, i in STOI.items()}
PAD, BOS, SEP, EOS = (STOI[t] for t in SPECIALS)
VOCAB_SIZE = len(VOCAB)
# 54 state tokens + BOS + SEP + a solution (Kociemba stays at or under 22 in the
# half-turn metric, God's Number being 20 for optimal solves) + EOS.
STATE_LEN = 54
MAX_SOLUTION = 26
MAX_SEQ = 1 + STATE_LEN + 1 + MAX_SOLUTION + 1
def encode_state(state: str) -> List[int]:
"""[BOS] + 54 colour tokens + [SEP] -- the prompt the model conditions on."""
if len(state) != STATE_LEN:
raise ValueError(f"expected {STATE_LEN} facelets, got {len(state)}")
return [BOS] + [STOI[c] for c in state] + [SEP]
def encode_solution(moves: List[str]) -> List[int]:
return [STOI[m] for m in moves] + [EOS]
def decode_solution(ids: List[int]) -> List[str]:
"""Stops at [EOS]; ignores anything that is not a move token."""
out = []
for i in ids:
if i == EOS:
break
tok = ITOS.get(int(i))
if tok in STOI and tok in MOVES:
out.append(tok)
return out
def encode_pair(state: str, moves: List[str]):
"""Returns (input_ids, labels) with labels masked to -100 over the prompt."""
prompt = encode_state(state)
answer = encode_solution(moves)
ids = prompt + answer
labels = [-100] * len(prompt) + answer
return ids, labels