| import torch |
| import torch.nn as nn |
| from torch import Tensor |
| from typing import Optional, Tuple |
| import sys |
| import pathlib |
|
|
| |
| root = pathlib.Path(__file__).resolve().parent |
| while root.parent != root: |
| if (root / "requirements.txt").exists() or (root / "README.md").exists(): |
| sys.path.append(str(root)) |
| break |
| root = root.parent |
|
|
| from model.attention import MultiHeadSelfAttention |
|
|
| class TransformerBlock(nn.Module): |
| """Pre-norm Transformer encoder block. |
| |
| Pre-norm (LN → sublayer → residual) มีเสถียรภาพ training |
| ดีกว่า post-norm โดยเฉพาะตอน network ลึกหรือ lr สูง |
| """ |
| def __init__(self, d_model: int, num_heads: int, d_ff: int, dropout: float = 0.1): |
| super().__init__() |
| |
| self.attn = MultiHeadSelfAttention(d_model, num_heads, dropout) |
|
|
| |
| self.norm1 = nn.LayerNorm(d_model) |
| self.norm2 = nn.LayerNorm(d_model) |
| |
| |
| self.ffn = nn.Sequential( |
| nn.Linear(d_model, d_ff), |
| nn.GELU(), |
| nn.Dropout(dropout), |
| nn.Linear(d_ff, d_model), |
| nn.Dropout(dropout) |
| ) |
|
|
| def forward(self, x: Tensor, padding_mask: Optional[Tensor] = None) -> Tuple[Tensor, Tensor]: |
| |
| |
|
|
| |
| |
| residual = x |
| normed_x = self.norm1(x) |
|
|
| |
| attn_out, attn_weights = self.attn(normed_x, key_padding_mask=padding_mask) |
|
|
| |
| x = residual + attn_out |
|
|
| |
| |
| residual = x |
| normed_x = self.norm2(x) |
| ffn_out = self.ffn(normed_x) |
|
|
| |
| x = residual + ffn_out |
| return x, attn_weights |
|
|
| if __name__ == "__main__": |
| |
| block = TransformerBlock(d_model=256, num_heads=8, d_ff=1024) |
| block.eval() |
|
|
| |
| dummy_x = torch.randn(2, 16, 256) |
| dummy_mask = torch.zeros(2, 16, dtype=torch.bool) |
| dummy_mask[0, 12:] = True |
|
|
| out, attn_weights = block(dummy_x, padding_mask=dummy_mask) |
|
|
| assert out.shape == (2, 16, 256), f"Wrong output shape: {out.shape}" |
| assert attn_weights.shape == (2, 8, 16, 16), f"Wrong attention weights shape: {attn_weights.shape}" |
| assert not torch.isnan(out).any(), "พบค่า NaN ในโมดูล" |
| assert not torch.isnan(attn_weights).any(), "พบค่า NaN ใน attention weights" |
| print("TransformerBlock test passed!") |