test / app.py
vercelgpt's picture
Create app.py
b6ac232 verified
Raw
History Blame Contribute Delete
5.66 kB
"""
Gradio ZeroGPU Space — Ollama-style LLM chat using llama-cpp-python.
GPU is acquired per request via @spaces.GPU, so the A100 is only held
while a token is being generated, not for the entire session lifetime.
"""
from __future__ import annotations
import os
import threading
from typing import Iterator
import gradio as gr
import spaces
from huggingface_hub import hf_hub_download
from llama_cpp import Llama
# ---------------------------------------------------------------------------
# Model configuration — change these to switch models
# ---------------------------------------------------------------------------
MODEL_REPO = os.getenv("MODEL_REPO", "LiquidAI/LFM2.5-230M-GGUF")
MODEL_FILE = os.getenv("MODEL_FILE", "LFM2.5-230M-F16.gguf")
CONTEXT_SIZE = int(os.getenv("CONTEXT_SIZE", "4096"))
# ---------------------------------------------------------------------------
# Load model once at startup (CPU map; GPU layers allocated at inference time)
# ---------------------------------------------------------------------------
print(f"Downloading {MODEL_FILE} from {MODEL_REPO} …")
model_path = hf_hub_download(repo_id=MODEL_REPO, filename=MODEL_FILE)
print("Model downloaded. Initialising llama-cpp …")
llm = Llama(
model_path=model_path,
n_ctx=CONTEXT_SIZE,
n_gpu_layers=-1, # offload ALL layers to GPU (set 0 for CPU-only)
verbose=False,
)
print("Model ready.")
# ---------------------------------------------------------------------------
# Inference — wrapped with @spaces.GPU so A100 is acquired per call
# ---------------------------------------------------------------------------
@spaces.GPU(duration=120)
def _generate(
messages: list[dict],
temperature: float,
max_new_tokens: int,
top_p: float,
) -> Iterator[str]:
"""Yield partial assistant responses token by token."""
stream = llm.create_chat_completion(
messages=messages,
temperature=temperature,
max_tokens=max_new_tokens,
top_p=top_p,
stream=True,
)
for chunk in stream:
delta = chunk["choices"][0]["delta"]
token = delta.get("content", "")
if token:
yield token
def build_messages(
history: list[dict],
system_prompt: str,
) -> list[dict]:
"""Convert Gradio history format to llama-cpp messages list."""
messages: list[dict] = []
if system_prompt.strip():
messages.append({"role": "system", "content": system_prompt.strip()})
for msg in history:
messages.append({"role": msg["role"], "content": msg["content"]})
return messages
def chat_fn(
message: str,
history: list[dict],
system_prompt: str,
temperature: float,
max_new_tokens: int,
top_p: float,
) -> Iterator[str]:
"""Gradio streaming chat handler."""
history = history or []
history.append({"role": "user", "content": message})
messages = build_messages(history, system_prompt)
partial = ""
for token in _generate(messages, temperature, max_new_tokens, top_p):
partial += token
yield partial
# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------
DEFAULT_SYSTEM = (
"You are a helpful, harmless, and honest AI assistant. "
"Answer concisely and clearly."
)
with gr.Blocks(title="ZeroGPU LLM Chat", theme=gr.themes.Soft()) as demo:
gr.Markdown(
f"""
# 🦙 ZeroGPU LLM Chat
**Model:** `{MODEL_REPO} / {MODEL_FILE}`
GPU is allocated on demand (ZeroGPU) — first response may take a few seconds while the Space warms up.
"""
)
with gr.Row():
with gr.Column(scale=3):
chatbot = gr.ChatInterface(
fn=chat_fn,
type="messages",
additional_inputs_accordion=gr.Accordion(
label="⚙️ Generation settings", open=False
),
additional_inputs=[
gr.Textbox(
value=DEFAULT_SYSTEM,
label="System prompt",
lines=3,
placeholder="Enter a system prompt …",
),
gr.Slider(
minimum=0.0,
maximum=2.0,
value=0.7,
step=0.05,
label="Temperature",
info="Higher = more creative, lower = more deterministic",
),
gr.Slider(
minimum=64,
maximum=2048,
value=512,
step=64,
label="Max new tokens",
info="Maximum number of tokens to generate per reply",
),
gr.Slider(
minimum=0.1,
maximum=1.0,
value=0.95,
step=0.05,
label="Top-p (nucleus sampling)",
),
],
examples=[
"Explain quantum entanglement in simple terms.",
"Write a Python function that checks if a string is a palindrome.",
"What are the pros and cons of renewable energy?",
"Translate 'Hello, how are you?' into French, German, and Japanese.",
],
cache_examples=False,
)
demo.queue(max_size=10)
if __name__ == "__main__":
demo.launch()