Yonetix-V1 / model.py
taojian4110's picture
Upload model.py with huggingface_hub
22d2bc7 verified
Raw
History Blame Contribute Delete
8.16 kB
"""
YonetixAI - 从零训练 Decoder-only Transformer
架构:GPT-like + RoPE + SwiGLU + Pre-LN
"""
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from dataclasses import dataclass
from typing import Optional
@dataclass
class ModelConfig:
vocab_size: int = 151936
hidden_dim: int = 768
num_layers: int = 12
num_heads: int = 12
ffn_hidden_dim: int = 3072
max_seq_len: int = 512
dropout: float = 0.1
layer_norm_eps: float = 1e-5
tie_word_embeddings: bool = True
init_std: float = 0.02
class RotaryEmbedding(nn.Module):
"""旋转位置编码 RoPE"""
def __init__(self, dim: int, max_seq_len: int = 512, theta: float = 10000.0):
super().__init__()
self.dim = dim
inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim))
self.register_buffer("inv_freq", inv_freq)
def forward(self, x: torch.Tensor, pos: Optional[torch.Tensor] = None):
seq_len = x.size(1)
if pos is None:
pos = torch.arange(seq_len, device=x.device, dtype=torch.float32)
inv_freq = self.inv_freq[None, :] # [1, dim/2]
pos = pos[:, None] # [seq, 1]
freqs = pos * inv_freq # [seq, dim/2]
# 扩展到全维度
freqs = torch.cat([freqs, freqs], dim=-1) # [seq, dim]
return freqs
def apply_rotary(x: torch.Tensor, freqs: torch.Tensor):
"""应用 RoPE 到 Q 或 K
x: [batch, heads, seq, dim]
freqs: [seq, dim]
"""
# 分离实部和虚部
x1 = x[..., ::2] # 偶数位
x2 = x[..., 1::2] # 奇数位
cos = freqs.cos()[None, None, :, :] # [1, 1, seq, dim]
sin = freqs.sin()[None, None, :, :]
# 确保维度匹配
cos = cos[..., ::2] # [1, 1, seq, dim/2]
sin = sin[..., ::2]
x_rot = torch.cat([x1 * cos - x2 * sin, x1 * sin + x2 * cos], dim=-1)
return x_rot
class Attention(nn.Module):
"""多头注意力 + RoPE"""
def __init__(self, config: ModelConfig):
super().__init__()
self.num_heads = config.num_heads
self.head_dim = config.hidden_dim // config.num_heads
self.hidden_dim = config.hidden_dim
self.q_proj = nn.Linear(config.hidden_dim, config.hidden_dim, bias=False)
self.k_proj = nn.Linear(config.hidden_dim, config.hidden_dim, bias=False)
self.v_proj = nn.Linear(config.hidden_dim, config.hidden_dim, bias=False)
self.o_proj = nn.Linear(config.hidden_dim, config.hidden_dim, bias=False)
self.dropout = nn.Dropout(config.dropout)
self.rotary = RotaryEmbedding(self.head_dim, config.max_seq_len)
def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None):
B, S, D = x.shape
q = self.q_proj(x).view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
k = self.k_proj(x).view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
v = self.v_proj(x).view(B, S, self.num_heads, self.head_dim).transpose(1, 2)
# RoPE
freqs = self.rotary(x) # [S, head_dim, 1]
q = apply_rotary(q, freqs)
k = apply_rotary(k, freqs)
# Flash Attention
attn = F.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=self.dropout.p if self.training else 0.0)
attn = attn.transpose(1, 2).contiguous().view(B, S, D)
return self.o_proj(attn)
class SwiGLU(nn.Module):
"""SwiGLU 激活函数"""
def __init__(self, hidden_dim: int, ffn_hidden_dim: int):
super().__init__()
self.gate = nn.Linear(hidden_dim, ffn_hidden_dim, bias=False)
self.up = nn.Linear(hidden_dim, ffn_hidden_dim, bias=False)
self.down = nn.Linear(ffn_hidden_dim, hidden_dim, bias=False)
def forward(self, x: torch.Tensor):
return self.down(F.silu(self.gate(x)) * self.up(x))
class TransformerBlock(nn.Module):
"""Transformer 层:Pre-LN + Attention + SwiGLU"""
def __init__(self, config: ModelConfig):
super().__init__()
self.attn_norm = nn.LayerNorm(config.hidden_dim, eps=config.layer_norm_eps)
self.attn = Attention(config)
self.ffn_norm = nn.LayerNorm(config.hidden_dim, eps=config.layer_norm_eps)
self.ffn = SwiGLU(config.hidden_dim, config.ffn_hidden_dim)
def forward(self, x: torch.Tensor, mask: Optional[torch.Tensor] = None):
x = x + self.attn(self.attn_norm(x), mask)
x = x + self.ffn(self.ffn_norm(x))
return x
class YonetixTransformer(nn.Module):
"""完整 Decoder-only Transformer"""
def __init__(self, config: ModelConfig):
super().__init__()
self.config = config
self.embed = nn.Embedding(config.vocab_size, config.hidden_dim)
self.layers = nn.ModuleList([TransformerBlock(config) for _ in range(config.num_layers)])
self.final_norm = nn.LayerNorm(config.hidden_dim, eps=config.layer_norm_eps)
self.lm_head = nn.Linear(config.hidden_dim, config.vocab_size, bias=False)
# 绑定 embedding 和 lm_head 权重
if config.tie_word_embeddings:
self.lm_head.weight = self.embed.weight
# 初始化
self.apply(self._init_weights)
def _init_weights(self, module):
if isinstance(module, nn.Linear):
torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.init_std)
if module.bias is not None:
torch.nn.init.zeros_(module.bias)
elif isinstance(module, nn.Embedding):
torch.nn.init.normal_(module.weight, mean=0.0, std=self.config.init_std)
def _create_causal_mask(self, seq_len: int, device: torch.device) -> torch.Tensor:
"""创建因果注意力掩码"""
mask = torch.triu(torch.full((seq_len, seq_len), float('-inf'), device=device), diagonal=1)
return mask[None, None, :, :] # [1, 1, S, S]
def forward(self, input_ids: torch.Tensor) -> torch.Tensor:
"""前向传播,返回 logits"""
B, S = input_ids.shape
h = self.embed(input_ids) * math.sqrt(self.config.hidden_dim)
mask = self._create_causal_mask(S, input_ids.device)
for layer in self.layers:
h = layer(h, mask)
h = self.final_norm(h)
logits = self.lm_head(h)
return logits
@torch.no_grad()
def generate(self, input_ids: torch.Tensor, max_new_tokens: int = 128,
temperature: float = 0.7, top_k: int = 50, top_p: float = 0.9):
"""自回归生成"""
self.eval()
for _ in range(max_new_tokens):
# 截断到 max_seq_len
if input_ids.size(1) > self.config.max_seq_len:
input_ids = input_ids[:, -self.config.max_seq_len:]
logits = self.forward(input_ids)
logits = 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 (nucleus)
if top_p < 1.0:
sorted_logits, sorted_indices = torch.sort(logits, descending=True)
cum_probs = torch.cumsum(F.softmax(sorted_logits, dim=-1), dim=-1)
sorted_indices_to_remove = cum_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)
return input_ids
def count_params(model: nn.Module) -> int:
return sum(p.numel() for p in model.parameters())
if __name__ == "__main__":
config = ModelConfig()
model = YonetixTransformer(config)
print(f"参数量: {count_params(model) / 1e6:.2f}M")
x = torch.randint(0, 100, (2, 32))
out = model(x)
print(f"输入: {x.shape}, 输出: {out.shape}")