SAI_35M / TransformerModel.py
thongbuind's picture
Keep a single inference source tree with original filenames
be8ee5f verified
Raw
History Blame Contribute Delete
5.15 kB
import torch
import torch.nn as nn
from .generate import generate
from .DecoderBlock import DecoderBlock
from .RotaryPositionalEmbedding import RotaryPositionalEmbedding
class TransformerModel(nn.Module):
def __init__(self, vocab_size: int, d_model: int, num_heads: int, num_kv_heads: int, num_layers: int, ff_dim: int, max_seq_len: int, dropout: float, pad_token_id: int = 0):
super().__init__()
self.d_model = d_model
self.num_heads = num_heads
self.num_kv_heads = num_kv_heads
self.num_layers = num_layers
self.pad_token_id = pad_token_id
self.max_seq_len = max_seq_len
self.embed = nn.Embedding(vocab_size, d_model)
d_k = d_model // num_heads
self.rope = RotaryPositionalEmbedding(d_k, max_seq_len)
self.blocks = nn.ModuleList([
DecoderBlock(d_model, num_heads, num_kv_heads, ff_dim, dropout)
for _ in range(num_layers)
])
self.norm = nn.RMSNorm(d_model, eps=1e-6)
self.lm_head = nn.Linear(d_model, vocab_size, bias=False)
self.lm_head.weight = self.embed.weight
causal = torch.triu(
torch.full((max_seq_len, max_seq_len), float('-inf')), diagonal=1
)
self.register_buffer("causal_mask", causal, persistent=False)
self._init_weights()
def _init_weights(self):
std = self.d_model ** -0.5
nn.init.normal_(self.embed.weight, mean=0.0, std=std)
for module in self.modules():
if isinstance(module, nn.Linear):
nn.init.normal_(module.weight, mean=0.0, std=std)
if module.bias is not None:
nn.init.zeros_(module.bias)
def _build_attn_mask(self, T: int, pad_mask, device):
attn_mask = self.causal_mask[:T, :T][None, None, :, :]
if pad_mask is not None:
pad = torch.zeros(pad_mask.shape[0], 1, 1, T, device=device)
pad.masked_fill_(~pad_mask[:, None, None, :], float('-inf'))
attn_mask = attn_mask + pad
return attn_mask
def forward_features(self, input_ids: torch.Tensor, attention_mask=None, has_padding: bool = True) -> torch.Tensor:
"""Giống forward() nhưng DỪNG TRƯỚC lm_head — dùng cho training loss chunked
(tránh vật lý hóa logits full (B*T, vocab_size))."""
pad_mask = attention_mask.bool() if attention_mask is not None \
else (input_ids != self.pad_token_id)
B, T = input_ids.shape
x = self.embed(input_ids)
pos = torch.arange(T, device=input_ids.device)
cos, sin = self.rope.get_cos_sin(pos)
# Chỉ build mask (và cộng pad-bias) khi batch này thực sự có token PAD.
# Nếu không có PAD, causal mask thuần == causal+pad mask về mặt toán học
# (pad-bias toàn số 0) -> bỏ qua an toàn, không đổi kết quả.
# has_padding được tính sẵn trên CPU trong collate_fn nên không tốn sync GPU ở đây.
attn_mask = self._build_attn_mask(T, pad_mask, x.device) if has_padding else None
for block in self.blocks:
x = block(x, cos, sin, attn_mask)
return self.norm(x) # (B, T, d_model) — CHƯA qua lm_head
def forward(self, input_ids: torch.Tensor, attention_mask=None, has_padding: bool = True) -> torch.Tensor:
x = self.forward_features(input_ids, attention_mask, has_padding)
return self.lm_head(x)
def init_cache(self, batch_size: int, max_gen_len: int, device: torch.device):
d_k = self.d_model // self.num_heads
return [
[
torch.empty(batch_size, self.num_kv_heads, max_gen_len, d_k, device=device),
torch.empty(batch_size, self.num_kv_heads, max_gen_len, d_k, device=device),
]
for _ in self.blocks
]
def prefill(self, input_ids: torch.Tensor, kv_cache=None):
B, T = input_ids.shape
x = self.embed(input_ids)
pos = torch.arange(T, device=input_ids.device)
cos, sin = self.rope.get_cos_sin(pos)
new_cache = []
for i, block in enumerate(self.blocks):
x, kv = block.prefill(x, cos, sin)
if kv_cache is not None:
kv_cache[i][0][:B, :, :T, :] = kv[0]
kv_cache[i][1][:B, :, :T, :] = kv[1]
new_cache.append(kv_cache[i])
else:
new_cache.append(kv)
logits = self.lm_head(self.norm(x))[:, -1, :]
return logits, new_cache
def decode_step(self, token_ids: torch.Tensor, kv_cache, cache_len: int):
token_ids = token_ids.view(-1, 1)
x = self.embed(token_ids)
pos = torch.arange(cache_len, cache_len + 1, device=token_ids.device)
cos, sin = self.rope.get_cos_sin(pos)
for block, kv in zip(self.blocks, kv_cache):
x = block.forward_with_cache(x, kv, cache_len, cos, sin)
return self.lm_head(self.norm(x))[:, 0, :]
def generate_response(self, user_input, tokenizer, **kwargs):
return generate(self, user_input, tokenizer, **kwargs)