Spaces:
Sleeping
Sleeping
Real fix: restore demo.launch() as entrypoint (required for ZeroGPU detection), expose /generate via Gradio's native api_name mechanism instead of a custom FastAPI route
Browse files- __pycache__/app.cpython-314.pyc +0 -0
- app.py +54 -57
- requirements.txt +0 -1
__pycache__/app.cpython-314.pyc
CHANGED
|
Binary files a/__pycache__/app.cpython-314.pyc and b/__pycache__/app.cpython-314.pyc differ
|
|
|
app.py
CHANGED
|
@@ -1,20 +1,26 @@
|
|
| 1 |
"""
|
| 2 |
chatPDB inference API — HuggingFace Space (Gradio SDK, ZeroGPU)
|
| 3 |
|
| 4 |
-
Exposes
|
| 5 |
-
Gradio
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
Cold-start note: first request after idle downloads the GGUF and allocates the GPU
|
| 20 |
(~60-120 s). Subsequent requests within the same GPU lease are fast.
|
|
@@ -25,9 +31,6 @@ import json
|
|
| 25 |
|
| 26 |
import gradio as gr
|
| 27 |
import spaces
|
| 28 |
-
import uvicorn
|
| 29 |
-
from fastapi import FastAPI, Request
|
| 30 |
-
from fastapi.responses import StreamingResponse
|
| 31 |
from huggingface_hub import hf_hub_download
|
| 32 |
|
| 33 |
REPO_ID = "Dellboy/chatpdb_32b_v1-GGUF"
|
|
@@ -51,13 +54,13 @@ def _generate_tokens(
|
|
| 51 |
max_tokens: int,
|
| 52 |
temperature: float,
|
| 53 |
repeat_penalty: float,
|
| 54 |
-
) ->
|
| 55 |
-
"""Run inside the ZeroGPU lease; collect all tokens and return.
|
| 56 |
|
| 57 |
ZeroGPU-decorated functions run in a bounded lease and can't hold a live generator open
|
| 58 |
-
across the function boundary, so tokens are collected here
|
| 59 |
-
|
| 60 |
-
|
| 61 |
"""
|
| 62 |
from llama_cpp import Llama
|
| 63 |
|
|
@@ -67,7 +70,7 @@ def _generate_tokens(
|
|
| 67 |
n_gpu_layers=N_GPU_LAYERS,
|
| 68 |
verbose=False,
|
| 69 |
)
|
| 70 |
-
|
| 71 |
for chunk in llm(
|
| 72 |
prompt,
|
| 73 |
max_tokens=max_tokens,
|
|
@@ -77,51 +80,45 @@ def _generate_tokens(
|
|
| 77 |
):
|
| 78 |
tok = chunk["choices"][0]["text"]
|
| 79 |
if tok:
|
| 80 |
-
|
| 81 |
-
return
|
| 82 |
|
| 83 |
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
app = FastAPI(title="chatPDB API")
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
@app.post("/generate")
|
| 90 |
-
async def generate(request: Request):
|
| 91 |
-
body = await request.json()
|
| 92 |
-
prompt = body.get("prompt", "")
|
| 93 |
-
max_tokens = int(body.get("max_tokens", 512))
|
| 94 |
-
temperature = float(body.get("temperature", 0.15))
|
| 95 |
-
repeat_penalty = float(body.get("repeat_penalty", 1.15))
|
| 96 |
-
|
| 97 |
-
tokens = _generate_tokens(prompt, max_tokens, temperature, repeat_penalty)
|
| 98 |
-
|
| 99 |
-
def event_stream():
|
| 100 |
-
for tok in tokens:
|
| 101 |
-
yield f"data: {json.dumps({'token': tok})}\n\n"
|
| 102 |
-
yield "data: [DONE]\n\n"
|
| 103 |
-
|
| 104 |
-
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
@app.get("/health")
|
| 108 |
-
async def health():
|
| 109 |
-
return {"status": "ok"}
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
# -- Gradio UI (minimal -- required for Gradio SDK / ZeroGPU), mounted at a sub-path so it can't
|
| 113 |
-
# shadow the routes registered directly on `app` above --
|
| 114 |
|
| 115 |
with gr.Blocks(title="chatPDB API") as demo:
|
| 116 |
gr.Markdown(
|
| 117 |
"## 🧬 chatPDB Inference API\n\n"
|
| 118 |
"Internal endpoint for [chatpdb.mdeller.com](https://chatpdb.mdeller.com). "
|
| 119 |
-
"
|
|
|
|
| 120 |
"**Cold start:** first request after idle takes ~60-120 s (GGUF download + GPU alloc)."
|
| 121 |
)
|
| 122 |
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
|
| 125 |
|
| 126 |
if __name__ == "__main__":
|
| 127 |
-
|
|
|
|
| 1 |
"""
|
| 2 |
chatPDB inference API — HuggingFace Space (Gradio SDK, ZeroGPU)
|
| 3 |
|
| 4 |
+
Exposes a Gradio API endpoint (api_name="generate") consumed by the Flask PTY app on the droplet
|
| 5 |
+
via Gradio's own REST protocol (POST /call/generate -> event_id, then GET /call/generate/<id> for
|
| 6 |
+
an SSE stream) -- NOT a hand-rolled custom FastAPI route.
|
| 7 |
+
|
| 8 |
+
Real fix history (found + fixed 2026-07-23, in order):
|
| 9 |
+
1. First attempt added a custom route via `@demo.app.post("/generate")` -- confirmed live via
|
| 10 |
+
curl that this silently doesn't take effect (405, `allow: GET`); Gradio's own catch-all SPA
|
| 11 |
+
route claims the path instead.
|
| 12 |
+
2. Second attempt used `gr.mount_gradio_app()` to mount Gradio under a FastAPI app that owns the
|
| 13 |
+
real routes -- this DID fix routing, but broke ZeroGPU entirely: the Space failed to start
|
| 14 |
+
with "No @spaces.GPU function detected during startup". Confirmed via web research (HF forum
|
| 15 |
+
threads on this exact error) that ZeroGPU's detection hook requires Gradio's own `demo.launch()`
|
| 16 |
+
to be the actual serving entrypoint -- a custom-FastAPI-primary architecture is not supported,
|
| 17 |
+
regardless of whether spaces.GPU is imported/used.
|
| 18 |
+
3. This version: `demo.launch()` is the real entrypoint again (restoring ZeroGPU detection), and
|
| 19 |
+
the /generate contract is exposed via Gradio's own native API mechanism instead of a custom
|
| 20 |
+
route -- a hidden Textbox pair wired to a `.click()` handler with `api_name="generate"`, which
|
| 21 |
+
Gradio automatically exposes as `POST /call/generate` + `GET /call/generate/<event_id>` (SSE).
|
| 22 |
+
This stays entirely within Gradio's own request-handling pipeline, which is what ZeroGPU's
|
| 23 |
+
detection actually requires.
|
| 24 |
|
| 25 |
Cold-start note: first request after idle downloads the GGUF and allocates the GPU
|
| 26 |
(~60-120 s). Subsequent requests within the same GPU lease are fast.
|
|
|
|
| 31 |
|
| 32 |
import gradio as gr
|
| 33 |
import spaces
|
|
|
|
|
|
|
|
|
|
| 34 |
from huggingface_hub import hf_hub_download
|
| 35 |
|
| 36 |
REPO_ID = "Dellboy/chatpdb_32b_v1-GGUF"
|
|
|
|
| 54 |
max_tokens: int,
|
| 55 |
temperature: float,
|
| 56 |
repeat_penalty: float,
|
| 57 |
+
) -> str:
|
| 58 |
+
"""Run inside the ZeroGPU lease; collect all tokens and return as one joined string.
|
| 59 |
|
| 60 |
ZeroGPU-decorated functions run in a bounded lease and can't hold a live generator open
|
| 61 |
+
across the function boundary, so tokens are collected here rather than streamed token-by-token
|
| 62 |
+
-- the client sees compute-then-deliver rather than true live latency, a real trade-off of the
|
| 63 |
+
ZeroGPU model.
|
| 64 |
"""
|
| 65 |
from llama_cpp import Llama
|
| 66 |
|
|
|
|
| 70 |
n_gpu_layers=N_GPU_LAYERS,
|
| 71 |
verbose=False,
|
| 72 |
)
|
| 73 |
+
parts: list[str] = []
|
| 74 |
for chunk in llm(
|
| 75 |
prompt,
|
| 76 |
max_tokens=max_tokens,
|
|
|
|
| 80 |
):
|
| 81 |
tok = chunk["choices"][0]["text"]
|
| 82 |
if tok:
|
| 83 |
+
parts.append(tok)
|
| 84 |
+
return "".join(parts)
|
| 85 |
|
| 86 |
|
| 87 |
+
def _generate_api(prompt: str, max_tokens: int, temperature: float, repeat_penalty: float) -> str:
|
| 88 |
+
"""Thin wrapper: chat_remote.py sends max_tokens/temperature/repeat_penalty as a JSON string
|
| 89 |
+
packed into one field isn't needed -- Gradio's API takes positional args directly, matching
|
| 90 |
+
the .click() inputs list below."""
|
| 91 |
+
return _generate_tokens(prompt, int(max_tokens), float(temperature), float(repeat_penalty))
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
|
| 94 |
with gr.Blocks(title="chatPDB API") as demo:
|
| 95 |
gr.Markdown(
|
| 96 |
"## 🧬 chatPDB Inference API\n\n"
|
| 97 |
"Internal endpoint for [chatpdb.mdeller.com](https://chatpdb.mdeller.com). "
|
| 98 |
+
"Consumed via Gradio's own API: `POST /call/generate` then `GET /call/generate/<event_id>` "
|
| 99 |
+
"(SSE).\n\n"
|
| 100 |
"**Cold start:** first request after idle takes ~60-120 s (GGUF download + GPU alloc)."
|
| 101 |
)
|
| 102 |
|
| 103 |
+
# Hidden inputs/outputs purely to register a real Gradio event with api_name="generate" --
|
| 104 |
+
# ZeroGPU's startup detection scans Gradio's own event/dependency graph, so the GPU-decorated
|
| 105 |
+
# function must be reachable through a real .click()/.submit() binding, not just referenced
|
| 106 |
+
# from a custom route.
|
| 107 |
+
with gr.Row(visible=False):
|
| 108 |
+
prompt_in = gr.Textbox()
|
| 109 |
+
max_tokens_in = gr.Number(value=512)
|
| 110 |
+
temperature_in = gr.Number(value=0.15)
|
| 111 |
+
repeat_penalty_in = gr.Number(value=1.15)
|
| 112 |
+
text_out = gr.Textbox()
|
| 113 |
+
trigger = gr.Button()
|
| 114 |
+
|
| 115 |
+
trigger.click(
|
| 116 |
+
fn=_generate_api,
|
| 117 |
+
inputs=[prompt_in, max_tokens_in, temperature_in, repeat_penalty_in],
|
| 118 |
+
outputs=text_out,
|
| 119 |
+
api_name="generate",
|
| 120 |
+
)
|
| 121 |
|
| 122 |
|
| 123 |
if __name__ == "__main__":
|
| 124 |
+
demo.launch()
|
requirements.txt
CHANGED
|
@@ -3,4 +3,3 @@ llama-cpp-python
|
|
| 3 |
gradio>=4.0
|
| 4 |
spaces
|
| 5 |
huggingface_hub
|
| 6 |
-
uvicorn
|
|
|
|
| 3 |
gradio>=4.0
|
| 4 |
spaces
|
| 5 |
huggingface_hub
|
|
|