Rename main.py to app.py

#1
by Tanzai2 - opened
Files changed (2) hide show
  1. app.py +285 -0
  2. main.py +0 -42
app.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import pickle
4
+ import torch.nn as nn
5
+ import torch.nn.functional as F
6
+ import math
7
+ import urllib.request
8
+ from bs4 import BeautifulSoup
9
+ from googlesearch import search
10
+
11
+ # ==========================================
12
+ # 0. คลาสตัวตัดคำระดับอักขระดั้งเดิม
13
+ # ==========================================
14
+ class CharTokenizer:
15
+ def __init__(self, text):
16
+ self.chars = sorted(list(set(text)))
17
+ self.vocab_size = len(self.chars)
18
+ self.stoi = { ch:i for i,ch in enumerate(self.chars) }
19
+ self.itos = { i:ch for i,ch in enumerate(self.chars) }
20
+
21
+ def encode(self, s):
22
+ return [self.stoi[c] for c in s if c in self.stoi]
23
+
24
+ def decode(self, l):
25
+ return ''.join([self.itos[i] for i in l if i in self.itos])
26
+
27
+ import __main__
28
+ __main__.CharTokenizer = CharTokenizer
29
+
30
+ # ==========================================
31
+ # 1. โครงสร้างสถาปัตยกรรมโมเดลขั้นสูง
32
+ # ==========================================
33
+ n_embd = 768
34
+ block_size = 256
35
+ n_heads = 12
36
+ n_kv_heads = 4
37
+ n_layers = 8
38
+ ffn_hidden_dim = 2048
39
+
40
+ class GemmaRMSNorm(nn.Module):
41
+ def __init__(self, dim: int, eps: float = 1e-6):
42
+ super().__init__()
43
+ self.eps = eps
44
+ self.weight = nn.Parameter(torch.zeros(dim))
45
+ def forward(self, x):
46
+ variance = x.pow(2).mean(-1, keepdim=True)
47
+ return x * torch.rsqrt(variance + self.eps) * (1.0 + self.weight)
48
+
49
+ class GemmaSwiGLU(nn.Module):
50
+ def __init__(self, d_in: int, d_hidden: int):
51
+ super().__init__()
52
+ self.gate_proj = nn.Linear(d_in, d_hidden, bias=False)
53
+ self.up_proj = nn.Linear(d_in, d_hidden, bias=False)
54
+ self.down_proj = nn.Linear(d_hidden, d_in, bias=False)
55
+ def forward(self, x):
56
+ return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))
57
+
58
+ class GemmaRotaryEmbedding(nn.Module):
59
+ def __init__(self, dim, max_seq_len=2048, theta=10000.0):
60
+ super().__init__()
61
+ self.dim = dim
62
+ inv_freq = 1.0 / (theta ** (torch.arange(0, dim, 2).float() / dim))
63
+ self.register_buffer("inv_freq", inv_freq, persistent=False)
64
+ t = torch.arange(max_seq_len, dtype=torch.float32)
65
+ freqs = torch.outer(t, self.inv_freq)
66
+ emb = torch.cat((freqs, freqs), dim=-1)
67
+ self.register_buffer("cos_cached", emb.cos(), persistent=False)
68
+ self.register_buffer("sin_cached", emb.sin(), persistent=False)
69
+ def forward(self, x, seq_len):
70
+ return self.cos_cached[:seq_len, :], self.sin_cached[:seq_len, :]
71
+
72
+ def rotate_half(x):
73
+ x1 = x[..., :x.shape[-1] // 2]
74
+ x2 = x[..., x.shape[-1] // 2:]
75
+ return torch.cat((-x2, x1), dim=-1)
76
+
77
+ def apply_rope(q, k, cos, sin):
78
+ cos = cos.unsqueeze(0).unsqueeze(2)
79
+ sin = sin.unsqueeze(0).unsqueeze(2)
80
+ q_embed = (q * cos) + (rotate_half(q) * sin)
81
+ k_embed = (k * cos) + (rotate_half(k) * sin)
82
+ return q_embed, k_embed
83
+
84
+ class GemmaGroupedAttention(nn.Module):
85
+ def __init__(self):
86
+ super().__init__()
87
+ self.head_dim = n_embd // n_heads
88
+ self.num_local_heads = n_heads
89
+ self.num_local_kv_heads = n_kv_heads
90
+ self.num_queries_per_kv = n_heads // n_kv_heads
91
+
92
+ self.q_proj = nn.Linear(n_embd, n_heads * self.head_dim, bias=False)
93
+ self.k_proj = nn.Linear(n_embd, n_kv_heads * self.head_dim, bias=False)
94
+ self.v_proj = nn.Linear(n_embd, n_kv_heads * self.head_dim, bias=False)
95
+ self.o_proj = nn.Linear(n_heads * self.head_dim, n_embd, bias=False)
96
+
97
+ self.rope = GemmaRotaryEmbedding(self.head_dim)
98
+ self.register_buffer('tril', torch.tril(torch.ones(block_size, block_size)))
99
+
100
+ def forward(self, x):
101
+ B, T, C = x.shape
102
+ q = self.q_proj(x).view(B, T, self.num_local_heads, self.head_dim)
103
+ k = self.k_proj(x).view(B, T, self.num_local_kv_heads, self.head_dim)
104
+ v = self.v_proj(x).view(B, T, self.num_local_kv_heads, self.head_dim)
105
+
106
+ cos, sin = self.rope(q, T)
107
+ q, k = apply_rope(q, k, cos, sin)
108
+
109
+ q = q.transpose(1, 2)
110
+ k = k.transpose(1, 2)
111
+ v = v.transpose(1, 2)
112
+
113
+ if self.num_queries_per_kv > 1:
114
+ k = k.repeat_interleave(self.num_queries_per_kv, dim=1)
115
+ v = v.repeat_interleave(self.num_queries_per_kv, dim=1)
116
+
117
+ scores = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
118
+ scores = torch.tanh(scores / 10.0) * 10.0
119
+ scores = scores.masked_fill(self.tril[:T, :T] == 0, float('-inf'))
120
+ attention_probs = F.softmax(scores, dim=-1)
121
+
122
+ output = attention_probs @ v
123
+ output = output.transpose(1, 2).contiguous().view(B, T, C)
124
+ return self.o_proj(output)
125
+
126
+ class GemmaDecoderBlock(nn.Module):
127
+ def __init__(self):
128
+ super().__init__()
129
+ self.attn = GemmaGroupedAttention()
130
+ self.ffn = GemmaSwiGLU(n_embd, ffn_hidden_dim)
131
+ self.input_layernorm = GemmaRMSNorm(n_embd)
132
+ self.post_attention_layernorm = GemmaRMSNorm(n_embd)
133
+ def forward(self, x):
134
+ x = x + self.attn(self.input_layernorm(x))
135
+ x = x + self.ffn(self.post_attention_layernorm(x))
136
+ return x
137
+
138
+ class DeepTanzGemmaModel(nn.Module):
139
+ def __init__(self, vocab_size):
140
+ super().__init__()
141
+ self.embed = nn.Embedding(vocab_size, n_embd)
142
+ self.layers = nn.ModuleList([GemmaDecoderBlock() for _ in range(n_layers)])
143
+ self.norm = GemmaRMSNorm(n_embd)
144
+ self.lm_head = nn.Linear(n_embd, vocab_size, bias=False)
145
+ self.embed.weight = self.lm_head.weight
146
+
147
+ # 🛠️ ซ่อมแซมระบบคิดย้อนกลับไปข้างหน้า (Forward Function) ที่หายไปเรียบร้อยครับ!
148
+ def forward(self, idx):
149
+ x = self.embed(idx) * math.sqrt(n_embd)
150
+ for layer in self.layers:
151
+ x = layer(x)
152
+ x = self.norm(x)
153
+ return self.lm_head(x)
154
+
155
+ # โหลดระบบตัดคำศัพท์และไฟล์โมเดลที่ผ่านการเทรน
156
+ with open('tokenizer.pkl', 'rb') as f:
157
+ tokenizer = pickle.load(f)
158
+
159
+ model = DeepTanzGemmaModel(tokenizer.vocab_size)
160
+ state_dict = torch.load('advanced_gemini.pt', map_location=torch.device('cpu'))
161
+ model.load_state_dict(state_dict)
162
+ model.eval()
163
+
164
+ # ==========================================
165
+ # 2. ฟังก์ชันเสริมระบบ Google Live Search ดึงข้อมูลสด
166
+ # ==========================================
167
+ def fetch_google_knowledge(query):
168
+ try:
169
+ search_results = list(search(query, num_results=1, lang="th"))
170
+ if not search_results:
171
+ return ""
172
+
173
+ target_url = search_results[0]
174
+ req = urllib.request.Request(target_url, headers={'User-Agent': 'Mozilla/5.0'})
175
+ html = urllib.request.urlopen(req, timeout=5).read()
176
+
177
+ soup = BeautifulSoup(html, 'html.parser')
178
+ for script in soup(["script", "style"]):
179
+ script.extract()
180
+
181
+ text = soup.get_text()
182
+ lines = (line.strip() for line in text.splitlines())
183
+ chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
184
+ clean_text = " ".join(chunk for chunk in chunks if chunk)
185
+
186
+ return clean_text[:180]
187
+ except Exception:
188
+ return ""
189
+
190
+ # ==========================================
191
+ # 3. เอนจิ้นประมวลผลข้อความคู่ขนาน Google Search
192
+ # ==========================================
193
+ def chat_engine_stream(user_input, history):
194
+ full_prompt = f"Q: {user_input}\nA: "
195
+ idx = torch.tensor([tokenizer.encode(full_prompt)], dtype=torch.long)
196
+ max_new_tokens = 180
197
+ generated_tokens = []
198
+
199
+ with torch.no_grad():
200
+ initial_logits = model(idx[:, -block_size:])[:, -1, :]
201
+ probs = F.softmax(initial_logits, dim=-1)
202
+ max_prob, _ = torch.max(probs, dim=-1)
203
+
204
+ if max_prob.item() < 0.15:
205
+ yield "⏳ ข้อมูลนี้ไม่อยู่ในหน่วยความจำเดิม... กำลังค้นหา Google เรียบลไทม์ให้ครับแทน..."
206
+ live_info = fetch_google_knowledge(user_input)
207
+
208
+ if live_info:
209
+ full_prompt = f"ข้อมูลเพิ่มเติมจากกูเกิ้ล: {live_info}\nQ: {user_input}\nA: "
210
+ idx = torch.tensor([tokenizer.encode(full_prompt)], dtype=torch.long)
211
+ else:
212
+ yield "ผมไม่สามารถตอบคำถามนี้ได้เนื่องจากไม่พบคลังข้อมูลบนระบบอินเทอร์เน็ตครับ"
213
+ return
214
+
215
+ for _ in range(max_new_tokens):
216
+ idx_cond = idx[:, -block_size:]
217
+ with torch.no_grad():
218
+ logits = model(idx_cond)[:, -1, :]
219
+
220
+ v, ix = torch.topk(logits, k=3)
221
+ filtered_logits = torch.full_like(logits, -float('Inf'))
222
+ filtered_logits.scatter_(1, ix, v)
223
+
224
+ idx_next = torch.multinomial(F.softmax(filtered_logits, dim=-1), num_samples=1)
225
+ idx = torch.cat((idx, idx_next), dim=1)
226
+
227
+ generated_tokens.append(idx_next.item())
228
+ generated_text = tokenizer.decode(generated_tokens)
229
+
230
+ if "\n" in generated_text or "Q:" in generated_text or "A:" in generated_text:
231
+ clean_output = generated_text.replace("\n", "").replace("Q:", "").replace("A:", "").strip()
232
+ if not clean_output:
233
+ yield "ผมไม่สามารถหาข้อสรุปจากเนื้อหาหน้าเว็บนี้ได้ครับ"
234
+ else:
235
+ yield clean_output
236
+ break
237
+
238
+ yield generated_text.strip()
239
+
240
+ # ==========================================
241
+ # 4. หน้ากากแอปพลิเคชัน Gradio ดาร์กธีมสไตล์ Google DeepMind
242
+ # ==========================================
243
+ custom_css = """
244
+ footer {visibility: hidden !important}
245
+ body, .gradio-container {
246
+ background-color: #0d0e12 !important;
247
+ font-family: 'Inter', system-ui, -apple-system, sans-serif !important;
248
+ max-width: 950px !important;
249
+ margin: 0 auto !important;
250
+ color: #e3e3e3 !important;
251
+ }
252
+ .center-header {
253
+ text-align: center;
254
+ margin-top: 50px;
255
+ margin-bottom: 30px;
256
+ }
257
+ .center-header h1 {
258
+ font-size: 2.8rem !important;
259
+ font-weight: 800 !important;
260
+ background: linear-gradient(135deg, #1ba2f6 0%, #a252ff 50%, #f6517a 100%);
261
+ -webkit-background-clip: text;
262
+ -webkit-text-fill-color: transparent;
263
+ letter-spacing: -1.5px;
264
+ }
265
+ .center-header p { color: #9aa0a6 !important; font-size: 1.15rem !important; }
266
+ .chatbot { border: none !important; background-color: #0d0e12 !important; }
267
+ .chatbot .user { background-color: #1e1f24 !important; color: #ffffff !important; border-radius: 22px 22px 4px 22px !important; }
268
+ .chatbot .bot { background-color: transparent !important; color: #e3e3e3 !important; }
269
+ .gradio-container .buttons { display: none !important; }
270
+ """
271
+
272
+ # ย้ายส่วนของการประกาศ css ไปวางไว้ที่พิกัด launch() ตอนเปิดรันตามกฎ Gradio 6.0
273
+ with gr.Blocks() as demo:
274
+ gr.HTML(
275
+ """
276
+ <div class="center-header">
277
+ <h1>✦ Tanz Gemma Live Search Engine ⚡</h1>
278
+ <p>สถาปัตยกรรมกลุ่มหัวเรือเชื่อมต่อโครงข่าย Google ค้นหาข้อมูลแบบพลวัตภายนอก</p>
279
+ </div>
280
+ """
281
+ )
282
+ gr.ChatInterface(fn=chat_engine_stream)
283
+
284
+ # ปรับส่งค่า css ควบคุมธีมหน้าต่างการใช้งานตรงนี้อย่างถูกต้อง
285
+ demo.launch(css=custom_css)
main.py DELETED
@@ -1,42 +0,0 @@
1
- from fastapi import FastAPI, Request
2
- from fastapi.middleware.cors import CORSMiddleware
3
- from pydantic import BaseModel
4
- import json
5
- import os
6
-
7
- app = FastAPI()
8
-
9
- # --- คงเดิม: เพิ่ม CORS เพื่อให้หน้าเว็บเข้าถึง API ได้ ---
10
- app.add_middleware(
11
- CORSMiddleware,
12
- allow_origins=["*"],
13
- allow_methods=["*"],
14
- allow_headers=["*"],
15
- )
16
-
17
- # --- แก้ไขจุดนี้: ย้ายไปเก็บที่ /tmp ซึ่งเป็นที่ที่เขียนได้เสมอ ---
18
- DB_FILE = "/tmp/update_data.json"
19
-
20
- class UpdateData(BaseModel):
21
- version: str
22
- description: str
23
- script: str
24
-
25
- @app.get("/api/update")
26
- async def get_update():
27
- if os.path.exists(DB_FILE):
28
- with open(DB_FILE, "r") as f:
29
- return json.load(f)
30
- # ถ้าไม่มีไฟล์ ให้คืนค่า Default
31
- return {"version": "1.0", "description": "ระบบเริ่มต้น", "script": ""}
32
-
33
- @app.post("/api/update")
34
- async def post_update(data: UpdateData):
35
- # เขียนไฟล์ลง /tmp/ แทน
36
- with open(DB_FILE, "w") as f:
37
- json.dump(data.model_dump(), f) # ใช้ .model_dump() แทน .dict() สำหรับ Pydantic v2
38
- return {"status": "success"}
39
-
40
- if __name__ == "__main__":
41
- import uvicorn
42
- uvicorn.run(app, host="0.0.0.0", port=7860)