feat: replace sentiment analysis demo with Gemma 4 chat assistant application
Browse files- .gitignore +3 -0
- README.md +4 -4
- app.py +287 -23
- requirements.txt +3 -1
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.env
|
README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.27.0
|
| 8 |
python_version: '3.12'
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Gemma 4 Chat Assistant
|
| 3 |
+
emoji: ✨
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
sdk: gradio
|
| 7 |
sdk_version: 6.27.0
|
| 8 |
python_version: '3.12'
|
app.py
CHANGED
|
@@ -1,34 +1,298 @@
|
|
| 1 |
-
import
|
| 2 |
import spaces
|
| 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 |
if __name__ == "__main__":
|
| 34 |
-
demo.launch()
|
|
|
|
| 1 |
+
import os
|
| 2 |
import spaces
|
| 3 |
+
import torch
|
| 4 |
+
from threading import Thread
|
| 5 |
+
from transformers import (
|
| 6 |
+
AutoModelForCausalLM,
|
| 7 |
+
AutoTokenizer,
|
| 8 |
+
TextIteratorStreamer,
|
| 9 |
+
BitsAndBytesConfig,
|
| 10 |
+
)
|
| 11 |
+
import gradio as gr
|
| 12 |
+
|
| 13 |
+
# ==============================================================================
|
| 14 |
+
# CẤU HÌNH MÔ HÌNH VÀ TOKEN
|
| 15 |
+
# ==============================================================================
|
| 16 |
+
# Bạn có thể đổi sang 'google/gemma-4-31B-it', 'google/gemma-2-9b-it' hoặc mô hình khác
|
| 17 |
+
# qua biến môi trường MODEL_ID trên Hugging Face Space Settings.
|
| 18 |
+
MODEL_ID = os.getenv("MODEL_ID", "google/gemma-4-31B-it")
|
| 19 |
+
HF_TOKEN = os.getenv("HF_TOKEN", None)
|
| 20 |
+
|
| 21 |
+
print(f"[*] Khởi tạo cấu hình cho mô hình: {MODEL_ID}")
|
| 22 |
+
|
| 23 |
+
# Cấu hình lượng tử hóa 4-bit (BitsAndBytes NF4) để tối ưu VRAM trên ZeroGPU
|
| 24 |
+
bnb_config = BitsAndBytesConfig(
|
| 25 |
+
load_in_4bit=True,
|
| 26 |
+
bnb_4bit_quant_type="nf4",
|
| 27 |
+
bnb_4bit_compute_dtype=torch.bfloat16,
|
| 28 |
+
bnb_4bit_use_double_quant=True,
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
# Tải Tokenizer và Model
|
| 32 |
+
print("[*] Đang tải Tokenizer...")
|
| 33 |
+
tokenizer = AutoTokenizer.from_pretrained(
|
| 34 |
+
MODEL_ID,
|
| 35 |
+
token=HF_TOKEN,
|
| 36 |
+
trust_remote_code=True,
|
| 37 |
+
)
|
| 38 |
|
| 39 |
+
print("[*] Đang tải Model với 4-bit Quantization...")
|
| 40 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 41 |
+
MODEL_ID,
|
| 42 |
+
quantization_config=bnb_config,
|
| 43 |
+
device_map="auto",
|
| 44 |
+
token=HF_TOKEN,
|
| 45 |
+
trust_remote_code=True,
|
| 46 |
)
|
| 47 |
+
print("[+] Mô hình đã sẵn sàng hoạt động!")
|
| 48 |
+
|
| 49 |
+
# ==============================================================================
|
| 50 |
+
# HÀM SUY LUẬN & STREAMING KẾT QUẢ VỚI ZERO-GPU
|
| 51 |
+
# ==============================================================================
|
| 52 |
+
@spaces.GPU(duration=120)
|
| 53 |
+
def chat_response(
|
| 54 |
+
message,
|
| 55 |
+
history,
|
| 56 |
+
system_prompt,
|
| 57 |
+
temperature,
|
| 58 |
+
max_new_tokens,
|
| 59 |
+
top_p,
|
| 60 |
+
):
|
| 61 |
+
if not message or not message.strip():
|
| 62 |
+
yield ""
|
| 63 |
+
return
|
| 64 |
+
|
| 65 |
+
# Xây dựng danh sách tin nhắn theo chuẩn hội thoại
|
| 66 |
+
conversation = []
|
| 67 |
+
|
| 68 |
+
# 1. Thêm system prompt nếu có
|
| 69 |
+
if system_prompt and system_prompt.strip():
|
| 70 |
+
conversation.append({"role": "system", "content": system_prompt.strip()})
|
| 71 |
+
|
| 72 |
+
# 2. Đọc lại lịch sử chat (tương thích cả dict messages và tuple)
|
| 73 |
+
for item in history:
|
| 74 |
+
if isinstance(item, dict):
|
| 75 |
+
role = item.get("role", "user")
|
| 76 |
+
# Gemma chat template quy ước role là 'user' và 'model'
|
| 77 |
+
if role == "assistant":
|
| 78 |
+
role = "model"
|
| 79 |
+
conversation.append({"role": role, "content": item.get("content", "")})
|
| 80 |
+
elif isinstance(item, (list, tuple)) and len(item) == 2:
|
| 81 |
+
user_text, bot_text = item
|
| 82 |
+
if user_text:
|
| 83 |
+
conversation.append({"role": "user", "content": str(user_text)})
|
| 84 |
+
if bot_text:
|
| 85 |
+
conversation.append({"role": "model", "content": str(bot_text)})
|
| 86 |
+
|
| 87 |
+
# 3. Thêm tin nhắn hiện tại của người dùng
|
| 88 |
+
conversation.append({"role": "user", "content": message.strip()})
|
| 89 |
+
|
| 90 |
+
# 4. Tạo prompt qua chat template của Gemma
|
| 91 |
+
try:
|
| 92 |
+
prompt = tokenizer.apply_chat_template(
|
| 93 |
+
conversation,
|
| 94 |
+
tokenize=False,
|
| 95 |
+
add_generation_prompt=True,
|
| 96 |
+
)
|
| 97 |
+
except Exception:
|
| 98 |
+
# Fallback nếu tokenizer không hỗ trợ role 'system' riêng rẽ
|
| 99 |
+
fallback_conv = []
|
| 100 |
+
for idx, item in enumerate(conversation):
|
| 101 |
+
if item["role"] == "system":
|
| 102 |
+
continue
|
| 103 |
+
content = item["content"]
|
| 104 |
+
if idx == (1 if system_prompt else 0):
|
| 105 |
+
content = f"[Hướng dẫn hệ thống: {system_prompt}]\n\n{content}"
|
| 106 |
+
fallback_conv.append({"role": item["role"], "content": content})
|
| 107 |
+
prompt = tokenizer.apply_chat_template(
|
| 108 |
+
fallback_conv,
|
| 109 |
+
tokenize=False,
|
| 110 |
+
add_generation_prompt=True,
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
# 5. Tokenize và đưa vào thiết bị của model
|
| 114 |
+
model_inputs = tokenizer([prompt], return_tensors="pt").to(model.device)
|
| 115 |
|
| 116 |
+
# 6. Thiết lập Streamer để bắn từng token ra ngoài màn hình
|
| 117 |
+
streamer = TextIteratorStreamer(
|
| 118 |
+
tokenizer,
|
| 119 |
+
skip_prompt=True,
|
| 120 |
+
skip_special_tokens=True,
|
| 121 |
+
)
|
| 122 |
|
| 123 |
+
generation_kwargs = dict(
|
| 124 |
+
model_inputs,
|
| 125 |
+
streamer=streamer,
|
| 126 |
+
max_new_tokens=int(max_new_tokens),
|
| 127 |
+
temperature=float(max(temperature, 0.01)),
|
| 128 |
+
top_p=float(top_p),
|
| 129 |
+
do_sample=True if temperature > 0.0 else False,
|
| 130 |
+
)
|
| 131 |
|
| 132 |
+
# Chạy model.generate trên luồng nền (background thread)
|
| 133 |
+
thread = Thread(target=model.generate, kwargs=generation_kwargs)
|
| 134 |
+
thread.start()
|
| 135 |
|
| 136 |
+
# 7. Nhận từng token và yield về giao diện theo thời gian thực
|
| 137 |
+
partial_text = ""
|
| 138 |
+
for token in streamer:
|
| 139 |
+
partial_text += token
|
| 140 |
+
yield partial_text
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
# ==============================================================================
|
| 144 |
+
# TÙY BIẾN GIAO DIỆN THEME GOOGLE GEMINI
|
| 145 |
+
# ==============================================================================
|
| 146 |
+
CUSTOM_CSS = """
|
| 147 |
+
@import url('https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;700&family=Inter:wght@400;500;600&display=swap');
|
| 148 |
+
|
| 149 |
+
:root {
|
| 150 |
+
--bg-primary: #131314;
|
| 151 |
+
--bg-secondary: #1e1f20;
|
| 152 |
+
--bg-bubble-user: #282a2c;
|
| 153 |
+
--text-primary: #e3e3e3;
|
| 154 |
+
--text-muted: #9aa0a6;
|
| 155 |
+
--border-color: #3c4043;
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
body, .gradio-container {
|
| 159 |
+
background-color: var(--bg-primary) !important;
|
| 160 |
+
color: var(--text-primary) !important;
|
| 161 |
+
font-family: 'Google Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
/* Tiêu đề phong cách Gemini Sparkle */
|
| 165 |
+
.gemini-title {
|
| 166 |
+
text-align: center;
|
| 167 |
+
margin: 1.5rem 0 0.5rem 0;
|
| 168 |
+
font-size: 2.2rem;
|
| 169 |
+
font-weight: 700;
|
| 170 |
+
background: linear-gradient(135deg, #4285f4 0%, #9b72cb 50%, #d96570 100%);
|
| 171 |
+
-webkit-background-clip: text;
|
| 172 |
+
-webkit-text-fill-color: transparent;
|
| 173 |
+
letter-spacing: -0.5px;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.gemini-subtitle {
|
| 177 |
+
text-align: center;
|
| 178 |
+
color: var(--text-muted);
|
| 179 |
+
font-size: 0.95rem;
|
| 180 |
+
margin-bottom: 1.5rem;
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
/* Chat container & Message styling */
|
| 184 |
+
.bubble-wrap {
|
| 185 |
+
background-color: var(--bg-primary) !important;
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
.message.user, [data-testid="user"] {
|
| 189 |
+
background-color: var(--bg-bubble-user) !important;
|
| 190 |
+
border: 1px solid var(--border-color) !important;
|
| 191 |
+
border-radius: 20px 20px 4px 20px !important;
|
| 192 |
+
color: #f1f3f4 !important;
|
| 193 |
+
padding: 12px 18px !important;
|
| 194 |
+
font-size: 15px !important;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.message.bot, [data-testid="bot"] {
|
| 198 |
+
background-color: transparent !important;
|
| 199 |
+
border: none !important;
|
| 200 |
+
border-radius: 20px 20px 20px 4px !important;
|
| 201 |
+
color: var(--text-primary) !important;
|
| 202 |
+
padding: 12px 18px !important;
|
| 203 |
+
font-size: 15px !important;
|
| 204 |
+
line-height: 1.65 !important;
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
/* Khối nhập liệu bo tròn mềm mại */
|
| 208 |
+
input[type="text"], textarea {
|
| 209 |
+
background-color: var(--bg-secondary) !important;
|
| 210 |
+
border: 1px solid var(--border-color) !important;
|
| 211 |
+
border-radius: 16px !important;
|
| 212 |
+
color: #ffffff !important;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
input[type="text"]:focus, textarea:focus {
|
| 216 |
+
border-color: #4285f4 !important;
|
| 217 |
+
box-shadow: 0 0 0 2px rgba(66, 133, 244, 0.25) !important;
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
/* Accordion cài đặt */
|
| 221 |
+
.gr-accordion {
|
| 222 |
+
background-color: var(--bg-secondary) !important;
|
| 223 |
+
border: 1px solid var(--border-color) !important;
|
| 224 |
+
border-radius: 12px !important;
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
footer {
|
| 228 |
+
display: none !important;
|
| 229 |
+
}
|
| 230 |
+
"""
|
| 231 |
+
|
| 232 |
+
# ==============================================================================
|
| 233 |
+
# KHỞI TẠO GIAO DIỆN GRADIO CHAT INTERFACE
|
| 234 |
+
# ==============================================================================
|
| 235 |
+
demo = gr.ChatInterface(
|
| 236 |
+
fn=chat_response,
|
| 237 |
+
type="messages",
|
| 238 |
+
title=None,
|
| 239 |
+
textbox=gr.Textbox(
|
| 240 |
+
placeholder="Hỏi Gemma bất kỳ điều gì (nhấn Enter để gửi)...",
|
| 241 |
+
lines=1,
|
| 242 |
+
max_lines=6,
|
| 243 |
+
container=False,
|
| 244 |
+
scale=7,
|
| 245 |
+
),
|
| 246 |
+
additional_inputs=[
|
| 247 |
+
gr.Textbox(
|
| 248 |
+
label="Chỉ dẫn hệ thống (System Prompt)",
|
| 249 |
+
value="Bạn là Gemma, một trợ lý trí tuệ nhân tạo thông minh, am hiểu và thân thiện. Hãy trả lời súc tích, chính xác, định dạng câu trả lời bằng Markdown đẹp mắt.",
|
| 250 |
+
lines=2,
|
| 251 |
+
),
|
| 252 |
+
gr.Slider(
|
| 253 |
+
minimum=0.0,
|
| 254 |
+
maximum=1.0,
|
| 255 |
+
value=0.7,
|
| 256 |
+
step=0.05,
|
| 257 |
+
label="Độ sáng tạo (Temperature)",
|
| 258 |
+
info="Giá trị thấp trả lời chuẩn xác hơn; giá trị cao trả lời phong phú và sáng tạo hơn.",
|
| 259 |
+
),
|
| 260 |
+
gr.Slider(
|
| 261 |
+
minimum=128,
|
| 262 |
+
maximum=4096,
|
| 263 |
+
value=1536,
|
| 264 |
+
step=128,
|
| 265 |
+
label="Độ dài tối đa (Max New Tokens)",
|
| 266 |
+
info="Số lượng token tối đa mô hình tạo ra trong mỗi lượt trả lời.",
|
| 267 |
+
),
|
| 268 |
+
gr.Slider(
|
| 269 |
+
minimum=0.1,
|
| 270 |
+
maximum=1.0,
|
| 271 |
+
value=0.9,
|
| 272 |
+
step=0.05,
|
| 273 |
+
label="Top-P (Nucleus Sampling)",
|
| 274 |
+
),
|
| 275 |
+
],
|
| 276 |
+
additional_inputs_accordion=gr.Accordion(
|
| 277 |
+
label="⚙️ Cài đặt tham số mô hình & System Prompt",
|
| 278 |
+
open=False,
|
| 279 |
+
),
|
| 280 |
+
examples=[
|
| 281 |
+
"Giải thích ngắn gọn cách hoạt động của cơ chế Multi-Head Attention trong mô hình Transformer.",
|
| 282 |
+
"Viết một kịch bản ngắn giới thiệu vẻ đẹp thiên nhiên và văn hóa cồng chiêng của vùng đất Gia Lai.",
|
| 283 |
+
"Tạo một hàm Python đọc tệp CSV, làm sạch dữ liệu thiếu và vẽ biểu đồ xu hướng bằng Matplotlib.",
|
| 284 |
+
"So sánh ưu điểm và nhược điểm giữa cấu trúc Dense Model và Mixture-of-Experts (MoE).",
|
| 285 |
+
],
|
| 286 |
+
css=CUSTOM_CSS,
|
| 287 |
+
theme=gr.themes.Base(
|
| 288 |
+
primary_hue="blue",
|
| 289 |
+
neutral_hue="neutral",
|
| 290 |
),
|
| 291 |
+
head="""
|
| 292 |
+
<div class="gemini-title">✨ Google Gemma Assistant</div>
|
| 293 |
+
<div class="gemini-subtitle">Trải nghiệm đối thoại AI phong cách Gemini • Tải trực tiếp mô hình Gemma 4 31B (4-bit ZeroGPU)</div>
|
| 294 |
+
""",
|
| 295 |
)
|
| 296 |
|
| 297 |
if __name__ == "__main__":
|
| 298 |
+
demo.queue().launch()
|
requirements.txt
CHANGED
|
@@ -1,2 +1,4 @@
|
|
| 1 |
-
transformers
|
| 2 |
torch
|
|
|
|
|
|
|
|
|
| 1 |
+
transformers>=4.45.0
|
| 2 |
torch
|
| 3 |
+
accelerate>=0.34.0
|
| 4 |
+
bitsandbytes>=0.43.0
|