miniQwen_v3 / modeling_minimalllm.py
chhatramani's picture
Upload trained model and tokenizer
7d52a65 verified
Raw
History Blame Contribute Delete
8.7 kB
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}