"""A ZeroGPU Gradio code-assistant demo for XHToken/Spark-X2.5-4B. Spark-X2.5-4B is a compact general-purpose model with strong coding and reasoning ability. This Space wraps it in a streaming chat UI tuned for programming tasks: write, explain, debug, refactor, test, and translate code, with an optional collapsible reasoning () trace. """ import os import threading import time import spaces # noqa: F401 (must be imported before torch / transformers) import gradio as gr import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer MODEL_ID = "XHToken/Spark-X2.5-4B" MAX_CONTEXT_TOKENS = 32_768 MIN_NEW_TOKENS = 256 MAX_NEW_TOKENS = 4096 # The model ships a custom `spark2_5` architecture via `trust_remote_code`. # Its custom attention path currently requires the eager implementation. tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True) model = AutoModelForCausalLM.from_pretrained( MODEL_ID, dtype=torch.bfloat16, trust_remote_code=True, attn_implementation="eager", ).to("cuda").eval() BASE_SYSTEM_PROMPT = ( "You are Spark Code, an expert programming assistant. " "Give correct, runnable code in fenced Markdown blocks with the language tag. " "Prefer clear, idiomatic, production-quality solutions. " "Explain briefly, call out edge cases, and state assumptions when the request is ambiguous." ) TASK_PRESETS = { "General coding": "", "Write new code": "Focus on writing a complete, self-contained implementation.", "Explain code": "Explain what the code does step by step, then summarize the key ideas.", "Debug & fix": "Identify the bug(s), explain the root cause, and provide a corrected version.", "Refactor": "Improve readability, structure, and performance without changing behavior.", "Write tests": "Produce thorough unit tests, including edge cases and failure modes.", "Translate language": "Port the code to the language the user requests, preserving behavior and idioms.", } def split_reasoning(text: str, enable_thinking: bool) -> tuple[str, str]: """Split a partial/complete generation into (reasoning, answer). With thinking enabled the chat template appends `` to the prompt, so the generated text is pure reasoning until it emits `` and then the answer. With thinking disabled the model answers directly. """ if not enable_thinking: return "", text.strip() if "" in text: reasoning, answer = text.split("", 1) return reasoning.replace("", "").strip(), answer.strip() return text.replace("", "").strip(), "" def history_to_messages(history: list[object] | None) -> list[dict[str, str]]: """Convert Gradio's chat history into Spark chat-template messages.""" messages: list[dict[str, str]] = [] for item in history or []: if isinstance(item, dict): content = item.get("content", "") if isinstance(content, list): content = "".join( block.get("text", "") for block in content if isinstance(block, dict) ) messages.append({"role": item.get("role", "user"), "content": str(content)}) else: user_text, assistant_text = item messages.extend( [ {"role": "user", "content": user_text}, {"role": "assistant", "content": assistant_text}, ] ) return messages def _estimate_duration( message=None, history=None, system_prompt=None, enable_thinking=None, max_new_tokens=2048, temperature=None, top_p=None, *args, **kwargs, ): """ZeroGPU duration callable: scale the reservation with the token budget.""" try: budget = int(max_new_tokens) except (TypeError, ValueError): budget = 1024 return min(240, 40 + budget // 8) @spaces.GPU(duration=_estimate_duration) def respond( message: str, history: list[object] | None, system_prompt: str, task: str, enable_thinking: bool, max_new_tokens: int, temperature: float, top_p: float, ): """Stream a coding answer from Spark-X2.5-4B, revealing its reasoning trace.""" history = history or [] if not message or not message.strip(): yield history, "" return task_hint = TASK_PRESETS.get(task, "") system_parts = [system_prompt.strip()] if system_prompt and system_prompt.strip() else [] if task_hint: system_parts.append(task_hint) system_text = "\n\n".join(system_parts) history_messages = history_to_messages(history) messages = [] if system_text: messages.append({"role": "system", "content": system_text}) messages.extend(history_messages) messages.append({"role": "user", "content": message.strip()}) prompt = tokenizer.apply_chat_template( messages, tokenize=False, add_generation_prompt=True, enable_thinking=bool(enable_thinking), ) inputs = tokenizer( prompt, return_tensors="pt", truncation=True, max_length=MAX_CONTEXT_TOKENS, ).to(model.device) streamer = TextIteratorStreamer( tokenizer, skip_prompt=True, skip_special_tokens=True ) gen_kwargs = dict( **inputs, streamer=streamer, max_new_tokens=int(max_new_tokens), do_sample=temperature > 0, temperature=max(float(temperature), 1e-5), top_p=float(top_p), # Spark's generation config uses -1 for disabled top-k; Transformers # expects 0 for the same behavior. top_k=0, use_cache=True, pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id, eos_token_id=tokenizer.eos_token_id, ) thread = threading.Thread(target=model.generate, kwargs=gen_kwargs, daemon=True) thread.start() base = history_messages + [{"role": "user", "content": message.strip()}] completion = "" last_emit = 0.0 for new_text in streamer: completion += new_text reasoning, answer = split_reasoning(completion, bool(enable_thinking)) display = answer or ("_Thinking…_" if reasoning else "") now = time.perf_counter() if now - last_emit >= 0.1: last_emit = now yield base + [{"role": "assistant", "content": display}], ( reasoning if enable_thinking else "" ) thread.join() reasoning, answer = split_reasoning(completion, bool(enable_thinking)) if answer: final_text = answer elif reasoning: final_text = ( reasoning + "\n\n> ⚠️ The model stopped before finishing its reasoning — " "increase **Max new tokens** and try again." ) else: final_text = "" yield base + [{"role": "assistant", "content": final_text}], ( reasoning if enable_thinking else "" ) with gr.Blocks(title="Spark-X2.5-4B Code Assistant") as demo: gr.Markdown( "# 💻 Spark-X2.5-4B Code Assistant\n" "A streaming coding assistant built on " "[XHToken/Spark-X2.5-4B](https://huggingface.co/XHToken/Spark-X2.5-4B) — " "a 4B model with strong coding, reasoning, and agentic ability. " "Write, explain, debug, refactor, test, and translate code." ) chatbot = gr.Chatbot( height=520, label="Conversation", render_markdown=True, ) with gr.Row(): message = gr.Textbox( label="Your request", placeholder="e.g. Write a Python LRU cache with O(1) get/put and unit tests…", lines=3, scale=8, ) send = gr.Button("Send", variant="primary", scale=1) with gr.Accordion("Task & generation settings", open=False): with gr.Row(): task = gr.Dropdown( choices=list(TASK_PRESETS.keys()), value="General coding", label="Task preset", ) enable_thinking = gr.Checkbox(label="Show reasoning trace", value=True) system_prompt = gr.Textbox( label="System prompt", value=BASE_SYSTEM_PROMPT, lines=3 ) with gr.Row(): max_new_tokens = gr.Slider( MIN_NEW_TOKENS, MAX_NEW_TOKENS, value=2048, step=256, label="Max new tokens" ) temperature = gr.Slider(0, 1.5, value=1.0, step=0.05, label="Temperature") top_p = gr.Slider(0.1, 1.0, value=0.95, step=0.05, label="Top-p") reasoning = gr.Textbox(label="Reasoning trace", lines=8, visible=True) clear = gr.ClearButton([message, chatbot, reasoning], value="Clear conversation") inputs = [ message, chatbot, system_prompt, task, enable_thinking, max_new_tokens, temperature, top_p, ] outputs = [chatbot, reasoning] send.click(respond, inputs=inputs, outputs=outputs).then(lambda: "", outputs=message) message.submit(respond, inputs=inputs, outputs=outputs).then( lambda: "", outputs=message ) gr.Examples( examples=[ ["Write a Python function that merges two sorted lists in O(n+m) and add pytest tests covering empty inputs and duplicates."], ["Explain what this does and its time complexity:\n\nfrom functools import lru_cache\n@lru_cache(maxsize=None)\ndef fib(n):\n return n if n < 2 else fib(n-1) + fib(n-2)"], ["This async Python snippet sometimes hangs. Find the bug and fix it:\n\nasync def main():\n results = [await fetch(u) for u in urls]\n return results"], ["Refactor this JavaScript into a clean, tested ES module:\n\nfunction p(a){var r=[];for(var i=0;i