Download tiny_gdn/ngram.py from KerzgrAI/Monody-Exp: direct link, hf CLI and curl.
- Browser
- Download file 11.2 kB
-
https://huggingface.co/KerzgrAI/Monody-Exp/resolve/main/tiny_gdn/ngram.py
- Command line
-
hf download hf://KerzgrAI/Monody-Exp/tiny_gdn/ngram.py
-
curl -L -o ngram.py https://huggingface.co/KerzgrAI/Monody-Exp/resolve/main/tiny_gdn/ngram.py
11.2 kB
| """Hashed n-gram addressing shared by Senryu-2's model, its host table store, and serving. | |
| Each table row is addressed with integer ops that give the same result on CPU | |
| and CUDA: canonical token ids (NFKC-normalized, lowercased, stripped surface | |
| forms) are multiplied by odd 31-bit constants, XOR-combined, mixed, and reduced | |
| modulo a distinct prime per table. Token ids stay below 2**16, so every | |
| product stays below 2**47 and int64 never overflows. | |
| Tables live in one concatenated ``[total_rows, embedding_dim]`` tensor; table | |
| ``t`` owns rows ``[offset_t, offset_t + rows_t)``. The prime row counts, the | |
| multipliers, and the canonical map are saved beside every table file and | |
| checked on load, so a table can never be read with a different address map. | |
| """ | |
| from __future__ import annotations | |
| import unicodedata | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| from safetensors import safe_open | |
| from safetensors.torch import save_file | |
| from tokenizers import Tokenizer | |
| from tiny_gdn.config import TinyGDNConfig | |
| NGRAM_TABLES_FILE = "ngram_tables.safetensors" | |
| NGRAM_TABLES_EMA_FILE = "ngram_tables_ema.safetensors" | |
| NGRAM_OPTIMIZER_FILE = "ngram_optimizer.safetensors" | |
| PRIME_STRIDE = 4099 | |
| HASH_SEED = 0x6B696D6F | |
| MIX_SHIFT = 29 | |
| LOAD_CHUNK_ROWS = 1 << 20 | |
| _MASK64 = (1 << 64) - 1 | |
| _MILLER_RABIN_BASES = (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37) | |
| def _splitmix64(state: int) -> tuple[int, int]: | |
| state = (state + 0x9E3779B97F4A7C15) & _MASK64 | |
| mixed = ((state ^ (state >> 30)) * 0xBF58476D1CE4E5B9) & _MASK64 | |
| mixed = ((mixed ^ (mixed >> 27)) * 0x94D049BB133111EB) & _MASK64 | |
| return state, mixed ^ (mixed >> 31) | |
| def _is_prime(value: int) -> bool: | |
| if value < 2: | |
| return False | |
| for base in _MILLER_RABIN_BASES: | |
| if value % base == 0: | |
| return value == base | |
| odd, twos = value - 1, 0 | |
| while odd % 2 == 0: | |
| odd //= 2 | |
| twos += 1 | |
| for base in _MILLER_RABIN_BASES: | |
| witness = pow(base, odd, value) | |
| if witness in (1, value - 1): | |
| continue | |
| for _ in range(twos - 1): | |
| witness = witness * witness % value | |
| if witness == value - 1: | |
| break | |
| else: | |
| return False | |
| return True | |
| def _next_prime(value: int) -> int: | |
| while not _is_prime(value): | |
| value += 1 | |
| return value | |
| class NGramLayout: | |
| """Where every n-gram of every order lands in the concatenated tables.""" | |
| orders: tuple[int, ...] | |
| heads_per_order: int | |
| table_rows: tuple[int, ...] | |
| table_offsets: tuple[int, ...] | |
| multipliers: tuple[tuple[int, ...], ...] | |
| embedding_dim: int | |
| pad_token_id: int | |
| def from_config(cls, config: TinyGDNConfig) -> NGramLayout: | |
| if not config.ngram_orders: | |
| raise ValueError("config has no n-gram memory") | |
| rows: list[int] = [] | |
| candidate = config.ngram_table_rows | |
| for _ in range(config.ngram_table_count): | |
| candidate = _next_prime(candidate + PRIME_STRIDE) | |
| rows.append(candidate) | |
| offsets = tuple(int(value) for value in np.cumsum([0, *rows[:-1]])) | |
| state = HASH_SEED | |
| multipliers: list[tuple[int, ...]] = [] | |
| max_order = max(config.ngram_orders) | |
| for _ in range(config.ngram_table_count): | |
| table: list[int] = [] | |
| for _ in range(max_order): | |
| state, value = _splitmix64(state) | |
| table.append((value >> 33) | (1 << 30) | 1) | |
| multipliers.append(tuple(table)) | |
| return cls( | |
| orders=tuple(config.ngram_orders), | |
| heads_per_order=config.ngram_heads_per_order, | |
| table_rows=tuple(rows), | |
| table_offsets=offsets, | |
| multipliers=tuple(multipliers), | |
| embedding_dim=config.ngram_embedding_dim, | |
| pad_token_id=config.bos_token_id, | |
| ) | |
| def table_count(self) -> int: | |
| return len(self.table_rows) | |
| def total_rows(self) -> int: | |
| return self.table_offsets[-1] + self.table_rows[-1] | |
| def max_order(self) -> int: | |
| return max(self.orders) | |
| def memory_dim(self) -> int: | |
| return self.table_count * self.embedding_dim | |
| def table_orders(self) -> tuple[int, ...]: | |
| return tuple(order for order in self.orders for _ in range(self.heads_per_order)) | |
| def metadata_tensors(self) -> dict[str, torch.Tensor]: | |
| return { | |
| "table_rows": torch.tensor(self.table_rows, dtype=torch.int64), | |
| "hash_multipliers": torch.tensor(self.multipliers, dtype=torch.int64), | |
| } | |
| def check_metadata(self, tensors: dict[str, torch.Tensor], source: Path) -> None: | |
| for name, expected in self.metadata_tensors().items(): | |
| found = tensors.get(name) | |
| if found is None or not torch.equal(found.cpu(), expected): | |
| raise ValueError(f"{source} was written for a different n-gram layout ({name})") | |
| def canonical_token_ids(tokenizer: Tokenizer, vocab_size: int) -> torch.Tensor: | |
| """Map every token id to the smallest id sharing its normalized surface form. | |
| Normalization is NFKC, then strip, then lowercase. Special tokens keep | |
| their own ids, whitespace-only tokens are only NFKC-merged, and tokens | |
| that decode to partial UTF-8 stay distinct. | |
| """ | |
| special = set(tokenizer.get_added_tokens_decoder()) | |
| canonical = np.arange(vocab_size, dtype=np.int64) | |
| first_by_key: dict[tuple[str, str], int] = {} | |
| for token_id in range(min(vocab_size, tokenizer.get_vocab_size())): | |
| if token_id in special: | |
| continue | |
| text = tokenizer.decode([token_id], skip_special_tokens=False) | |
| if "\ufffd" in text: | |
| key = ("bytes", tokenizer.id_to_token(token_id) or str(token_id)) | |
| else: | |
| normalized = unicodedata.normalize("NFKC", text) | |
| folded = normalized.strip().lower() | |
| key = ("text", folded) if folded else ("space", normalized) | |
| canonical[token_id] = first_by_key.setdefault(key, token_id) | |
| return torch.from_numpy(canonical) | |
| def document_distance(tokens: torch.Tensor, bos_token_id: int, row_starts: torch.Tensor | None = None) -> torch.Tensor: | |
| """Offset of every token in a flat ``[N]`` stream from its document's first token. | |
| Documents start at position 0, at every BOS token, and at ``row_starts``. | |
| """ | |
| starts = tokens == bos_token_id | |
| starts[0] = True | |
| if row_starts is not None: | |
| starts[row_starts] = True | |
| positions = torch.arange(tokens.numel(), device=tokens.device) | |
| first = torch.where(starts, positions, torch.zeros_like(positions)).cummax(dim=0).values | |
| return positions - first | |
| def ngram_row_indices( | |
| canonical: torch.Tensor, | |
| layout: NGramLayout, | |
| *, | |
| distance: torch.Tensor | None = None, | |
| history: torch.Tensor | None = None, | |
| ) -> torch.Tensor: | |
| """Global table rows ``[..., L, tables]`` for the n-grams ending at each token. | |
| ``canonical`` holds int64 canonical ids ``[..., L]``. Tokens before a | |
| document start read as the pad id: with ``distance`` (same shape) a | |
| document starts wherever the distance is zero; with ``history`` (the | |
| ``max_order - 1`` canonical ids before position 0) the sequence continues | |
| a cached one; with neither, position 0 starts the only document. | |
| """ | |
| span = layout.max_order | |
| length = canonical.shape[-1] | |
| pad = layout.pad_token_id | |
| if history is not None: | |
| if history.shape[-1] != span - 1: | |
| raise ValueError(f"history must hold {span - 1} tokens") | |
| extended = torch.cat([history, canonical], dim=-1) | |
| shifted = [extended[..., span - 1 - back : span - 1 - back + length] for back in range(span)] | |
| else: | |
| shifted = [canonical] | |
| for back in range(1, span): | |
| if back >= length: | |
| previous = torch.full_like(canonical, pad) | |
| else: | |
| padding = canonical.new_full((*canonical.shape[:-1], back), pad) | |
| previous = torch.cat([padding, canonical[..., :-back]], dim=-1) | |
| if distance is not None: | |
| previous = torch.where(distance >= back, previous, pad) | |
| shifted.append(previous) | |
| columns = [] | |
| for table, order in enumerate(layout.table_orders): | |
| multipliers = layout.multipliers[table] | |
| mixed = shifted[0] * multipliers[0] | |
| for back in range(1, order): | |
| mixed = mixed ^ (shifted[back] * multipliers[back]) | |
| mixed = mixed ^ (mixed >> MIX_SHIFT) | |
| columns.append(torch.remainder(mixed, layout.table_rows[table]) + layout.table_offsets[table]) | |
| return torch.stack(columns, dim=-1) | |
| def save_table_file(path: Path, tables: torch.Tensor, layout: NGramLayout, canonical: torch.Tensor) -> None: | |
| if tables.shape != (layout.total_rows, layout.embedding_dim): | |
| raise ValueError(f"tables have shape {tuple(tables.shape)}, layout needs {(layout.total_rows, layout.embedding_dim)}") | |
| save_file( | |
| { | |
| "tables": tables.contiguous(), | |
| "canonical_ids": canonical.to(dtype=torch.int64, device="cpu").contiguous(), | |
| **layout.metadata_tensors(), | |
| }, | |
| str(path), | |
| ) | |
| def read_table_metadata(path: Path, layout: NGramLayout) -> torch.Tensor: | |
| """Check ``path`` against ``layout`` and return its canonical token map.""" | |
| with safe_open(str(path), framework="pt", device="cpu") as handle: | |
| metadata = {name: handle.get_tensor(name) for name in ("table_rows", "hash_multipliers")} | |
| layout.check_metadata(metadata, path) | |
| # get_tensor returns a view of a mapping of the whole file, which a kept | |
| # view would hold open; FUSE mounts such as ntfs-3g then cannot delete it. | |
| return handle.get_tensor("canonical_ids").clone() | |
| def copy_rows_from_file(path: Path, name: str, destination: torch.Tensor) -> None: | |
| """Copy tensor ``name`` of a safetensors file into ``destination`` in row chunks.""" | |
| with safe_open(str(path), framework="pt", device="cpu") as handle: | |
| source = handle.get_slice(name) | |
| shape = tuple(source.get_shape()) | |
| if shape != tuple(destination.shape): | |
| raise ValueError(f"{path} holds {name} of shape {shape}, expected {tuple(destination.shape)}") | |
| for start in range(0, shape[0], LOAD_CHUNK_ROWS): | |
| stop = min(start + LOAD_CHUNK_ROWS, shape[0]) | |
| destination[start:stop].copy_(source[start:stop]) | |
| def load_table_file( | |
| path: Path, | |
| layout: NGramLayout, | |
| *, | |
| device: torch.device | str = "cpu", | |
| dtype: torch.dtype = torch.float32, | |
| out: torch.Tensor | None = None, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """``(tables, canonical_ids)`` from ``path``, copied in row chunks to bound host memory.""" | |
| canonical = read_table_metadata(path, layout) | |
| tables = ( | |
| torch.empty((layout.total_rows, layout.embedding_dim), dtype=dtype, device=device) | |
| if out is None | |
| else out | |
| ) | |
| copy_rows_from_file(path, "tables", tables) | |
| return tables, canonical | |