Download modeling_dense.py from Can111/ShellElastic-baseline-SampledStandalone-375M-906M: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/Can111/ShellElastic-baseline-SampledStandalone-375M-906M/resolve/main/modeling_dense.py
- Command line
-
hf download hf://Can111/ShellElastic-baseline-SampledStandalone-375M-906M/modeling_dense.py
-
curl -L -o modeling_dense.py https://huggingface.co/Can111/ShellElastic-baseline-SampledStandalone-375M-906M/resolve/main/modeling_dense.py
11.9 kB
| """Reference implementation of the dense decoder-only transformer behind the Standalone and Sampled standalone | |
| models of "ShellElastic: Scalable Elastic Pretraining with Simultaneous Maximal Updates and Hyperparameter | |
| Transfer". | |
| Each released model is an ordinary dense decoder. The released weights already include the constant forward | |
| multipliers of the model (for these models, the FFN output multiplier 1/e, where e is the FFN expansion), so this | |
| file contains no scaling constants. | |
| Architecture (one layer, pre-norm): | |
| h = x + Attention(RMSNorm(x)) | |
| out = h + SwiGLU(RMSNorm(h)) | |
| * RMSNorm with eps 1e-5; this file computes the statistics in float32 and casts the result back to the input dtype | |
| before the learned gain is applied. | |
| * Attention: separate query/key/value projections without biases; queries and keys are RMS-normalized within | |
| each head (the gains are stored at full width, one per channel); rotary position embeddings in the | |
| rotate-half form with base 10,000; causal softmax attention with scale 1/sqrt(head_dim); the attention output | |
| is multiplied elementwise by sigmoid(x W_gate^T) before the output projection. | |
| * SwiGLU feed-forward network: down_proj(silu(gate_proj(x)) * up_proj(x)). | |
| * Untied input embedding and output head, no biases anywhere, context length 2,048, and the 50,432-token GPT-NeoX | |
| vocabulary (Table 8 of the paper; token ids of the GPT-NeoX-20B tokenizer, Section 4.1). | |
| Rotary tables: ``rope_precision`` is ``"bfloat16"`` (the default) or ``"float32"``. ``"bfloat16"`` rounds the | |
| positions and inverse frequencies to bfloat16, their product to bfloat16, and cos/sin of those angles to bfloat16, | |
| in every compute precision; ``"float32"`` computes the tables in float32. | |
| Dependencies: torch, and safetensors for loading the weights. | |
| Example: | |
| import torch | |
| from modeling_dense import DenseDecoder | |
| model = DenseDecoder.from_pretrained("path/to/model_dir", device="cuda", dtype=torch.bfloat16) | |
| input_ids = torch.tensor([[...]], device="cuda") # GPT-NeoX-20B token ids | |
| logits = model(input_ids) # [batch, tokens, 50432], float32 | |
| logits, loss = model(input_ids[:, :-1], labels=input_ids[:, 1:]) | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from dataclasses import asdict, dataclass, fields | |
| import torch | |
| import torch.nn.functional as F | |
| from torch import nn | |
| __all__ = ["DenseConfig", "DenseDecoder"] | |
| class DenseConfig: | |
| vocab_size: int = 50432 | |
| hidden_size: int = 384 | |
| intermediate_size: int = 1536 | |
| num_hidden_layers: int = 32 | |
| num_attention_heads: int = 6 | |
| head_dim: int = 64 | |
| max_position_embeddings: int = 2048 | |
| rope_theta: float = 10000.0 | |
| rope_precision: str = "bfloat16" | |
| rms_norm_eps: float = 1e-5 | |
| def from_dict(cls, values: dict) -> "DenseConfig": | |
| """Build a config from a dict, ignoring keys that only describe the release (method, tier, ...).""" | |
| known = {f.name for f in fields(cls)} | |
| return cls(**{k: v for k, v in values.items() if k in known}) | |
| def from_json_file(cls, path: str) -> "DenseConfig": | |
| with open(path) as f: | |
| return cls.from_dict(json.load(f)) | |
| def to_dict(self) -> dict: | |
| return asdict(self) | |
| class RMSNorm(nn.Module): | |
| """RMSNorm over the last dimension, or within consecutive groups of ``group_size`` channels. | |
| The normalization runs in float32; the result is cast back to the input dtype and then multiplied by the | |
| per-channel gain (one gain per channel, also in grouped mode). | |
| """ | |
| def __init__(self, dim: int, eps: float, group_size: int | None = None) -> None: | |
| super().__init__() | |
| self.eps = eps | |
| self.group_size = group_size | |
| self.weight = nn.Parameter(torch.ones(dim)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| h = x.float() | |
| if self.group_size is not None: | |
| h = h.unflatten(-1, (-1, self.group_size)) | |
| h = h * torch.rsqrt(h.pow(2).mean(-1, keepdim=True) + self.eps) | |
| if self.group_size is not None: | |
| h = h.flatten(-2) | |
| return h.to(x.dtype) * self.weight | |
| def rotary_cos_sin(config: DenseConfig, num_positions: int, device: torch.device) -> tuple[torch.Tensor, torch.Tensor]: | |
| """Rotary tables of shape [num_positions, head_dim] in float32 (rotate-half layout).""" | |
| dim = config.head_dim | |
| frequencies = 1.0 / (config.rope_theta ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim)) # on CPU | |
| frequencies = frequencies.to(device) | |
| positions = torch.arange(num_positions, device=device, dtype=torch.float32) | |
| if config.rope_precision == "bfloat16": | |
| # Every step rounded to bfloat16 (the product of two bfloat16 numbers is exact in float32, so rounding it | |
| # once gives the bfloat16 product). | |
| def bf16(t: torch.Tensor) -> torch.Tensor: | |
| return t.to(torch.bfloat16).float() | |
| angles = bf16(torch.outer(bf16(positions), bf16(frequencies))) | |
| angles = torch.cat((angles, angles), dim=-1) | |
| return bf16(angles.cos()), bf16(angles.sin()) | |
| if config.rope_precision != "float32": | |
| raise ValueError(f"rope_precision must be 'bfloat16' or 'float32', got {config.rope_precision!r}") | |
| angles = torch.outer(positions, frequencies) | |
| angles = torch.cat((angles, angles), dim=-1) | |
| return angles.cos(), angles.sin() | |
| def rotate_half(x: torch.Tensor) -> torch.Tensor: | |
| """(a, b) -> (-b, a) over the two halves of the last dimension.""" | |
| half = x.shape[-1] // 2 | |
| return torch.cat((-x[..., half:], x[..., :half]), dim=-1) | |
| def apply_rotary(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| """x: [batch, tokens, heads, head_dim]; cos, sin: [tokens, head_dim], already in x's dtype.""" | |
| cos = cos[None, :, None, :] | |
| sin = sin[None, :, None, :] | |
| return x * cos + rotate_half(x) * sin | |
| class Attention(nn.Module): | |
| def __init__(self, config: DenseConfig) -> None: | |
| super().__init__() | |
| self.num_heads = config.num_attention_heads | |
| self.head_dim = config.head_dim | |
| width = config.num_attention_heads * config.head_dim | |
| self.q_proj = nn.Linear(config.hidden_size, width, bias=False) | |
| self.k_proj = nn.Linear(config.hidden_size, width, bias=False) | |
| self.v_proj = nn.Linear(config.hidden_size, width, bias=False) | |
| self.gate_proj = nn.Linear(config.hidden_size, width, bias=False) # sigmoid output gate | |
| self.o_proj = nn.Linear(width, config.hidden_size, bias=False) | |
| self.q_norm = RMSNorm(width, config.rms_norm_eps, group_size=config.head_dim) | |
| self.k_norm = RMSNorm(width, config.rms_norm_eps, group_size=config.head_dim) | |
| def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| batch, length, _ = x.shape | |
| heads = (batch, length, self.num_heads, self.head_dim) | |
| q = self.q_norm(self.q_proj(x)).view(heads) | |
| k = self.k_norm(self.k_proj(x)).view(heads) | |
| v = self.v_proj(x).view(heads) | |
| q = apply_rotary(q, cos, sin) | |
| k = apply_rotary(k, cos, sin) | |
| out = F.scaled_dot_product_attention( | |
| q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2), is_causal=True, scale=self.head_dim**-0.5 | |
| ) | |
| out = out.transpose(1, 2).reshape(batch, length, self.num_heads * self.head_dim) | |
| out = out * torch.sigmoid(self.gate_proj(x)) | |
| return self.o_proj(out) | |
| class MLP(nn.Module): | |
| def __init__(self, config: DenseConfig) -> None: | |
| super().__init__() | |
| self.gate_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False) | |
| self.up_proj = nn.Linear(config.hidden_size, config.intermediate_size, bias=False) | |
| self.down_proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x)) | |
| class DecoderLayer(nn.Module): | |
| def __init__(self, config: DenseConfig) -> None: | |
| super().__init__() | |
| self.attn_norm = RMSNorm(config.hidden_size, config.rms_norm_eps) | |
| self.attn = Attention(config) | |
| self.mlp_norm = RMSNorm(config.hidden_size, config.rms_norm_eps) | |
| self.mlp = MLP(config) | |
| def forward(self, x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| h = x + self.attn(self.attn_norm(x), cos, sin) | |
| return h + self.mlp(self.mlp_norm(h)) | |
| class DenseDecoder(nn.Module): | |
| """Decoder-only language model. ``forward`` returns float32 logits (and the mean cross-entropy loss when | |
| ``labels`` is given; label -100 is ignored).""" | |
| def __init__(self, config: DenseConfig) -> None: | |
| super().__init__() | |
| self.config = config | |
| self.embed_tokens = nn.Embedding(config.vocab_size, config.hidden_size) | |
| self.layers = nn.ModuleList(DecoderLayer(config) for _ in range(config.num_hidden_layers)) | |
| self.norm = RMSNorm(config.hidden_size, config.rms_norm_eps) | |
| self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) | |
| def hidden_states(self, input_ids: torch.Tensor) -> torch.Tensor: | |
| """Final hidden states after the last norm, [batch, tokens, hidden_size], in the model's dtype.""" | |
| length = input_ids.shape[1] | |
| if length > self.config.max_position_embeddings: | |
| raise ValueError(f"{length} tokens exceed the context of {self.config.max_position_embeddings}") | |
| x = self.embed_tokens(input_ids) | |
| cos, sin = rotary_cos_sin(self.config, length, input_ids.device) | |
| cos, sin = cos.to(x.dtype), sin.to(x.dtype) | |
| for layer in self.layers: | |
| x = layer(x, cos, sin) | |
| return self.norm(x) | |
| def forward(self, input_ids: torch.Tensor, labels: torch.Tensor | None = None): | |
| logits = self.lm_head(self.hidden_states(input_ids)).float() | |
| if labels is None: | |
| return logits | |
| loss = F.cross_entropy(logits.flatten(0, 1), labels.flatten(), ignore_index=-100) | |
| return logits, loss | |
| def generate(self, input_ids: torch.Tensor, max_new_tokens: int, temperature: float = 0.0) -> torch.Tensor: | |
| """Simple sampling without a key/value cache (greedy when temperature is 0).""" | |
| for _ in range(max_new_tokens): | |
| context = input_ids[:, -self.config.max_position_embeddings:] | |
| next_logits = self(context)[:, -1] | |
| if temperature > 0: | |
| probs = torch.softmax(next_logits / temperature, dim=-1) | |
| next_ids = torch.multinomial(probs, num_samples=1) | |
| else: | |
| next_ids = next_logits.argmax(dim=-1, keepdim=True) | |
| input_ids = torch.cat([input_ids, next_ids], dim=1) | |
| return input_ids | |
| def from_pretrained(cls, directory: str, device="cpu", dtype: torch.dtype = torch.float32) -> "DenseDecoder": | |
| """Load ``config.json`` and ``model.safetensors`` from a released model directory. | |
| The weights are read and cast to ``dtype`` on the CPU and then moved to ``device``, so the device holds only | |
| one copy of the model, in ``dtype``.""" | |
| from safetensors.torch import load_file | |
| config = DenseConfig.from_json_file(os.path.join(directory, "config.json")) | |
| state = load_file(os.path.join(directory, "model.safetensors"), device="cpu") | |
| if dtype is not None: | |
| for name in list(state): # cast on the CPU, one tensor at a time | |
| if state[name].is_floating_point(): | |
| state[name] = state[name].to(dtype) | |
| with torch.device("meta"): | |
| model = cls(config) | |
| model.load_state_dict(state, strict=True, assign=True) | |
| return model.to(device).eval() | |