statehead-10m / modeling_statehead.py
haybales's picture
Upload main checkpoint (chck_100M)
b863715 verified
Raw
History Blame Contribute Delete
26.7 kB
from __future__ import annotations
import os
import shutil
import warnings
from collections.abc import Sequence
from pathlib import Path
from typing import Any
import torch
import torch.nn as nn
import torch.nn.functional as F
from transformers import PreTrainedModel
from transformers.generation import GenerationMixin
from transformers.modeling_outputs import (
BaseModelOutputWithPast,
CausalLMOutputWithPast,
)
from .configuration_statehead import StateHeadConfig
def _sqrt_with_clipped_grad(x: torch.Tensor, max_grad: float) -> torch.Tensor:
eps = 1.0 / (4.0 * max_grad * max_grad)
return torch.sqrt(torch.clamp(x, min=0.0) + eps)
def _retention_write_scale_from_logits(a_logits: torch.Tensor, max_grad: float) -> torch.Tensor:
a = torch.sigmoid(a_logits)
scale_square = torch.sigmoid(-a_logits) * (1.0 + a)
return _sqrt_with_clipped_grad(scale_square, max_grad)
def _statehead_scan_torch(
gates: torch.Tensor,
initial_state: torch.Tensor,
attention_mask: torch.Tensor | None = None,
write_scale_by_retention: bool = False,
write_scale_by_retention_multiplier: float = 1.0,
write_scale_sqrt_grad_clip: float = 1000.0,
) -> tuple[torch.Tensor, torch.Tensor]:
batch, seq_len, four, n_head, head_dim = gates.shape
if four != 4:
raise ValueError("gates must have shape [batch, seq, 4, n_head, head_dim]")
if initial_state.shape != (batch, n_head, head_dim):
raise ValueError("initial_state must have shape [batch, n_head, head_dim]")
if attention_mask is not None:
if attention_mask.shape != (batch, seq_len):
raise ValueError("attention_mask must have shape [batch, seq]")
attention_mask = attention_mask.to(dtype=gates.dtype, device=gates.device)
state = initial_state
outputs: list[torch.Tensor] = []
for t in range(seq_len):
a_logits, b_logits, c_logits, o_logits = gates[:, t].unbind(dim=1)
a = torch.sigmoid(a_logits)
b = torch.sigmoid(b_logits)
c = torch.tanh(c_logits)
o = torch.sigmoid(o_logits)
write = b * c
if write_scale_by_retention:
write = (
write_scale_by_retention_multiplier
* _retention_write_scale_from_logits(a_logits, write_scale_sqrt_grad_clip)
* write
)
next_state = a * state + write
y = o * next_state
if attention_mask is not None:
mask = attention_mask[:, t].view(batch, 1, 1)
state = mask * next_state + (1.0 - mask) * state
y = mask * y
else:
state = next_state
outputs.append(y)
return torch.stack(outputs, dim=1), state
def _statehead_scan_parallel(
gates: torch.Tensor,
initial_state: torch.Tensor,
attention_mask: torch.Tensor | None = None,
chunk_size: int = 64,
write_scale_by_retention: bool = False,
write_scale_by_retention_multiplier: float = 1.0,
write_scale_sqrt_grad_clip: float = 1000.0,
) -> tuple[torch.Tensor, torch.Tensor]:
batch, seq_len, four, n_head, head_dim = gates.shape
if four != 4:
raise ValueError("gates must have shape [batch, seq, 4, n_head, head_dim]")
if initial_state.shape != (batch, n_head, head_dim):
raise ValueError("initial_state must have shape [batch, n_head, head_dim]")
if attention_mask is not None and attention_mask.shape != (batch, seq_len):
raise ValueError("attention_mask must have shape [batch, seq]")
if chunk_size < 1:
raise ValueError("chunk_size must be positive")
original_dtype = gates.dtype
scan_dtype = torch.float32 if gates.dtype in {torch.float16, torch.bfloat16} else gates.dtype
gates = gates.to(scan_dtype)
state = initial_state.to(scan_dtype)
if attention_mask is not None:
attention_mask = attention_mask.to(dtype=scan_dtype, device=gates.device)
a_logits, b_logits, c_logits, o_logits = gates.unbind(dim=2)
a = torch.sigmoid(a_logits)
u = torch.sigmoid(b_logits) * torch.tanh(c_logits)
if write_scale_by_retention:
u = (
write_scale_by_retention_multiplier
* _retention_write_scale_from_logits(a_logits, write_scale_sqrt_grad_clip)
* u
)
o = torch.sigmoid(o_logits)
mask = None
if attention_mask is not None:
mask = attention_mask.view(batch, seq_len, 1, 1)
a = mask * a + (1.0 - mask)
u = mask * u
chunk_size = min(chunk_size, seq_len)
n_chunks = (seq_len + chunk_size - 1) // chunk_size
padded_seq_len = n_chunks * chunk_size
pad_len = padded_seq_len - seq_len
if pad_len:
pad_shape = (batch, pad_len, n_head, head_dim)
a = torch.cat([a, torch.ones(pad_shape, dtype=scan_dtype, device=gates.device)], dim=1)
u = torch.cat([u, torch.zeros(pad_shape, dtype=scan_dtype, device=gates.device)], dim=1)
a = a.view(batch, n_chunks, chunk_size, n_head, head_dim)
u = u.view(batch, n_chunks, chunk_size, n_head, head_dim)
prefix_a, prefix_u = _statehead_associative_prefix(a, u, dim=2)
chunk_a = prefix_a[:, :, -1]
chunk_u = prefix_u[:, :, -1]
chunk_prefix_a, chunk_prefix_u = _statehead_associative_prefix(chunk_a, chunk_u, dim=1)
states_after_chunks = chunk_prefix_a * state.unsqueeze(1) + chunk_prefix_u
chunk_initials = torch.cat([state.unsqueeze(1), states_after_chunks[:, :-1]], dim=1)
states = prefix_a * chunk_initials.unsqueeze(2) + prefix_u
states = states.reshape(batch, padded_seq_len, n_head, head_dim)[:, :seq_len]
y = o * states
if mask is not None:
y = mask * y
return y.to(original_dtype), states_after_chunks[:, -1].to(original_dtype)
def _statehead_associative_prefix(
a: torch.Tensor,
u: torch.Tensor,
*,
dim: int,
) -> tuple[torch.Tensor, torch.Tensor]:
"""Parallel prefix for elementwise recurrences s[t] = a[t] * s[t-1] + u[t]."""
size = a.shape[dim]
offset = 1
while offset < size:
head = [slice(None)] * a.ndim
tail = [slice(None)] * a.ndim
prev = [slice(None)] * a.ndim
head[dim] = slice(0, offset)
tail[dim] = slice(offset, None)
prev[dim] = slice(0, -offset)
head_idx = tuple(head)
tail_idx = tuple(tail)
prev_idx = tuple(prev)
a_tail = a[tail_idx]
u_tail = u[tail_idx]
a = torch.cat([a[head_idx], a_tail * a[prev_idx]], dim=dim)
u = torch.cat([u[head_idx], a_tail * u[prev_idx] + u_tail], dim=dim)
offset <<= 1
return a, u
def _statehead_scan_chunk_loop(
gates: torch.Tensor,
initial_state: torch.Tensor,
attention_mask: torch.Tensor | None = None,
chunk_size: int = 64,
write_scale_by_retention: bool = False,
write_scale_by_retention_multiplier: float = 1.0,
write_scale_sqrt_grad_clip: float = 1000.0,
) -> tuple[torch.Tensor, torch.Tensor]:
batch, seq_len, four, n_head, head_dim = gates.shape
if four != 4:
raise ValueError("gates must have shape [batch, seq, 4, n_head, head_dim]")
if initial_state.shape != (batch, n_head, head_dim):
raise ValueError("initial_state must have shape [batch, n_head, head_dim]")
if attention_mask is not None and attention_mask.shape != (batch, seq_len):
raise ValueError("attention_mask must have shape [batch, seq]")
if chunk_size < 1:
raise ValueError("chunk_size must be positive")
original_dtype = gates.dtype
scan_dtype = torch.float32 if gates.dtype in {torch.float16, torch.bfloat16} else gates.dtype
gates = gates.to(scan_dtype)
state = initial_state.to(scan_dtype)
if attention_mask is not None:
attention_mask = attention_mask.to(dtype=scan_dtype, device=gates.device)
chunks: list[torch.Tensor] = []
for start in range(0, seq_len, chunk_size):
stop = min(start + chunk_size, seq_len)
chunk_outputs: list[torch.Tensor] = []
for t in range(start, stop):
a_logits, b_logits, c_logits, o_logits = gates[:, t].unbind(dim=1)
a = torch.sigmoid(a_logits)
b = torch.sigmoid(b_logits)
c = torch.tanh(c_logits)
o = torch.sigmoid(o_logits)
write = b * c
if write_scale_by_retention:
write = (
write_scale_by_retention_multiplier
* _retention_write_scale_from_logits(a_logits, write_scale_sqrt_grad_clip)
* write
)
next_state = a * state + write
y = o * next_state
if attention_mask is not None:
mask = attention_mask[:, t].view(batch, 1, 1)
state = mask * next_state + (1.0 - mask) * state
y = mask * y
else:
state = next_state
chunk_outputs.append(y)
chunks.append(torch.stack(chunk_outputs, dim=1))
return torch.cat(chunks, dim=1).to(original_dtype), state.to(original_dtype)
_compiled_parallel_scan = None
def _parallel_scan_fn():
"""Opt-in compiled scan: set STATEHEAD_COMPILE=1. Inference-only (compiled
backward measured slower), dynamic shapes, bit-exact vs eager; falls back to
eager permanently if compilation or the first compiled call fails."""
global _compiled_parallel_scan
if _compiled_parallel_scan is None:
try:
_compiled_parallel_scan = torch.compile(_statehead_scan_parallel, dynamic=True)
except Exception:
_compiled_parallel_scan = _statehead_scan_parallel
return _compiled_parallel_scan
def statehead_scan(
gates: torch.Tensor,
initial_state: torch.Tensor,
attention_mask: torch.Tensor | None = None,
backend: str = "torch",
chunk_size: int = 64,
write_scale_by_retention: bool = False,
write_scale_by_retention_multiplier: float = 1.0,
write_scale_sqrt_grad_clip: float = 1000.0,
) -> tuple[torch.Tensor, torch.Tensor]:
if backend == "auto":
backend = "parallel"
if backend == "torch":
backend = "parallel"
if backend == "parallel":
args = (
gates,
initial_state,
attention_mask,
chunk_size,
write_scale_by_retention,
write_scale_by_retention_multiplier,
write_scale_sqrt_grad_clip,
)
if os.environ.get("STATEHEAD_COMPILE", "0") == "1" and not torch.is_grad_enabled():
global _compiled_parallel_scan
try:
return _parallel_scan_fn()(*args)
except Exception:
_compiled_parallel_scan = _statehead_scan_parallel
return _statehead_scan_parallel(*args)
if backend == "chunk_loop":
return _statehead_scan_chunk_loop(
gates,
initial_state,
attention_mask,
chunk_size,
write_scale_by_retention,
write_scale_by_retention_multiplier,
write_scale_sqrt_grad_clip,
)
if backend == "torch_loop":
return _statehead_scan_torch(
gates,
initial_state,
attention_mask,
write_scale_by_retention,
write_scale_by_retention_multiplier,
write_scale_sqrt_grad_clip,
)
if backend == "triton":
warnings.warn(
"The Triton StateHead scan is not wired yet; falling back to the PyTorch scan.",
RuntimeWarning,
stacklevel=2,
)
return _statehead_scan_parallel(
gates,
initial_state,
attention_mask,
chunk_size,
write_scale_by_retention,
write_scale_by_retention_multiplier,
write_scale_sqrt_grad_clip,
)
raise ValueError(f"Unknown StateHead scan backend: {backend}")
def count_unique_parameters(model: nn.Module) -> int:
seen: set[int] = set()
total = 0
for parameter in model.parameters():
pointer = id(parameter)
if pointer in seen:
continue
seen.add(pointer)
total += parameter.numel()
return total
class StateHeadMLP(nn.Module):
def __init__(self, config: StateHeadConfig) -> None:
super().__init__()
hidden_dim = config.mlp_ratio * config.n_embd
self.up = nn.Linear(config.n_embd, hidden_dim)
self.down = nn.Linear(hidden_dim, config.n_embd)
self.dropout = nn.Dropout(config.dropout)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.dropout(self.down(F.gelu(self.up(x))))
class StateHeadBank(nn.Module):
def __init__(self, config: StateHeadConfig) -> None:
super().__init__()
self.n_head = config.n_head
self.head_dim = config.head_dim
self.n_embd = config.n_embd
self.scan_backend = config.scan_backend
self.scan_chunk_size = config.scan_chunk_size
self.write_scale_by_retention = config.state_write_scale_by_retention
self.write_scale_by_retention_multiplier = config.state_write_scale_by_retention_multiplier
self.write_scale_sqrt_grad_clip = config.state_write_scale_sqrt_grad_clip
self.gate = nn.Linear(config.n_embd, 4 * config.n_embd)
self.proj = nn.Linear(config.n_embd, config.n_embd)
self.dropout = nn.Dropout(config.dropout)
if config.learned_initial_state:
initial_state = torch.zeros(config.n_head, config.head_dim)
if config.initial_state_std > 0:
initial_state.normal_(mean=0.0, std=config.initial_state_std)
self.initial_state = nn.Parameter(initial_state)
else:
self.register_parameter("initial_state", None)
def reset_statehead_bias(self, retention_bias: float, write_gate_bias: float, output_gate_bias: float) -> None:
with torch.no_grad():
self.gate.bias.zero_()
self.gate.bias[: self.n_embd].fill_(retention_bias)
self.gate.bias[self.n_embd : 2 * self.n_embd].fill_(write_gate_bias)
self.gate.bias[3 * self.n_embd :].fill_(output_gate_bias)
def zero_state(self, batch_size: int, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor:
return torch.zeros(batch_size, self.n_head, self.head_dim, device=device, dtype=dtype)
def fresh_state(self, batch_size: int, *, device: torch.device, dtype: torch.dtype) -> torch.Tensor:
if self.initial_state is None:
return self.zero_state(batch_size, device=device, dtype=dtype)
return self.initial_state.to(device=device, dtype=dtype).unsqueeze(0).expand(batch_size, -1, -1)
def forward(
self,
x: torch.Tensor,
state: torch.Tensor | None = None,
attention_mask: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
batch, seq_len, _ = x.shape
if state is None:
state = self.fresh_state(batch, device=x.device, dtype=x.dtype)
gates = self.gate(x).view(batch, seq_len, 4, self.n_head, self.head_dim)
y, next_state = statehead_scan(
gates,
state,
attention_mask,
self.scan_backend,
self.scan_chunk_size,
self.write_scale_by_retention,
self.write_scale_by_retention_multiplier,
self.write_scale_sqrt_grad_clip,
)
y = y.reshape(batch, seq_len, self.n_embd)
return self.dropout(self.proj(y)), next_state
class StateHeadBlock(nn.Module):
def __init__(self, config: StateHeadConfig) -> None:
super().__init__()
self.residual_scale = config.residual_scale
self.norm_state = nn.RMSNorm(config.n_embd, eps=config.layer_norm_eps)
self.state_bank = StateHeadBank(config)
self.norm_mlp = nn.RMSNorm(config.n_embd, eps=config.layer_norm_eps)
self.mlp = StateHeadMLP(config)
def forward(
self,
x: torch.Tensor,
state: torch.Tensor | None = None,
attention_mask: torch.Tensor | None = None,
) -> tuple[torch.Tensor, torch.Tensor]:
y, next_state = self.state_bank(self.norm_state(x), state, attention_mask)
x = x + self.residual_scale * y
x = x + self.residual_scale * self.mlp(self.norm_mlp(x))
return x, next_state
class StateHeadBackbone(nn.Module):
def __init__(self, config: StateHeadConfig) -> None:
super().__init__()
self.config = config
self.token_embedding = nn.Embedding(config.vocab_size, config.n_embd)
self.position_embedding = (
nn.Embedding(config.block_size, config.n_embd) if config.use_position_embeddings else None
)
self.dropout = nn.Dropout(config.dropout)
self.blocks = nn.ModuleList(StateHeadBlock(config) for _ in range(config.n_layer))
self.norm = nn.RMSNorm(config.n_embd, eps=config.layer_norm_eps)
def reset_statehead_biases(self) -> None:
for block in self.blocks:
block.state_bank.reset_statehead_bias(
self.config.state_retention_bias,
self.config.state_write_gate_bias,
self.config.state_output_gate_bias,
)
def zero_state(self, batch_size: int, *, device: torch.device, dtype: torch.dtype) -> tuple[torch.Tensor, ...]:
return tuple(block.state_bank.fresh_state(batch_size, device=device, dtype=dtype) for block in self.blocks)
def forward(
self,
input_ids: torch.LongTensor | None = None,
attention_mask: torch.Tensor | None = None,
inputs_embeds: torch.Tensor | None = None,
position_ids: torch.LongTensor | None = None,
past_key_values: Sequence[torch.Tensor] | None = None,
use_cache: bool = False,
output_hidden_states: bool = False,
) -> BaseModelOutputWithPast:
if input_ids is None and inputs_embeds is None:
raise ValueError("Either input_ids or inputs_embeds must be provided")
if input_ids is not None and inputs_embeds is not None:
raise ValueError("Only one of input_ids or inputs_embeds may be provided")
if inputs_embeds is None:
inputs_embeds = self.token_embedding(input_ids)
batch_size, seq_len, _ = inputs_embeds.shape
if attention_mask is not None:
if attention_mask.shape[-1] != seq_len:
attention_mask = attention_mask[:, -seq_len:]
attention_mask = attention_mask.to(device=inputs_embeds.device)
x = inputs_embeds
if self.position_embedding is not None:
if position_ids is None:
position_ids = torch.arange(seq_len, device=x.device).unsqueeze(0)
if position_ids.shape[-1] != seq_len:
position_ids = position_ids[:, -seq_len:]
position_ids = position_ids.clamp(max=self.config.block_size - 1)
x = x + self.position_embedding(position_ids)
x = self.dropout(x)
if past_key_values is None:
states: Sequence[torch.Tensor | None] = [None] * len(self.blocks)
else:
if len(past_key_values) != len(self.blocks):
raise ValueError("past_key_values must contain one state per layer")
states = past_key_values
all_hidden_states: tuple[torch.Tensor, ...] | None = () if output_hidden_states else None
if all_hidden_states is not None:
all_hidden_states += (x,)
next_states: list[torch.Tensor] = []
for block, state in zip(self.blocks, states, strict=True):
x, next_state = block(x, state, attention_mask)
next_states.append(next_state)
if all_hidden_states is not None:
all_hidden_states += (x,)
x = self.norm(x)
return BaseModelOutputWithPast(
last_hidden_state=x,
past_key_values=tuple(next_states) if use_cache else None,
hidden_states=all_hidden_states,
)
class StateHeadPreTrainedModel(PreTrainedModel):
config_class = StateHeadConfig
base_model_prefix = "statehead"
supports_gradient_checkpointing = False
_no_split_modules = ["StateHeadBlock"]
def _init_weights(self, module: nn.Module) -> None:
if isinstance(module, nn.Linear):
module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
if module.bias is not None:
module.bias.data.zero_()
elif isinstance(module, nn.Embedding):
module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
if module.padding_idx is not None:
module.weight.data[module.padding_idx].zero_()
def save_pretrained(self, save_directory: str | Path, *args: Any, **kwargs: Any):
result = super().save_pretrained(save_directory, *args, **kwargs)
save_path = Path(save_directory)
source_dir = Path(__file__).parent
for filename in ("configuration_statehead.py", "modeling_statehead.py"):
shutil.copy2(source_dir / filename, save_path / filename)
return result
class StateHeadModel(StateHeadPreTrainedModel):
def __init__(self, config: StateHeadConfig) -> None:
super().__init__(config)
self.statehead = StateHeadBackbone(config)
self.post_init()
self.statehead.reset_statehead_biases()
def get_input_embeddings(self) -> nn.Embedding:
return self.statehead.token_embedding
def set_input_embeddings(self, value: nn.Embedding) -> None:
self.statehead.token_embedding = value
def forward(
self,
input_ids: torch.LongTensor | None = None,
attention_mask: torch.Tensor | None = None,
inputs_embeds: torch.Tensor | None = None,
position_ids: torch.LongTensor | None = None,
past_key_values: Sequence[torch.Tensor] | None = None,
use_cache: bool | None = None,
output_hidden_states: bool | None = None,
return_dict: bool | None = None,
**_: Any,
) -> BaseModelOutputWithPast | tuple[torch.Tensor, ...]:
use_cache = use_cache if use_cache is not None else self.config.use_cache
output_hidden_states = (
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
)
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
outputs = self.statehead(
input_ids=input_ids,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
position_ids=position_ids,
past_key_values=past_key_values,
use_cache=use_cache,
output_hidden_states=output_hidden_states,
)
if return_dict:
return outputs
result: tuple[torch.Tensor, ...] = (outputs.last_hidden_state,)
if use_cache:
result += (outputs.past_key_values,)
if output_hidden_states:
result += (outputs.hidden_states,)
return result
class StateHeadForCausalLM(StateHeadPreTrainedModel, GenerationMixin):
_tied_weights_keys = ["lm_head.weight"]
def __init__(self, config: StateHeadConfig) -> None:
super().__init__(config)
self.statehead = StateHeadBackbone(config)
self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
self.post_init()
self.statehead.reset_statehead_biases()
def get_input_embeddings(self) -> nn.Embedding:
return self.statehead.token_embedding
def set_input_embeddings(self, value: nn.Embedding) -> None:
self.statehead.token_embedding = value
def get_output_embeddings(self) -> nn.Linear:
return self.lm_head
def set_output_embeddings(self, new_embeddings: nn.Linear) -> None:
self.lm_head = new_embeddings
def prepare_inputs_for_generation(
self,
input_ids: torch.LongTensor,
past_key_values: Sequence[torch.Tensor] | None = None,
attention_mask: torch.Tensor | None = None,
**kwargs: Any,
) -> dict[str, Any]:
if past_key_values is not None:
input_ids = input_ids[:, -1:]
if attention_mask is not None:
attention_mask = attention_mask[:, -1:]
return {
"input_ids": input_ids,
"past_key_values": past_key_values,
"attention_mask": attention_mask,
"use_cache": kwargs.get("use_cache", True),
}
def forward(
self,
input_ids: torch.LongTensor | None = None,
attention_mask: torch.Tensor | None = None,
inputs_embeds: torch.Tensor | None = None,
position_ids: torch.LongTensor | None = None,
past_key_values: Sequence[torch.Tensor] | None = None,
labels: torch.LongTensor | None = None,
use_cache: bool | None = None,
output_hidden_states: bool | None = None,
return_dict: bool | None = None,
**_: Any,
) -> CausalLMOutputWithPast | tuple[torch.Tensor, ...]:
use_cache = use_cache if use_cache is not None else self.config.use_cache
output_hidden_states = (
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
)
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
outputs = self.statehead(
input_ids=input_ids,
attention_mask=attention_mask,
inputs_embeds=inputs_embeds,
position_ids=position_ids,
past_key_values=past_key_values,
use_cache=use_cache,
output_hidden_states=output_hidden_states,
)
logits = self.lm_head(outputs.last_hidden_state)
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.size(-1)),
shift_labels.view(-1),
ignore_index=-100,
)
if return_dict:
return CausalLMOutputWithPast(
loss=loss,
logits=logits,
past_key_values=outputs.past_key_values,
hidden_states=outputs.hidden_states,
)
result: tuple[torch.Tensor, ...] = (logits,)
if use_cache:
result += (outputs.past_key_values,)
if output_hidden_states:
result += (outputs.hidden_states,)
return ((loss,) + result) if loss is not None else result