GautamKishore's picture
Upload folder using huggingface_hub
c5f9f0d verified
Raw
History Blame Contribute Delete
11.6 kB
"""pico-type: a tiny byte-level multi-head content classifier."""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Dict, Optional, Tuple
import torch
import torch.nn as nn
import torch.nn.functional as F
TIERS: Dict[str, int] = {
"tiny": 16,
"small": 64,
"base": 192,
"pro": 576,
}
@dataclass
class PicoTypeConfig:
max_bytes: int = 1024
embed_dim: int = 96
trunk_dim: int = 192
num_heads: int = 4
num_attn_layers: int = 2
rope_theta: float = 500_000.0
conv_kernels: Tuple[int, ...] = (3, 5, 7)
tiers: Dict[str, int] = field(default_factory=lambda: dict(TIERS))
num_coarse: int = 12
num_modality: int = 8
num_subtype: int = 24
num_code_lang: int = 62
num_text_lang: int = 30
num_file_mime: int = 90
num_risk: int = 6
dropout: float = 0.1
undetected_threshold: float = 0.4
risk_threshold: float = 0.5
pad_byte: int = 0
def tier_dim(self, tier: str) -> int:
if tier not in self.tiers:
raise KeyError(f"unknown tier '{tier}', available: {list(self.tiers)}")
return self.tiers[tier]
class ByteEmbed(nn.Module):
def __init__(self, num_bytes: int = 256, dim: int = 96):
super().__init__()
self.embed = nn.Embedding(num_bytes, dim)
nn.init.normal_(self.embed.weight, std=0.02)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.embed(x)
class ConvBlock(nn.Module):
def __init__(self, in_dim: int, out_dim: int, kernel_size: int, dropout: float = 0.1):
super().__init__()
padding = kernel_size // 2
self.conv = nn.Conv1d(in_dim, out_dim, kernel_size, padding=padding, bias=False)
self.norm = nn.LayerNorm(out_dim)
self.act = nn.GELU()
self.drop = nn.Dropout(dropout)
if in_dim != out_dim:
self.proj = nn.Conv1d(in_dim, out_dim, kernel_size=1, bias=False)
else:
self.proj = nn.Identity()
def forward(self, x: torch.Tensor) -> torch.Tensor:
residual = self.proj(x)
h = self.conv(x)
h = h.transpose(1, 2)
h = self.norm(h)
h = self.act(h)
h = self.drop(h)
h = h.transpose(1, 2)
return h + residual
def _apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor:
d = x.size(-1)
x1, x2 = x[..., : d // 2], x[..., d // 2 :]
rot = torch.cat([-x2, x1], dim=-1)
return x * cos + rot * sin
class RotaryPosEmb(nn.Module):
def __init__(self, dim: int, theta: float = 500_000.0, max_seq: int = 4096):
super().__init__()
if dim % 2 != 0:
raise ValueError("rope dim must be even")
inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
self.register_buffer("inv_freq", inv_freq, persistent=False)
self._max_seq = max_seq
self._cached_seq = 0
self._cached_dim = dim
self._build_cache(max_seq, dim)
def _build_cache(self, max_seq: int, dim: int) -> None:
t = torch.arange(max_seq, dtype=self.inv_freq.dtype, device=self.inv_freq.device)
freqs = torch.einsum("i,j->ij", t, self.inv_freq)
emb = torch.cat([freqs, freqs], dim=-1)
self.register_buffer("cos_cached", emb.cos()[None, None, :, :], persistent=False)
self.register_buffer("sin_cached", emb.sin()[None, None, :, :], persistent=False)
self._cached_seq = max_seq
self._cached_dim = dim
def forward(self, q: torch.Tensor, k: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
seq = q.size(-2)
if seq > self._cached_seq:
self._build_cache(max(seq, self._cached_seq * 2), self._cached_dim)
cos = self.cos_cached[:, :, :seq, :]
sin = self.sin_cached[:, :, :seq, :]
return _apply_rope(q, cos, sin), _apply_rope(k, cos, sin)
class AttnBlock(nn.Module):
def __init__(
self,
dim: int,
num_heads: int,
dropout: float = 0.1,
rope_theta: float = 500_000.0,
):
super().__init__()
if dim % num_heads != 0:
raise ValueError(f"dim {dim} not divisible by num_heads {num_heads}")
self.num_heads = num_heads
self.head_dim = dim // num_heads
self.qkv = nn.Linear(dim, 3 * dim, bias=False)
self.out_proj = nn.Linear(dim, dim)
self.norm1 = nn.LayerNorm(dim)
self.norm2 = nn.LayerNorm(dim)
self.mlp = nn.Sequential(
nn.Linear(dim, 4 * dim),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(4 * dim, dim),
)
self.attn_drop = dropout
self.resid_drop = nn.Dropout(dropout)
self.rope = RotaryPosEmb(self.head_dim, rope_theta)
def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None) -> torch.Tensor:
B, L, D = x.shape
h = self.norm1(x)
qkv = self.qkv(h).reshape(B, L, 3, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
q, k, v = qkv[0], qkv[1], qkv[2]
q, k = self.rope(q, k)
attn_mask_float = None
if mask is not None:
attn_mask_float = mask[:, None, None, :].to(dtype=q.dtype)
attn_mask_float = torch.where(attn_mask_float.to(torch.bool), 0.0, float("-inf"))
out = F.scaled_dot_product_attention(
q, k, v,
attn_mask=attn_mask_float,
dropout_p=self.attn_drop if self.training else 0.0,
is_causal=False,
)
out = out.transpose(1, 2).reshape(B, L, D)
x = x + self.resid_drop(self.out_proj(out))
x = x + self.mlp(self.norm2(x))
return x
class Pool(nn.Module):
def forward(
self,
x: torch.Tensor,
mask: Optional[torch.Tensor] = None,
) -> torch.Tensor:
if mask is not None:
m = mask.unsqueeze(-1).to(x.dtype)
denom = m.sum(dim=1).clamp(min=1.0)
mean = (x * m).sum(dim=1) / denom
x_for_max = x.masked_fill(m == 0, float("-inf"))
max_ = x_for_max.max(dim=1).values
x_for_std = x * m
mean_for_std = x_for_std.sum(dim=1, keepdim=True) / denom.unsqueeze(1)
sq = ((x_for_std - mean_for_std * m) ** 2) * m
std = sq.sum(dim=1) / denom
std = std.sqrt().clamp(min=0.0)
else:
mean = x.mean(dim=1)
max_ = x.max(dim=1).values
std = x.std(dim=1, unbiased=False)
return torch.cat([mean, max_, std], dim=-1)
class MatryoshkaHead(nn.Module):
def __init__(
self,
in_dim: int,
tier_dims: Dict[str, int],
out_dim: int,
):
super().__init__()
if in_dim != max(tier_dims.values()):
raise ValueError(
f"trunk dim {in_dim} must equal max tier dim {max(tier_dims.values())}"
)
self.tier_dims = dict(tier_dims)
self.linears = nn.ModuleDict({
name: nn.Linear(d, out_dim) for name, d in tier_dims.items()
})
for linear in self.linears.values():
nn.init.zeros_(linear.bias)
nn.init.normal_(linear.weight, std=0.02)
def forward(self, x: torch.Tensor, tier: str) -> torch.Tensor:
if tier not in self.linears:
raise KeyError(f"unknown tier '{tier}', available: {list(self.linears)}")
d = self.tier_dims[tier]
sliced = x[..., :d]
return self.linears[tier](sliced)
class PicoType(nn.Module):
def __init__(self, config: Optional[PicoTypeConfig] = None):
super().__init__()
self.config = config or PicoTypeConfig()
cfg = self.config
self.embed = ByteEmbed(256, cfg.embed_dim)
in_dim = cfg.embed_dim
self.conv_blocks = nn.ModuleList()
for k in cfg.conv_kernels:
self.conv_blocks.append(
ConvBlock(in_dim, cfg.trunk_dim, k, cfg.dropout)
)
in_dim = cfg.trunk_dim
self.attn_blocks = nn.ModuleList([
AttnBlock(cfg.trunk_dim, cfg.num_heads, cfg.dropout, cfg.rope_theta)
for _ in range(cfg.num_attn_layers)
])
self.pool = Pool()
self.trunk_dim = 3 * cfg.trunk_dim
self.heads = nn.ModuleDict({
"coarse": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_coarse),
"modality": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_modality),
"subtype": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_subtype),
"code_lang": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_code_lang),
"text_lang": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_text_lang),
"file_mime": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_file_mime),
"risk": MatryoshkaHead(self.trunk_dim, cfg.tiers, cfg.num_risk),
})
def forward(
self,
bytes_input: torch.Tensor,
mask: Optional[torch.Tensor] = None,
tier: str = "base",
) -> Dict[str, torch.Tensor]:
if bytes_input.dtype != torch.long:
bytes_input = bytes_input.long()
if bytes_input.size(1) > self.config.max_bytes:
bytes_input = bytes_input[:, : self.config.max_bytes]
if mask is not None:
mask = mask[:, : self.config.max_bytes]
if mask is None:
mask = torch.ones_like(bytes_input, dtype=torch.bool)
else:
mask = mask.to(device=bytes_input.device, dtype=torch.bool)
empty_rows = mask.sum(dim=1) == 0
if empty_rows.any():
mask = mask.clone()
mask[empty_rows, 0] = True
x = self.embed(bytes_input)
x = x.transpose(1, 2)
for block in self.conv_blocks:
x = block(x)
x = x.transpose(1, 2)
for block in self.attn_blocks:
x = block(x, mask)
pooled = self.pool(x, mask)
return {name: head(pooled, tier) for name, head in self.heads.items()}
def parameter_count(self, tier: Optional[str] = None) -> int:
if tier is None:
return sum(p.numel() for p in self.parameters())
kept = 0
for name, p in self.named_parameters():
if ".linears." in name:
if f".linears.{tier}." in name:
kept += p.numel()
else:
kept += p.numel()
return kept
def tier_sizes(self) -> Dict[str, int]:
return {tier: self.parameter_count(tier) for tier in self.config.tiers}
def encode_bytes(data: bytes, max_len: int = 1024, pad: int = 0) -> Tuple[torch.Tensor, torch.Tensor]:
raw = list(data[:max_len])
seq = raw + [pad] * (max_len - len(raw))
mask = [1] * len(raw) + [0] * (max_len - len(raw))
return torch.tensor([seq], dtype=torch.long), torch.tensor([mask], dtype=torch.long)
def smoke_test() -> Dict[str, int]:
cfg = PicoTypeConfig(max_bytes=128)
model = PicoType(cfg).eval()
sample = (
b"def hello():\n print('hi')\n\nif __name__ == '__main__':\n hello()\n"
)
x, mask = encode_bytes(sample, max_len=cfg.max_bytes, pad=cfg.pad_byte)
with torch.no_grad():
logits = model(x, mask, tier="base")
sizes = {tier: model.parameter_count(tier) for tier in cfg.tiers}
return {
"logits_keys": len(logits),
"tiers": len(cfg.tiers),
**sizes,
}
if __name__ == "__main__":
info = smoke_test()
for k, v in info.items():
print(f"{k}: {v}")