File size: 8,217 Bytes
35700de | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | """glint-router-1m: standalone inference. torch + tokenizers + safetensors, nothing else.
one forward pass over the prompt gives you every field at once: domain,
complexity, code, math, reasoning, long_output, route, and a 64-d projection
used for user-defined categories. no decoding loop, no output parsing.
"""
from __future__ import annotations
import json
import math
from dataclasses import dataclass
from pathlib import Path
import torch
from torch import Tensor, nn
from torch.nn import functional
HERE = Path(__file__).parent
PAD_ID = 0
BOS_ID = 1
BINARY_FIELDS = ("code", "math", "reasoning", "long_output")
COMPLEXITY_LEVELS = 5
DOMAINS = (
"programming", "web_dev", "databases", "devops_sysadmin", "security",
"data_science_ml", "math", "science", "engineering", "reasoning_puzzle",
"creative_writing", "professional_writing", "editing_grammar", "translation",
"factual_qa", "education", "business", "finance", "legal", "health",
"travel", "food", "entertainment", "lifestyle", "other",
)
@dataclass(frozen=True)
class RouterConfig:
vocab_size: int = 4096
dim: int = 128
n_heads: int = 8
layers: int = 3
ffn_hidden: int = 208
max_len: int = 256
rope_base: float = 10_000.0
proj_dim: int = 64
n_domains: int = len(DOMAINS)
def build_rope_cache(config: RouterConfig) -> tuple[Tensor, Tensor]:
head_dim = config.dim // config.n_heads
positions = torch.arange(config.max_len, dtype=torch.float32)
inv_freq = 1.0 / (config.rope_base ** (torch.arange(0, head_dim, 2).float() / head_dim))
angles = torch.outer(positions, inv_freq)
return torch.cos(angles), torch.sin(angles)
def apply_rope(x: Tensor, cos: Tensor, sin: Tensor) -> Tensor:
x_even, x_odd = x[..., 0::2], x[..., 1::2]
rotated_even = x_even * cos - x_odd * sin
rotated_odd = x_even * sin + x_odd * cos
return torch.stack((rotated_even, rotated_odd), dim=-1).flatten(-2)
class SwiGlu(nn.Module):
def __init__(self, dim: int, hidden: int) -> None:
super().__init__()
self.gate_up = nn.Linear(dim, 2 * hidden, bias=False)
self.down = nn.Linear(hidden, dim, bias=False)
def forward(self, x: Tensor) -> Tensor:
gate, up = self.gate_up(x).chunk(2, dim=-1)
return self.down(functional.silu(gate) * up)
class RouterAttention(nn.Module):
def __init__(self, config: RouterConfig) -> None:
super().__init__()
self.n_heads = config.n_heads
self.head_dim = config.dim // config.n_heads
self.qkv = nn.Linear(config.dim, 3 * config.dim, bias=False)
self.out = nn.Linear(config.dim, config.dim, bias=False)
def forward(self, x: Tensor, cos: Tensor, sin: Tensor, attn_mask: Tensor) -> Tensor:
batch, seq_len, dim = x.shape
q, k, v = self.qkv(x).split(dim, dim=-1)
shape = (batch, seq_len, self.n_heads, self.head_dim)
q = apply_rope(q.view(shape).transpose(1, 2), cos, sin)
k = apply_rope(k.view(shape).transpose(1, 2), cos, sin)
v = v.view(shape).transpose(1, 2)
attended = functional.scaled_dot_product_attention(q, k, v, attn_mask=attn_mask)
return self.out(attended.transpose(1, 2).reshape(batch, seq_len, dim))
class RouterBlock(nn.Module):
def __init__(self, config: RouterConfig) -> None:
super().__init__()
self.attn_norm = nn.RMSNorm(config.dim)
self.attn = RouterAttention(config)
self.ffn_norm = nn.RMSNorm(config.dim)
self.ffn = SwiGlu(config.dim, config.ffn_hidden)
def forward(self, x: Tensor, cos: Tensor, sin: Tensor, attn_mask: Tensor) -> Tensor:
x = x + self.attn(self.attn_norm(x), cos, sin, attn_mask)
return x + self.ffn(self.ffn_norm(x))
class GlintRouter(nn.Module):
def __init__(self, config: RouterConfig | None = None) -> None:
super().__init__()
config = config or RouterConfig()
self.config = config
self.embed = nn.Embedding(config.vocab_size, config.dim, padding_idx=PAD_ID)
self.blocks = nn.ModuleList(RouterBlock(config) for _ in range(config.layers))
self.final_norm = nn.RMSNorm(config.dim)
cos, sin = build_rope_cache(config)
self.register_buffer("rope_cos", cos, persistent=False)
self.register_buffer("rope_sin", sin, persistent=False)
pooled = 2 * config.dim
self.domain_head = nn.Linear(pooled, config.n_domains)
self.complexity_dir = nn.Linear(pooled, 1, bias=False)
self.complexity_bias = nn.Parameter(torch.zeros(COMPLEXITY_LEVELS - 1))
self.binary_head = nn.Linear(pooled, len(BINARY_FIELDS))
self.route_head = nn.Linear(pooled, 1)
self.proj_head = nn.Linear(pooled, config.proj_dim)
self.register_buffer("temperature", torch.ones(3), persistent=True)
def encode(self, tokens: Tensor) -> Tensor:
valid = tokens != PAD_ID
seq_len = tokens.shape[1]
cos = self.rope_cos[:seq_len].to(self.embed.weight.dtype)
sin = self.rope_sin[:seq_len].to(self.embed.weight.dtype)
attn_mask = torch.zeros(tokens.shape, dtype=self.embed.weight.dtype,
device=tokens.device)
attn_mask = attn_mask.masked_fill(~valid, float("-inf"))[:, None, None, :]
x = self.embed(tokens)
for block in self.blocks:
x = block(x, cos, sin, attn_mask)
x = self.final_norm(x)
mask = valid.unsqueeze(-1)
mean = (x * mask).sum(dim=1) / mask.sum(dim=1).clamp(min=1)
maximum = torch.nan_to_num(x.masked_fill(~mask, float("-inf")).max(dim=1).values,
neginf=0.0)
return torch.cat((mean, maximum), dim=-1)
def forward(self, tokens: Tensor) -> dict[str, Tensor]:
pooled = self.encode(tokens)
return {
"domain": self.domain_head(pooled),
"complexity": self.complexity_dir(pooled) + self.complexity_bias,
"binary": self.binary_head(pooled),
"route": self.route_head(pooled).squeeze(-1),
"proj": functional.normalize(self.proj_head(pooled), dim=-1),
}
def calibrated(self, tokens: Tensor) -> dict[str, Tensor]:
"""probabilities with the fitted temperatures applied. the routing
arithmetic in policy.py runs on these numbers, so they have to mean
something. temperatures were fitted on held-out data after training."""
out = self.forward(tokens)
route_t, complexity_t, binary_t = self.temperature.unbind()
return {
"domain": out["domain"].softmax(dim=-1),
"complexity": (out["complexity"] / complexity_t).sigmoid(),
"binary": (out["binary"] / binary_t).sigmoid(),
"route": (out["route"] / route_t).sigmoid(),
"proj": out["proj"],
}
def complexity_from_cumulative(probabilities: Tensor) -> Tensor:
"""coral decode. level = 1 + how many thresholds the prompt clears."""
return 1 + (probabilities > 0.5).sum(dim=-1)
def encode_batch(tokenizer, texts: list[str], max_len: int) -> Tensor:
"""bos-prefixed, right-padded. long prompts lose their tail, because the
instruction verb lives at the front and the pasted context lives at the back."""
out = torch.full((len(texts), max_len), PAD_ID, dtype=torch.long)
for row, text in enumerate(texts):
ids = [BOS_ID] + tokenizer.encode(text).ids[: max_len - 1]
out[row, : len(ids)] = torch.tensor(ids, dtype=torch.long)
return out
def load_router(directory: Path = HERE, device: str = "cpu"):
"""returns (model, tokenizer). reads config.json + model.safetensors + tokenizer.json."""
from safetensors.torch import load_file
from tokenizers import Tokenizer
directory = Path(directory)
config = RouterConfig(**json.loads((directory / "config.json").read_text())["model"])
model = GlintRouter(config).to(device)
model.load_state_dict(load_file(directory / "model.safetensors", device=device))
model.eval()
return model, Tokenizer.from_file(str(directory / "tokenizer.json"))
def count_parameters(model: nn.Module) -> int:
return sum(p.numel() for p in model.parameters())
|