tommytracx commited on
Commit
c341719
·
verified ·
1 Parent(s): a0ef6f6

gr.Code(language='rust') is not supported in gradio 5.50 -- drop it

Browse files
Files changed (1) hide show
  1. app.py +196 -193
app.py CHANGED
@@ -1,193 +1,196 @@
1
- """THOX Rust Coder — hosted Qwen3-Coder-REAP-25B-A3B-Rust (GGUF).
2
-
3
- SIZING: "3B active" IS NOT A MEMORY BUDGET
4
- ------------------------------------------
5
- This is a Mixture-of-Experts model: 25B total parameters, ~3B active per token.
6
- Those two numbers govern different resources and it is easy to conflate them:
7
-
8
- active params (~3B) -> COMPUTE per token. Decode is as cheap as a 3B dense.
9
- total params (25B) -> MEMORY. Every expert must be resident, because the
10
- router may select any of them on any token.
11
-
12
- So a Q4_K_M GGUF is **15.1 GB of RAM/VRAM**, not "3B-worth". You cannot fit this
13
- on a tier sized for a 3B model. What MoE buys you here is speed-per-byte, not a
14
- smaller footprint -- which is exactly why a CPU tier is viable at all: we pay
15
- 25B-sized memory but only 3B-sized arithmetic.
16
-
17
- TIER
18
- ----
19
- Starts on `cpu-upgrade` (8 vCPU / 32 GB, ~$0.03/hr). That fits Q4_K_M with room
20
- for the KV cache, and the 3B active path keeps CPU decode tolerable. If measured
21
- throughput is too slow to be a useful coding assistant, escalate to a GPU tier --
22
- but escalate on a MEASUREMENT, not on the assumption that 25B implies a GPU.
23
- """
24
-
25
- from __future__ import annotations
26
-
27
- import os
28
- import time
29
- import uuid
30
-
31
- import gradio as gr
32
- from fastapi import FastAPI
33
- from huggingface_hub import hf_hub_download
34
- from pydantic import BaseModel
35
-
36
- MODEL_REPO = os.environ.get("THOX_MODEL_REPO", "Em-80/Qwen3-coder-REAP-25B-A3B-Rust-GGUF")
37
- MODEL_FILE = os.environ.get("THOX_MODEL_FILE", "Qwen3-Coder-REAP-25B-A3B-Rust-Q4_K_M.gguf")
38
- N_CTX = int(os.environ.get("THOX_N_CTX", "8192"))
39
-
40
- SYSTEM = (
41
- "You are THOX Rust Coder. You write correct, idiomatic Rust. Prefer showing "
42
- "compiling code over prose. If a request is ambiguous, state the assumption "
43
- "you made in one line, then give the code."
44
- )
45
-
46
- _llm = None
47
-
48
-
49
- def _usable_cpus() -> int:
50
- """Threads from the cgroup quota, not the host.
51
-
52
- `os.cpu_count()` reports the HOST's core count inside a container. On a
53
- sibling Space this oversubscribed a 2-vCPU cgroup ~8x and cost ~290x
54
- throughput -- the model ran slower than the edge device it was meant to
55
- offload. Read the quota.
56
- """
57
- try:
58
- quota, period = open("/sys/fs/cgroup/cpu.max").read().split()
59
- if quota != "max":
60
- return max(1, int(int(quota) / int(period)))
61
- except Exception:
62
- pass
63
- try:
64
- q = int(open("/sys/fs/cgroup/cpu/cpu.cfs_quota_us").read())
65
- p = int(open("/sys/fs/cgroup/cpu/cpu.cfs_period_us").read())
66
- if q > 0:
67
- return max(1, q // p)
68
- except Exception:
69
- pass
70
- try:
71
- return max(1, len(os.sched_getaffinity(0)))
72
- except Exception:
73
- return os.cpu_count() or 2
74
-
75
-
76
- def llm():
77
- global _llm
78
- if _llm is None:
79
- from llama_cpp import Llama
80
-
81
- path = hf_hub_download(MODEL_REPO, MODEL_FILE,
82
- token=os.environ.get("HF_TOKEN"))
83
- _llm = Llama(
84
- model_path=path,
85
- n_ctx=N_CTX,
86
- n_threads=_usable_cpus(),
87
- # -1 offloads every layer when a GPU is present, and is simply
88
- # ignored on a CPU build -- so the same image works on both tiers.
89
- n_gpu_layers=int(os.environ.get("THOX_GPU_LAYERS", "-1")),
90
- verbose=False,
91
- )
92
- return _llm
93
-
94
-
95
- def generate(messages, max_tokens=512, temperature=0.2):
96
- t0 = time.time()
97
- # create_chat_completion uses the chat template embedded in the GGUF, rather
98
- # than a hand-rolled one. Qwen3-Coder is ChatML, but reading it from the file
99
- # means a re-quant with a different template does not silently break output.
100
- out = llm().create_chat_completion(
101
- messages=messages, max_tokens=max_tokens, temperature=temperature,
102
- )
103
- dt = time.time() - t0
104
- text = out["choices"][0]["message"]["content"]
105
- n = out.get("usage", {}).get("completion_tokens") or 0
106
- return text, n, dt
107
-
108
-
109
- api = FastAPI()
110
-
111
-
112
- class Msg(BaseModel):
113
- role: str
114
- content: str
115
-
116
-
117
- class ChatRequest(BaseModel):
118
- model: str | None = None
119
- messages: list[Msg]
120
- max_tokens: int | None = 512
121
- temperature: float | None = 0.2
122
-
123
-
124
- @api.get("/healthz")
125
- def healthz():
126
- return {
127
- "status": "ok",
128
- "model": MODEL_REPO,
129
- "file": MODEL_FILE,
130
- "role": "thox-rust-coder",
131
- "n_ctx": N_CTX,
132
- "threads": _usable_cpus(),
133
- "loaded": _llm is not None,
134
- }
135
-
136
-
137
- @api.post("/v1/chat/completions")
138
- def chat_completions(req: ChatRequest):
139
- msgs = [m.model_dump() for m in req.messages]
140
- if not any(m["role"] == "system" for m in msgs):
141
- msgs = [{"role": "system", "content": SYSTEM}] + msgs
142
- text, n, dt = generate(msgs, req.max_tokens or 512, req.temperature or 0.2)
143
- return {
144
- "id": "chatcmpl-" + uuid.uuid4().hex[:12],
145
- "object": "chat.completion",
146
- "created": int(time.time()),
147
- "model": "thox-rust-coder",
148
- "choices": [{"index": 0, "finish_reason": "stop",
149
- "message": {"role": "assistant", "content": text}}],
150
- "usage": {"completion_tokens": n},
151
- "thox_perf": {"tok_per_s": round(n / dt, 1) if dt else None,
152
- "seconds": round(dt, 2)},
153
- }
154
-
155
-
156
- def ui(prompt, max_tokens):
157
- text, n, dt = generate(
158
- [{"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}],
159
- int(max_tokens),
160
- )
161
- tps = n / dt if dt else 0
162
- return text, f"{n} tok in {dt:.1f}s = {tps:.1f} tok/s"
163
-
164
-
165
- demo = gr.Interface(
166
- fn=ui,
167
- inputs=[gr.Textbox(label="Prompt", lines=4,
168
- value="Write a Rust function that parses a semver string "
169
- "into (major, minor, patch), returning Result."),
170
- gr.Slider(64, 2048, value=512, step=64, label="max tokens")],
171
- outputs=[gr.Code(label="THOX Rust Coder", language="rust"),
172
- gr.Textbox(label="Measured")],
173
- title="THOX Rust Coder Qwen3-Coder-REAP-25B-A3B-Rust",
174
- description=(
175
- "Rust-specialised MoE coder, Apache-2.0, served as GGUF.\n\n"
176
- "`POST /v1/chat/completions` (OpenAI-shaped, ThoxRoute-registerable) · "
177
- "`GET /healthz`\n\n"
178
- "**25B total / ~3B active.** Memory is sized by the 25B (all experts stay "
179
- "resident); speed is sized by the 3B. That combination is what makes a "
180
- "CPU tier viable."
181
- ),
182
- )
183
-
184
- app = gr.mount_gradio_app(api, demo, path="/")
185
-
186
-
187
- if __name__ == "__main__":
188
- # Defining `app` does not serve it -- without this the process exits 0 and
189
- # the Space reports RUNTIME_ERROR with no traceback to read.
190
- import uvicorn
191
-
192
- uvicorn.run(app, host="0.0.0.0",
193
- port=int(os.environ.get("GRADIO_SERVER_PORT", 7860)))
 
 
 
 
1
+ """THOX Rust Coder — hosted Qwen3-Coder-REAP-25B-A3B-Rust (GGUF).
2
+
3
+ SIZING: "3B active" IS NOT A MEMORY BUDGET
4
+ ------------------------------------------
5
+ This is a Mixture-of-Experts model: 25B total parameters, ~3B active per token.
6
+ Those two numbers govern different resources and it is easy to conflate them:
7
+
8
+ active params (~3B) -> COMPUTE per token. Decode is as cheap as a 3B dense.
9
+ total params (25B) -> MEMORY. Every expert must be resident, because the
10
+ router may select any of them on any token.
11
+
12
+ So a Q4_K_M GGUF is **15.1 GB of RAM/VRAM**, not "3B-worth". You cannot fit this
13
+ on a tier sized for a 3B model. What MoE buys you here is speed-per-byte, not a
14
+ smaller footprint -- which is exactly why a CPU tier is viable at all: we pay
15
+ 25B-sized memory but only 3B-sized arithmetic.
16
+
17
+ TIER
18
+ ----
19
+ Starts on `cpu-upgrade` (8 vCPU / 32 GB, ~$0.03/hr). That fits Q4_K_M with room
20
+ for the KV cache, and the 3B active path keeps CPU decode tolerable. If measured
21
+ throughput is too slow to be a useful coding assistant, escalate to a GPU tier --
22
+ but escalate on a MEASUREMENT, not on the assumption that 25B implies a GPU.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import os
28
+ import time
29
+ import uuid
30
+
31
+ import gradio as gr
32
+ from fastapi import FastAPI
33
+ from huggingface_hub import hf_hub_download
34
+ from pydantic import BaseModel
35
+
36
+ MODEL_REPO = os.environ.get("THOX_MODEL_REPO", "Em-80/Qwen3-coder-REAP-25B-A3B-Rust-GGUF")
37
+ MODEL_FILE = os.environ.get("THOX_MODEL_FILE", "Qwen3-Coder-REAP-25B-A3B-Rust-Q4_K_M.gguf")
38
+ N_CTX = int(os.environ.get("THOX_N_CTX", "8192"))
39
+
40
+ SYSTEM = (
41
+ "You are THOX Rust Coder. You write correct, idiomatic Rust. Prefer showing "
42
+ "compiling code over prose. If a request is ambiguous, state the assumption "
43
+ "you made in one line, then give the code."
44
+ )
45
+
46
+ _llm = None
47
+
48
+
49
+ def _usable_cpus() -> int:
50
+ """Threads from the cgroup quota, not the host.
51
+
52
+ `os.cpu_count()` reports the HOST's core count inside a container. On a
53
+ sibling Space this oversubscribed a 2-vCPU cgroup ~8x and cost ~290x
54
+ throughput -- the model ran slower than the edge device it was meant to
55
+ offload. Read the quota.
56
+ """
57
+ try:
58
+ quota, period = open("/sys/fs/cgroup/cpu.max").read().split()
59
+ if quota != "max":
60
+ return max(1, int(int(quota) / int(period)))
61
+ except Exception:
62
+ pass
63
+ try:
64
+ q = int(open("/sys/fs/cgroup/cpu/cpu.cfs_quota_us").read())
65
+ p = int(open("/sys/fs/cgroup/cpu/cpu.cfs_period_us").read())
66
+ if q > 0:
67
+ return max(1, q // p)
68
+ except Exception:
69
+ pass
70
+ try:
71
+ return max(1, len(os.sched_getaffinity(0)))
72
+ except Exception:
73
+ return os.cpu_count() or 2
74
+
75
+
76
+ def llm():
77
+ global _llm
78
+ if _llm is None:
79
+ from llama_cpp import Llama
80
+
81
+ path = hf_hub_download(MODEL_REPO, MODEL_FILE,
82
+ token=os.environ.get("HF_TOKEN"))
83
+ _llm = Llama(
84
+ model_path=path,
85
+ n_ctx=N_CTX,
86
+ n_threads=_usable_cpus(),
87
+ # -1 offloads every layer when a GPU is present, and is simply
88
+ # ignored on a CPU build -- so the same image works on both tiers.
89
+ n_gpu_layers=int(os.environ.get("THOX_GPU_LAYERS", "-1")),
90
+ verbose=False,
91
+ )
92
+ return _llm
93
+
94
+
95
+ def generate(messages, max_tokens=512, temperature=0.2):
96
+ t0 = time.time()
97
+ # create_chat_completion uses the chat template embedded in the GGUF, rather
98
+ # than a hand-rolled one. Qwen3-Coder is ChatML, but reading it from the file
99
+ # means a re-quant with a different template does not silently break output.
100
+ out = llm().create_chat_completion(
101
+ messages=messages, max_tokens=max_tokens, temperature=temperature,
102
+ )
103
+ dt = time.time() - t0
104
+ text = out["choices"][0]["message"]["content"]
105
+ n = out.get("usage", {}).get("completion_tokens") or 0
106
+ return text, n, dt
107
+
108
+
109
+ api = FastAPI()
110
+
111
+
112
+ class Msg(BaseModel):
113
+ role: str
114
+ content: str
115
+
116
+
117
+ class ChatRequest(BaseModel):
118
+ model: str | None = None
119
+ messages: list[Msg]
120
+ max_tokens: int | None = 512
121
+ temperature: float | None = 0.2
122
+
123
+
124
+ @api.get("/healthz")
125
+ def healthz():
126
+ return {
127
+ "status": "ok",
128
+ "model": MODEL_REPO,
129
+ "file": MODEL_FILE,
130
+ "role": "thox-rust-coder",
131
+ "n_ctx": N_CTX,
132
+ "threads": _usable_cpus(),
133
+ "loaded": _llm is not None,
134
+ }
135
+
136
+
137
+ @api.post("/v1/chat/completions")
138
+ def chat_completions(req: ChatRequest):
139
+ msgs = [m.model_dump() for m in req.messages]
140
+ if not any(m["role"] == "system" for m in msgs):
141
+ msgs = [{"role": "system", "content": SYSTEM}] + msgs
142
+ text, n, dt = generate(msgs, req.max_tokens or 512, req.temperature or 0.2)
143
+ return {
144
+ "id": "chatcmpl-" + uuid.uuid4().hex[:12],
145
+ "object": "chat.completion",
146
+ "created": int(time.time()),
147
+ "model": "thox-rust-coder",
148
+ "choices": [{"index": 0, "finish_reason": "stop",
149
+ "message": {"role": "assistant", "content": text}}],
150
+ "usage": {"completion_tokens": n},
151
+ "thox_perf": {"tok_per_s": round(n / dt, 1) if dt else None,
152
+ "seconds": round(dt, 2)},
153
+ }
154
+
155
+
156
+ def ui(prompt, max_tokens):
157
+ text, n, dt = generate(
158
+ [{"role": "system", "content": SYSTEM}, {"role": "user", "content": prompt}],
159
+ int(max_tokens),
160
+ )
161
+ tps = n / dt if dt else 0
162
+ return text, f"{n} tok in {dt:.1f}s = {tps:.1f} tok/s"
163
+
164
+
165
+ demo = gr.Interface(
166
+ fn=ui,
167
+ inputs=[gr.Textbox(label="Prompt", lines=4,
168
+ value="Write a Rust function that parses a semver string "
169
+ "into (major, minor, patch), returning Result."),
170
+ gr.Slider(64, 2048, value=512, step=64, label="max tokens")],
171
+ # gradio 5.50's gr.Code has a fixed language allow-list and "rust" is NOT
172
+ # on it -- passing it raises ValueError at import and the Space exits 1.
173
+ # No highlighting is better than no Space.
174
+ outputs=[gr.Code(label="THOX Rust Coder"),
175
+ gr.Textbox(label="Measured")],
176
+ title="THOX Rust Coder Qwen3-Coder-REAP-25B-A3B-Rust",
177
+ description=(
178
+ "Rust-specialised MoE coder, Apache-2.0, served as GGUF.\n\n"
179
+ "`POST /v1/chat/completions` (OpenAI-shaped, ThoxRoute-registerable) · "
180
+ "`GET /healthz`\n\n"
181
+ "**25B total / ~3B active.** Memory is sized by the 25B (all experts stay "
182
+ "resident); speed is sized by the 3B. That combination is what makes a "
183
+ "CPU tier viable."
184
+ ),
185
+ )
186
+
187
+ app = gr.mount_gradio_app(api, demo, path="/")
188
+
189
+
190
+ if __name__ == "__main__":
191
+ # Defining `app` does not serve it -- without this the process exits 0 and
192
+ # the Space reports RUNTIME_ERROR with no traceback to read.
193
+ import uvicorn
194
+
195
+ uvicorn.run(app, host="0.0.0.0",
196
+ port=int(os.environ.get("GRADIO_SERVER_PORT", 7860)))