217 lines
8.4 KiB
Python
217 lines
8.4 KiB
Python
"""
|
|
AILO Model for HuggingFace Transformers - Matching original architecture
|
|
"""
|
|
|
|
import torch
|
|
import torch.nn as nn
|
|
import torch.nn.functional as F
|
|
import math
|
|
from typing import Optional, Tuple, Union
|
|
|
|
from transformers import PreTrainedModel
|
|
from transformers.generation import GenerationMixin
|
|
from transformers.modeling_outputs import CausalLMOutputWithPast
|
|
|
|
try:
|
|
from .configuration_ailo import AILOConfig
|
|
except ImportError:
|
|
from configuration_ailo import AILOConfig
|
|
|
|
|
|
class RotaryPositionalEmbedding(nn.Module):
|
|
"""Rotary Position Embedding (RoPE)."""
|
|
|
|
def __init__(self, dim: int, max_seq_len: int = 512, base: int = 10000):
|
|
super().__init__()
|
|
inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
|
|
self.register_buffer("inv_freq", inv_freq)
|
|
self.max_seq_len = max_seq_len
|
|
|
|
def forward(self, x: torch.Tensor, seq_len: int) -> Tuple[torch.Tensor, torch.Tensor]:
|
|
t = torch.arange(seq_len, device=x.device).type_as(self.inv_freq)
|
|
freqs = torch.einsum("i,j->ij", t, self.inv_freq)
|
|
emb = torch.cat((freqs, freqs), dim=-1)
|
|
return emb.cos(), emb.sin()
|
|
|
|
|
|
def apply_rotary_pos_emb(q, k, cos, sin):
|
|
"""Apply rotary position embedding."""
|
|
def rotate_half(x):
|
|
x1, x2 = x[..., :x.shape[-1]//2], x[..., x.shape[-1]//2:]
|
|
return torch.cat((-x2, x1), dim=-1)
|
|
|
|
q_embed = (q * cos) + (rotate_half(q) * sin)
|
|
k_embed = (k * cos) + (rotate_half(k) * sin)
|
|
return q_embed, k_embed
|
|
|
|
|
|
class AILOAttention(nn.Module):
|
|
"""Multi-head attention matching original structure."""
|
|
|
|
def __init__(self, config: AILOConfig):
|
|
super().__init__()
|
|
self.n_heads = config.num_attention_heads
|
|
self.head_dim = config.hidden_size // config.num_attention_heads
|
|
self.scale = self.head_dim ** -0.5
|
|
|
|
# Match original: separate q, k, v projections
|
|
self.q_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
|
|
self.k_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
|
|
self.v_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
|
|
self.out_proj = nn.Linear(config.hidden_size, config.hidden_size, bias=False)
|
|
|
|
self.dropout = nn.Dropout(config.attention_probs_dropout_prob)
|
|
self.rotary = RotaryPositionalEmbedding(self.head_dim, config.max_position_embeddings)
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
B, T, C = x.shape
|
|
|
|
q = self.q_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
|
k = self.k_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
|
v = self.v_proj(x).view(B, T, self.n_heads, self.head_dim).transpose(1, 2)
|
|
|
|
cos, sin = self.rotary(x, T)
|
|
cos, sin = cos.unsqueeze(0).unsqueeze(0), sin.unsqueeze(0).unsqueeze(0)
|
|
q, k = apply_rotary_pos_emb(q, k, cos, sin)
|
|
|
|
attn = (q @ k.transpose(-2, -1)) * self.scale
|
|
|
|
# Causal mask
|
|
causal_mask = torch.triu(torch.ones(T, T, device=x.device), diagonal=1).bool()
|
|
attn = attn.masked_fill(causal_mask.unsqueeze(0).unsqueeze(0), float('-inf'))
|
|
|
|
attn = F.softmax(attn, dim=-1)
|
|
attn = self.dropout(attn)
|
|
|
|
out = (attn @ v).transpose(1, 2).reshape(B, T, C)
|
|
return self.out_proj(out)
|
|
|
|
|
|
class AILOMLP(nn.Module):
|
|
"""Feed-forward with SwiGLU - matching original w1, w2, w3 structure."""
|
|
|
|
def __init__(self, config: AILOConfig):
|
|
super().__init__()
|
|
# Match original: w1 [3072, 768], w2 [768, 3072], w3 [3072, 768]
|
|
self.w1 = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
|
|
self.w2 = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)
|
|
self.w3 = nn.Linear(config.hidden_size, config.intermediate_size, bias=False)
|
|
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
# SwiGLU: w2(silu(w1(x)) * w3(x))
|
|
return self.dropout(self.w2(F.silu(self.w1(x)) * self.w3(x)))
|
|
|
|
|
|
class AILOBlock(nn.Module):
|
|
"""Transformer block matching original structure."""
|
|
|
|
def __init__(self, config: AILOConfig):
|
|
super().__init__()
|
|
self.ln1 = nn.LayerNorm(config.hidden_size, elementwise_affine=True, bias=False)
|
|
self.attn = AILOAttention(config)
|
|
self.ln2 = nn.LayerNorm(config.hidden_size, elementwise_affine=True, bias=False)
|
|
self.ff = AILOMLP(config)
|
|
|
|
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
x = x + self.attn(self.ln1(x))
|
|
x = x + self.ff(self.ln2(x))
|
|
return x
|
|
|
|
|
|
class AILOPreTrainedModel(PreTrainedModel):
|
|
"""Base class for AILO models."""
|
|
|
|
config_class = AILOConfig
|
|
base_model_prefix = "ailo"
|
|
|
|
def _init_weights(self, module):
|
|
if isinstance(module, nn.Linear):
|
|
nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
|
elif isinstance(module, nn.Embedding):
|
|
nn.init.normal_(module.weight, mean=0.0, std=0.02)
|
|
|
|
|
|
class AILOForCausalLM(AILOPreTrainedModel, GenerationMixin):
|
|
"""AILO model for causal language modeling - matching original structure."""
|
|
|
|
def __init__(self, config: AILOConfig):
|
|
super().__init__(config)
|
|
|
|
# Match original naming: tok_emb, blocks, ln_f, head
|
|
self.tok_emb = nn.Embedding(config.vocab_size, config.hidden_size)
|
|
self.blocks = nn.ModuleList([AILOBlock(config) for _ in range(config.num_hidden_layers)])
|
|
self.ln_f = nn.LayerNorm(config.hidden_size, elementwise_affine=True, bias=False)
|
|
self.head = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
|
|
|
# Weight tying
|
|
self.head.weight = self.tok_emb.weight
|
|
|
|
self.post_init()
|
|
|
|
def forward(
|
|
self,
|
|
input_ids: torch.LongTensor,
|
|
attention_mask: Optional[torch.Tensor] = None,
|
|
labels: Optional[torch.LongTensor] = None,
|
|
**kwargs
|
|
) -> CausalLMOutputWithPast:
|
|
x = self.tok_emb(input_ids)
|
|
|
|
for block in self.blocks:
|
|
x = block(x)
|
|
|
|
x = self.ln_f(x)
|
|
logits = self.head(x)
|
|
|
|
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))
|
|
|
|
return CausalLMOutputWithPast(loss=loss, logits=logits)
|
|
|
|
def prepare_inputs_for_generation(self, input_ids, **kwargs):
|
|
return {"input_ids": input_ids}
|
|
|
|
@torch.no_grad()
|
|
def generate(
|
|
self,
|
|
input_ids: torch.LongTensor,
|
|
max_new_tokens: int = 100,
|
|
temperature: float = 0.8,
|
|
top_k: int = 50,
|
|
top_p: float = 0.95,
|
|
**kwargs
|
|
) -> torch.LongTensor:
|
|
"""Generate text tokens."""
|
|
for _ in range(max_new_tokens):
|
|
idx_cond = input_ids[:, -512:] # Max context
|
|
outputs = self(idx_cond)
|
|
logits = outputs.logits[:, -1, :] / temperature
|
|
|
|
# Top-k
|
|
if top_k > 0:
|
|
v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
|
|
logits[logits < v[:, [-1]]] = float('-inf')
|
|
|
|
# Top-p
|
|
if top_p < 1.0:
|
|
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
|
|
cumulative_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
|
|
sorted_indices_to_remove = cumulative_probs > top_p
|
|
sorted_indices_to_remove[..., 1:] = sorted_indices_to_remove[..., :-1].clone()
|
|
sorted_indices_to_remove[..., 0] = 0
|
|
indices_to_remove = sorted_indices_to_remove.scatter(1, sorted_indices, sorted_indices_to_remove)
|
|
logits[indices_to_remove] = float('-inf')
|
|
|
|
probs = F.softmax(logits, dim=-1)
|
|
next_token = torch.multinomial(probs, num_samples=1)
|
|
input_ids = torch.cat([input_ids, next_token], dim=1)
|
|
|
|
if next_token.item() == self.config.eos_token_id:
|
|
break
|
|
|
|
return input_ids
|