Spaces:
Sleeping
Sleeping
File size: 5,913 Bytes
4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 2373965 550b9a8 2373965 5fe1541 550b9a8 2373965 550b9a8 2373965 5fe1541 550b9a8 2373965 550b9a8 2373965 550b9a8 2373965 4131912 5fe1541 98ad17b 4131912 98ad17b 4131912 550b9a8 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 ccc8ac4 4131912 | 1 2 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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | """
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() |