| """ |
| 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_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")) |
|
|
| |
| |
| |
| 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, |
| verbose=False, |
| ) |
| print("Model ready.") |
|
|
| |
| |
| |
| @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 |
|
|
|
|
| |
| |
| |
| 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() |
|
|