Text Generation
Transformers
Safetensors
thox_microprocessor
thox
tiny-language-model
edge-ai
custom-code
custom_code
Instructions to use Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories
- SGLang
How to use Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories with Docker Model Runner:
docker model run hf.co/Thox-ai/ThoxMicroprocessor-8M-Base-TinyStories
File size: 15,337 Bytes
7496ed2 | 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 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 | """Hugging Face remote-code implementation of THOX Microprocessor.
This file is uploaded verbatim to the model repository, so it must be
self-contained: it may not import the ``thox_microprocessor`` package. That
duplication is deliberate but dangerous — v0.1 shipped a copy whose submodules
were named ``n1``/``post``/``n2`` while the training package used
``attn_norm``/``post_attn_norm``/``mixer_norm``, so a published checkpoint would
have loaded with three randomly-initialised norms per layer and no error.
``tests/test_hub_parity.py`` now asserts strict state-dict compatibility and
logit equality between the two implementations.
Scope note: this reference path recomputes the full prefix for each decoding
step rather than maintaining a KV cache. It is portable across transformers
versions, which matters more for remote code than decode throughput; the
``thox_microprocessor`` package implements cached decoding for training and
deployment.
"""
from __future__ import annotations
import math
from typing import List, Optional, Tuple, Union
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import PreTrainedModel
from transformers.modeling_outputs import CausalLMOutputWithPast
try: # transformers >= 4.50 stopped mixing GenerationMixin into PreTrainedModel
from transformers import GenerationMixin
except ImportError: # pragma: no cover - older transformers
from transformers.generation import GenerationMixin
from .configuration_thox_microprocessor import ThoxMicroprocessorConfig
class RMSNorm(nn.Module):
def __init__(self, dim: int, eps: float = 1e-6) -> None:
super().__init__()
self.weight = nn.Parameter(torch.ones(dim))
self.eps = eps
def forward(self, x: torch.Tensor) -> torch.Tensor:
variance = x.float().pow(2).mean(dim=-1, keepdim=True)
return x * torch.rsqrt(variance + self.eps).to(dtype=x.dtype) * self.weight
class QKRMSNorm(RMSNorm):
"""Per-head Q/K RMS normalization applied before RoPE."""
def _rope_frequencies(seq_len: int, head_dim: int, theta: float, device) -> Tuple[torch.Tensor, torch.Tensor]:
inv_freq = 1.0 / (theta ** (torch.arange(0, head_dim, 2, device=device, dtype=torch.float32) / head_dim))
positions = torch.arange(seq_len, device=device, dtype=torch.float32)
angles = torch.outer(positions, inv_freq)
return angles.cos(), angles.sin()
def _apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
even = x[..., 0::2]
odd = x[..., 1::2]
cos = cos[None, None, :, :].to(dtype=x.dtype)
sin = sin[None, None, :, :].to(dtype=x.dtype)
return torch.stack((even * cos - odd * sin, even * sin + odd * cos), dim=-1).flatten(-2)
def _fwht_block(x: torch.Tensor) -> torch.Tensor:
n = x.shape[-1]
if n <= 0 or n & (n - 1):
raise ValueError(f"FWHT input width must be a power of two, got {n}")
y = x
h = 1
while h < n:
shape = y.shape
y = y.reshape(*shape[:-1], -1, 2, h)
a = y[..., 0, :]
b = y[..., 1, :]
y = torch.cat((a + b, a - b), dim=-1).reshape(*shape)
h *= 2
return y * (1.0 / math.sqrt(n))
def _stride_shuffle(x: torch.Tensor, block_size: int) -> torch.Tensor:
nblocks = x.shape[-1] // block_size
return x.reshape(*x.shape[:-1], block_size, nblocks).transpose(-1, -2).reshape(x.shape)
def _stride_unshuffle(x: torch.Tensor, block_size: int) -> torch.Tensor:
nblocks = x.shape[-1] // block_size
return x.reshape(*x.shape[:-1], nblocks, block_size).transpose(-1, -2).reshape(x.shape)
class BlockHadamardMixer(nn.Module):
def __init__(self, dim: int, block_size: int = 64, center: bool = True, shuffle: bool = False) -> None:
super().__init__()
if dim % block_size != 0:
raise ValueError(f"dim ({dim}) must be divisible by block_size ({block_size})")
self.dim = dim
self.block_size = block_size
self.num_blocks = dim // block_size
self.center = center
self.shuffle = shuffle and self.num_blocks > 1
self.d1 = nn.Parameter(torch.ones(dim))
self.d2 = nn.Parameter(torch.ones(dim))
self.d3 = nn.Parameter(torch.full((dim,), 0.02))
def forward(self, x: torch.Tensor) -> torch.Tensor:
shape = x.shape
y = x * self.d1
if self.shuffle:
y = _stride_shuffle(y, self.block_size)
y = y.reshape(*shape[:-1], self.num_blocks, self.block_size)
y = _fwht_block(y)
y = F.silu(y * self.d2.reshape(self.num_blocks, self.block_size))
if self.center:
y = y - y.mean(dim=-1, keepdim=True)
y = _fwht_block(y).reshape(shape)
if self.shuffle:
y = _stride_unshuffle(y, self.block_size)
return y * self.d3
class GroupedQueryAttention(nn.Module):
def __init__(self, config: ThoxMicroprocessorConfig) -> None:
super().__init__()
self.config = config
self.head_dim = config.d_model // config.num_heads
kv_dim = config.num_kv_heads * self.head_dim
self.q_proj = nn.Linear(config.d_model, config.d_model, bias=False)
self.k_proj = nn.Linear(config.d_model, kv_dim, bias=False)
self.v_proj = nn.Linear(config.d_model, kv_dim, bias=False)
self.out_proj = nn.Linear(config.d_model, config.d_model, bias=False)
self.head_gate = nn.Linear(config.d_model, config.num_heads, bias=False)
self.q_norm = QKRMSNorm(self.head_dim, config.rms_eps) if config.use_qk_norm else nn.Identity()
self.k_norm = QKRMSNorm(self.head_dim, config.rms_eps) if config.use_qk_norm else nn.Identity()
def forward(
self,
x: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
attn_bias: Optional[torch.Tensor] = None,
) -> torch.Tensor:
cfg = self.config
batch, seq_len, _ = x.shape
q = self.q_proj(x).view(batch, seq_len, cfg.num_heads, self.head_dim).transpose(1, 2)
k = self.k_proj(x).view(batch, seq_len, cfg.num_kv_heads, self.head_dim).transpose(1, 2)
v = self.v_proj(x).view(batch, seq_len, cfg.num_kv_heads, self.head_dim).transpose(1, 2)
q = _apply_rope(self.q_norm(q), cos, sin)
k = _apply_rope(self.k_norm(k), cos, sin)
repeats = cfg.num_heads // cfg.num_kv_heads
if repeats > 1:
k = k.repeat_interleave(repeats, dim=1)
v = v.repeat_interleave(repeats, dim=1)
scores = torch.matmul(q, k.transpose(-2, -1)) * (1.0 / math.sqrt(self.head_dim))
if attn_bias is not None:
scores = scores + attn_bias
probs = F.softmax(scores.float(), dim=-1).to(dtype=q.dtype)
probs = F.dropout(probs, p=cfg.attention_dropout, training=self.training)
context = torch.matmul(probs, v)
gate = torch.sigmoid(self.head_gate(x)).transpose(1, 2).unsqueeze(-1)
context = (context * gate).transpose(1, 2).contiguous().view(batch, seq_len, cfg.d_model)
return self.out_proj(context)
class ThoxMicroBlock(nn.Module):
def __init__(self, config: ThoxMicroprocessorConfig, layer_index: int = 0) -> None:
super().__init__()
self.config = config
self.attn_norm = RMSNorm(config.d_model, config.rms_eps)
self.attn = GroupedQueryAttention(config)
self.post_attn_norm = (
RMSNorm(config.d_model, config.rms_eps) if config.use_sandwich_norm else nn.Identity()
)
if config.use_hadamard_mixer:
self.mixer_norm = RMSNorm(config.d_model, config.rms_eps)
self.mixer = BlockHadamardMixer(
config.d_model,
config.hadamard_block_size,
center=config.mixer_center,
shuffle=config.mixer_block_shuffle and (layer_index % 2 == 1),
)
else:
self.mixer_norm = None
self.mixer = None
if config.use_residual_gate:
self.attn_gate = nn.Parameter(torch.tensor(0.0))
self.mixer_gate = nn.Parameter(torch.tensor(0.0))
else:
self.register_parameter("attn_gate", None)
self.register_parameter("mixer_gate", None)
def forward(
self,
x: torch.Tensor,
cos: torch.Tensor,
sin: torch.Tensor,
attn_bias: Optional[torch.Tensor] = None,
) -> torch.Tensor:
update = self.post_attn_norm(self.attn(self.attn_norm(x), cos, sin, attn_bias))
if self.attn_gate is not None:
update = torch.sigmoid(self.attn_gate) * update
x = x + update
if self.mixer is not None:
update = self.mixer(self.mixer_norm(x))
if self.mixer_gate is not None:
update = torch.sigmoid(self.mixer_gate) * update
x = x + update
return x
class ThoxMicroprocessorPreTrainedModel(PreTrainedModel):
config_class = ThoxMicroprocessorConfig
base_model_prefix = "thox_microprocessor"
supports_gradient_checkpointing = False
_supports_cache_class = False
_supports_sdpa = False
_supports_flash_attn_2 = False
# With tied embeddings there is no second tensor to tie, so nothing is
# shared and this stays empty. An empty dict is iterated identically by the
# transformers 4.x (list) and 5.x (mapping) code paths.
_tied_weights_keys: dict = {}
def _init_weights(self, module: nn.Module) -> None:
std = self.config.initializer_range
if isinstance(module, (nn.Linear, nn.Embedding)):
nn.init.normal_(module.weight, mean=0.0, std=std)
if isinstance(module, nn.Linear) and module.bias is not None:
nn.init.zeros_(module.bias)
elif isinstance(module, RMSNorm):
nn.init.ones_(module.weight)
elif isinstance(module, BlockHadamardMixer):
nn.init.ones_(module.d1)
nn.init.ones_(module.d2)
nn.init.constant_(module.d3, 0.02)
class ThoxMicroprocessorForCausalLM(ThoxMicroprocessorPreTrainedModel, GenerationMixin):
def __init__(self, config: ThoxMicroprocessorConfig) -> None:
super().__init__(config)
self.embed_tokens = nn.Embedding(config.vocab_size, config.d_model)
self.embed_dropout = nn.Dropout(config.embedding_dropout)
self.layers = nn.ModuleList(
[ThoxMicroBlock(config, layer_index=i) for i in range(config.num_layers)]
)
self.final_norm = RMSNorm(config.d_model, config.rms_eps)
self.lm_head = (
None
if config.tie_word_embeddings
else nn.Linear(config.d_model, config.vocab_size, bias=False)
)
self.post_init()
# Depth-scaled residual init, applied after post_init so it is not
# overwritten. This must match the training package exactly.
residual_std = config.initializer_range / math.sqrt(2 * config.num_layers)
for layer in self.layers:
nn.init.normal_(layer.attn.out_proj.weight, mean=0.0, std=residual_std)
def get_input_embeddings(self) -> nn.Module:
return self.embed_tokens
def set_input_embeddings(self, value: nn.Module) -> None:
self.embed_tokens = value
def get_output_embeddings(self):
return self.lm_head
def set_output_embeddings(self, value) -> None:
self.lm_head = value
def _output_weight(self) -> torch.Tensor:
return self.embed_tokens.weight if self.lm_head is None else self.lm_head.weight
def _attention_bias(self, batch, seq_len, attention_mask, dtype, device) -> torch.Tensor:
neg = torch.finfo(dtype).min
causal = torch.ones(seq_len, seq_len, dtype=torch.bool, device=device).tril()
bias = torch.where(
causal,
torch.zeros((), dtype=dtype, device=device),
torch.full((), neg, dtype=dtype, device=device),
).expand(batch, 1, seq_len, seq_len)
if attention_mask is not None:
if attention_mask.ndim != 2 or attention_mask.shape != (batch, seq_len):
raise ValueError(
f"attention_mask must have shape [{batch}, {seq_len}], "
f"got {tuple(attention_mask.shape)}"
)
pad = torch.where(
attention_mask.to(torch.bool),
torch.zeros((), dtype=dtype, device=device),
torch.full((), neg, dtype=dtype, device=device),
)
bias = bias + pad[:, None, None, :]
return bias
def forward(
self,
input_ids: Optional[torch.LongTensor] = None,
attention_mask: Optional[torch.Tensor] = None,
inputs_embeds: Optional[torch.FloatTensor] = None,
labels: Optional[torch.LongTensor] = None,
past_key_values: Optional[List[torch.FloatTensor]] = None,
use_cache: Optional[bool] = None,
return_dict: Optional[bool] = None,
**kwargs,
) -> Union[Tuple, CausalLMOutputWithPast]:
if (input_ids is None) == (inputs_embeds is None):
raise ValueError("provide exactly one of input_ids or inputs_embeds")
return_dict = True if return_dict is None else return_dict
if inputs_embeds is None:
if input_ids.ndim != 2:
raise ValueError(
f"input_ids must have shape [batch, seq_len], got {tuple(input_ids.shape)}"
)
inputs_embeds = self.embed_tokens(input_ids)
batch, seq_len, _ = inputs_embeds.shape
if seq_len > self.config.max_seq_len:
raise ValueError(
f"sequence length {seq_len} exceeds max_seq_len={self.config.max_seq_len}"
)
x = self.embed_dropout(inputs_embeds * math.sqrt(self.config.d_model))
device = x.device
cos, sin = _rope_frequencies(seq_len, self.config.head_dim, self.config.rope_theta, device)
attn_bias = self._attention_bias(batch, seq_len, attention_mask, x.dtype, device)
for layer in self.layers:
x = layer(x, cos, sin, attn_bias)
x = self.final_norm(x)
logits = F.linear(x, self._output_weight())
loss = None
if labels is not None:
if labels.shape[:2] != (batch, seq_len):
raise ValueError("labels must have shape [batch, seq_len]")
loss = F.cross_entropy(
logits[:, :-1].contiguous().view(-1, self.config.vocab_size).float(),
labels[:, 1:].contiguous().view(-1),
ignore_index=-100,
)
if not return_dict:
return (loss, logits) if loss is not None else (logits,)
return CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=None)
def prepare_inputs_for_generation(self, input_ids, attention_mask=None, **kwargs):
# No KV cache: always re-encode the (truncated) prefix.
window = self.config.max_seq_len
input_ids = input_ids[:, -window:]
if attention_mask is not None:
attention_mask = attention_mask[:, -window:]
return {"input_ids": input_ids, "attention_mask": attention_mask, "use_cache": False}
__all__ = [
"ThoxMicroprocessorConfig",
"ThoxMicroprocessorPreTrainedModel",
"ThoxMicroprocessorForCausalLM",
]
|