| """ |
| 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, :] |
| pos = pos[:, None] |
| freqs = pos * inv_freq |
| |
| freqs = torch.cat([freqs, freqs], dim=-1) |
| 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, :, :] |
| sin = freqs.sin()[None, None, :, :] |
| |
| cos = cos[..., ::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) |
|
|
| |
| freqs = self.rotary(x) |
| q = apply_rotary(q, freqs) |
| k = apply_rotary(k, freqs) |
|
|
| |
| 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) |
|
|
| |
| 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, :, :] |
|
|
| 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): |
| |
| 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 |
|
|
| |
| if top_k > 0: |
| v, _ = torch.topk(logits, min(top_k, logits.size(-1))) |
| logits[logits < v[:, -1:]] = float('-inf') |
|
|
| |
| 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}") |
|
|