Files changed (1) hide show
  1. app.py +124 -34
app.py CHANGED
@@ -2,25 +2,32 @@ import os
2
  import sys
3
  import time
4
  import re
 
5
  import multiprocessing
 
 
 
 
 
 
 
6
 
7
  # ---------------------------------------------------------------------------
8
- # ⚡ 1. ENVIRONMENT TUNING
9
  # ---------------------------------------------------------------------------
10
- # Auto-detect physical CPU cores to avoid hyperthreading contention
 
11
  try:
12
  import psutil
13
  PHYSICAL_CORES = psutil.cpu_count(logical=False) or max(1, multiprocessing.cpu_count() // 2)
14
  except Exception:
15
  PHYSICAL_CORES = max(1, multiprocessing.cpu_count() // 2)
16
 
17
- # Lock thread pools strictly to physical CPU cores
18
  os.environ["OMP_NUM_THREADS"] = str(PHYSICAL_CORES)
19
  os.environ["MKL_NUM_THREADS"] = str(PHYSICAL_CORES)
20
  os.environ["OPENBLAS_NUM_THREADS"] = str(PHYSICAL_CORES)
21
  os.environ["OMP_PROC_BIND"] = "TRUE"
22
 
23
- # Raise OS scheduler priority (if permitted by the environment)
24
  try:
25
  p = psutil.Process()
26
  if sys.platform == "win32":
@@ -30,54 +37,142 @@ try:
30
  except Exception:
31
  pass
32
 
33
- # Import heavy libraries AFTER environment variables are set
34
  import gradio as gr
35
  import llama_cpp
36
  from llama_cpp import Llama
37
  from huggingface_hub import hf_hub_download
38
 
39
  # ---------------------------------------------------------------------------
40
- # ⚡ 2. MODEL DOWNLOAD & LLAMA-CPP INITIALIZATION
41
  # ---------------------------------------------------------------------------
42
  NUMA_STRATEGY = getattr(llama_cpp, "GGML_NUMA_STRATEGY_DISTRIBUTE", False)
43
 
44
- print("Downloading model...")
45
  model_path = hf_hub_download(
46
  repo_id="unsloth/Qwen3.6-35B-A3B-GGUF",
47
  filename="Qwen3.6-35B-A3B-UD-IQ3_XXS.gguf",
48
  )
49
  print("Model downloaded successfully!")
50
 
51
- # Load GGUF model optimized for CPU execution
52
  llm = Llama(
53
  model_path=model_path,
54
  n_gpu_layers=0, # 0 for CPU execution
55
  n_ctx=4096,
56
- chat_format="chatml", # ChatML format prevents KV cache issues
57
 
58
  # Threading optimizations
59
  n_threads=PHYSICAL_CORES,
60
  n_threads_batch=PHYSICAL_CORES,
61
  numa=NUMA_STRATEGY,
62
 
63
- # Memory & Cache Management
64
- use_mlock=False, # False is safer for Hugging Face Spaces to avoid OOM
65
- use_mmap=True,
66
- flash_attn=False, # Disabled on CPU
67
 
68
- # Batch Memory Alignment
69
  n_batch=256,
70
- n_ubatch=64,
 
 
 
 
71
  )
72
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  # ---------------------------------------------------------------------------
74
- # ⚡ 3. ROBUST HISTORY CLEANER
75
  # ---------------------------------------------------------------------------
76
  def clean_assistant_reply(text):
77
  if not text:
78
  return ""
79
-
80
- # Safely handle Gradio 5 / Multimodal list & dict formats
81
  if isinstance(text, list):
82
  extracted = []
83
  for part in text:
@@ -89,22 +184,14 @@ def clean_assistant_reply(text):
89
  elif not isinstance(text, str):
90
  text = str(text)
91
 
92
- # 1. Strip reasoning blocks (<think>...</think>)
93
  cleaned = re.sub(r'<think>.*?</think>\n*', '', text, flags=re.DOTALL)
94
  cleaned = cleaned.replace("</think>", "")
95
-
96
- # 2. Strip previous timing stats footers
97
  cleaned = re.sub(r'\n\n---\n\*⏱️.*?\*$', '', cleaned)
98
-
99
  return cleaned.strip()
100
 
101
- # ---------------------------------------------------------------------------
102
- # ⚡ 4. GRADIO RESPOND FUNCTION
103
- # ---------------------------------------------------------------------------
104
  def respond(message, history, system_message, max_tokens, temperature, top_p):
105
  messages = [{"role": "system", "content": system_message}]
106
 
107
- # Parse chat history safely
108
  for item in history:
109
  if isinstance(item, dict):
110
  role = item.get("role", "user")
@@ -138,7 +225,6 @@ def respond(message, history, system_message, max_tokens, temperature, top_p):
138
  token_count += 1
139
  yield response
140
 
141
- # Calculate generation speed
142
  end_time = time.time()
143
  elapsed_time = end_time - start_time
144
 
@@ -148,20 +234,24 @@ def respond(message, history, system_message, max_tokens, temperature, top_p):
148
  response += stats_footer
149
  yield response
150
 
151
- # ---------------------------------------------------------------------------
152
- # ⚡ 5. GRADIO INTERFACE LAUNCH
153
- # ---------------------------------------------------------------------------
154
  chatbot = gr.ChatInterface(
155
  fn=respond,
156
  additional_inputs=[
157
- gr.Textbox(value="You are a highly capable coding assistant.", label="System message"),
158
  gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
159
  gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.1, label="Temperature"),
160
  gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p"),
161
  ],
162
- title="Qwopus 3.5 Coder (Ultra-Optimized CPU Edition)",
163
  )
164
 
 
 
 
 
 
 
165
  if __name__ == "__main__":
166
- # Specify the 0.0.0.0 host and port 7860 to ensure HF spaces can access the health check
167
- chatbot.launch(server_name="0.0.0.0", server_port=7860)
 
 
2
  import sys
3
  import time
4
  import re
5
+ import json
6
  import multiprocessing
7
+ from typing import List, Dict, Any, Optional, Union, AsyncGenerator
8
+
9
+ # FastAPI & Pydantic imports
10
+ from fastapi import FastAPI, Request, HTTPException
11
+ from fastapi.responses import StreamingResponse, JSONResponse
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ from pydantic import BaseModel, Field
14
 
15
  # ---------------------------------------------------------------------------
16
+ # ⚡ 1. CPU & THREADING OPTIMIZATIONS FOR MoE
17
  # ---------------------------------------------------------------------------
18
+ # MoE models are heavily bottlenecked by CPU RAM bandwidth during expert routing.
19
+ # Locking threads to physical cores avoids hyperthreading thrashing.
20
  try:
21
  import psutil
22
  PHYSICAL_CORES = psutil.cpu_count(logical=False) or max(1, multiprocessing.cpu_count() // 2)
23
  except Exception:
24
  PHYSICAL_CORES = max(1, multiprocessing.cpu_count() // 2)
25
 
 
26
  os.environ["OMP_NUM_THREADS"] = str(PHYSICAL_CORES)
27
  os.environ["MKL_NUM_THREADS"] = str(PHYSICAL_CORES)
28
  os.environ["OPENBLAS_NUM_THREADS"] = str(PHYSICAL_CORES)
29
  os.environ["OMP_PROC_BIND"] = "TRUE"
30
 
 
31
  try:
32
  p = psutil.Process()
33
  if sys.platform == "win32":
 
37
  except Exception:
38
  pass
39
 
 
40
  import gradio as gr
41
  import llama_cpp
42
  from llama_cpp import Llama
43
  from huggingface_hub import hf_hub_download
44
 
45
  # ---------------------------------------------------------------------------
46
+ # ⚡ 2. MODEL DOWNLOAD & LLAMA-CPP MoE INITIALIZATION
47
  # ---------------------------------------------------------------------------
48
  NUMA_STRATEGY = getattr(llama_cpp, "GGML_NUMA_STRATEGY_DISTRIBUTE", False)
49
 
50
+ print("Downloading MoE model GGUF...")
51
  model_path = hf_hub_download(
52
  repo_id="unsloth/Qwen3.6-35B-A3B-GGUF",
53
  filename="Qwen3.6-35B-A3B-UD-IQ3_XXS.gguf",
54
  )
55
  print("Model downloaded successfully!")
56
 
57
+ # Load GGUF model optimized for CPU MoE execution
58
  llm = Llama(
59
  model_path=model_path,
60
  n_gpu_layers=0, # 0 for CPU execution
61
  n_ctx=4096,
 
62
 
63
  # Threading optimizations
64
  n_threads=PHYSICAL_CORES,
65
  n_threads_batch=PHYSICAL_CORES,
66
  numa=NUMA_STRATEGY,
67
 
68
+ # MoE Cache & RAM Bandwidth Optimizations:
69
+ # 1. Quantized KV Cache (Q4_0) saves RAM and frees CPU cache space for routing
70
+ type_k=getattr(llama_cpp, "GGML_TYPE_Q4_0", 2),
71
+ type_v=getattr(llama_cpp, "GGML_TYPE_Q4_0", 2),
72
 
73
+ # 2. Smaller ubatch prevents L2/L3 cache eviction during active expert evaluation
74
  n_batch=256,
75
+ n_ubatch=32,
76
+
77
+ use_mlock=False,
78
+ use_mmap=True,
79
+ flash_attn=False,
80
  )
81
 
82
+ MODEL_NAME = "Qwen3.6-35B-A3B"
83
+
84
+ # ---------------------------------------------------------------------------
85
+ # ⚡ 3. FASTAPI INITIALIZATION & OPENAI PYDANTIC SCHEMAS
86
+ # ---------------------------------------------------------------------------
87
+ app = FastAPI(title="OpenAI-Compatible CPU MoE Server", version="1.0.0")
88
+
89
+ # Enable CORS for external web frontend requests
90
+ app.add_middleware(
91
+ CORSMiddleware,
92
+ allow_origins=["*"],
93
+ allow_credentials=True,
94
+ allow_methods=["*"],
95
+ allow_headers=["*"],
96
+ )
97
+
98
+ class ChatMessage(BaseModel):
99
+ role: str
100
+ content: Union[str, List[Dict[str, Any]]]
101
+
102
+ class ChatCompletionRequest(BaseModel):
103
+ model: Optional[str] = MODEL_NAME
104
+ messages: List[ChatMessage]
105
+ temperature: Optional[float] = 0.7
106
+ top_p: Optional[float] = 0.95
107
+ max_tokens: Optional[int] = 512
108
+ stream: Optional[bool] = False
109
+
110
+ # ---------------------------------------------------------------------------
111
+ # ⚡ 4. OPENAI-COMPATIBLE REST ENDPOINTS
112
+ # ---------------------------------------------------------------------------
113
+ @app.get("/v1/models")
114
+ async def list_models():
115
+ """OpenAI standard /v1/models endpoint."""
116
+ return {
117
+ "object": "list",
118
+ "data": [
119
+ {
120
+ "id": MODEL_NAME,
121
+ "object": "model",
122
+ "created": int(time.time()),
123
+ "owned_by": "local",
124
+ }
125
+ ],
126
+ }
127
+
128
+ async def openai_stream_generator(messages_dict: list, max_tokens: int, temperature: float, top_p: float):
129
+ """Generates standard SSE streaming tokens for OpenAI client libraries."""
130
+ for chunk in llm.create_chat_completion(
131
+ messages=messages_dict,
132
+ max_tokens=max_tokens,
133
+ temperature=temperature,
134
+ top_p=top_p,
135
+ stream=True,
136
+ ):
137
+ yield f"data: {json.dumps(chunk)}\n\n"
138
+ yield "data: [DONE]\n\n"
139
+
140
+ @app.post("/v1/chat/completions")
141
+ async def chat_completions(req: ChatCompletionRequest):
142
+ """OpenAI standard /v1/chat/completions endpoint."""
143
+ try:
144
+ # Convert pydantic messages to standard list of dicts
145
+ messages_dict = [{"role": m.role, "content": m.content} for m in req.messages]
146
+
147
+ if req.stream:
148
+ return StreamingResponse(
149
+ openai_stream_generator(
150
+ messages_dict=messages_dict,
151
+ max_tokens=req.max_tokens,
152
+ temperature=req.temperature,
153
+ top_p=req.top_p,
154
+ ),
155
+ media_type="text/event-stream",
156
+ )
157
+ else:
158
+ response = llm.create_chat_completion(
159
+ messages=messages_dict,
160
+ max_tokens=req.max_tokens,
161
+ temperature=req.temperature,
162
+ top_p=req.top_p,
163
+ stream=False,
164
+ )
165
+ return JSONResponse(content=response)
166
+
167
+ except Exception as e:
168
+ raise HTTPException(status_code=500, detail=str(e))
169
+
170
  # ---------------------------------------------------------------------------
171
+ # ⚡ 5. GRADIO UI & HISTORY CLEANER
172
  # ---------------------------------------------------------------------------
173
  def clean_assistant_reply(text):
174
  if not text:
175
  return ""
 
 
176
  if isinstance(text, list):
177
  extracted = []
178
  for part in text:
 
184
  elif not isinstance(text, str):
185
  text = str(text)
186
 
 
187
  cleaned = re.sub(r'<think>.*?</think>\n*', '', text, flags=re.DOTALL)
188
  cleaned = cleaned.replace("</think>", "")
 
 
189
  cleaned = re.sub(r'\n\n---\n\*⏱️.*?\*$', '', cleaned)
 
190
  return cleaned.strip()
191
 
 
 
 
192
  def respond(message, history, system_message, max_tokens, temperature, top_p):
193
  messages = [{"role": "system", "content": system_message}]
194
 
 
195
  for item in history:
196
  if isinstance(item, dict):
197
  role = item.get("role", "user")
 
225
  token_count += 1
226
  yield response
227
 
 
228
  end_time = time.time()
229
  elapsed_time = end_time - start_time
230
 
 
234
  response += stats_footer
235
  yield response
236
 
 
 
 
237
  chatbot = gr.ChatInterface(
238
  fn=respond,
239
  additional_inputs=[
240
+ gr.Textbox(value="You are a helpful coding assistant.", label="System message"),
241
  gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
242
  gr.Slider(minimum=0.1, maximum=2.0, value=0.7, step=0.1, label="Temperature"),
243
  gr.Slider(minimum=0.1, maximum=1.0, value=0.95, step=0.05, label="Top-p"),
244
  ],
245
+ title="Qwen 3.6 MoE (FastAPI + Gradio Engine)",
246
  )
247
 
248
+ # Mount Gradio UI inside FastAPI at the root path "/"
249
+ app = gr.mount_gradio_app(app, chatbot, path="/")
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # ⚡ 6. SERVER LAUNCH
253
+ # ---------------------------------------------------------------------------
254
  if __name__ == "__main__":
255
+ import uvicorn
256
+ # Port 7860 is required by Hugging Face Spaces for health checks
257
+ uvicorn.run(app, host="0.0.0.0", port=7860)