safffrron's picture
Upload folder using huggingface_hub
8e4dac5 verified
Raw
History Blame Contribute Delete
5.43 kB
"""Tiny deterministic predictor for vocabulary rows omitted from an artifact.
The predictor borrows the idea of intra-frame prediction from codecs: a token's
string is side information already present in the tokenizer, so only a small
linear dictionary has to be stored. Exact retained rows are scattered over the
prediction during restoration.
"""
from __future__ import annotations
import math
from typing import Any
import torch
from .pack import pack_tensor, state_dict_bytes, unpack_tensor
FEATURE_DIM = 272
def _token_strings(tokenizer: Any, ids: list[int]) -> list[str]:
tokens = tokenizer.convert_ids_to_tokens(ids)
if isinstance(tokens, str):
tokens = [tokens]
return ["" if token is None else str(token) for token in tokens]
def token_string_features(tokenizer: Any, ids: torch.Tensor) -> torch.Tensor:
"""Return tokenizer-derived features without storing anything per token.
Layout: 256 normalized UTF-8 byte counts, eight log-length buckets, four
first-byte classes and four last-byte classes. The last two blocks retain a
little order information while keeping the dictionary under one megabyte.
"""
flat_ids = ids.detach().cpu().long().reshape(-1)
strings = _token_strings(tokenizer, [int(value) for value in flat_ids.tolist()])
features = torch.zeros((len(strings), FEATURE_DIM), dtype=torch.float32)
for row, token in enumerate(strings):
encoded = token.encode("utf-8", errors="replace") or b"\x00"
byte_ids = torch.tensor(list(encoded), dtype=torch.int64)
counts = torch.bincount(byte_ids, minlength=256).float()
features[row, :256] = counts / counts.square().sum().sqrt().clamp_min(1.0)
length_bucket = min(7, int(math.log2(max(1, len(encoded)))))
features[row, 256 + length_bucket] = 1.0
features[row, 264 + encoded[0] // 64] = 1.0
features[row, 268 + encoded[-1] // 64] = 1.0
return features
def _sample_ids(vocab_size: int, sample_size: int, offset: float = 0.0) -> torch.Tensor:
count = min(vocab_size, sample_size)
if count == vocab_size:
return torch.arange(vocab_size, dtype=torch.int64)
positions = (torch.arange(count, dtype=torch.float64) + offset) * vocab_size / count
return positions.floor().clamp_max(vocab_size - 1).long().unique()
def fit_token_predictor(
weight: torch.Tensor,
tokenizer: Any,
*,
sample_size: int = 65_536,
heldout_size: int = 4_096,
ridge: float = 1e-2,
device: str = "cpu",
) -> tuple[dict[str, Any], dict[str, float | int | str]]:
"""Fit and INT8-pack a ridge dictionary for a full embedding matrix."""
if weight.ndim != 2 or weight.shape[0] < 2:
raise ValueError(f"expected a vocabulary matrix, found {tuple(weight.shape)}")
if ridge <= 0:
raise ValueError("ridge must be positive")
train_ids = _sample_ids(weight.shape[0], sample_size)
x = token_string_features(tokenizer, train_ids).to(device)
y = weight.detach().cpu().index_select(0, train_ids).float().to(device)
gram = x.T @ x
gram.diagonal().add_(ridge)
basis = torch.linalg.solve(gram, x.T @ y).cpu()
heldout_ids = _sample_ids(weight.shape[0], heldout_size, offset=0.5)
# Exclude any collision with the evenly spaced training grid.
train_set = set(int(value) for value in train_ids.tolist())
heldout_list = [int(value) for value in heldout_ids.tolist() if int(value) not in train_set]
if not heldout_list:
heldout_list = [int(train_ids[-1])]
heldout_ids = torch.tensor(heldout_list, dtype=torch.int64)
hx = token_string_features(tokenizer, heldout_ids)
target = weight.detach().cpu().index_select(0, heldout_ids).float()
prediction = hx @ basis
cosine = torch.nn.functional.cosine_similarity(prediction, target, dim=1).mean()
denominator = target.square().mean().sqrt().clamp_min(1e-12)
relative_rmse = (prediction - target).square().mean().sqrt() / denominator
packed_basis = pack_tensor(basis, bits=8, group_size=256)
entry: dict[str, Any] = {
"kind": "token_string_ridge_v1",
"feature_dim": FEATURE_DIM,
"basis": packed_basis,
"vocab_size": int(weight.shape[0]),
"hidden_size": int(weight.shape[1]),
"ridge": float(ridge),
"sample_size": int(train_ids.numel()),
}
report: dict[str, float | int | str] = {
"kind": entry["kind"],
"stored_bytes": state_dict_bytes({"basis": packed_basis}),
"sample_size": int(train_ids.numel()),
"heldout_size": int(heldout_ids.numel()),
"heldout_mean_cosine": float(cosine),
"heldout_relative_rmse": float(relative_rmse),
}
return entry, report
def predict_token_rows(
entry: dict[str, Any],
tokenizer: Any,
*,
batch_size: int = 8_192,
) -> torch.Tensor:
"""Decode a predictor into the original dense vocabulary matrix."""
if entry.get("kind") != "token_string_ridge_v1":
raise ValueError(f"unsupported token predictor: {entry.get('kind')!r}")
basis = unpack_tensor(entry["basis"]).float()
rows = []
for start in range(0, int(entry["vocab_size"]), batch_size):
stop = min(int(entry["vocab_size"]), start + batch_size)
ids = torch.arange(start, stop, dtype=torch.int64)
rows.append((token_string_features(tokenizer, ids) @ basis).to(torch.bfloat16))
return torch.cat(rows, dim=0)