Spaces:
Running on Zero
Running on Zero
File size: 18,989 Bytes
0796f2b 76fe7d1 0796f2b 008f64a 0796f2b 6ddf598 0796f2b 6ddf598 0796f2b 6ddf598 0796f2b 5b5fd5a 0796f2b 008f64a 0796f2b 5b5fd5a 0796f2b 008f64a 0796f2b | 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 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 | import os
import time
# Check if spaces library is available (for Hugging Face Spaces ZeroGPU)
# Importing spaces BEFORE torch is a strict ZeroGPU requirement
try:
import spaces
has_spaces = True
print("Hugging Face Spaces library loaded successfully.")
except ImportError:
has_spaces = False
print("Hugging Face Spaces library not found. Running in standard environment.")
import torch
from typing import Generator
from fastapi.responses import HTMLResponse
from gradio import Server
# Initialize the Gradio Server (extends FastAPI)
app = Server()
# Define HTML path
HTML_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "index.html")
# Set device
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"System detected device: {device}")
# Determine if model should be loaded
force_load = os.environ.get("FORCE_MODEL_LOAD", "false").lower() == "true"
is_fallback = True
model = None
tokenizer = None
MODEL_ID = "KyleHessling1/Qwopus3.6-27B-Fusion-GGUF:Q4_K_M"
if device == "cuda" or force_load:
try:
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer, GenerationConfig
from threading import Thread
print(f"Attempting to load model '{MODEL_ID}'...")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
if torch.backends.mps.is_available() and force_load:
print("Loading model on MPS (Apple Silicon GPU) with float16...")
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.float16,
low_cpu_mem_usage=True
).to("mps")
else:
print("Loading model in bfloat16...")
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
torch_dtype=torch.bfloat16,
device_map="auto",
low_cpu_mem_usage=True
)
is_fallback = False
print("Model loaded successfully!")
except Exception as e:
print(f"Error loading model: {e}")
print("Falling back to simulation mode.")
is_fallback = True
else:
print("No CUDA GPU detected and FORCE_MODEL_LOAD is false. Falling back to simulation mode.")
is_fallback = True
def get_mock_response(message: str) -> tuple[str, str]:
"""Generates detailed mock thinking and answers for local development testing."""
message_lower = message.lower()
if "palindrom" in message_lower or "leetcode" in message_lower or "code" in message_lower or "python" in message_lower:
thought = (
"1. We need to solve the Longest Palindromic Substring problem.\n"
"2. Let's analyze the constraints and possible approaches.\n"
" - Approach 1: Brute Force. Check all O(N^2) substrings. Checking takes O(N), total O(N^3). Too slow.\n"
" - Approach 2: Dynamic Programming. Let DP[i][j] be true if substring s[i..j] is a palindrome.\n"
" - DP[i][j] = (s[i] == s[j]) && (j - i < 3 || DP[i+1][j-1])\n"
" - Time complexity: O(N^2), Space complexity: O(N^2).\n"
" - Approach 3: Expand Around Center. For each index, expand outward for both odd and even length palindromes.\n"
" - Time complexity: O(N^2), Space complexity: O(1). This is optimal in terms of space.\n"
" - Approach 4: Manacher's Algorithm. Dynamic programming combined with centers expansion. O(N) time and space.\n"
"3. Let's implement the Expand Around Center approach as it is highly readable and O(1) space.\n"
"4. Verification: check boundary cases like single character, empty string, string with all identical characters, etc.\n"
"5. Formulate final response with explanation, code, and complexity analysis."
)
body = (
"Here is the optimal Python implementation of the **Longest Palindromic Substring** problem using the **Expand Around Center** approach (\\(O(N^2)\\) time, \\(O(1)\\) space).\n\n"
"### Expand Around Center (Python)\n\n"
"```python\n"
"class Solution:\n"
" def longestPalindrome(self, s: str) -> str:\n"
" if not s or len(s) < 1:\n"
" return \"\"\n"
" \n"
" start, end = 0, 0\n"
" \n"
" def expand_around_center(left: int, right: int) -> int:\n"
" while left >= 0 and right < len(s) and s[left] == s[right]:\n"
" left -= 1\n"
" right += 1\n"
" # Return the length of the palindrome found\n"
" return right - left - 1\n"
" \n"
" for i in range(len(s)):\n"
" # Odd-length palindromes (single character center)\n"
" len1 = expand_around_center(i, i)\n"
" # Even-length palindromes (two character center)\n"
" len2 = expand_around_center(i, i + 1)\n"
" \n"
" max_len = max(len1, len2)\n"
" if max_len > end - start:\n"
" # Adjust start and end indices based on current center\n"
" start = i - (max_len - 1) // 2\n"
" end = i + max_len // 2\n"
" \n"
" return s[start:end + 1]\n"
"```\n\n"
"### Complexity Analysis\n"
"- **Time Complexity:** \\(O(N^2)\\). We expand around \\(2N - 1\\) centers. For each center, expansion can take up to \\(O(N)\\) steps.\n"
"- **Space Complexity:** \\(O(1)\\). Only constant extra space is used."
)
elif "deck" in message_lower or "probab" in message_lower or "card" in message_lower or "math" in message_lower or "solve" in message_lower or "equation" in message_lower:
thought = (
"1. The user is asking a probability/math question: 'If a card is drawn from a standard deck, what is the probability that it is a spade or a face card?'\n"
"2. Let's define the sample space and events:\n"
" - Total cards in a standard deck: N(S) = 52.\n"
" - Event A: Drawing a spade. There are 13 spades in a deck. So N(A) = 13.\n"
" - Event B: Drawing a face card (Jack, Queen, King). There are 3 face cards per suit, and 4 suits. So N(B) = 3 * 4 = 12.\n"
" - We need to find the probability of Spade OR Face Card: P(A or B).\n"
"3. Let's recall the addition rule of probability:\n"
" - P(A or B) = P(A) + P(B) - P(A and B)\n"
"4. What is Event (A and B)? It is drawing a card that is both a spade AND a face card.\n"
" - These are the Jack of Spades, Queen of Spades, and King of Spades. N(A and B) = 3.\n"
"5. Let's plug the numbers in:\n"
" - P(A) = 13/52\n"
" - P(B) = 12/52\n"
" - P(A and B) = 3/52\n"
" - P(A or B) = 13/52 + 12/52 - 3/52 = (13 + 12 - 3)/52 = 22/52.\n"
"6. Simplify the fraction:\n"
" - 22/52 = 11/26.\n"
" - Decimal value: ~0.4231 (or 42.3%).\n"
"7. Structure the explanation clearly, showing the formulas, steps, and intermediate values using LaTeX mathematical notations."
)
body = (
"To find the probability that a randomly drawn card from a standard deck is either a **spade** or a **face card**, we can use the addition rule of probability.\n\n"
"### 1. Identify the Sample Spaces\n"
"- **Total cards in a deck:** \\(N(S) = 52\\)\n"
"- **Spades in a deck (Event \\(A\\)):** There are 13 spades. Hence, \\(N(A) = 13\\).\n"
"- **Face cards in a deck (Event \\(B\\)):** There are 3 face cards (Jack, Queen, King) per suit, across 4 suits. Hence, \\(N(B) = 3 \\times 4 = 12\\).\n\n"
"### 2. Find the Intersection (Spade Face Cards)\n"
"Some cards belong to both sets: Jack of Spades, Queen of Spades, and King of Spades. \n"
"Let this intersection be Event \\(A \\cap B\\):\n"
"\\[N(A \\cap B) = 3\\]\n\n"
"### 3. Apply the Addition Rule\n"
"The probability of the union of two events is given by:\n"
"\\[P(A \\cup B) = P(A) + P(B) - P(A \\cap B)\\]\n\n"
"Substitute the values:\n"
"\\[P(A \\cup B) = \\frac{13}{52} + \\frac{12}{52} - \\frac{3}{52}\\]\n"
"\\[P(A \\cup B) = \\frac{13 + 12 - 3}{52} = \\frac{22}{52}\\]\n\n"
"### 4. Simplify the Result\n"
"Reducing \\(\\frac{22}{52}\\) by dividing the numerator and denominator by 2:\n"
"\\[P(A \\cup B) = \\frac{11}{26} \\approx 0.4231 \\text{ (or } 42.31\\%\\text{)}\\]\n\n"
"**Conclusion:** The probability of drawing a spade or a face card is **\\(\\frac{11}{26}\\)**, which is approximately **42.3%**."
)
elif "box" in message_lower or "fruit" in message_lower or "label" in message_lower or "logic" in message_lower:
thought = (
"1. Three boxes: Box A (labeled Apples), Box B (labeled Oranges), Box C (labeled Mixed).\n"
"2. Fact: *Every single label is incorrect*.\n"
" - Box labeled Apples has Oranges or Mixed.\n"
" - Box labeled Oranges has Apples or Mixed.\n"
" - Box labeled Mixed has Apples or Oranges.\n"
"3. Let's draw a fruit from the \"Mixed\" box. Why?\n"
" - Since the label \"Mixed\" is wrong, it must contain either 100% Apples or 100% Oranges.\n"
" - If we draw a fruit and it's an Apple, then the \"Mixed\" box is 100% Apples.\n"
" - Now we have the remaining boxes labeled \"Apples\" and \"Oranges\".\n"
" - The remaining contents are \"Oranges\" and \"Mixed\".\n"
" - The box labeled \"Oranges\" cannot contain Oranges (as all labels are wrong). So it must contain \"Mixed\".\n"
" - Consequently, the box labeled \"Apples\" must contain \"Oranges\".\n"
"4. What if we drew an Orange? By symmetry:\n"
" - The \"Mixed\" box is 100% Oranges.\n"
" - The box labeled \"Apples\" cannot contain Apples, so it must contain \"Mixed\".\n"
" - The box labeled \"Oranges\" must contain \"Apples\".\n"
"5. The proof is sound and deterministic. Explain case by case with a markdown table."
)
body = (
"This is a classic logical puzzle. The key to solving it is to **draw a fruit from the box labeled \"Mixed\"**.\n\n"
"Here is the step-by-step reasoning:\n\n"
"### 1. Understand the Rules\n"
"- There are three boxes containing: **Apples**, **Oranges**, or **Mixed (both)**.\n"
"- **Crucial Fact:** *All* labels are currently incorrect. This means:\n"
" - The box labeled **\"Apples\"** does *not* contain only apples (it contains either Oranges or Mixed).\n"
" - The box labeled **\"Oranges\"** does *not* contain only oranges (it contains either Apples or Mixed).\n"
" - The box labeled **\"Mixed\"** does *not* contain both (it contains either 100% Apples or 100% Oranges).\n\n"
"### 2. Choose the \"Mixed\" Box First\n"
"Since you know the box labeled \"Mixed\" is *definitely* not mixed, it must be either **100% Apples** or **100% Oranges**.\n\n"
"- Draw one fruit from the box labeled **\"Mixed\"**.\n\n"
"#### Case A: You draw an Apple\n"
"1. Since this box cannot be mixed, and it contains an apple, it **must contain only Apples**. (Label this box **Apples**).\n"
"2. You have two boxes left, labeled **\"Apples\"** and **\"Oranges\"**, and two contents left to assign: **Oranges** and **Mixed**.\n"
"3. Look at the box labeled **\"Oranges\"**. Because all labels are wrong, this box *cannot* contain Oranges. Therefore, it **must contain Mixed**.\n"
"4. This leaves the box labeled **\"Apples\"** to **contain only Oranges**.\n\n"
"#### Case B: You draw an Orange\n"
"1. Since this box cannot be mixed, and it contains an orange, it **must contain only Oranges**. (Label this box **Oranges**).\n"
"2. You have two boxes left, labeled **\"Apples\"** and **\"Oranges\"**, and two contents left to assign: **Apples** and **Mixed**.\n"
"3. Look at the box labeled **\"Apples\"**. Because all labels are wrong, this box *cannot* contain Apples. Therefore, it **must contain Mixed**.\n"
"4. This leaves the box labeled **\"Oranges\"** to **contain only Apples**.\n\n"
"### Summary Table\n\n"
"| Label on Box | Drawn Fruit | Actual Contents | Box 2 Actual | Box 3 Actual |\n"
"| :--- | :--- | :--- | :--- | :--- |\n"
"| **\"Mixed\"** | Apple 🍎 | **Apples** | **\"Oranges\"** label $\\rightarrow$ **Mixed** | **\"Apples\"** label $\\rightarrow$ **Oranges** |\n"
"| **\"Mixed\"** | Orange 🍊 | **Oranges** | **\"Apples\"** label $\\rightarrow$ **Mixed** | **\"Oranges\"** label $\\rightarrow$ **Apples** |\n\n"
"By drawing just **one fruit from the \"Mixed\" box**, you can confidently relabel all three boxes!"
)
else:
thought = (
"1. The user prompt is general: '" + message + "'.\n"
"2. Let's formulate a structured response that explains who I am and how I can help.\n"
"3. Demonstrate reasoning by showing my system stats, architecture (Qwen2.5-Coder backbone), and optimal use cases (coding, math, logic).\n"
"4. Conclude with a helpful, inviting sign-off."
)
body = (
"Hello! I am Qwopus, a small language model optimized for deep, verifiable reasoning in math, coding, and STEM.\n\n"
"To answer your request, here is a summary of how I operate:\n\n"
"### 1. Model Capabilities\n"
"- **Architecture:** Finetuned on top of Qwen3.5-27B using a curriculum-based SFT pipeline and Reinforcement Learning (MGPO).\n"
"- **Reasoning Process:** I generate intermediate thoughts inside `<think>...</think>` tags to verify my hypotheses and correct mistakes before showing the final result.\n\n"
"### 2. Suggested Prompts\n"
"- **Math:** Ask me complex algebra, probability, or number theory questions.\n"
"- **Coding:** Give me algorithmic coding challenges, code optimization tasks, or debugging requests.\n"
"- **Logic:** Present me with riddles, brain teasers, or rule-based scheduling problems.\n\n"
"Feel free to write a mathematical problem or code request to see me think through it step-by-step!"
)
return thought, body
def simulate_inference(message: str):
"""Simulates token-by-token streaming of the reasoning and body response."""
thought, body = get_mock_response(message)
full_text = f"<think>\n{thought}\n</think>\n{body}"
accumulated = ""
words = []
current_word = ""
for char in full_text:
if char in (" ", "\n"):
if current_word:
words.append(current_word)
current_word = ""
words.append(char)
else:
current_word += char
if current_word:
words.append(current_word)
for word in words:
accumulated += word
yield accumulated
# Simulating slightly slower output for thinking steps to make it feel deliberate
if "<think>" in accumulated and "</think>" not in accumulated:
time.sleep(0.012 if word not in ("\n", " ") else 0.004)
else:
time.sleep(0.006 if word not in ("\n", " ") else 0.002)
def infer_real(message: str, system_prompt: str, temperature: float, top_p: float, max_tokens: int):
"""Executes actual model generation using the transformers library."""
from transformers import TextIteratorStreamer, GenerationConfig
from threading import Thread
messages = []
if system_prompt:
messages.append({"role": "system", "content": system_prompt})
messages.append({"role": "user", "content": message})
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
# Configure generation
gen_kwargs = {
"max_new_tokens": max_tokens,
"do_sample": True if temperature > 0.0 else False,
"top_k": None,
}
if temperature > 0.0:
gen_kwargs["temperature"] = temperature
gen_kwargs["top_p"] = top_p
generation_config = GenerationConfig(**gen_kwargs)
generation_kwargs = dict(
**model_inputs,
streamer=streamer,
generation_config=generation_config
)
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
accumulated = ""
for new_text in streamer:
accumulated += new_text
yield accumulated
# Define API Endpoint
# Using the wrapper helper to handle spaces.GPU decorator safely
if has_spaces:
@app.api()
@spaces.GPU
def predict(
message: str,
system_prompt: str = "You were made by Arush",
temperature: float = 1.0,
top_p: float = 0.95,
max_tokens: int = 4096
) -> Generator[str, None, None]:
if is_fallback:
yield from simulate_inference(message)
else:
yield from infer_real(message, system_prompt, temperature, top_p, max_tokens)
else:
@app.api()
def predict(
message: str,
system_prompt: str = "You were made by Arush",
temperature: float = 1.0,
top_p: float = 0.95,
max_tokens: int = 4096
) -> Generator[str, None, None]:
if is_fallback:
yield from simulate_inference(message)
else:
yield from infer_real(message, system_prompt, temperature, top_p, max_tokens)
# Standard FastAPI Route to serve the frontend html
@app.get("/", response_class=HTMLResponse)
def homepage():
try:
with open(HTML_PATH, "r", encoding="utf-8") as f:
return HTMLResponse(content=f.read())
except FileNotFoundError:
return HTMLResponse(content="<h1>index.html not found! Please create the frontend page.</h1>", status_code=404)
if __name__ == "__main__":
app.launch(show_error=True)
|