bard-small / bard_core.py
lowerkey's picture
Upload folder using huggingface_hub
76d09fe verified
Raw History Blame Contribute Delete
16.6 kB
"""The network of bard-small."""
import re
import math
import time
from functools import lru_cache
import torch
from torch import nn
from torch.nn import functional as F
try:
from torch.nn.attention.flex_attention import create_block_mask, flex_attention
except ImportError: # pragma: no cover
create_block_mask = flex_attention = None
from torch.utils.checkpoint import checkpoint
@lru_cache(maxsize=32)
def sliding_window_block_mask(T: int, window: int, device: str):
def mask_mod(b, h, q_idx, kv_idx):
causal = q_idx >= kv_idx
in_window = (q_idx - kv_idx) < window
return causal & in_window
return create_block_mask(mask_mod, B=None, H=None, Q_LEN=T, KV_LEN=T, device=device)
def apply_rope(x, base=10000.0, offset=0):
T, D = x.shape[-2], x.shape[-1]
inv_freq = base ** (-torch.arange(0, D, 2, device=x.device, dtype=torch.float32) / D)
ang = torch.arange(offset, offset + T, device=x.device, dtype=torch.float32)[:, None] * inv_freq
cos, sin = ang.cos().to(x.dtype), ang.sin().to(x.dtype)
x1, x2 = x[..., : D // 2], x[..., D // 2:]
return torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
class LayerKV:
def __init__(self, window):
self.window = window
self.k = self.v = None
self.lo = self.hi = 0
self.start = 0
def append(self, k, v):
T = k.shape[2]
if self.k is None:
cap = max(64, 2 * T)
self.k = k.new_empty(k.shape[:2] + (cap,) + k.shape[3:])
self.v = v.new_empty(self.k.shape)
elif self.hi + T > self.k.shape[2]:
live = self.hi - self.lo
cap = self.k.shape[2]
if live + T > cap // 2:
cap = 2 * (live + T)
k_buf = self.k.new_empty(self.k.shape[:2] + (cap,) + self.k.shape[3:])
v_buf = self.v.new_empty(k_buf.shape)
k_buf[:, :, :live] = self.k[:, :, self.lo:self.hi]
v_buf[:, :, :live] = self.v[:, :, self.lo:self.hi]
self.k, self.v, self.lo, self.hi = k_buf, v_buf, 0, live
self.k[:, :, self.hi:self.hi + T] = k
self.v[:, :, self.hi:self.hi + T] = v
self.hi += T
return self.k[:, :, self.lo:self.hi], self.v[:, :, self.lo:self.hi], self.start
def trim(self):
if self.window is not None and self.hi - self.lo > self.window - 1:
drop = self.hi - self.lo - (self.window - 1)
self.lo += drop
self.start += drop
class KVCache:
def __init__(self, transformer):
self.windows = [block.window for block in transformer.blocks]
self.reset()
def reset(self):
self.layers = [LayerKV(w) for w in self.windows]
self.pos = 0
def __len__(self):
return self.pos
def attend(q, k, v, rope, window, block_mask, cache=None, pos=0):
if cache is None:
if rope:
q, k = apply_rope(q), apply_rope(k)
if block_mask is None:
return F.scaled_dot_product_attention(q, k, v, is_causal=True)
return flex_attention(q, k, v, block_mask=block_mask)
T = q.shape[2]
if rope:
q, k = apply_rope(q, offset=pos), apply_rope(k, offset=pos)
k_all, v_all, k_start = cache.append(k, v)
S = k_all.shape[2]
if T == 1 and (window is None or S <= window):
out = F.scaled_dot_product_attention(q, k_all, v_all)
elif S == T and (window is None or T <= window):
out = F.scaled_dot_product_attention(q, k_all, v_all, is_causal=True)
else:
q_pos = torch.arange(pos, pos + T, device=q.device)[:, None]
k_pos = torch.arange(k_start, k_start + S, device=q.device)[None, :]
mask = k_pos <= q_pos
if window is not None:
mask = mask & (q_pos - k_pos < window)
out = F.scaled_dot_product_attention(q, k_all, v_all, attn_mask=mask)
cache.trim()
return out
class Attention(nn.Module):
def __init__(self, d_model, heads, window=None, rope=False):
super().__init__()
assert d_model % heads == 0
self.d_model = d_model
self.heads = heads
self.head_dim = d_model // heads
self.window = window
self.rope = rope
self.qkvg = nn.Linear(d_model, 4 * d_model, bias=False)
self.out = nn.Linear(d_model, d_model, bias=False)
self.qnorm = nn.RMSNorm(self.head_dim)
self.knorm = nn.RMSNorm(self.head_dim)
def forward(self, x, block_mask=None, cache=None, pos=0):
B, T, E = x.shape
H, D = self.heads, self.head_dim
q, k, v, g = self.qkvg(x).split(self.d_model, dim=-1)
q = self.qnorm(q.view(B, T, H, D)).transpose(1, 2)
k = self.knorm(k.view(B, T, H, D)).transpose(1, 2)
v = v.view(B, T, H, D).transpose(1, 2)
out = attend(q, k, v, self.rope, self.window, block_mask, cache, pos)
out = out.transpose(1, 2).reshape(B, T, E)
out = out * torch.sigmoid(g)
return self.out(out)
class FFN(nn.Module):
def __init__(self, d_model, expansion=4, match_naive_params=True):
super().__init__()
hidden = d_model * expansion
if match_naive_params:
hidden = int(hidden * 2 / 3)
hidden = max(128, (hidden // 128) * 128)
self.hidden = hidden
self.W_gate_up = nn.Linear(d_model, 2 * hidden, bias=False)
self.W_down = nn.Linear(hidden, d_model, bias=False)
def forward(self, x):
gate, value = self.W_gate_up(x).chunk(2, dim=-1)
return self.W_down(F.silu(gate) * value)
class Block(nn.Module):
def __init__(self, d_model, heads, ffn_expansion=4, window=None, rope=False):
super().__init__()
self.attn = Attention(d_model, heads, window, rope)
self.ffn = FFN(d_model, ffn_expansion)
self.norm1 = nn.RMSNorm(d_model)
self.norm2 = nn.RMSNorm(d_model)
@property
def window(self):
return self.attn.window
def forward(self, x, block_mask=None, cache=None, pos=0):
x = x + self.attn(self.norm1(x), block_mask, cache, pos)
x = x + self.ffn(self.norm2(x))
return x
class ParallelBlock(nn.Module):
def __init__(self, d_model, heads, ffn_expansion=4, window=None, rope=False):
super().__init__()
assert d_model % heads == 0
self.d_model, self.heads, self.head_dim = d_model, heads, d_model // heads
self.window = window
self.rope = rope
hidden = int(d_model * ffn_expansion * 2 / 3)
self.hidden = max(128, (hidden // 128) * 128)
self.norm = nn.RMSNorm(d_model)
self.W_in = nn.Linear(d_model, 4 * d_model + 2 * self.hidden, bias=False)
self.W_out = nn.Linear(d_model + self.hidden, d_model, bias=False)
self.qnorm = nn.RMSNorm(self.head_dim)
self.knorm = nn.RMSNorm(self.head_dim)
def forward(self, x, block_mask=None, cache=None, pos=0):
B, T, E = x.shape
H, D = self.heads, self.head_dim
q, k, v, g, gate, up = self.W_in(self.norm(x)).split(
[E, E, E, E, self.hidden, self.hidden], dim=-1)
q = self.qnorm(q.view(B, T, H, D)).transpose(1, 2)
k = self.knorm(k.view(B, T, H, D)).transpose(1, 2)
v = v.view(B, T, H, D).transpose(1, 2)
a = attend(q, k, v, self.rope, self.window, block_mask, cache, pos)
a = a.transpose(1, 2).reshape(B, T, E) * torch.sigmoid(g)
f = F.silu(gate) * up
return x + self.W_out(torch.cat([a, f], dim=-1))
def _init_weights(module):
if isinstance(module, nn.Linear):
nn.init.normal_(module.weight, mean=0.0, std=0.02)
if module.bias is not None:
nn.init.zeros_(module.bias)
class CausalTransformer(nn.Module):
def __init__(
self,
d_model: int,
heads: int,
n_blocks: int,
ffn_expansion: int = 4,
window: int = 1024,
global_every: int = 4,
grad_checkpoint: bool = True,
compile_blocks: bool = True,
parallel: bool = False,
rope: str = "local",
):
super().__init__()
self.grad_checkpoint = grad_checkpoint
windows = []
for i in range(n_blocks):
is_global = ((i + 1) % global_every == 0) or (i == n_blocks - 1)
windows.append(None if is_global else window)
self.windows = windows
block_cls = ParallelBlock if parallel else Block
self.blocks = nn.ModuleList(
[block_cls(d_model, heads, ffn_expansion, w,
rope=(rope == "all" or (rope == "local" and w is not None)))
for w in windows]
)
self.norm = nn.RMSNorm(d_model)
self.apply(_init_weights)
residual_std = 0.02 / math.sqrt(2 * n_blocks)
for block in self.blocks:
if parallel:
nn.init.normal_(block.W_out.weight, mean=0.0, std=residual_std)
else:
nn.init.normal_(block.attn.out.weight, mean=0.0, std=residual_std)
nn.init.normal_(block.ffn.W_down.weight, mean=0.0, std=residual_std)
if compile_blocks:
for block in self.blocks:
block.compile()
def block_masks(self, T, device):
return {
w: sliding_window_block_mask(T, w, str(device))
for w in set(self.windows)
if w is not None and w < T
}
cache_offset = 0
"""While a cached forward runs: the absolute position of x[:, 0] (hooks that index
positions, like steering, need it). 0 otherwise."""
def forward(self, x, masks=None, cache=None):
if cache is not None:
pos = cache.pos
self.cache_offset = pos
try:
for block, layer_cache in zip(self.blocks, cache.layers):
x = block(x, None, layer_cache, pos)
finally:
self.cache_offset = 0
cache.pos += x.shape[1]
return self.norm(x)
if masks is None:
masks = self.block_masks(x.shape[1], x.device)
for block in self.blocks:
m = masks.get(block.window)
if self.grad_checkpoint and self.training:
x = checkpoint(block, x, m, use_reentrant=False)
else:
x = block(x, m)
return self.norm(x)
MAX_DIGITS = 8
LEN_SHIFT = 27
VALUE_MASK = (1 << LEN_SHIFT) - 1
NUMBER_RE = re.compile(r"( ?)([0-9]+)")
def pack(digits: str) -> int:
assert 0 < len(digits) <= MAX_DIGITS
return int(digits) | (len(digits) << LEN_SHIFT)
def unpack(packed: int) -> str:
return str(packed & VALUE_MASK).zfill(packed >> LEN_SHIFT)
def split_numbers(text):
out, pos = [], 0
for m in NUMBER_RE.finditer(text):
if m.start() > pos:
out.append(text[pos:m.start()])
spaced, digits = bool(m.group(1)), m.group(2)
for i in range(0, len(digits), MAX_DIGITS):
out.append((spaced and i == 0, pack(digits[i:i + MAX_DIGITS])))
pos = m.end()
if pos < len(text):
out.append(text[pos:])
return out
def _lengths_values(packed):
packed = packed.long()
return packed >> LEN_SHIFT, packed & VALUE_MASK
def _pow10(n, device):
return 10 ** torch.arange(n, device=device, dtype=torch.long)
class NumberEncoder(nn.Module):
def __init__(self, d_model, n_freq=4):
super().__init__()
self.n_freq = n_freq
n_feat = MAX_DIGITS * (2 * n_freq + 1) + MAX_DIGITS + 1
self.mlp = nn.Sequential(nn.Linear(n_feat, d_model), nn.SiLU(), nn.Linear(d_model, d_model))
for m in self.mlp:
if isinstance(m, nn.Linear):
nn.init.normal_(m.weight, std=0.02)
nn.init.zeros_(m.bias)
def forward(self, packed):
L, value = _lengths_values(packed)
digits = value.unsqueeze(-1) // _pow10(MAX_DIGITS, packed.device) % 10
present = (torch.arange(MAX_DIGITS, device=packed.device) < L.unsqueeze(-1)).float()
freqs = torch.arange(1, self.n_freq + 1, dtype=torch.float32, device=packed.device)
angle = 2 * math.pi * digits.float().unsqueeze(-1) / 10 * freqs
places = torch.cat([angle.sin(), angle.cos()], -1) * present.unsqueeze(-1)
feats = torch.cat([
places.flatten(-2),
present,
F.one_hot((L - 1).clamp(min=0), MAX_DIGITS).float() * (L > 0).unsqueeze(-1),
(torch.log10(value.float() + 1) / MAX_DIGITS).unsqueeze(-1),
], -1)
out = self.mlp(feats.to(self.mlp[0].weight.dtype))
return out * (packed != 0).unsqueeze(-1).to(out.dtype)
class NumberDecoder(nn.Module):
def __init__(self, d_model, width=256, heads=4, layers=2):
super().__init__()
self.len_head = nn.Sequential(nn.Linear(d_model, width, bias=False), nn.SiLU(),
nn.Linear(width, MAX_DIGITS, bias=False))
self.ctx = nn.Linear(d_model, width, bias=False)
self.len_emb = nn.Embedding(MAX_DIGITS, width)
self.place_emb = nn.Embedding(MAX_DIGITS, width)
self.prev_emb = nn.Embedding(11, width)
self.blocks = nn.ModuleList([Block(width, heads) for _ in range(layers)])
self.norm = nn.RMSNorm(width)
self.out = nn.Linear(width, 10, bias=False)
for m in self.modules():
if isinstance(m, (nn.Linear, nn.Embedding)):
nn.init.normal_(m.weight, std=0.02)
for b in self.blocks:
nn.init.normal_(b.attn.out.weight, std=0.02 / math.sqrt(2 * layers))
nn.init.normal_(b.ffn.W_down.weight, std=0.02 / math.sqrt(2 * layers))
def _digit_logits(self, h, L, prev):
j = torch.arange(MAX_DIGITS, device=h.device)
power = (L.unsqueeze(-1) - 1 - j).clamp(min=0)
x = (self.ctx(h) + self.len_emb(L - 1)).unsqueeze(1) + self.place_emb(power) + self.prev_emb(prev)
for b in self.blocks:
x = b(x)
return self.out(self.norm(x))
@staticmethod
def _msb_digits(packed):
L, value = _lengths_values(packed)
power = (L.unsqueeze(-1) - 1 - torch.arange(MAX_DIGITS, device=packed.device)).clamp(min=0)
digits = value.unsqueeze(-1) // (10 ** power) % 10
valid = torch.arange(MAX_DIGITS, device=packed.device) < L.unsqueeze(-1)
return L, digits, valid
def nll(self, h, packed, weight=None):
L, digits, valid = self._msb_digits(packed)
prev = torch.cat([torch.full_like(digits[:, :1], 10), digits[:, :-1]], 1)
len_nll = F.cross_entropy(self.len_head(h).float(), L - 1, reduction="none")
logits = self._digit_logits(h, L, prev).float()
dig_nll = F.cross_entropy(logits.view(-1, 10), digits.view(-1), reduction="none")
per_num = len_nll + (dig_nll.view_as(digits) * valid).sum(-1)
return per_num.sum() if weight is None else (per_num * weight).sum()
@torch.no_grad()
def generate(self, h, temperature=0.0):
def pick(logits):
if temperature <= 0:
return logits.argmax(-1)
return torch.multinomial(F.softmax(logits.float() / temperature, -1), 1).squeeze(-1)
L = pick(self.len_head(h)) + 1
prev = torch.full((h.shape[0], MAX_DIGITS), 10, dtype=torch.long, device=h.device)
digits = torch.zeros_like(prev)
for j in range(MAX_DIGITS):
digits[:, j] = pick(self._digit_logits(h, L, prev)[:, j])
if j + 1 < MAX_DIGITS:
prev[:, j + 1] = digits[:, j]
power = (L.unsqueeze(-1) - 1 - torch.arange(MAX_DIGITS, device=h.device)).clamp(min=0)
valid = torch.arange(MAX_DIGITS, device=h.device) < L.unsqueeze(-1)
value = (digits * 10 ** power * valid).sum(-1)
return value | (L << LEN_SHIFT)
LOGIT_SOFTCAP = 30.0
def softcap(logits):
return LOGIT_SOFTCAP * torch.tanh(logits.float() / LOGIT_SOFTCAP)
class LM(nn.Module):
def __init__(self, vocab, d_model, heads, n_blocks, window, global_every, parallel=True, rope="local"):
super().__init__()
self.emb = nn.Embedding(vocab, d_model)
self.num_enc = NumberEncoder(d_model)
self.body = CausalTransformer(d_model, heads, n_blocks, window=window, global_every=global_every,
grad_checkpoint=False, parallel=parallel, rope=rope, compile_blocks=False)
self.head = nn.Linear(d_model, vocab, bias=False)
self.num_dec = NumberDecoder(d_model)
def hidden(self, idx, vals, masks=None, cache=None):
return self.body(self.emb(idx) + self.num_enc(vals), masks, cache)