Spaces:
Sleeping
Sleeping
| """ | |
| Gradio chat interface for nanochat models on Hugging Face Spaces. | |
| Downloads the model from HF Hub and serves a streaming chat UI. | |
| """ | |
| import os | |
| import json | |
| import random | |
| import torch | |
| import gradio as gr | |
| from huggingface_hub import snapshot_download | |
| # --------------------------------------------------------------------------- | |
| # Configuration | |
| # --------------------------------------------------------------------------- | |
| MODEL_REPO = os.environ.get("MODEL_REPO", "Slothwolf/MiniChat-0.9B") | |
| MODEL_STEP = int(os.environ.get("MODEL_STEP", "486")) | |
| DEFAULT_TEMPERATURE = float(os.environ.get("DEFAULT_TEMPERATURE", "0.8")) | |
| DEFAULT_MAX_TOKENS = int(os.environ.get("DEFAULT_MAX_TOKENS", "1024")) | |
| DEFAULT_TOP_K = int(os.environ.get("DEFAULT_TOP_K", "50")) | |
| # --------------------------------------------------------------------------- | |
| # Download and load model | |
| # --------------------------------------------------------------------------- | |
| print(f"Downloading model from {MODEL_REPO}...") | |
| model_dir = snapshot_download(MODEL_REPO) | |
| # Point nanochat at the downloaded directory so get_tokenizer() finds tokenizer/ | |
| os.environ["NANOCHAT_BASE_DIR"] = model_dir | |
| from nanochat.checkpoint_manager import build_model | |
| from nanochat.engine import Engine | |
| print(f"Loading model (step {MODEL_STEP}) on CPU...") | |
| device = torch.device("cpu") | |
| model, tokenizer, meta = build_model(model_dir, MODEL_STEP, device, "eval") | |
| engine = Engine(model, tokenizer) | |
| print("Model loaded and ready!") | |
| # Grab special token ids once | |
| bos_id = tokenizer.get_bos_token_id() | |
| user_start = tokenizer.encode_special("<|user_start|>") | |
| user_end = tokenizer.encode_special("<|user_end|>") | |
| assistant_start = tokenizer.encode_special("<|assistant_start|>") | |
| assistant_end = tokenizer.encode_special("<|assistant_end|>") | |
| def normalize_history(history): | |
| if not history: | |
| return [] | |
| if isinstance(history[0], dict): | |
| pairs = [] | |
| user_msg = None | |
| for msg in history: | |
| role = msg.get("role") | |
| content = msg.get("content", "") | |
| if not isinstance(content, str): | |
| content = str(content) if content is not None else "" | |
| if role == "user": | |
| if user_msg is not None: | |
| pairs.append([user_msg, None]) | |
| user_msg = content | |
| elif role == "assistant": | |
| if user_msg is not None: | |
| pairs.append([user_msg, content]) | |
| user_msg = None | |
| else: | |
| pairs.append(["", content]) | |
| if user_msg is not None: | |
| pairs.append([user_msg, None]) | |
| return pairs | |
| normalized = [] | |
| for turn in history: | |
| if isinstance(turn, (list, tuple)): | |
| user = turn[0] if len(turn) > 0 else None | |
| assistant = turn[1] if len(turn) > 1 else None | |
| user = str(user) if user is not None else "" | |
| assistant = str(assistant) if assistant is not None else "" | |
| normalized.append([user, assistant]) | |
| else: | |
| continue | |
| return normalized | |
| # --------------------------------------------------------------------------- | |
| # Chat function (streaming) | |
| # --------------------------------------------------------------------------- | |
| def chat(message, history, temperature, max_tokens, top_k): | |
| """Generate a streaming response for the chat interface.""" | |
| history = normalize_history(history) | |
| # Build the full token sequence from conversation history | |
| # history is a list of [user_msg, assistant_msg] tuples | |
| tokens = [bos_id] | |
| for user_msg, assistant_msg in history: | |
| user_text = user_msg if isinstance(user_msg, str) else str(user_msg) if user_msg is not None else "" | |
| tokens.append(user_start) | |
| tokens.extend(tokenizer.encode(user_msg)) | |
| tokens.append(user_end) | |
| if assistant_msg: | |
| tokens.append(assistant_start) | |
| tokens.extend(tokenizer.encode(assistant_msg)) | |
| tokens.append(assistant_end) | |
| # Add the current user message and prime the assistant | |
| tokens.append(user_start) | |
| tokens.extend(tokenizer.encode(message)) | |
| tokens.append(user_end) | |
| tokens.append(assistant_start) | |
| # Generate tokens with streaming | |
| response = "" | |
| accumulated_tokens = [] | |
| last_clean_text = "" | |
| for token_column, token_masks in engine.generate( | |
| tokens, | |
| num_samples=1, | |
| max_tokens=int(max_tokens), | |
| temperature=float(temperature), | |
| top_k=int(top_k) if int(top_k) > 0 else None, | |
| seed=random.randint(0, 2**31 - 1), | |
| ): | |
| token = token_column[0] | |
| if token == assistant_end or token == bos_id: | |
| break | |
| # Accumulate tokens and decode, handling multi-byte UTF-8 | |
| accumulated_tokens.append(token) | |
| current_text = tokenizer.decode(accumulated_tokens) | |
| if not current_text.endswith("\ufffd"): | |
| new_text = current_text[len(last_clean_text):] | |
| if new_text: | |
| response += new_text | |
| last_clean_text = current_text | |
| yield response | |
| # --------------------------------------------------------------------------- | |
| # Gradio UI | |
| # --------------------------------------------------------------------------- | |
| demo = gr.ChatInterface( | |
| chat, | |
| additional_inputs=[ | |
| gr.Slider(0.0, 2.0, value=DEFAULT_TEMPERATURE, step=0.1, label="Temperature"), | |
| gr.Slider(1, 2048, value=DEFAULT_MAX_TOKENS, step=1, label="Max Tokens"), | |
| gr.Slider(0, 200, value=DEFAULT_TOP_K, step=1, label="Top-K (0 = disabled)"), | |
| ], | |
| title="MiniChat", | |
| description=( | |
| "Chat with a **0.9B parameter** GPT-like model trained with " | |
| "[nanochat](https://github.com/karpathy/nanochat) by Andrej Karpathy. " | |
| "Running on CPU — generation will be slow." | |
| ), | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |