import math import torch import torch.nn as nn import torch.nn.functional as F from transformers import PretrainedConfig, PreTrainedModel from transformers.modeling_outputs import CausalLMOutput def repeat_kv(hidden_states: torch.Tensor, n_rep: int) -> torch.Tensor: """ This is the equivalent of torch.repeat_interleave(x, dim=1, repeats=n_rep). The hidden states go from (batch, num_key_value_heads, seqlen, head_dim) to (batch, num_attention_heads, seqlen, head_dim) """ # Extract dimensions from input tensor batch, num_key_value_heads, slen, head_dim = hidden_states.shape # Early return if no repetition is needed if n_rep == 1: return hidden_states # Add a new dimension at index 2 (after num_key_value_heads) and expand # Shape transformation: # (batch, num_key_value_heads, slen, head_dim) # -> (batch, num_key_value_heads, 1, slen, head_dim) [via None indexing] # -> (batch, num_key_value_heads, n_rep, slen, head_dim) [via expand] hidden_states = hidden_states[:, :, None, :, :].expand(batch, num_key_value_heads, n_rep, slen, head_dim) # Flatten the num_key_value_heads and n_rep dimensions together # Final shape: (batch, num_key_value_heads * n_rep, slen, head_dim) # This effectively repeats each key/value head n_rep times return hidden_states.reshape(batch, num_key_value_heads * n_rep, slen, head_dim) class Rotary(nn.Module): def __init__(self, dim: int, max_seq_len: int): super().__init__() angular_freq = (1 / 10000) ** torch.linspace(0, 1, steps=dim//4, dtype=torch.float32) angular_freq = torch.cat([angular_freq, angular_freq.new_zeros(dim//4)]) t = torch.arange(max_seq_len, dtype=torch.float32) theta = torch.einsum("i,j -> ij", t, angular_freq) self.register_buffer('cos', theta.cos(), persistent=False) self.register_buffer('sin', theta.sin(), persistent=False) def forward(self, x_BTHD: torch.Tensor): assert self.cos.size(0) >= x_BTHD.size(-3) cos, sin = self.cos[None, :x_BTHD.size(-3), None, :], self.sin[None, :x_BTHD.size(-3), None, :] x1, x2 = x_BTHD.to(dtype=torch.float32).chunk(2, dim=-1) y1 = x1 * cos + x2 * sin y2 = x1 * (-sin) + x2 * cos return torch.cat((y1, y2), 3).type_as(x_BTHD) class Qwen3Attention(nn.Module): def __init__(self, config): super().__init__() self.d_model = config.d_model self.n_heads = config.n_heads self.n_kv_heads = config.n_kv_heads self.n_kv_groups = config.n_kv_groups self.d_k = config.d_k # Separate linear layers for Q, K, V self.q_proj = nn.Linear(self.d_model, self.n_heads * self.d_k, bias=config.attention_bias) self.k_proj = nn.Linear(self.d_model, self.n_kv_heads * self.d_k, bias=config.attention_bias) self.v_proj = nn.Linear(self.d_model, self.n_kv_heads * self.d_k, bias=config.attention_bias) self.w_o = nn.Linear(self.d_model, self.d_model, bias=False) # QK-Normalization layers self.q_norm = nn.RMSNorm(self.d_k, eps=config.rms_norm_eps) self.k_norm = nn.RMSNorm(self.d_k, eps=config.rms_norm_eps) self.rotary = Rotary(self.d_k, config.max_seq_len) self.dropout = config.dropout def forward(self, x): batch_size, seq_len = x.size(0), x.size(1) # 1. Project Q, K, V separately q = self.q_proj(x) k = self.k_proj(x) v = self.v_proj(x) # 2. Reshape into heads q = q.view(batch_size, seq_len, self.n_heads, self.d_k) k = k.view(batch_size, seq_len, self.n_kv_heads, self.d_k) v = v.view(batch_size, seq_len, self.n_kv_heads, self.d_k) # 3. Apply QK-Norm q = self.q_norm(q) k = self.k_norm(k) # 4. Apply RoPE # Transpose to (batch, seq_len, n_heads, d_k) -> (batch, n_heads, seq_len, d_k) for rotary q = self.rotary(q.permute(0, 2, 1, 3)).permute(0, 2, 1, 3) k = self.rotary(k.permute(0, 2, 1, 3)).permute(0, 2, 1, 3) # Transpose for attention: (batch, seq_len, n_heads, d_k) -> (batch, n_heads, seq_len, d_k) Q = q.transpose(1, 2) K = k.transpose(1, 2) V = v.transpose(1, 2) # 5. Repeat K and V heads for GQA K = repeat_kv(K, self.n_kv_groups) V = repeat_kv(V, self.n_kv_groups) # 6. Scaled Dot-Product Attention attn_output = F.scaled_dot_product_attention( Q, K, V, is_causal=True, dropout_p=self.dropout if self.training else 0.0 ) # 7. Reshape and final projection attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) return self.w_o(attn_output) class SwiGLUFeedForward(nn.Module): def __init__(self, d_model: int, d_ff: int, dropout: float = 0.1): super().__init__() self.gate_proj = nn.Linear(d_model, d_ff, bias=False) self.down_proj = nn.Linear(d_ff, d_model, bias=False) self.up_proj = nn.Linear(d_model, d_ff, bias=False) self.dropout = nn.Dropout(dropout) def forward(self, x): # Implementation of the SwiGLU activation function # F.silu is the Swish activation function activated_x = F.silu(self.gate_proj(x)) * self.up_proj(x) return self.down_proj(self.dropout(activated_x)) class TransformerBlock(nn.Module): def __init__(self, config): super().__init__() self.attention = Qwen3Attention(config) self.feed_forward = SwiGLUFeedForward(config.d_model, config.d_ff, config.dropout) self.norm1 = nn.RMSNorm(config.d_model, eps=config.rms_norm_eps) self.norm2 = nn.RMSNorm(config.d_model, eps=config.rms_norm_eps) self.dropout = nn.Dropout(config.dropout) def forward(self, x): attn_out = self.attention(self.norm1(x)) x = x + self.dropout(attn_out) ff_out = self.feed_forward(self.norm2(x)) x = x + self.dropout(ff_out) return x from transformers import PreTrainedModel, GenerationMixin from transformers.modeling_outputs import CausalLMOutput import torch.nn as nn import torch.nn.functional as F class MinimalLLMForCausalLM(PreTrainedModel, GenerationMixin): config_class = MinimalLLMConfig base_model_prefix = "model" main_input_name = "input_ids" def __init__(self, config: MinimalLLMConfig): super().__init__(config) self.token_embedding = nn.Embedding(config.vocab_size, config.d_model) self.position_dropout = nn.Dropout(config.dropout) self.transformer_blocks = nn.ModuleList( [TransformerBlock(config) for _ in range(config.n_layers)] ) self.norm = nn.RMSNorm(config.d_model, eps=config.rms_norm_eps) self.output_dropout = nn.Dropout(config.dropout) self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False) # Removed manual weight tying. Will use self.tie_weights() instead. self.post_init() self.tie_weights() # Call tie_weights() to handle weight tying canonically def get_input_embeddings(self): return self.token_embedding def set_input_embeddings(self, value): self.token_embedding = value # Re-tie weights if embedding is replaced self.tie_weights() def get_output_embeddings(self): return self.lm_head def set_output_embeddings(self, new_embeddings): self.lm_head = new_embeddings # Removed custom _get_tied_weights_keys. PreTrainedModel's default will detect # tied weights after self.tie_weights() is called. def forward(self, input_ids=None, labels=None, **kwargs): if input_ids is None: raise ValueError("input_ids must be provided") x = self.token_embedding(input_ids) * (self.config.d_model ** 0.5) x = self.position_dropout(x) for block in self.transformer_blocks: x = block(x) x = self.norm(x) x = self.output_dropout(x) logits = self.lm_head(x) loss = None if labels is not None: # Standard causal LM shift 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 CausalLMOutput(loss=loss, logits=logits) def prepare_inputs_for_generation(self, input_ids, **kwargs): # This model does not use KV-cache, so generation can still work without it. return {"input_ids": input_ids}