import os import torch import torch.nn as nn import torch.nn.functional as F from tokenizers import Tokenizer, pre_tokenizers, decoders MODEL_PATH = "model.pt" TOKENIZER_PATH = "tokenizer.json" D_MODEL = 512 NUM_HEADS = 8 NUM_KV_HEADS = 8 NUM_LAYERS = 18 D_FF = 2048 MAX_SEQ_LENGTH = 768 VOCAB_SIZE_LIMIT = 32768 USE_LAYER_SCALE = False class BPETokenizer: def __init__(self, vocab_size=15000): self.tokenizer = None self.vocab = {} self.token_to_id = {} self.id_to_token = {} def load(self, filepath): self.tokenizer = Tokenizer.from_file(filepath) self.tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) self.tokenizer.decoder = decoders.ByteLevel() self.vocab = self.tokenizer.get_vocab() self.token_to_id = self.vocab self.id_to_token = {v: k for k, v in self.vocab.items()} print(f"Tokenizer loaded. Vocabulary size: {self.tokenizer.get_vocab_size()}") def encode(self, text): return self.tokenizer.encode(text).ids def decode(self, ids): return self.tokenizer.decode(ids, skip_special_tokens=False) class FastRMSNorm(nn.Module): def __init__(self, d_model, eps=1e-5): super().__init__() self.eps = eps self.weight = nn.Parameter(torch.ones(d_model)) def forward(self, x): return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.weight class RotaryEmbedding(nn.Module): def __init__(self, dim, max_seq_len=4096, theta=10000.0): super().__init__() self.dim = dim self.theta = theta self.max_seq_len = max_seq_len inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim)) self.register_buffer("inv_freq", inv_freq) self._update_cache(max_seq_len) def _update_cache(self, seq_len): self.max_seq_len = seq_len t = torch.arange(self.max_seq_len, device=self.inv_freq.device, dtype=self.inv_freq.dtype) freqs = torch.outer(t, self.inv_freq) emb = torch.cat((freqs, freqs), dim=-1) self.register_buffer("cos_cached", emb.cos()[None, None, :, :], persistent=False) self.register_buffer("sin_cached", emb.sin()[None, None, :, :], persistent=False) def forward(self, x, seq_len): if seq_len > self.max_seq_len: self._update_cache(seq_len) return ( self.cos_cached[:, :, :seq_len, ...].to(dtype=x.dtype), self.sin_cached[:, :, :seq_len, ...].to(dtype=x.dtype) ) def apply_rotary_pos_emb(x, cos, sin): def rotate_half(x): x1, x2 = x.chunk(2, dim=-1) return torch.cat((-x2, x1), dim=-1) return (x * cos) + (rotate_half(x) * sin) def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor: batch, n_kv_heads, seqlen, head_dim = x.shape if n_rep == 1: return x return ( x[:, :, None, :, :] .expand(batch, n_kv_heads, n_rep, seqlen, head_dim) .reshape(batch, n_kv_heads * n_rep, seqlen, head_dim) ) def apply_swiglu(gate_up): a, b = gate_up.chunk(2, dim=-1) return F.silu(a) * b class TokenEmbedding(nn.Embedding): def __init__(self, vocab_size, d_model, pad_id=None): padding_idx = pad_id if pad_id is not None and pad_id >= 0 else None super().__init__(num_embeddings=vocab_size, embedding_dim=d_model, padding_idx=padding_idx) class GroupedQueryAttention(nn.Module): def __init__(self, d_model, num_heads, num_kv_heads): super().__init__() self.d_model = d_model self.num_heads = num_heads self.num_kv_heads = num_kv_heads self.head_dim = d_model // num_heads self.n_rep = self.num_heads // self.num_kv_heads self.W_q = nn.Linear(d_model, num_heads * self.head_dim, bias=False) self.W_k = nn.Linear(d_model, num_kv_heads * self.head_dim, bias=False) self.W_v = nn.Linear(d_model, num_kv_heads * self.head_dim, bias=False) self.W_o = nn.Linear(num_heads * self.head_dim, d_model, bias=False) self.rope = RotaryEmbedding(self.head_dim) self.q_norm = FastRMSNorm(self.head_dim) self.k_norm = FastRMSNorm(self.head_dim) def forward(self, q_in, k_in, v_in): batch_size, seq_len, _ = q_in.shape q = self.W_q(q_in) k = self.W_k(k_in) v = self.W_v(v_in) q = q.view(batch_size, seq_len, self.num_heads, self.head_dim) k = k.view(batch_size, seq_len, self.num_kv_heads, self.head_dim) v = v.view(batch_size, seq_len, self.num_kv_heads, self.head_dim) q = self.q_norm(q) k = self.k_norm(k) q = q.transpose(1, 2) k = k.transpose(1, 2) v = v.transpose(1, 2) cos_q, sin_q = self.rope(q, q.shape[2]) cos_k, sin_k = self.rope(k, k.shape[2]) q = apply_rotary_pos_emb(q, cos_q, sin_q) k = apply_rotary_pos_emb(k, cos_k, sin_k) k = repeat_kv(k, self.n_rep) v = repeat_kv(v, self.n_rep) context = F.scaled_dot_product_attention( q, k, v, attn_mask=None, dropout_p=0.0, is_causal=True ) context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model) return self.W_o(context) class PositionWiseFeedForward(nn.Module): def __init__(self, d_model, d_ff, bias=False): super().__init__() self.gate_up_proj = nn.Linear(d_model, d_ff * 2, bias=bias) self.down_proj = nn.Linear(d_ff, d_model, bias=bias) def forward(self, x): gate_up = self.gate_up_proj(x) activated = apply_swiglu(gate_up) return self.down_proj(activated) class DecoderLayer(nn.Module): def __init__(self, d_model, num_heads, num_kv_heads, d_ff, use_layer_scale=False): super().__init__() self.norm1 = FastRMSNorm(d_model) self.norm2 = FastRMSNorm(d_model) self.attn = GroupedQueryAttention(d_model, num_heads, num_kv_heads) self.ffn = PositionWiseFeedForward(d_model, d_ff) self.use_layer_scale = use_layer_scale if self.use_layer_scale: init_value = 1e-4 self.ls_1 = nn.Parameter(torch.ones(d_model) * init_value) self.ls_2 = nn.Parameter(torch.ones(d_model) * init_value) def forward(self, x): x_norm = self.norm1(x) attn_out = self.attn(x_norm, x_norm, x_norm) x = x + (attn_out * self.ls_1 if self.use_layer_scale else attn_out) x_norm = self.norm2(x) ffn_out = self.ffn(x_norm) x = x + (ffn_out * self.ls_2 if self.use_layer_scale else ffn_out) return x class Transformer(nn.Module): def __init__(self, vocab_size, d_model, num_heads, num_kv_heads, num_layers, d_ff, pad_id, use_layer_scale=False): super().__init__() self.pad_id = pad_id self.embedding = TokenEmbedding(vocab_size, d_model, pad_id) self.layers = nn.ModuleList([ DecoderLayer(d_model, num_heads, num_kv_heads, d_ff, use_layer_scale) for _ in range(num_layers) ]) self.norm_f = FastRMSNorm(d_model) self.fc_out = nn.Linear(d_model, vocab_size, bias=False) def forward(self, x): x = self.embedding(x) for layer in self.layers: x = layer(x) x = self.norm_f(x) return self.fc_out(x) @torch.no_grad() def generate_response(model, tokenizer, user_input, max_seq_length, device, temperature=0.7, top_k=50, repetition_penalty=1.2): model.eval() SOS_ID = tokenizer.token_to_id.get("", None) EOS_ID = tokenizer.token_to_id.get("", None) if not user_input.strip(): return "Please say something." input_ids = tokenizer.encode(user_input) if SOS_ID is not None: input_ids = [SOS_ID] + input_ids generated_tokens_set = set() generated_new_tokens = [] for _ in range(max_seq_length - len(input_ids)): x_tensor = torch.LongTensor([input_ids]).to(device) output = model(x_tensor) last_logits = output[0, -1, :] / temperature for token_id in generated_tokens_set: if last_logits[token_id] > 0: last_logits[token_id] /= repetition_penalty else: last_logits[token_id] *= repetition_penalty if top_k > 0: v, _ = torch.topk(last_logits, top_k) last_logits[last_logits < v[-1]] = -float('Inf') probs = torch.softmax(last_logits, dim=-1) next_word_id = torch.multinomial(probs, num_samples=1).item() if next_word_id == EOS_ID: break input_ids.append(next_word_id) generated_new_tokens.append(next_word_id) generated_tokens_set.add(next_word_id) raw_response = tokenizer.decode(generated_new_tokens) for st in ["", "", ""]: raw_response = raw_response.replace(st, "") return raw_response.strip().capitalize() if __name__ == '__main__': device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"Using device: {device}") if not os.path.exists(TOKENIZER_PATH): raise FileNotFoundError(f"Tokenizer not found at {TOKENIZER_PATH}") tokenizer = BPETokenizer(vocab_size=VOCAB_SIZE_LIMIT) tokenizer.load(TOKENIZER_PATH) vocab_size = len(tokenizer.vocab) PAD_ID = tokenizer.token_to_id.get("", 0) print("Initializing model architecture...") model = Transformer( vocab_size=vocab_size, d_model=D_MODEL, num_heads=NUM_HEADS, num_kv_heads=NUM_KV_HEADS, num_layers=NUM_LAYERS, d_ff=D_FF, pad_id=PAD_ID, use_layer_scale=USE_LAYER_SCALE ) if not os.path.exists(MODEL_PATH): raise FileNotFoundError(f"Model file not found: {MODEL_PATH}") print(f"Loading weights from {MODEL_PATH}...") model.load_state_dict(torch.load(MODEL_PATH, map_location="cpu")) print("Weights loaded successfully!") dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16 model = model.to(dtype=dtype).to(device) print("Model ready! Type 'exit' to quit.\n") while True: try: user_msg = input("You: ") if user_msg.lower() in ['exit', 'quit']: break response = generate_response( model=model, tokenizer=tokenizer, user_input=user_msg, max_seq_length=MAX_SEQ_LENGTH, device=device ) print(f"Bot: {response}\n") except KeyboardInterrupt: print("\nExiting...") break