Instructions to use BorisTM/loss-guided-static-multi with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- sentence-transformers
How to use BorisTM/loss-guided-static-multi with sentence-transformers:
from sentence_transformers import SentenceTransformer model = SentenceTransformer("BorisTM/loss-guided-static-multi") sentences = [ "The weather is lovely today.", "It's so sunny outside!", "He drove to the stadium." ] embeddings = model.encode(sentences) similarities = model.similarity(embeddings, embeddings) print(similarities.shape) # [3, 3] - Notebooks
- Google Colab
- Kaggle
Download ngram_table.py from BorisTM/loss-guided-static-multi: direct link, hf CLI and curl.
- Browser
- Download file 3.81 kB
-
https://huggingface.co/BorisTM/loss-guided-static-multi/resolve/main/ngram_table.py
- Command line
-
hf download hf://BorisTM/loss-guided-static-multi/ngram_table.py
-
curl -L -o ngram_table.py https://huggingface.co/BorisTM/loss-guided-static-multi/resolve/main/ngram_table.py
3.81 kB
| """Hashed token n-gram rows for a static encoder. | |
| Fifteen representation-side interventions in this repo produced nothing, and the | |
| per-language numbers say why: quality tracks *tokenisation*, not capacity. | |
| Spearman between tokens-per-word and held-out Recall@1 is -0.607 over 57 | |
| space-separated languages; the third that tokenise worst average 0.191 against | |
| 0.295 for the third that tokenise best. Georgian needs 5.7 tokens per word and | |
| scores 0.050. | |
| Those languages do not lack a good vector for a word — they lack any row that | |
| *means* a word. A Transformer reassembles the pieces with attention; a | |
| mean-pooled static encoder cannot. The only repair available to it is a bigger | |
| lookup unit. | |
| So each adjacent token n-gram inside a sentence gets a row, hashed into a fixed | |
| table, pooled alongside the unigram rows: | |
| h(x) = ( sum_w E[w] + sum_g F[hash(g)] ) / (|U| + |G|) | |
| Allocation across languages needs no rule: a fragmented language simply emits | |
| more distinct n-grams and takes more of the table, while a language that already | |
| tokenises into words gains little. The budget follows the deficit by itself. | |
| Rows start at zero, so the pooled direction — and therefore every cosine — is | |
| identical to the unconditioned model at step zero, while the rows still receive | |
| gradient from the first batch. Unlike every conditioning head tried here, an | |
| n-gram row is a *new input feature* rather than a second route to an existing | |
| one, so the shared table cannot absorb it: the feature does not otherwise exist. | |
| """ | |
| from __future__ import annotations | |
| import torch | |
| from torch import nn | |
| # Odd 32-bit constants; the product mixes the low bits that adjacent token ids | |
| # share, which matters because ids are correlated within a language. | |
| _MIX_A = 2654435761 | |
| _MIX_B = 40503 | |
| class NgramTable(nn.Module): | |
| def __init__(self, buckets: int, dim: int, orders: tuple[int, ...] = (2,)) -> None: | |
| super().__init__() | |
| if any(order < 2 for order in orders): | |
| raise ValueError("n-gram orders must be at least 2") | |
| self.buckets = int(buckets) | |
| self.orders = tuple(orders) | |
| self.rows = nn.Parameter(torch.zeros(self.buckets, dim)) | |
| def extra_repr(self) -> str: | |
| return f"buckets={self.buckets}, orders={self.orders}" | |
| def _hash(self, ids: torch.Tensor, order: int) -> torch.Tensor: | |
| value = torch.zeros_like(ids[0]) | |
| for position in range(order): | |
| value = value * _MIX_A + ids[position] * _MIX_B + position | |
| return value.abs() % self.buckets | |
| def gather( | |
| self, | |
| content: torch.Tensor, | |
| segment: torch.Tensor, | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Rows and their sentence index for every n-gram inside a sentence.""" | |
| vectors: list[torch.Tensor] = [] | |
| segments: list[torch.Tensor] = [] | |
| for order in self.orders: | |
| if content.numel() <= order: | |
| continue | |
| window = [content[i: content.numel() - order + 1 + i] for i in range(order)] | |
| starts = segment[: segment.numel() - order + 1] | |
| # An n-gram must lie inside one sentence, so every position in the | |
| # window has to carry the same segment id. | |
| same = torch.ones_like(starts, dtype=torch.bool) | |
| for i in range(1, order): | |
| same &= segment[i: segment.numel() - order + 1 + i] == starts | |
| if not bool(same.any()): | |
| continue | |
| index = self._hash([w[same] for w in window], order) | |
| vectors.append(self.rows[index]) | |
| segments.append(starts[same]) | |
| if not vectors: | |
| empty = content.new_zeros((0,), dtype=torch.long) | |
| return self.rows.new_zeros((0, self.rows.shape[1])), empty | |
| return torch.cat(vectors), torch.cat(segments) | |