| import os |
| import spaces |
| import torch |
| from threading import Thread |
| from transformers import ( |
| AutoModelForCausalLM, |
| AutoTokenizer, |
| TextIteratorStreamer, |
| BitsAndBytesConfig, |
| ) |
| import gradio as gr |
|
|
| |
| |
| |
| |
| |
| MODEL_ID = os.getenv("MODEL_ID", "google/gemma-4-31B-it") |
| HF_TOKEN = os.getenv("HF_TOKEN", None) |
|
|
| print(f"[*] Khởi tạo cấu hình cho mô hình: {MODEL_ID}") |
|
|
| |
| bnb_config = BitsAndBytesConfig( |
| load_in_4bit=True, |
| bnb_4bit_quant_type="nf4", |
| bnb_4bit_compute_dtype=torch.bfloat16, |
| bnb_4bit_use_double_quant=True, |
| ) |
|
|
| |
| print("[*] Đang tải Tokenizer...") |
| tokenizer = AutoTokenizer.from_pretrained( |
| MODEL_ID, |
| token=HF_TOKEN, |
| trust_remote_code=True, |
| ) |
|
|
| print("[*] Đang tải Model với 4-bit Quantization...") |
| model = AutoModelForCausalLM.from_pretrained( |
| MODEL_ID, |
| quantization_config=bnb_config, |
| device_map="auto", |
| token=HF_TOKEN, |
| trust_remote_code=True, |
| ) |
| print("[+] Mô hình đã sẵn sàng hoạt động!") |
|
|
| |
| |
| |
| @spaces.GPU(duration=120) |
| def chat_response( |
| message, |
| history, |
| system_prompt, |
| temperature, |
| max_new_tokens, |
| top_p, |
| ): |
| if not message or not message.strip(): |
| yield "" |
| return |
|
|
| |
| conversation = [] |
|
|
| |
| if system_prompt and system_prompt.strip(): |
| conversation.append({"role": "system", "content": system_prompt.strip()}) |
|
|
| |
| for item in history: |
| if isinstance(item, dict): |
| role = item.get("role", "user") |
| |
| if role == "assistant": |
| role = "model" |
| conversation.append({"role": role, "content": item.get("content", "")}) |
| elif isinstance(item, (list, tuple)) and len(item) == 2: |
| user_text, bot_text = item |
| if user_text: |
| conversation.append({"role": "user", "content": str(user_text)}) |
| if bot_text: |
| conversation.append({"role": "model", "content": str(bot_text)}) |
|
|
| |
| conversation.append({"role": "user", "content": message.strip()}) |
|
|
| |
| try: |
| prompt = tokenizer.apply_chat_template( |
| conversation, |
| tokenize=False, |
| add_generation_prompt=True, |
| ) |
| except Exception: |
| |
| fallback_conv = [] |
| for idx, item in enumerate(conversation): |
| if item["role"] == "system": |
| continue |
| content = item["content"] |
| if idx == (1 if system_prompt else 0): |
| content = f"[Hướng dẫn hệ thống: {system_prompt}]\n\n{content}" |
| fallback_conv.append({"role": item["role"], "content": content}) |
| prompt = tokenizer.apply_chat_template( |
| fallback_conv, |
| tokenize=False, |
| add_generation_prompt=True, |
| ) |
|
|
| |
| model_inputs = tokenizer([prompt], return_tensors="pt").to(model.device) |
|
|
| |
| streamer = TextIteratorStreamer( |
| tokenizer, |
| skip_prompt=True, |
| skip_special_tokens=True, |
| ) |
|
|
| generation_kwargs = dict( |
| model_inputs, |
| streamer=streamer, |
| max_new_tokens=int(max_new_tokens), |
| temperature=float(max(temperature, 0.01)), |
| top_p=float(top_p), |
| do_sample=True if temperature > 0.0 else False, |
| ) |
|
|
| |
| thread = Thread(target=model.generate, kwargs=generation_kwargs) |
| thread.start() |
|
|
| |
| partial_text = "" |
| for token in streamer: |
| partial_text += token |
| yield partial_text |
|
|
|
|
| |
| |
| |
| DEFAULT_SYSTEM_PROMPT = ( |
| "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." |
| ) |
|
|
| |
| custom_theme = gr.themes.Soft( |
| primary_hue="blue", |
| secondary_hue="indigo", |
| neutral_hue="slate", |
| font=[gr.themes.GoogleFont("Inter"), "sans-serif"], |
| ) |
|
|
| CUSTOM_CSS = """ |
| @import url('https://fonts.googleapis.com/css2?family=Google+Sans:wght@400;500;600;700&family=Inter:wght@400;500;600&display=swap'); |
| |
| /* Toàn bộ nền trang sáng sủa, sạch sẽ phong cách Google */ |
| body, .gradio-container { |
| background-color: #f8fafd !important; |
| color: #1f2937 !important; |
| font-family: 'Google Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important; |
| max-width: 960px !important; |
| margin: 0 auto !important; |
| } |
| |
| /* Tiêu đề phong cách Gemini Sparkle Gradient */ |
| h1 { |
| text-align: center !important; |
| font-size: 2.2rem !important; |
| font-weight: 700 !important; |
| background: linear-gradient(135deg, #1a73e8 0%, #8b5cf6 50%, #ec4899 100%) !important; |
| -webkit-background-clip: text !important; |
| -webkit-text-fill-color: transparent !important; |
| letter-spacing: -0.5px !important; |
| margin-bottom: 0.25rem !important; |
| } |
| |
| /* Mô tả phụ bên dưới tiêu đề */ |
| .description, p.description { |
| text-align: center !important; |
| color: #5f6368 !important; |
| font-size: 0.95rem !important; |
| margin-bottom: 1.25rem !important; |
| } |
| |
| /* Khung Chatbot chính: Nền trắng, bo tròn mềm mại, đổ bóng nhẹ */ |
| [data-testid="chatbot"], .chatbot { |
| background-color: #ffffff !important; |
| border: 1px solid #e2e8f0 !important; |
| border-radius: 20px !important; |
| box-shadow: 0 4px 20px -2px rgba(0, 0, 0, 0.05) !important; |
| min-height: 520px !important; |
| } |
| |
| /* Tin nhắn Người dùng: Nền xanh nhạt dịu mắt (Google Blue tint), chữ xanh đậm rõ nét */ |
| [data-testid="user"], .message.user, .user-row .message { |
| background-color: #e8f0fe !important; |
| border: 1px solid #d2e3fc !important; |
| color: #174ea6 !important; |
| border-radius: 20px 20px 4px 20px !important; |
| padding: 12px 18px !important; |
| font-size: 15px !important; |
| font-weight: 500 !important; |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04) !important; |
| } |
| |
| [data-testid="user"] *, .message.user * { |
| color: #174ea6 !important; |
| } |
| |
| /* Tin nhắn Trợ lý Bot: Chữ tối rõ nét trên nền trắng, dễ đọc */ |
| [data-testid="bot"], .message.bot, .bot-row .message { |
| background-color: transparent !important; |
| color: #1f2937 !important; |
| border: none !important; |
| font-size: 15px !important; |
| line-height: 1.7 !important; |
| padding: 12px 18px !important; |
| } |
| |
| [data-testid="bot"] *, .message.bot * { |
| color: #1f2937 !important; |
| } |
| |
| /* Code block hiển thị đẹp mắt và tương phản chuẩn */ |
| pre { |
| background-color: #1e293b !important; |
| border-radius: 10px !important; |
| padding: 14px 16px !important; |
| color: #f8fafc !important; |
| } |
| |
| pre code, pre span { |
| color: #f8fafc !important; |
| } |
| |
| /* Ô nhập câu hỏi dạng viên thuốc (Pill shape) chuẩn Google */ |
| input[type="text"], textarea { |
| background-color: #ffffff !important; |
| border: 1.5px solid #dadce0 !important; |
| border-radius: 26px !important; |
| color: #1f2937 !important; |
| padding: 12px 20px !important; |
| font-size: 15px !important; |
| box-shadow: 0 2px 6px rgba(0, 0, 0, 0.04) !important; |
| } |
| |
| input[type="text"]:focus, textarea:focus { |
| border-color: #1a73e8 !important; |
| box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.15) !important; |
| } |
| |
| /* Khối Accordion cài đặt: Thu gọn, nền trắng, tinh gọn */ |
| details, .gr-accordion { |
| background-color: #ffffff !important; |
| border: 1px solid #e2e8f0 !important; |
| border-radius: 14px !important; |
| box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03) !important; |
| margin-top: 0.75rem !important; |
| } |
| |
| label, span.label-text { |
| color: #374151 !important; |
| font-weight: 500 !important; |
| } |
| |
| footer { |
| display: none !important; |
| } |
| """ |
|
|
| |
| |
| |
| demo = gr.ChatInterface( |
| fn=chat_response, |
| title="✨ Google Gemma Assistant", |
| description="Trải nghiệm đối thoại AI phong cách Google Gemini • Tải trực tiếp mô hình Gemma 4 31B (4-bit ZeroGPU)", |
| textbox=gr.Textbox( |
| placeholder="Hỏi Gemma bất kỳ điều gì (nhấn Enter để gửi)...", |
| lines=1, |
| max_lines=6, |
| container=False, |
| scale=7, |
| ), |
| additional_inputs=[ |
| gr.Textbox( |
| label="Chỉ dẫn hệ thống (System Prompt)", |
| value=DEFAULT_SYSTEM_PROMPT, |
| lines=2, |
| ), |
| gr.Slider( |
| minimum=0.0, |
| maximum=1.0, |
| value=0.7, |
| step=0.05, |
| label="Độ sáng tạo (Temperature)", |
| 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.", |
| ), |
| gr.Slider( |
| minimum=128, |
| maximum=4096, |
| value=1536, |
| step=128, |
| label="Độ dài tối đa (Max New Tokens)", |
| info="Số lượng token tối đa mô hình tạo ra trong mỗi lượt trả lời.", |
| ), |
| gr.Slider( |
| minimum=0.1, |
| maximum=1.0, |
| value=0.9, |
| step=0.05, |
| label="Top-P (Nucleus Sampling)", |
| ), |
| ], |
| additional_inputs_accordion=gr.Accordion( |
| label="⚙️ Cài đặt tham số mô hình & System Prompt", |
| open=False, |
| ), |
| examples=[ |
| [ |
| "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.", |
| DEFAULT_SYSTEM_PROMPT, |
| 0.7, |
| 1536, |
| 0.9, |
| ], |
| [ |
| "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.", |
| DEFAULT_SYSTEM_PROMPT, |
| 0.7, |
| 1536, |
| 0.9, |
| ], |
| [ |
| "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.", |
| DEFAULT_SYSTEM_PROMPT, |
| 0.7, |
| 1536, |
| 0.9, |
| ], |
| [ |
| "So sánh ưu điểm và nhược điểm giữa cấu trúc Dense Model và Mixture-of-Experts (MoE).", |
| DEFAULT_SYSTEM_PROMPT, |
| 0.7, |
| 1536, |
| 0.9, |
| ], |
| ], |
| cache_examples=False, |
| ) |
|
|
| if __name__ == "__main__": |
| demo.queue().launch(theme=custom_theme, css=CUSTOM_CSS) |
|
|
|
|