Spaces:
Running
on
Zero
Running
on
Zero
| """ | |
| ChatGPT Clone - 日本語対応チャットボット | |
| Hugging Face Spaces (ZeroGPU) 対応版 | |
| 使用モデル: | |
| - elyza/Llama-3-ELYZA-JP-8B | |
| - Fugaku-LLM/Fugaku-LLM-13B | |
| """ | |
| import gradio as gr | |
| import torch | |
| from transformers import AutoModelForCausalLM, AutoTokenizer | |
| import os | |
| from typing import List, Tuple | |
| # Hugging Face token from environment variable | |
| HF_TOKEN = os.getenv("HF_TOKEN") | |
| # トークンのチェック | |
| if not HF_TOKEN: | |
| print("警告: HF_TOKENが設定されていません。プライベートモデルへのアクセスが制限される場合があります。") | |
| # Check if running on ZeroGPU | |
| try: | |
| import spaces | |
| IS_ZEROGPU = True | |
| print("ZeroGPU環境を検出しました。") | |
| except ImportError: | |
| IS_ZEROGPU = False | |
| print("通常のGPU/CPU環境で実行しています。") | |
| class ChatBot: | |
| def __init__(self): | |
| self.model = None | |
| self.tokenizer = None | |
| self.current_model = None | |
| def load_model(self, model_name: str): | |
| """モデルとトークナイザーをロード""" | |
| if self.current_model == model_name and self.model is not None: | |
| return | |
| try: | |
| # メモリクリア | |
| if self.model is not None: | |
| del self.model | |
| del self.tokenizer | |
| if torch.cuda.is_available(): | |
| torch.cuda.empty_cache() | |
| torch.cuda.synchronize() | |
| # トークナイザーロード | |
| self.tokenizer = AutoTokenizer.from_pretrained( | |
| model_name, | |
| token=HF_TOKEN, | |
| trust_remote_code=True, | |
| padding_side="left" | |
| ) | |
| # パッドトークンの設定 | |
| if self.tokenizer.pad_token is None: | |
| self.tokenizer.pad_token = self.tokenizer.eos_token | |
| self.tokenizer.pad_token_id = self.tokenizer.eos_token_id | |
| # モデルロード(ZeroGPU対応) | |
| self.model = AutoModelForCausalLM.from_pretrained( | |
| model_name, | |
| token=HF_TOKEN, | |
| torch_dtype=torch.float16, | |
| low_cpu_mem_usage=True, | |
| trust_remote_code=True, | |
| load_in_8bit=False, # ZeroGPU環境では8bit量子化は使わない | |
| device_map=None # ZeroGPU環境では自動マッピングしない | |
| ) | |
| self.current_model = model_name | |
| print(f"モデル {model_name} のロードが完了しました。") | |
| except Exception as e: | |
| print(f"モデルのロード中にエラーが発生しました: {str(e)}") | |
| raise | |
| def _generate_response_gpu(self, message: str, history: List[Tuple[str, str]], model_name: str, | |
| temperature: float = 0.7, max_tokens: int = 512) -> str: | |
| """GPU上で応答を生成する実際の処理""" | |
| # モデルロード | |
| self.load_model(model_name) | |
| # GPUに移動 | |
| self.model.to('cuda') | |
| # プロンプト構築 | |
| prompt = self._build_prompt(message, history) | |
| # トークナイズ | |
| inputs = self.tokenizer.encode(prompt, return_tensors="pt").to('cuda') | |
| # 生成 | |
| with torch.no_grad(): | |
| outputs = self.model.generate( | |
| inputs, | |
| max_new_tokens=max_tokens, | |
| temperature=temperature, | |
| do_sample=True, | |
| top_p=0.95, | |
| top_k=50, | |
| repetition_penalty=1.1, | |
| pad_token_id=self.tokenizer.pad_token_id, | |
| eos_token_id=self.tokenizer.eos_token_id | |
| ) | |
| # デコード | |
| response = self.tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True) | |
| # CPUに戻す(メモリ節約) | |
| self.model.to('cpu') | |
| torch.cuda.empty_cache() | |
| torch.cuda.synchronize() | |
| return response.strip() | |
| def generate_response(self, message: str, history: List[Tuple[str, str]], model_name: str, | |
| temperature: float = 0.7, max_tokens: int = 512) -> str: | |
| """メッセージに対する応答を生成""" | |
| if IS_ZEROGPU: | |
| # ZeroGPU環境の場合 | |
| return self._generate_response_gpu(message, history, model_name, temperature, max_tokens) | |
| else: | |
| # 通常環境の場合 | |
| self.load_model(model_name) | |
| device = 'cuda' if torch.cuda.is_available() else 'cpu' | |
| if device == 'cuda': | |
| self.model.to(device) | |
| prompt = self._build_prompt(message, history) | |
| inputs = self.tokenizer.encode(prompt, return_tensors="pt").to(device) | |
| with torch.no_grad(): | |
| outputs = self.model.generate( | |
| inputs, | |
| max_new_tokens=max_tokens, | |
| temperature=temperature, | |
| do_sample=True, | |
| top_p=0.95, | |
| top_k=50, | |
| repetition_penalty=1.1, | |
| pad_token_id=self.tokenizer.pad_token_id, | |
| eos_token_id=self.tokenizer.eos_token_id | |
| ) | |
| response = self.tokenizer.decode(outputs[0][inputs.shape[1]:], skip_special_tokens=True) | |
| return response.strip() | |
| def _build_prompt(self, message: str, history: List[Tuple[str, str]]) -> str: | |
| """会話履歴からプロンプトを構築""" | |
| prompt = "" | |
| # 履歴を追加(最新3件のみ使用 - メモリ効率のため) | |
| for user_msg, assistant_msg in history[-3:]: | |
| prompt += f"User: {user_msg}\nAssistant: {assistant_msg}\n\n" | |
| # 現在のメッセージを追加 | |
| prompt += f"User: {message}\nAssistant: " | |
| return prompt | |
| # ChatBotインスタンス | |
| chatbot = ChatBot() | |
| # ZeroGPU環境の場合、GPUデコレータを適用 | |
| if IS_ZEROGPU: | |
| chatbot._generate_response_gpu = spaces.GPU(duration=120)(chatbot._generate_response_gpu) | |
| def respond(message: str, history: List[Tuple[str, str]], model_name: str, | |
| temperature: float, max_tokens: int) -> Tuple[List[Tuple[str, str]], str]: | |
| """Gradioのコールバック関数""" | |
| if not message: | |
| return history, "" | |
| try: | |
| # 応答生成 | |
| response = chatbot.generate_response(message, history, model_name, temperature, max_tokens) | |
| # 履歴に追加 | |
| history.append((message, response)) | |
| return history, "" | |
| except RuntimeError as e: | |
| if "out of memory" in str(e).lower(): | |
| error_msg = "メモリ不足エラー: より小さいモデルを使用するか、最大トークン数を減らしてください。" | |
| else: | |
| error_msg = f"実行時エラー: {str(e)}" | |
| history.append((message, error_msg)) | |
| return history, "" | |
| except Exception as e: | |
| error_msg = f"エラーが発生しました: {str(e)}" | |
| history.append((message, error_msg)) | |
| return history, "" | |
| def clear_chat() -> Tuple[List, str]: | |
| """チャット履歴をクリア""" | |
| return [], "" | |
| # Gradio UI | |
| with gr.Blocks(title="ChatGPT Clone", theme=gr.themes.Soft()) as app: | |
| gr.Markdown("# 🤖 ChatGPT Clone") | |
| gr.Markdown(""" | |
| 日本語対応のLLMを使用したチャットボットです。 | |
| **使用可能モデル:** | |
| - [elyza/Llama-3-ELYZA-JP-8B](https://huggingface.co/elyza/Llama-3-ELYZA-JP-8B) | |
| - [Fugaku-LLM/Fugaku-LLM-13B](https://huggingface.co/Fugaku-LLM/Fugaku-LLM-13B) | |
| """) | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| chatbot_ui = gr.Chatbot( | |
| label="Chat", | |
| height=500, | |
| show_label=False, | |
| container=True | |
| ) | |
| with gr.Row(): | |
| msg_input = gr.Textbox( | |
| label="メッセージを入力", | |
| placeholder="ここにメッセージを入力してください...", | |
| lines=2, | |
| scale=4, | |
| show_label=False | |
| ) | |
| send_btn = gr.Button("送信", variant="primary", scale=1) | |
| with gr.Row(): | |
| clear_btn = gr.Button("🗑️ 新しい会話", variant="secondary") | |
| with gr.Column(scale=1): | |
| model_select = gr.Dropdown( | |
| choices=[ | |
| "elyza/Llama-3-ELYZA-JP-8B", | |
| "Fugaku-LLM/Fugaku-LLM-13B", | |
| ], | |
| value="elyza/Llama-3-ELYZA-JP-8B", | |
| label="モデル選択", | |
| interactive=True | |
| ) | |
| temperature = gr.Slider( | |
| minimum=0.1, | |
| maximum=1.0, | |
| value=0.7, | |
| step=0.1, | |
| label="Temperature", | |
| info="生成の創造性を調整" | |
| ) | |
| max_tokens = gr.Slider( | |
| minimum=64, | |
| maximum=512, | |
| value=256, | |
| step=64, | |
| label="最大トークン数", | |
| info="生成する最大トークン数" | |
| ) | |
| gr.Markdown(""" | |
| ### 使い方 | |
| 1. モデルを選択 | |
| 2. メッセージを入力 | |
| 3. 送信ボタンをクリック | |
| ### 注意事項 | |
| - 初回のモデル読み込みには時間がかかります | |
| - ZeroGPU使用により高速推論が可能 | |
| - 1回の生成は120秒以内に完了します | |
| - 大きなモデル使用時は、短めの応答になる場合があります | |
| """) | |
| # イベントハンドラ | |
| msg_input.submit( | |
| fn=respond, | |
| inputs=[msg_input, chatbot_ui, model_select, temperature, max_tokens], | |
| outputs=[chatbot_ui, msg_input] | |
| ) | |
| send_btn.click( | |
| fn=respond, | |
| inputs=[msg_input, chatbot_ui, model_select, temperature, max_tokens], | |
| outputs=[chatbot_ui, msg_input] | |
| ) | |
| clear_btn.click( | |
| fn=clear_chat, | |
| outputs=[chatbot_ui, msg_input] | |
| ) | |
| if __name__ == "__main__": | |
| # Hugging Face Spaces環境かどうかを確認 | |
| is_hf_spaces = os.getenv("SPACE_ID") is not None | |
| app.launch( | |
| share=False, | |
| show_error=True, | |
| server_name="0.0.0.0" if is_hf_spaces else "127.0.0.1", | |
| server_port=7860 | |
| ) |