File size: 5,633 Bytes
927a79a
 
 
 
 
 
14a68cc
 
 
 
 
 
 
 
927a79a
14a68cc
 
927a79a
14a68cc
 
 
 
 
 
 
 
 
 
 
 
927a79a
14a68cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927a79a
14a68cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927a79a
 
 
 
 
 
 
14a68cc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
927a79a
 
14a68cc
 
 
 
 
 
 
 
 
927a79a
14a68cc
 
 
 
 
 
 
 
 
 
 
927a79a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# 1. ต้อง import spaces เป็นบรรทัดแรกสุดสำหรับ Hugging Face ZeroGPU
try:
    import spaces
except ImportError:
    spaces = None

import os
import re
import torch
import torch.nn as nn
import torch.nn.functional as F
from pythainlp.tokenize import word_tokenize
import gradio as gr

# Setup Device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 2. โหลด Vocab และ Weights
vocab_file = "vocab_v6.pt"
model_file = "my_llm_v6_big_brain.pth"

if not os.path.exists(vocab_file) or not os.path.exists(model_file):
    raise FileNotFoundError("❌ ไม่พบไฟล์ vocab_v6.pt หรือ my_llm_v6_big_brain.pth ในโฟลเดอร์!")

vocab_data = torch.load(vocab_file, map_location="cpu")
word2idx = vocab_data['word2idx']
idx2word = vocab_data['idx2word']
vocab_size = len(word2idx)
SPECIAL_TOKENS = ["<|user|>", "<|bot|>", "<|end|>"]

# 3. Helper Functions
def preprocess_text(text):
    text = re.sub(r'([\+\-\*/=])', r' \1 ', text)
    text = re.sub(r'\s+', ' ', text)
    return text.strip()

def tokenize_input(text):
    text = preprocess_text(text)
    sub_tokens = word_tokenize(text, engine="newmm")
    return [w for w in sub_tokens if w.strip()]

def encode(tokens):
    return [word2idx.get(w, 1) for w in tokens]

def decode(token_ids):
    words = []
    for i in token_ids:
        w = idx2word.get(i, '')
        if w not in SPECIAL_TOKENS and i not in [0, 1]:
            words.append(w)
    return "".join(words)

# 4. Model Architecture (V6 Big Brain)
MAX_LEN = 64

class BigBrainMiniLLM(nn.Module):
    def __init__(self, vocab_size, d_model=512, nhead=8, num_layers=6, dropout=0.15):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, d_model)
        self.pos_embedding = nn.Embedding(MAX_LEN, d_model)
        self.drop = nn.Dropout(dropout)
        
        encoder_layer = nn.TransformerEncoderLayer(
            d_model=d_model, nhead=nhead, dim_feedforward=2048, dropout=dropout, batch_first=True
        )
        self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=num_layers)
        self.fc_out = nn.Linear(d_model, vocab_size)

    def forward(self, x):
        seq_len = x.size(1)
        positions = torch.arange(0, seq_len, device=x.device).unsqueeze(0)
        out = self.drop(self.embedding(x) + self.pos_embedding(positions))
        mask = torch.triu(torch.full((seq_len, seq_len), float('-inf'), device=x.device), diagonal=1)
        out = self.transformer(out, mask=mask)
        return self.fc_out(out)

model = BigBrainMiniLLM(vocab_size=vocab_size, d_model=512, nhead=8, num_layers=6, dropout=0.15).to(device)
model.load_state_dict(torch.load(model_file, map_location=device))
model.eval()

# 5. ZeroGPU Decorator Wrapper
def gpu_decorator(func):
    if spaces is not None and hasattr(spaces, "GPU"):
        return spaces.GPU(func)
    return func

@gpu_decorator
def bot_response(message, history, temp, top_k, rep_penalty):
    if not message.strip():
        return ""
        
    tokens = ["<|user|>"] + tokenize_input(message) + ["<|bot|>"]
    encoded = encode(tokens)
    input_tensor = torch.tensor(encoded, dtype=torch.long).unsqueeze(0).to(device)
    
    generated = list(encoded)
    bot_start_len = len(encoded)
    
    with torch.no_grad():
        for _ in range(40):
            if input_tensor.size(1) >= MAX_LEN - 1:
                break
            outputs = model(input_tensor)
            logits = outputs[0, -1, :] / temp
            
            # Repetition Penalty
            generated_bot = generated[bot_start_len:]
            for t in set(generated_bot):
                if logits[t] < 0:
                    logits[t] *= rep_penalty
                else:
                    logits[t] /= rep_penalty
            
            if top_k > 0:
                v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
                logits[logits < v[-1]] = -float('Inf')
                
            probs = F.softmax(logits, dim=-1)
            next_token = torch.multinomial(probs, num_samples=1).item()
            
            if next_token == 0 or idx2word.get(next_token) == "<|end|>":
                break
                
            generated.append(next_token)
            input_tensor = torch.tensor(generated, dtype=torch.long).unsqueeze(0).to(device)
            
    return decode(generated[bot_start_len:])

# 6. Build Gradio Interface (ย้าย theme ไปไว้ที่ launch() ตาม Gradio 6.0)
with gr.Blocks() as demo:
    gr.Markdown(
        """
        # 🧠 Thai Mini-LLM V6 (Big Brain 23.7M)
        ### โมเดลภาษาไทยขนาดจิ๋ว เทรนจาก 0 ด้วยสถาปัตยกรรม Transformer (Word-Level)
        """
    )
    
    with gr.Row():
        with gr.Column(scale=3):
            gr.ChatInterface(
                fn=bot_response,
                additional_inputs=[
                    gr.Slider(0.1, 1.5, value=0.5, step=0.1, label="Temperature (ความมั่ว/ความคิดสร้างสรรค์)"),
                    gr.Slider(1, 50, value=10, step=1, label="Top-K (การจำกัดขอบเขตคลังคำ)"),
                    gr.Slider(1.0, 2.5, value=1.3, step=0.1, label="Repetition Penalty (บทลงโทษคำซ้ำ)"),
                ],
            )
            
    gr.Markdown("--- \n *Created with ❤️ | Custom PyTorch Model trained from Scratch*")

if __name__ == "__main__":
    demo.launch(theme=gr.themes.Soft())