Text Generation
Transformers
Safetensors
English
weevil_recurrent
weevil
recurrent
gated-deltanet
custom_code
Instructions to use ELBennett/Weevil-Recurrent-16M-Base with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use ELBennett/Weevil-Recurrent-16M-Base with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="ELBennett/Weevil-Recurrent-16M-Base", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("ELBennett/Weevil-Recurrent-16M-Base", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use ELBennett/Weevil-Recurrent-16M-Base with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "ELBennett/Weevil-Recurrent-16M-Base" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ELBennett/Weevil-Recurrent-16M-Base", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }'Use Docker
docker model run hf.co/ELBennett/Weevil-Recurrent-16M-Base
- SGLang
How to use ELBennett/Weevil-Recurrent-16M-Base 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 "ELBennett/Weevil-Recurrent-16M-Base" \ --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": "ELBennett/Weevil-Recurrent-16M-Base", "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 "ELBennett/Weevil-Recurrent-16M-Base" \ --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": "ELBennett/Weevil-Recurrent-16M-Base", "prompt": "Once upon a time,", "max_tokens": 512, "temperature": 0.5 }' - Docker Model Runner
How to use ELBennett/Weevil-Recurrent-16M-Base with Docker Model Runner:
docker model run hf.co/ELBennett/Weevil-Recurrent-16M-Base
Download modeling_weevil_recurrent.py from ELBennett/Weevil-Recurrent-16M-Base: direct link, hf CLI and curl.
- Browser
- Download file 35.4 kB
-
https://huggingface.co/ELBennett/Weevil-Recurrent-16M-Base/resolve/main/modeling_weevil_recurrent.py
- Command line
-
hf download hf://ELBennett/Weevil-Recurrent-16M-Base/modeling_weevil_recurrent.py
-
curl -L -o modeling_weevil_recurrent.py https://huggingface.co/ELBennett/Weevil-Recurrent-16M-Base/resolve/main/modeling_weevil_recurrent.py
35.4 kB
| """PyTorch implementation of the Weevil recurrent GDN2 decoder. | |
| Prelude runs once. The four-layer core then repeats ``core_passes`` times, | |
| adding ``input_scale * prelude`` on every pass. Coda runs once. Next-token | |
| logits are tied through the factorized embedding. | |
| Each mixer keeps a convolution window and a Gated DeltaNet-2 state. Prefill | |
| runs the chunk schedule over the prompt and stores that state. Later tokens | |
| advance the same state one step at a time. Depth 16 is the evaluated setting. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| from transformers import PreTrainedModel | |
| from transformers.generation import GenerationMixin | |
| from transformers.modeling_outputs import CausalLMOutputWithPast | |
| from .configuration_weevil_recurrent import WeevilRecurrentConfig | |
| def _dtype(name: str) -> torch.dtype: | |
| return torch.bfloat16 if name == "bfloat16" else torch.float32 | |
| def apply_linear(x: torch.Tensor, layer: nn.Linear) -> torch.Tensor: | |
| weight = layer.weight.to(dtype=x.dtype) | |
| bias = None if layer.bias is None else layer.bias.to(dtype=x.dtype) | |
| return F.linear(x, weight, bias) | |
| def rmsnorm(x: torch.Tensor, eps: float) -> torch.Tensor: | |
| x32 = x.float() | |
| inv = torch.rsqrt(x32.square().mean(dim=-1, keepdim=True) + eps) | |
| return (x32 * inv).to(dtype=x.dtype) | |
| class WeevilRecurrentState: | |
| """Convolution windows and GDN2 states for one batch of sequences. | |
| ``recurrent_s`` has a separate state for every core pass. Passes share | |
| weights, but each pass sees a different residual and keeps its own time | |
| state. ``seq_length`` is how many tokens have already been folded in. | |
| """ | |
| def __init__( | |
| self, | |
| prelude_s: torch.Tensor, | |
| prelude_hist: torch.Tensor, | |
| recurrent_s: torch.Tensor, | |
| recurrent_hist: torch.Tensor, | |
| coda_s: torch.Tensor, | |
| coda_hist: torch.Tensor, | |
| seq_length: int, | |
| core_passes: int, | |
| ) -> None: | |
| self.prelude_s = prelude_s | |
| self.prelude_hist = prelude_hist | |
| self.recurrent_s = recurrent_s | |
| self.recurrent_hist = recurrent_hist | |
| self.coda_s = coda_s | |
| self.coda_hist = coda_hist | |
| self.seq_length = seq_length | |
| self.core_passes = core_passes | |
| def get_seq_length(self, layer_idx: int = 0) -> int: | |
| return self.seq_length | |
| def get_max_length(self) -> int: | |
| return -1 | |
| def batch_size(self) -> int: | |
| return self.prelude_s.shape[1] | |
| def clone(self) -> "WeevilRecurrentState": | |
| """Independent copy. Token steps write the state in place, so a fork has to own its tensors.""" | |
| return WeevilRecurrentState( | |
| self.prelude_s.clone(), | |
| self.prelude_hist.clone(), | |
| self.recurrent_s.clone(), | |
| self.recurrent_hist.clone(), | |
| self.coda_s.clone(), | |
| self.coda_hist.clone(), | |
| self.seq_length, | |
| self.core_passes, | |
| ) | |
| def repeat(self, count: int) -> "WeevilRecurrentState": | |
| """``count`` independent copies of each sequence, in batch order.""" | |
| if count < 1: | |
| raise ValueError("count must be positive") | |
| def copies(tensor: torch.Tensor, dim: int) -> torch.Tensor: | |
| return tensor.repeat_interleave(count, dim=dim) | |
| return WeevilRecurrentState( | |
| copies(self.prelude_s, 1), | |
| copies(self.prelude_hist, 2), | |
| copies(self.recurrent_s, 2), | |
| copies(self.recurrent_hist, 3), | |
| copies(self.coda_s, 1), | |
| copies(self.coda_hist, 2), | |
| self.seq_length, | |
| self.core_passes, | |
| ) | |
| class WeevilCausalLMOutput(CausalLMOutputWithPast): | |
| """Causal LM output. ``recurrent_state`` is the carried GDN2 state.""" | |
| recurrent_state: WeevilRecurrentState | None = None | |
| class CausalConv(nn.Module): | |
| def __init__(self, channels: int, kernel: int) -> None: | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.empty(channels, kernel)) | |
| def forward( | |
| self, x: torch.Tensor, hist: torch.Tensor | None | |
| ) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Causal depthwise conv. ``weight[:, -1]`` multiplies the current token. | |
| ``hist`` is the previous ``kernel - 1`` inputs, or ``None`` for zeros. | |
| Returns the activated outputs and the window to carry forward. | |
| """ | |
| channels, kernel = self.weight.shape | |
| lookback = kernel - 1 | |
| if hist is None: | |
| hist = x.new_zeros(x.shape[0], lookback, channels) | |
| full = x if lookback == 0 else torch.cat((hist, x), dim=1) | |
| rhs = self.weight.to(dtype=x.dtype).view(channels, 1, kernel) | |
| y = F.conv1d(full.transpose(1, 2), rhs, groups=channels) | |
| new_hist = full[:, -lookback:] if lookback else full[:, :0] | |
| return F.silu(y.transpose(1, 2)), new_hist | |
| def _gdn_prepare(q, k, v, g, b, w, valid, chunk_size): | |
| """Mask, normalize, and clamp. Shared by the chunk prefill and the token step.""" | |
| if valid is not None: | |
| mask = valid.to(dtype=q.dtype)[..., None, None] | |
| q, k, v, g, b, w = q * mask, k * mask, v * mask, g * mask, b * mask, w * mask | |
| q = q.float() | |
| k = k.float() | |
| q = q * torch.rsqrt(q.square().sum(dim=-1, keepdim=True) + 1e-6) | |
| k = k * torch.rsqrt(k.square().sum(dim=-1, keepdim=True) + 1e-6) | |
| q = q * (q.shape[-1] ** -0.5) | |
| v = v.float() | |
| g = torch.clamp(g.float(), min=-60.0 / chunk_size) | |
| return q, k, v, g, b.float(), w.float() | |
| def _forward_substitute(lower: torch.Tensor) -> torch.Tensor: | |
| size = lower.shape[-1] | |
| solved = lower | |
| index = torch.arange(size, device=lower.device) | |
| for row_index in range(1, size): | |
| mask = (index < row_index).to(dtype=lower.dtype) | |
| row = solved[..., row_index, :] | |
| masked_row = row * mask | |
| masked_block = solved * mask[..., :, None] * mask[..., None, :] | |
| updated = row + torch.matmul(masked_row.unsqueeze(-2), masked_block).squeeze(-2) | |
| updated = torch.where(mask.bool(), updated, row) | |
| solved = solved.clone() | |
| solved[..., row_index, :] = updated | |
| eye = torch.eye(size, dtype=solved.dtype, device=solved.device) | |
| return solved + eye | |
| def gdn2_chunk(q, k, v, g, b, w, chunk_size: int) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Chunk prefill from a zero state. Inputs are already prepared, ``[B, T, H, D]``.""" | |
| def heads_first(tensor: torch.Tensor) -> torch.Tensor: | |
| return tensor.permute(0, 2, 1, 3) | |
| q, k, v, g, b, w = (heads_first(tensor) for tensor in (q, k, v, g, b, w)) | |
| batch, heads, length, k_dim = k.shape | |
| v_dim = v.shape[-1] | |
| pad = (chunk_size - length % chunk_size) % chunk_size | |
| if pad: | |
| padding = (0, 0, 0, pad) | |
| q, k, v, g, b, w = (F.pad(tensor, padding) for tensor in (q, k, v, g, b, w)) | |
| total = length + pad | |
| n_chunks = total // chunk_size | |
| def as_chunks(tensor: torch.Tensor) -> torch.Tensor: | |
| return tensor.reshape(batch, heads, n_chunks, chunk_size, tensor.shape[-1]) | |
| q, k, v, g, b, w = (as_chunks(tensor) for tensor in (q, k, v, g, b, w)) | |
| g = torch.cumsum(g, dim=-2) | |
| decay = torch.exp(g) | |
| center = g[..., -1:, :] * 0.5 | |
| decay_centered = torch.exp(g - center) | |
| inv_decay_centered = torch.exp(center - g) | |
| erase = decay * b * k | |
| erase_centered = decay_centered * b * k | |
| key_inv = k * inv_decay_centered | |
| write = w * v | |
| interaction = -torch.matmul(erase_centered, key_inv.transpose(-1, -2)) | |
| strict = torch.tril(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device), diagonal=-1) | |
| lower = torch.where(strict, interaction, torch.zeros_like(interaction)) | |
| transition = _forward_substitute(lower) | |
| write = torch.matmul(transition, write) | |
| k_cumdecay = torch.matmul(transition, erase) | |
| query_decay = q * decay | |
| query_decay_centered = q * decay_centered | |
| g_last = g[..., -1:, :] | |
| state = q.new_zeros(batch, heads, k_dim, v_dim) | |
| outputs = [] | |
| causal = torch.tril(torch.ones(chunk_size, chunk_size, dtype=torch.bool, device=q.device)) | |
| for index in range(n_chunks): | |
| q_decay = query_decay[:, :, index] | |
| q_centered = query_decay_centered[:, :, index] | |
| key_inv_c = key_inv[:, :, index] | |
| write_c = write[:, :, index] | |
| k_cum_c = k_cumdecay[:, :, index] | |
| key_c = k[:, :, index] | |
| g_c = g[:, :, index] | |
| g_end = g_last[:, :, index] | |
| attn = torch.matmul(q_centered, key_inv_c.transpose(-1, -2)) | |
| attn = torch.where(causal, attn, torch.zeros_like(attn)) | |
| residual = write_c - torch.matmul(k_cum_c, state) | |
| out = torch.matmul(q_decay, state) + torch.matmul(attn, residual) | |
| key_bar = key_c * torch.exp(g_end - g_c) | |
| decay_state = torch.exp(g_end.squeeze(-2)).unsqueeze(-1) | |
| state = state * decay_state + torch.matmul(key_bar.transpose(-1, -2), residual) | |
| outputs.append(out) | |
| stacked = torch.stack(outputs, dim=2).reshape(batch, heads, total, v_dim)[:, :, :length] | |
| return stacked.permute(0, 2, 1, 3), state | |
| def _gdn2_token_pytorch(q, k, v, g, b, w, state: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Reference one-step update. ``q``..``w`` are ``[B, H, D]``, state is ``[B, H, D, D]``.""" | |
| alpha = g.exp() | |
| decayed = state * alpha.unsqueeze(-1) | |
| erase = torch.einsum("bhd,bhde->bhe", b * k, decayed) | |
| updated = decayed - torch.einsum("bhd,bhe->bhde", k, erase) | |
| updated = updated + torch.einsum("bhd,bhe->bhde", k, w * v) | |
| out = torch.einsum("bhde,bhd->bhe", updated, q) | |
| return out, updated | |
| try: | |
| import triton | |
| import triton.language as tl | |
| def _gdn2_token_kernel( | |
| q_ptr, k_ptr, v_ptr, g_ptr, b_ptr, w_ptr, s_ptr, o_ptr, | |
| stride_vb, stride_vh, stride_sb, stride_sh, stride_sd, | |
| D: tl.constexpr, | |
| ): | |
| """One head. Load the DxD state, apply the delta rule, store it back.""" | |
| pid_b = tl.program_id(0) | |
| pid_h = tl.program_id(1) | |
| offs = tl.arange(0, D) | |
| base = pid_b * stride_vb + pid_h * stride_vh | |
| sbase = pid_b * stride_sb + pid_h * stride_sh | |
| q = tl.load(q_ptr + base + offs) | |
| k = tl.load(k_ptr + base + offs) | |
| v = tl.load(v_ptr + base + offs) | |
| g = tl.load(g_ptr + base + offs) | |
| b = tl.load(b_ptr + base + offs) | |
| w = tl.load(w_ptr + base + offs) | |
| state = tl.load(s_ptr + sbase + offs[:, None] * stride_sd + offs[None, :]) | |
| alpha = tl.exp(g) | |
| decayed = state * alpha[:, None] | |
| erase = tl.sum(decayed * (b * k)[:, None], axis=0) | |
| updated = decayed + k[:, None] * ((w * v)[None, :] - erase[None, :]) | |
| tl.store(s_ptr + sbase + offs[:, None] * stride_sd + offs[None, :], updated) | |
| tl.store(o_ptr + base + offs, tl.sum(updated * q[:, None], axis=0)) | |
| def _conv_token_kernel( | |
| proj_ptr, hist_ptr, weight_ptr, out_ptr, | |
| proj_stride, hist_stride_stream, hist_stride_batch, hist_stride_tap, weight_stride, | |
| channels, | |
| BLOCK: tl.constexpr, | |
| ): | |
| """Four-tap depthwise conv for one token, then slide the window down.""" | |
| batch = tl.program_id(0) | |
| stream = tl.program_id(1) | |
| block = tl.program_id(2) | |
| offs = block * BLOCK + tl.arange(0, BLOCK) | |
| mask = offs < channels | |
| hist_base = stream * hist_stride_stream + batch * hist_stride_batch | |
| weight_base = (stream * channels + offs) * weight_stride | |
| h0 = tl.load(hist_ptr + hist_base + 0 * hist_stride_tap + offs, mask=mask, other=0.0).to(tl.float32) | |
| h1 = tl.load(hist_ptr + hist_base + 1 * hist_stride_tap + offs, mask=mask, other=0.0).to(tl.float32) | |
| h2 = tl.load(hist_ptr + hist_base + 2 * hist_stride_tap + offs, mask=mask, other=0.0).to(tl.float32) | |
| current = tl.load(proj_ptr + batch * proj_stride + stream * channels + offs, mask=mask, other=0.0) | |
| w0 = tl.load(weight_ptr + weight_base + 0, mask=mask, other=0.0).to(tl.float32) | |
| w1 = tl.load(weight_ptr + weight_base + 1, mask=mask, other=0.0).to(tl.float32) | |
| w2 = tl.load(weight_ptr + weight_base + 2, mask=mask, other=0.0).to(tl.float32) | |
| w3 = tl.load(weight_ptr + weight_base + 3, mask=mask, other=0.0).to(tl.float32) | |
| acc = h0 * w0 + h1 * w1 + h2 * w2 + current.to(tl.float32) * w3 | |
| out = (acc * tl.sigmoid(acc)).to(current.dtype) | |
| tl.store(out_ptr + (batch * 3 + stream) * channels + offs, out, mask=mask) | |
| tl.store(hist_ptr + hist_base + 0 * hist_stride_tap + offs, h1.to(current.dtype), mask=mask) | |
| tl.store(hist_ptr + hist_base + 1 * hist_stride_tap + offs, h2.to(current.dtype), mask=mask) | |
| tl.store(hist_ptr + hist_base + 2 * hist_stride_tap + offs, current, mask=mask) | |
| except ImportError: # pragma: no cover - CPU wheels have no Triton | |
| _gdn2_token_kernel = None | |
| _conv_token_kernel = None | |
| def _conv_token_cuda(projected: torch.Tensor, hist: torch.Tensor, weight: torch.Tensor): | |
| """Slide the q/k/v windows and return the activated conv outputs, each ``[B, 1, C]``.""" | |
| batch = projected.shape[0] | |
| channels = hist.shape[-1] | |
| out = torch.empty(batch, 3, channels, device=projected.device, dtype=projected.dtype) | |
| block = 128 | |
| _conv_token_kernel[(batch, 3, (channels + block - 1) // block)]( | |
| projected, hist, weight, out, | |
| projected.stride(0), hist.stride(0), hist.stride(1), hist.stride(2), weight.stride(0), | |
| channels, BLOCK=block, | |
| ) | |
| return out[:, 0:1], out[:, 1:2], out[:, 2:3] | |
| def _gdn2_token_cuda(q, k, v, g, b, w, state: torch.Tensor) -> torch.Tensor: | |
| batch, heads, dim = q.shape | |
| out = torch.empty(batch, heads, dim, device=q.device, dtype=torch.float32) | |
| _gdn2_token_kernel[batch, heads]( | |
| q, k, v, g, b, w, state, out, | |
| q.stride(0), q.stride(1), state.stride(0), state.stride(1), state.stride(2), | |
| D=dim, num_warps=4, | |
| ) | |
| return out | |
| def gdn2_token(q, k, v, g, b, w, state: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| """One time step. Inputs are prepared ``[B, 1, H, D]``. ``state`` is ``[B, H, D, D]``. | |
| On CUDA the state is updated in place. The returned state is that same tensor. | |
| """ | |
| q, k, v, g, b, w = (tensor[:, 0] for tensor in (q, k, v, g, b, w)) | |
| vectors = (q, k, v, g, b, w) | |
| inplace = ( | |
| _gdn2_token_kernel is not None | |
| and state.is_cuda | |
| and state.is_contiguous() | |
| and state.dtype == torch.float32 | |
| and all(tensor.is_contiguous() and tensor.dtype == torch.float32 for tensor in vectors) | |
| ) | |
| if inplace: | |
| out = _gdn2_token_cuda(*vectors, state) | |
| return out[:, None], state | |
| out, updated = _gdn2_token_pytorch(*vectors, state) | |
| return out[:, None], updated | |
| class Mixer(nn.Module): | |
| def __init__(self, config: WeevilRecurrentConfig) -> None: | |
| super().__init__() | |
| hidden = config.hidden_size | |
| width = config.num_heads * config.head_dim | |
| head = config.head_dim | |
| self.config = config | |
| self.q_proj = nn.Linear(hidden, width, bias=False) | |
| self.k_proj = nn.Linear(hidden, width, bias=False) | |
| self.v_proj = nn.Linear(hidden, width, bias=False) | |
| self.q_conv = CausalConv(width, config.conv_kernel) | |
| self.k_conv = CausalConv(width, config.conv_kernel) | |
| self.v_conv = CausalConv(width, config.conv_kernel) | |
| self.f_in = nn.Linear(hidden, head, bias=False) | |
| self.f_out = nn.Linear(head, width, bias=False) | |
| self.b_proj = nn.Linear(hidden, width, bias=False) | |
| self.w_proj = nn.Linear(hidden, width, bias=False) | |
| self.g_in = nn.Linear(hidden, head, bias=False) | |
| self.g_out = nn.Linear(head, width, bias=True) | |
| self.o_proj = nn.Linear(width, hidden, bias=False) | |
| self.o_norm = nn.Parameter(torch.ones(head)) | |
| self.A_log = nn.Parameter(torch.empty(config.num_heads)) | |
| self.dt_bias = nn.Parameter(torch.empty(width)) | |
| self._packed_input = None | |
| self._packed_conv = None | |
| def _pack(self, dtype: torch.dtype) -> tuple[torch.Tensor, torch.Tensor]: | |
| """One input GEMM and one depthwise conv cover q, k, v, erase, write, and the two small ins.""" | |
| if ( | |
| self._packed_input is not None | |
| and self._packed_input.dtype == dtype | |
| and self._packed_input.device == self.q_proj.weight.device | |
| ): | |
| return self._packed_input, self._packed_conv | |
| parts = ( | |
| self.q_proj.weight, | |
| self.k_proj.weight, | |
| self.v_proj.weight, | |
| self.b_proj.weight, | |
| self.w_proj.weight, | |
| self.f_in.weight, | |
| self.g_in.weight, | |
| ) | |
| self._packed_input = torch.cat([part.detach().to(dtype=dtype) for part in parts], dim=0) | |
| convs = (self.q_conv.weight, self.k_conv.weight, self.v_conv.weight) | |
| self._packed_conv = torch.cat([part.detach().to(dtype=dtype) for part in convs], dim=0) | |
| return self._packed_input, self._packed_conv | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| valid: torch.Tensor | None, | |
| s: torch.Tensor | None, | |
| hist: torch.Tensor | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| config = self.config | |
| compute = _dtype(config.compute_dtype) | |
| batch, length, _ = x.shape | |
| heads, head = config.num_heads, config.head_dim | |
| width = heads * head | |
| x_c = x.to(dtype=compute) | |
| packed_w, packed_conv = self._pack(compute) | |
| projected = F.linear(x_c, packed_w) | |
| q, k, v, erase_in, write_in, decay_in, gate_in = projected.split( | |
| (width, width, width, width, width, head, head), dim=-1 | |
| ) | |
| projected_q, projected_k, projected_v = q, k, v | |
| channels = packed_conv.shape[0] | |
| lookback = packed_conv.shape[1] - 1 | |
| fast_conv = ( | |
| _conv_token_kernel is not None | |
| and length == 1 | |
| and hist is not None | |
| and lookback == 3 | |
| and projected.is_cuda | |
| ) | |
| if fast_conv: | |
| q, k, v = _conv_token_cuda(projected, hist, packed_conv) | |
| new_hist = hist | |
| else: | |
| qkv = torch.cat((projected_q, projected_k, projected_v), dim=-1) | |
| if hist is None: | |
| conv_hist = qkv.new_zeros(batch, lookback, channels) | |
| else: | |
| conv_hist = torch.cat((hist[0], hist[1], hist[2]), dim=-1) | |
| full = torch.cat((conv_hist, qkv), dim=1) | |
| if length == 1: | |
| activated = F.silu((full * packed_conv.transpose(0, 1)).sum(dim=1, keepdim=True)) | |
| else: | |
| y = F.conv1d( | |
| full.transpose(1, 2), | |
| packed_conv.view(channels, 1, lookback + 1), | |
| groups=channels, | |
| ) | |
| activated = F.silu(y.transpose(1, 2)) | |
| q, k, v = activated.split((width, width, width), dim=-1) | |
| if length == 1 and hist is not None and lookback: | |
| for shift in range(lookback - 1): | |
| hist[:, :, shift].copy_(hist[:, :, shift + 1]) | |
| hist[0, :, -1].copy_(projected_q[:, 0]) | |
| hist[1, :, -1].copy_(projected_k[:, 0]) | |
| hist[2, :, -1].copy_(projected_v[:, 0]) | |
| new_hist = hist | |
| else: | |
| new_window = full[:, -lookback:] | |
| q_hist, k_hist, v_hist = new_window.split((width, width, width), dim=-1) | |
| new_hist = torch.stack((q_hist, k_hist, v_hist), dim=0) | |
| decay = F.softplus( | |
| apply_linear(decay_in, self.f_out).float() + self.dt_bias.float() | |
| ) | |
| decay = decay.reshape(batch, length, heads, head) | |
| rate = torch.exp(self.A_log.float()) | |
| log_decay = -rate[None, None, :, None] * decay | |
| erase = torch.sigmoid(erase_in).reshape(batch, length, heads, head) | |
| write = torch.sigmoid(write_in).reshape(batch, length, heads, head) | |
| q, k, v, g, b, w = _gdn_prepare( | |
| q.reshape(batch, length, heads, head), | |
| k.reshape(batch, length, heads, head), | |
| v.reshape(batch, length, heads, head), | |
| log_decay, | |
| erase, | |
| write, | |
| valid, | |
| config.chunk_size, | |
| ) | |
| if length == 1: | |
| if s is None: | |
| s = q.new_zeros(batch, heads, head, head) | |
| out, s_new = gdn2_token(q, k, v, g, b, w, s) | |
| else: | |
| out, s_new = gdn2_chunk(q, k, v, g, b, w, config.chunk_size) | |
| gate = apply_linear(gate_in, self.g_out).reshape(batch, length, heads, head) | |
| out32 = out.float() | |
| inv = torch.rsqrt(out32.square().mean(dim=-1, keepdim=True) + config.norm_eps) | |
| out = out32 * inv * self.o_norm * torch.sigmoid(gate.float()) | |
| flat = out.reshape(batch, length, config.num_heads * head).to(dtype=compute) | |
| return apply_linear(flat, self.o_proj), s_new, new_hist | |
| class SwiGLU(nn.Module): | |
| def __init__(self, config: WeevilRecurrentConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.in_proj = nn.Linear(config.hidden_size, 2 * config.intermediate_size, bias=False) | |
| self.out_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| compute = _dtype(self.config.compute_dtype) | |
| gate, up = apply_linear(x.to(dtype=compute), self.in_proj).chunk(2, dim=-1) | |
| return apply_linear(F.silu(gate) * up, self.out_proj) | |
| class Gate(nn.Module): | |
| def __init__(self, hidden: int) -> None: | |
| super().__init__() | |
| self.pre = nn.Parameter(torch.empty(hidden)) | |
| self.post = nn.Parameter(torch.empty(hidden)) | |
| self.alpha = nn.Parameter(torch.empty(2)) | |
| self.bias = nn.Parameter(torch.empty(2)) | |
| def forward(self, normed: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| flat = normed.float() | |
| pre = (flat * self.pre.float()).sum(dim=-1) | |
| post = (flat * self.post.float()).sum(dim=-1) | |
| pre = self.alpha[0].float() * pre + self.bias[0].float() | |
| post = self.alpha[1].float() * post + self.bias[1].float() | |
| return torch.sigmoid(pre), 2 * torch.sigmoid(post) | |
| class Block(nn.Module): | |
| def __init__(self, config: WeevilRecurrentConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.mixer = Mixer(config) | |
| self.mlp = SwiGLU(config) | |
| self.mixer_gate = Gate(config.hidden_size) | |
| self.mlp_gate = Gate(config.hidden_size) | |
| def _read(self, residual: torch.Tensor, gate: Gate) -> tuple[torch.Tensor, torch.Tensor]: | |
| normed = rmsnorm(residual, self.config.norm_eps) | |
| pre, post = gate(normed) | |
| hidden = (pre.unsqueeze(-1) * normed).to(dtype=_dtype(self.config.compute_dtype)) | |
| return hidden, post | |
| def forward( | |
| self, | |
| residual: torch.Tensor, | |
| valid: torch.Tensor | None, | |
| s: torch.Tensor | None, | |
| hist: torch.Tensor | None, | |
| ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| hidden, post = self._read(residual, self.mixer_gate) | |
| mixed, s_new, hist_new = self.mixer(hidden, valid, s, hist) | |
| residual = residual + post.unsqueeze(-1) * mixed.float() | |
| hidden, post = self._read(residual, self.mlp_gate) | |
| residual = residual + post.unsqueeze(-1) * self.mlp(hidden).float() | |
| if valid is not None: | |
| residual = residual * valid.to(dtype=residual.dtype).unsqueeze(-1) | |
| return residual, s_new, hist_new | |
| class WeevilRecurrentPreTrainedModel(PreTrainedModel): | |
| config_class = WeevilRecurrentConfig | |
| base_model_prefix = "model" | |
| supports_gradient_checkpointing = False | |
| _no_split_modules = ["Block"] | |
| class Stage(nn.Module): | |
| def __init__(self, config: WeevilRecurrentConfig, count: int) -> None: | |
| super().__init__() | |
| self.layers = nn.ModuleList(Block(config) for _ in range(count)) | |
| def forward( | |
| self, | |
| hidden: torch.Tensor, | |
| valid: torch.Tensor | None, | |
| s: torch.Tensor | None, | |
| hist: torch.Tensor | None, | |
| ) -> torch.Tensor: | |
| for index, layer in enumerate(self.layers): | |
| layer_s = None if s is None else s[index] | |
| layer_hist = None if hist is None else hist[index] | |
| hidden, s_new, hist_new = layer(hidden, valid, layer_s, layer_hist) | |
| if s is not None: | |
| if s_new.data_ptr() != s[index].data_ptr(): | |
| s[index].copy_(s_new) | |
| if hist_new.data_ptr() != hist[index].data_ptr(): | |
| hist[index].copy_(hist_new) | |
| return hidden | |
| class WeevilRecurrentModel(WeevilRecurrentPreTrainedModel): | |
| def __init__(self, config: WeevilRecurrentConfig) -> None: | |
| super().__init__(config) | |
| self.embed = nn.Embedding(config.vocab_size, config.embedding_dim) | |
| self.proj = nn.Linear(config.embedding_dim, config.hidden_size, bias=False) | |
| self.input_scale = nn.Parameter(torch.zeros(config.hidden_size)) | |
| self.prelude = Stage(config, config.prelude_layers) | |
| self.recurrent = Stage(config, config.recurrent_layers) | |
| self.coda = Stage(config, config.coda_layers) | |
| self.post_init() | |
| def empty_state(self, batch: int, passes: int, device: torch.device) -> WeevilRecurrentState: | |
| config = self.config | |
| heads, dim = config.num_heads, config.head_dim | |
| width = heads * dim | |
| lookback = config.conv_kernel - 1 | |
| compute = _dtype(config.compute_dtype) | |
| def states(count: int) -> torch.Tensor: | |
| return torch.zeros(count, batch, heads, dim, dim, device=device) | |
| def windows(count: int) -> torch.Tensor: | |
| return torch.zeros(count, 3, batch, lookback, width, device=device, dtype=compute) | |
| return WeevilRecurrentState( | |
| prelude_s=states(config.prelude_layers), | |
| prelude_hist=windows(config.prelude_layers), | |
| recurrent_s=states(passes * config.recurrent_layers).view( | |
| passes, config.recurrent_layers, batch, heads, dim, dim | |
| ), | |
| recurrent_hist=windows(passes * config.recurrent_layers).view( | |
| passes, config.recurrent_layers, 3, batch, lookback, width | |
| ), | |
| coda_s=states(config.coda_layers), | |
| coda_hist=windows(config.coda_layers), | |
| seq_length=0, | |
| core_passes=passes, | |
| ) | |
| def embed_tokens(self, input_ids: torch.Tensor, valid: torch.Tensor | None) -> torch.Tensor: | |
| compute = _dtype(self.config.compute_dtype) | |
| tokens = self.embed(input_ids).to(dtype=compute) | |
| hidden = apply_linear(tokens, self.proj).float() | |
| if valid is not None: | |
| hidden = hidden * valid.to(dtype=hidden.dtype).unsqueeze(-1) | |
| return hidden | |
| def _run( | |
| self, | |
| embedded: torch.Tensor, | |
| valid: torch.Tensor | None, | |
| state: WeevilRecurrentState, | |
| passes: int, | |
| ) -> torch.Tensor: | |
| if embedded.shape[1] > 1 and state.seq_length != 0: | |
| hidden = embedded | |
| for step in range(embedded.shape[1]): | |
| step_valid = None if valid is None else valid[:, step : step + 1] | |
| hidden_step = self._run(hidden[:, step : step + 1], step_valid, state, passes) | |
| if step == 0: | |
| outputs = hidden_step | |
| else: | |
| outputs = torch.cat((outputs, hidden_step), dim=1) | |
| return outputs | |
| prelude = self.prelude(embedded, valid, state.prelude_s, state.prelude_hist) | |
| injected = self.input_scale.float() * prelude | |
| hidden = prelude | |
| for index in range(passes): | |
| hidden = self.recurrent( | |
| hidden + injected, valid, state.recurrent_s[index], state.recurrent_hist[index] | |
| ) | |
| hidden = self.coda(hidden, valid, state.coda_s, state.coda_hist) | |
| hidden = rmsnorm(hidden, self.config.norm_eps).to(dtype=_dtype(self.config.compute_dtype)) | |
| if valid is not None: | |
| hidden = hidden * valid.to(dtype=hidden.dtype).unsqueeze(-1) | |
| state.seq_length += embedded.shape[1] | |
| return hidden | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor, | |
| attention_mask: torch.Tensor | None = None, | |
| core_passes: int | None = None, | |
| past_key_values: WeevilRecurrentState | None = None, | |
| use_cache: bool | None = None, | |
| **kwargs, | |
| ) -> tuple[torch.Tensor, WeevilRecurrentState | None]: | |
| passes = self.config.core_passes if core_passes is None else core_passes | |
| if passes < 1: | |
| raise ValueError("core_passes must be positive") | |
| if attention_mask is not None and attention_mask.shape[-1] != input_ids.shape[-1]: | |
| attention_mask = attention_mask[:, -input_ids.shape[-1] :] | |
| valid = None if attention_mask is None else attention_mask.to(dtype=torch.bool) | |
| state = past_key_values | |
| if state is None and use_cache: | |
| state = self.empty_state(input_ids.shape[0], passes, input_ids.device) | |
| if state is not None: | |
| if state.core_passes != passes: | |
| raise ValueError( | |
| f"state was built for {state.core_passes} core passes, not {passes}" | |
| ) | |
| if state.batch_size != input_ids.shape[0]: | |
| raise ValueError("state batch does not match input_ids") | |
| embedded = self.embed_tokens(input_ids, valid) | |
| if state is None: | |
| state = self.empty_state(input_ids.shape[0], passes, input_ids.device) | |
| hidden = self._run(embedded, valid, state, passes) | |
| state = None | |
| else: | |
| hidden = self._run(embedded, valid, state, passes) | |
| return hidden, state | |
| class WeevilRecurrentForCausalLM(WeevilRecurrentPreTrainedModel, GenerationMixin): | |
| def __init__(self, config: WeevilRecurrentConfig) -> None: | |
| super().__init__(config) | |
| self.model = WeevilRecurrentModel(config) | |
| self.post_init() | |
| self.generation_config.use_cache = True | |
| def _supports_default_dynamic_cache(cls) -> bool: | |
| return False | |
| def forward( | |
| self, | |
| input_ids: torch.Tensor | None = None, | |
| attention_mask: torch.Tensor | None = None, | |
| core_passes: int | None = None, | |
| past_key_values: WeevilRecurrentState | None = None, | |
| use_cache: bool | None = None, | |
| labels: torch.Tensor | None = None, | |
| **kwargs, | |
| ) -> WeevilCausalLMOutput: | |
| if input_ids is None: | |
| raise ValueError("input_ids is required") | |
| if use_cache is None: | |
| use_cache = past_key_values is not None or labels is None | |
| hidden, state = self.model( | |
| input_ids, | |
| attention_mask=attention_mask, | |
| core_passes=core_passes, | |
| past_key_values=past_key_values, | |
| use_cache=use_cache, | |
| ) | |
| compute = _dtype(self.config.compute_dtype) | |
| low = hidden.to(dtype=compute) @ self.model.proj.weight.to(dtype=compute) | |
| logits = (low @ self.model.embed.weight.to(dtype=compute).T).float() | |
| loss = None | |
| if labels is not None: | |
| shift_logits = logits[:, :-1].contiguous() | |
| shift_labels = labels[:, 1:].contiguous() | |
| loss = F.cross_entropy(shift_logits.view(-1, shift_logits.shape[-1]), shift_labels.view(-1)) | |
| return WeevilCausalLMOutput( | |
| loss=loss, | |
| logits=logits, | |
| past_key_values=state, | |
| recurrent_state=state, | |
| ) | |
| def score_continuations( | |
| self, | |
| input_ids: torch.Tensor, | |
| continuations: torch.Tensor | list, | |
| continuation_mask: torch.Tensor | None = None, | |
| core_passes: int | None = None, | |
| ) -> torch.Tensor: | |
| """Mean log-probability of each continuation after one prefill of ``input_ids``. | |
| ``continuations`` is a list of token-id sequences or a right-padded ``[choices, length]`` | |
| tensor. A padded tensor uses ``pad_token_id`` as padding unless ``continuation_mask`` | |
| marks the real tokens. The prompt state is copied once per choice, so the choices share | |
| the prefill and do not interfere. | |
| """ | |
| if input_ids.ndim == 1: | |
| input_ids = input_ids.unsqueeze(0) | |
| if input_ids.shape[0] != 1: | |
| raise ValueError("score_continuations prefills one prompt") | |
| encoded, mask = _pack_continuations( | |
| continuations, continuation_mask, self.config.pad_token_id, input_ids.device | |
| ) | |
| prefill = self(input_ids, core_passes=core_passes, use_cache=True) | |
| first = F.log_softmax(prefill.logits[0, -1].float(), dim=-1) | |
| token_scores = first.new_zeros(encoded.shape) | |
| token_scores[:, 0] = first[encoded[:, 0]] | |
| if encoded.shape[1] > 1 and bool(mask[:, 1:].any()): | |
| branch = prefill.recurrent_state.repeat(encoded.shape[0]) | |
| scored = self( | |
| encoded[:, :-1], | |
| attention_mask=mask[:, :-1].to(dtype=torch.long), | |
| past_key_values=branch, | |
| core_passes=core_passes, | |
| use_cache=True, | |
| ) | |
| taken = F.log_softmax(scored.logits.float(), dim=-1) | |
| token_scores[:, 1:] = taken.gather(-1, encoded[:, 1:].unsqueeze(-1)).squeeze(-1) | |
| return (token_scores * mask).sum(dim=-1) / mask.sum(dim=-1) | |
| def _pack_continuations(continuations, mask, pad_id: int, device: torch.device): | |
| if isinstance(continuations, torch.Tensor): | |
| if continuations.ndim == 1: | |
| continuations = continuations.unsqueeze(0) | |
| if continuations.ndim != 2: | |
| raise ValueError("continuations tensor must have shape [choices, length]") | |
| encoded = continuations.to(device=device, dtype=torch.long) | |
| if encoded.shape[0] == 0 or encoded.shape[1] == 0: | |
| raise ValueError("each continuation must contain at least one token") | |
| if mask is None: | |
| mask = encoded.ne(pad_id) | |
| else: | |
| mask = mask.to(device=device, dtype=torch.bool) | |
| if mask.shape != encoded.shape: | |
| raise ValueError("continuation_mask must match continuations") | |
| if not bool(mask[:, 0].all()): | |
| raise ValueError("continuations must be left-aligned") | |
| return encoded, mask | |
| rows = [list(row) for row in continuations] | |
| if not rows or any(len(row) == 0 for row in rows): | |
| raise ValueError("each continuation must contain at least one token") | |
| width = max(len(row) for row in rows) | |
| encoded = torch.full((len(rows), width), pad_id, dtype=torch.long, device=device) | |
| valid = torch.zeros(len(rows), width, dtype=torch.bool, device=device) | |
| for index, row in enumerate(rows): | |
| encoded[index, : len(row)] = torch.tensor(row, dtype=torch.long, device=device) | |
| valid[index, : len(row)] = True | |
| return encoded, valid | |
| WeevilRecurrentConfig.register_for_auto_class() | |
| WeevilRecurrentModel.register_for_auto_class("AutoModel") | |
| WeevilRecurrentForCausalLM.register_for_auto_class("AutoModelForCausalLM") | |