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 vocab_assignment.py from BorisTM/loss-guided-static-multi: direct link, hf CLI and curl.
- Browser
- Download file 3.25 kB
-
https://huggingface.co/BorisTM/loss-guided-static-multi/resolve/main/vocab_assignment.py
- Command line
-
hf download hf://BorisTM/loss-guided-static-multi/vocab_assignment.py
-
curl -L -o vocab_assignment.py https://huggingface.co/BorisTM/loss-guided-static-multi/resolve/main/vocab_assignment.py
3.25 kB
| """K vocabularies, and a learned assignment of languages to them. | |
| The earlier fusion attempt merged *per token*: two languages could share a row | |
| for one word and keep separate rows for the next. That is cell-level sharing, | |
| not vocabulary sharing, and it is why the readout came out as mush — the merge | |
| decision was never coupled across a language's tokens. | |
| Here the decision lives at the language level. Each language holds one | |
| distribution over K vocabularies, used for every token it emits: | |
| E[l, w] = E_shared[w] + sum_k pi[l, k] * Delta_k[w] | |
| `pi` is L x K — 48 numbers for twelve languages and four vocabularies — so the | |
| grouping is read directly off the parameter instead of being inferred from row | |
| distances. An entropy penalty pushes each language toward one vocabulary, and K | |
| is the explicit axis: K=1 is today's single shared vocabulary, K=L with a hard | |
| assignment is one per language, and where quality stops improving is the answer | |
| to how many a multilingual encoder actually needs. | |
| Offsets start at zero so the model is numerically identical to the shared one at | |
| step zero. The assignment logits carry the small random asymmetry instead: with | |
| identical offsets and a uniform assignment every expert would receive the same | |
| gradient and the K vocabularies would collapse back into one by symmetry. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| import torch | |
| from torch import nn | |
| class VocabAssignment(nn.Module): | |
| def __init__(self, index_path: str, vocab_size: int, dim: int, | |
| n_languages: int, n_vocabs: int = 4, seed: int = 20260727) -> None: | |
| super().__init__() | |
| data = np.load(index_path) | |
| tokens = np.unique(data["token_index"].astype(np.int64)) | |
| self.n_tokens = int(tokens.size) | |
| self.n_vocabs = int(n_vocabs) | |
| slot = torch.full((vocab_size,), -1, dtype=torch.long) | |
| slot[torch.tensor(tokens)] = torch.arange(self.n_tokens) | |
| self.register_buffer("slot", slot, persistent=False) | |
| self.offsets = nn.Parameter(torch.zeros(self.n_vocabs, self.n_tokens, dim)) | |
| generator = torch.Generator().manual_seed(seed) | |
| self.logits = nn.Parameter( | |
| torch.randn(n_languages, self.n_vocabs, generator=generator) * 0.01) | |
| def assignment(self) -> torch.Tensor: | |
| return torch.softmax(self.logits, dim=-1) | |
| def entropy(self) -> torch.Tensor: | |
| """Mean assignment entropy; penalising it drives each language to one slot.""" | |
| pi = self.assignment() | |
| return -(pi * torch.log(pi.clamp_min(1e-9))).sum(-1).mean() | |
| def forward(self, token_ids: torch.Tensor, lang_per_token: torch.Tensor, | |
| shared: torch.Tensor) -> torch.Tensor: | |
| index = self.slot[token_ids] | |
| has = index >= 0 | |
| if not bool(has.any()): | |
| return shared | |
| safe = index.clamp(min=0) | |
| # (K, N, dim) gathered offsets weighted by each token's language row of pi | |
| gathered = self.offsets[:, safe] | |
| weights = self.assignment()[lang_per_token] | |
| delta = torch.einsum("nk,knd->nd", weights, gathered) | |
| return shared + delta * has.unsqueeze(1) | |
| def extra_repr(self) -> str: | |
| return f"tokens={self.n_tokens}, vocabs={self.n_vocabs}" | |