InfiniteDev commited on
Commit
d69be04
·
verified ·
1 Parent(s): a75092d

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. README.md +42 -8
  2. __pycache__/app.cpython-314.pyc +0 -0
  3. app.py +266 -0
  4. requirements.txt +3 -0
README.md CHANGED
@@ -1,13 +1,47 @@
1
  ---
2
- title: Spark X2.5 4b Code
3
- emoji: 🚀
4
- colorFrom: green
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 6.26.0
8
- python_version: '3.12'
9
  app_file: app.py
10
- pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Spark-X2.5-4B Code Assistant
3
+ emoji: 💻
4
+ colorFrom: indigo
5
+ colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 6.15.1
 
8
  app_file: app.py
9
+ python_version: "3.12"
10
+ startup_duration_timeout: 1h
11
+ short_description: Streaming coding assistant powered by Spark-X2.5-4B.
12
  ---
13
 
14
+ # 💻 Spark-X2.5-4B Code Assistant
15
+
16
+ A streaming code-assistant demo for
17
+ [XHToken/Spark-X2.5-4B](https://huggingface.co/XHToken/Spark-X2.5-4B), a compact
18
+ general-purpose language model with strong coding, reasoning, agentic, and
19
+ multilingual ability.
20
+
21
+ The app runs the model directly in a Hugging Face **ZeroGPU** Space. It is tuned
22
+ for programming tasks — write, explain, debug, refactor, test, and translate code
23
+ — with task presets, a configurable system prompt, sampling controls, and an
24
+ optional collapsible `<think>` reasoning trace.
25
+
26
+ ## Features
27
+
28
+ - **Streaming responses** via `TextIteratorStreamer`, so code appears as it is generated.
29
+ - **Reasoning trace** toggle — inspect Spark's `enable_thinking` chain-of-thought.
30
+ - **Task presets** — general coding, write, explain, debug & fix, refactor, tests, translate.
31
+ - **Markdown code rendering** with copy button, plus a copyable reasoning panel.
32
+ - Exposed as an **MCP server** (`mcp_server=True`), so each handler is callable as a tool.
33
+
34
+ ## Notes
35
+
36
+ - The model is loaded with the official Transformers configuration, chat template,
37
+ and custom `spark2_5` architecture (`trust_remote_code=True`) from its repository.
38
+ - Inference uses `bfloat16` on the temporary ZeroGPU allocation, with the eager
39
+ attention implementation required by the model's custom attention path.
40
+ - Context is capped at 32,768 tokens here to keep interactive requests practical;
41
+ the model itself supports a much larger native context window.
42
+ - Defaults follow the model card: `temperature=1.0`, `top_p=0.95`, thinking on.
43
+
44
+ ## License
45
+
46
+ This demo code is Apache-2.0. The underlying model is released under its
47
+ [Apache-2.0 license](https://huggingface.co/XHToken/Spark-X2.5-4B).
__pycache__/app.cpython-314.pyc ADDED
Binary file (13.3 kB). View file
 
app.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """A ZeroGPU Gradio code-assistant demo for XHToken/Spark-X2.5-4B.
2
+
3
+ Spark-X2.5-4B is a compact general-purpose model with strong coding and
4
+ reasoning ability. This Space wraps it in a streaming chat UI tuned for
5
+ programming tasks: write, explain, debug, refactor, test, and translate code,
6
+ with an optional collapsible reasoning (<think>) trace.
7
+ """
8
+
9
+ import os
10
+ import threading
11
+ import time
12
+
13
+ import spaces # noqa: F401 (must be imported before torch / transformers)
14
+
15
+ import gradio as gr
16
+ import torch
17
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
18
+
19
+
20
+ MODEL_ID = "XHToken/Spark-X2.5-4B"
21
+ MAX_CONTEXT_TOKENS = 32_768
22
+ MIN_NEW_TOKENS = 128
23
+ MAX_NEW_TOKENS = 3072
24
+
25
+ # The model ships a custom `spark2_5` architecture via `trust_remote_code`.
26
+ # Its custom attention path currently requires the eager implementation.
27
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
28
+ model = AutoModelForCausalLM.from_pretrained(
29
+ MODEL_ID,
30
+ torch_dtype=torch.bfloat16,
31
+ trust_remote_code=True,
32
+ attn_implementation="eager",
33
+ ).to("cuda").eval()
34
+
35
+ BASE_SYSTEM_PROMPT = (
36
+ "You are Spark Code, an expert programming assistant. "
37
+ "Give correct, runnable code in fenced Markdown blocks with the language tag. "
38
+ "Prefer clear, idiomatic, production-quality solutions. "
39
+ "Explain briefly, call out edge cases, and state assumptions when the request is ambiguous."
40
+ )
41
+
42
+ TASK_PRESETS = {
43
+ "General coding": "",
44
+ "Write new code": "Focus on writing a complete, self-contained implementation.",
45
+ "Explain code": "Explain what the code does step by step, then summarize the key ideas.",
46
+ "Debug & fix": "Identify the bug(s), explain the root cause, and provide a corrected version.",
47
+ "Refactor": "Improve readability, structure, and performance without changing behavior.",
48
+ "Write tests": "Produce thorough unit tests, including edge cases and failure modes.",
49
+ "Translate language": "Port the code to the language the user requests, preserving behavior and idioms.",
50
+ }
51
+
52
+
53
+ def split_reasoning(text: str) -> tuple[str, str]:
54
+ """Split a partial/complete generation into (reasoning, answer).
55
+
56
+ Handles three states: still thinking (no closing tag yet), finished
57
+ thinking, and thinking disabled (template emits `</think>` immediately).
58
+ """
59
+ if "<think>" in text and "</think>" not in text:
60
+ return text.split("<think>", 1)[1].strip(), ""
61
+ if "</think>" in text:
62
+ reasoning, answer = text.split("</think>", 1)
63
+ return reasoning.replace("<think>", "").strip(), answer.strip()
64
+ return "", text.replace("<think>", "").strip()
65
+
66
+
67
+ def history_to_messages(history: list[object] | None) -> list[dict[str, str]]:
68
+ """Convert Gradio's chat history into Spark chat-template messages."""
69
+ messages: list[dict[str, str]] = []
70
+ for item in history or []:
71
+ if isinstance(item, dict):
72
+ content = item.get("content", "")
73
+ if isinstance(content, list):
74
+ content = "".join(
75
+ block.get("text", "") for block in content if isinstance(block, dict)
76
+ )
77
+ messages.append({"role": item.get("role", "user"), "content": str(content)})
78
+ else:
79
+ user_text, assistant_text = item
80
+ messages.extend(
81
+ [
82
+ {"role": "user", "content": user_text},
83
+ {"role": "assistant", "content": assistant_text},
84
+ ]
85
+ )
86
+ return messages
87
+
88
+
89
+ def _estimate_duration(
90
+ message=None,
91
+ history=None,
92
+ system_prompt=None,
93
+ enable_thinking=None,
94
+ max_new_tokens=1024,
95
+ temperature=None,
96
+ top_p=None,
97
+ *args,
98
+ **kwargs,
99
+ ):
100
+ """ZeroGPU duration callable: scale the reservation with the token budget."""
101
+ try:
102
+ budget = int(max_new_tokens)
103
+ except (TypeError, ValueError):
104
+ budget = 1024
105
+ return min(240, 40 + budget // 8)
106
+
107
+
108
+ @spaces.GPU(duration=_estimate_duration)
109
+ def respond(
110
+ message: str,
111
+ history: list[object] | None,
112
+ system_prompt: str,
113
+ task: str,
114
+ enable_thinking: bool,
115
+ max_new_tokens: int,
116
+ temperature: float,
117
+ top_p: float,
118
+ ):
119
+ """Stream a coding answer from Spark-X2.5-4B, revealing its reasoning trace."""
120
+ history = history or []
121
+ if not message or not message.strip():
122
+ yield history, ""
123
+ return
124
+
125
+ task_hint = TASK_PRESETS.get(task, "")
126
+ system_parts = [system_prompt.strip()] if system_prompt and system_prompt.strip() else []
127
+ if task_hint:
128
+ system_parts.append(task_hint)
129
+ system_text = "\n\n".join(system_parts)
130
+
131
+ history_messages = history_to_messages(history)
132
+ messages = []
133
+ if system_text:
134
+ messages.append({"role": "system", "content": system_text})
135
+ messages.extend(history_messages)
136
+ messages.append({"role": "user", "content": message.strip()})
137
+
138
+ prompt = tokenizer.apply_chat_template(
139
+ messages,
140
+ tokenize=False,
141
+ add_generation_prompt=True,
142
+ enable_thinking=bool(enable_thinking),
143
+ )
144
+ inputs = tokenizer(
145
+ prompt,
146
+ return_tensors="pt",
147
+ truncation=True,
148
+ max_length=MAX_CONTEXT_TOKENS,
149
+ ).to(model.device)
150
+
151
+ streamer = TextIteratorStreamer(
152
+ tokenizer, skip_prompt=True, skip_special_tokens=False
153
+ )
154
+ gen_kwargs = dict(
155
+ **inputs,
156
+ streamer=streamer,
157
+ max_new_tokens=int(max_new_tokens),
158
+ do_sample=temperature > 0,
159
+ temperature=max(float(temperature), 1e-5),
160
+ top_p=float(top_p),
161
+ # Spark's generation config uses -1 for disabled top-k; Transformers
162
+ # expects 0 for the same behavior.
163
+ top_k=0,
164
+ use_cache=True,
165
+ pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id,
166
+ eos_token_id=tokenizer.eos_token_id,
167
+ )
168
+ thread = threading.Thread(target=model.generate, kwargs=gen_kwargs, daemon=True)
169
+ thread.start()
170
+
171
+ base = history_messages + [{"role": "user", "content": message.strip()}]
172
+ completion = ""
173
+ last_emit = 0.0
174
+ for new_text in streamer:
175
+ completion += new_text
176
+ reasoning, answer = split_reasoning(completion)
177
+ display = answer if answer else ("_Thinking…_" if reasoning else "")
178
+ now = time.perf_counter()
179
+ if now - last_emit >= 0.1:
180
+ last_emit = now
181
+ yield base + [{"role": "assistant", "content": display}], (
182
+ reasoning if enable_thinking else ""
183
+ )
184
+ thread.join()
185
+
186
+ reasoning, answer = split_reasoning(completion)
187
+ final = base + [{"role": "assistant", "content": answer or reasoning}]
188
+ yield final, reasoning if enable_thinking else ""
189
+
190
+
191
+ with gr.Blocks(title="Spark-X2.5-4B Code Assistant", theme=gr.themes.Soft()) as demo:
192
+ gr.Markdown(
193
+ "# 💻 Spark-X2.5-4B Code Assistant\n"
194
+ "A streaming coding assistant built on "
195
+ "[XHToken/Spark-X2.5-4B](https://huggingface.co/XHToken/Spark-X2.5-4B) — "
196
+ "a 4B model with strong coding, reasoning, and agentic ability. "
197
+ "Write, explain, debug, refactor, test, and translate code."
198
+ )
199
+
200
+ chatbot = gr.Chatbot(
201
+ height=520,
202
+ label="Conversation",
203
+ show_copy_button=True,
204
+ render_markdown=True,
205
+ )
206
+ with gr.Row():
207
+ message = gr.Textbox(
208
+ label="Your request",
209
+ placeholder="e.g. Write a Python LRU cache with O(1) get/put and unit tests…",
210
+ lines=3,
211
+ scale=8,
212
+ )
213
+ send = gr.Button("Send", variant="primary", scale=1)
214
+
215
+ with gr.Accordion("Task & generation settings", open=False):
216
+ with gr.Row():
217
+ task = gr.Dropdown(
218
+ choices=list(TASK_PRESETS.keys()),
219
+ value="General coding",
220
+ label="Task preset",
221
+ )
222
+ enable_thinking = gr.Checkbox(label="Show reasoning trace", value=True)
223
+ system_prompt = gr.Textbox(
224
+ label="System prompt", value=BASE_SYSTEM_PROMPT, lines=3
225
+ )
226
+ with gr.Row():
227
+ max_new_tokens = gr.Slider(
228
+ MIN_NEW_TOKENS, MAX_NEW_TOKENS, value=1024, step=128, label="Max new tokens"
229
+ )
230
+ temperature = gr.Slider(0, 1.5, value=1.0, step=0.05, label="Temperature")
231
+ top_p = gr.Slider(0.1, 1.0, value=0.95, step=0.05, label="Top-p")
232
+ reasoning = gr.Textbox(
233
+ label="Reasoning trace", lines=8, visible=True, show_copy_button=True
234
+ )
235
+ clear = gr.ClearButton([message, chatbot, reasoning], value="Clear conversation")
236
+
237
+ inputs = [
238
+ message,
239
+ chatbot,
240
+ system_prompt,
241
+ task,
242
+ enable_thinking,
243
+ max_new_tokens,
244
+ temperature,
245
+ top_p,
246
+ ]
247
+ outputs = [chatbot, reasoning]
248
+ send.click(respond, inputs=inputs, outputs=outputs).then(lambda: "", outputs=message)
249
+ message.submit(respond, inputs=inputs, outputs=outputs).then(
250
+ lambda: "", outputs=message
251
+ )
252
+
253
+ gr.Examples(
254
+ examples=[
255
+ ["Write a Python function that merges two sorted lists in O(n+m) and add pytest tests covering empty inputs and duplicates."],
256
+ ["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)"],
257
+ ["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"],
258
+ ["Refactor this JavaScript into a clean, tested ES module:\n\nfunction p(a){var r=[];for(var i=0;i<a.length;i++){if(a[i]%2==0)r.push(a[i]*a[i]);}return r;}"],
259
+ ],
260
+ inputs=[message],
261
+ label="Try a coding example",
262
+ )
263
+
264
+
265
+ if __name__ == "__main__":
266
+ demo.queue(default_concurrency_limit=1).launch(mcp_server=True)
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ transformers==4.57.1
2
+ accelerate>=1.5.0
3
+ sentencepiece>=0.2.0