Dellboy commited on
Commit
00bec90
·
verified ·
1 Parent(s): 5be713f

Switch to Gradio SDK -- ZeroGPU only works with Gradio SDK, not Docker/FastAPI

Browse files
Files changed (4) hide show
  1. Dockerfile +0 -18
  2. README.md +9 -4
  3. app.py +70 -56
  4. requirements.txt +4 -4
Dockerfile DELETED
@@ -1,18 +0,0 @@
1
- FROM nvidia/cuda:12.1.0-cudnn8-devel-ubuntu22.04
2
-
3
- ENV DEBIAN_FRONTEND=noninteractive
4
- RUN apt-get update && apt-get install -y python3 python3-pip git && rm -rf /var/lib/apt/lists/*
5
-
6
- WORKDIR /app
7
- COPY requirements.txt .
8
-
9
- # Build llama-cpp-python with CUDA support
10
- ENV CMAKE_ARGS="-DGGML_CUDA=on"
11
- RUN pip3 install --no-cache-dir llama-cpp-python --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu121
12
-
13
- RUN pip3 install --no-cache-dir fastapi uvicorn huggingface_hub spaces
14
-
15
- COPY app.py .
16
-
17
- EXPOSE 7860
18
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
README.md CHANGED
@@ -3,13 +3,18 @@ title: chatPDB API
3
  emoji: 🧬
4
  colorFrom: green
5
  colorTo: blue
6
- sdk: docker
7
- app_port: 7860
8
  pinned: false
9
  ---
10
 
11
  # chatPDB Inference API
12
 
13
- ZeroGPU-backed streaming inference endpoint for chatPDB 32B v1 (Q4_K_M GGUF).
14
 
15
- Endpoint: `POST /generate` returns `text/event-stream` of token chunks.
 
 
 
 
 
 
3
  emoji: 🧬
4
  colorFrom: green
5
  colorTo: blue
6
+ sdk: gradio
7
+ app_file: app.py
8
  pinned: false
9
  ---
10
 
11
  # chatPDB Inference API
12
 
13
+ ZeroGPU-backed inference endpoint for chatPDB 32B v1 (Q4_K_M GGUF).
14
 
15
+ Consumed by the Flask PTY app at [chatpdb.mdeller.com](https://chatpdb.mdeller.com).
16
+
17
+ **Endpoint:** `POST /generate` — returns `text/event-stream` of token chunks.
18
+
19
+ **Cold start:** first request after idle downloads the ~18.4 GB GGUF and allocates the ZeroGPU
20
+ A10G (~60-120 s). This is a portfolio demo; availability is best-effort.
app.py CHANGED
@@ -1,97 +1,111 @@
1
  """
2
- chatPDB inference API — HuggingFace Space (ZeroGPU)
3
 
4
- Serves a streaming /generate endpoint backed by llama-cpp-python.
5
- The GGUF is pulled from the Hub on first request and cached for the session.
 
 
 
 
 
 
6
  """
7
  from __future__ import annotations
8
 
9
  import json
10
- import os
11
- from pathlib import Path
12
- from typing import Generator
13
 
 
14
  import spaces
15
- from fastapi import FastAPI
16
  from fastapi.responses import StreamingResponse
17
  from huggingface_hub import hf_hub_download
18
 
19
- # ---------------------------------------------------------------------------
20
- # Model config
21
- # ---------------------------------------------------------------------------
22
-
23
  REPO_ID = "Dellboy/chatpdb_32b_v1-GGUF"
24
  FILENAME = "chatpdb_32b_v1_q4km.gguf"
25
- MODEL_PATH: Path | None = None # set after first download
26
-
27
  N_CTX = 1536 # matches chatPDB's real training max_seq_length (config/train_config.yaml)
28
  N_GPU_LAYERS = -1 # offload all layers to GPU
29
 
30
- # ---------------------------------------------------------------------------
31
- # App
32
- # ---------------------------------------------------------------------------
33
-
34
- app = FastAPI(title="chatPDB API")
35
-
36
-
37
- def _get_model():
38
- """Download GGUF on first call, return cached Llama instance."""
39
- global MODEL_PATH
40
- from llama_cpp import Llama
41
 
42
- if MODEL_PATH is None:
43
- MODEL_PATH = Path(hf_hub_download(repo_id=REPO_ID, filename=FILENAME))
44
 
45
- return Llama(
46
- model_path=str(MODEL_PATH),
47
- n_ctx=N_CTX,
48
- n_gpu_layers=N_GPU_LAYERS,
49
- verbose=False,
50
- )
51
 
52
 
53
- @spaces.GPU
54
  def _generate_tokens(
55
  prompt: str,
56
  max_tokens: int,
57
  temperature: float,
58
  repeat_penalty: float,
59
- ) -> Generator[str, None, None]:
60
- """Run inference inside the ZeroGPU lease."""
61
- llm = _get_model()
62
- stream = llm(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  prompt,
64
  max_tokens=max_tokens,
65
  temperature=temperature,
66
  repeat_penalty=repeat_penalty,
67
  stream=True,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  )
69
- for chunk in stream:
70
- token = chunk["choices"][0]["text"]
71
- if token:
72
- yield token
73
 
74
 
75
- @app.post("/generate")
76
- async def generate(request: dict):
77
- """
78
- POST /generate
79
- Body: { "prompt": "...", "max_tokens": 512, "temperature": 0.15, "repeat_penalty": 1.15 }
80
- Returns: text/event-stream of token strings
81
- """
82
- prompt = request.get("prompt", "")
83
- max_tokens = int(request.get("max_tokens", 512))
84
- temperature = float(request.get("temperature", 0.15))
85
- repeat_penalty = float(request.get("repeat_penalty", 1.15))
86
 
87
  def event_stream():
88
- for token in _generate_tokens(prompt, max_tokens, temperature, repeat_penalty):
89
- yield f"data: {json.dumps({'token': token})}\n\n"
90
  yield "data: [DONE]\n\n"
91
 
92
  return StreamingResponse(event_stream(), media_type="text/event-stream")
93
 
94
 
95
- @app.get("/health")
96
- def health():
97
  return {"status": "ok"}
 
 
 
 
 
1
  """
2
+ chatPDB inference API — HuggingFace Space (Gradio SDK, ZeroGPU)
3
 
4
+ Exposes POST /generate (SSE) consumed by the Flask PTY app on the droplet.
5
+ Gradio SDK is required for ZeroGPU (confirmed live 2026-07-23: requesting ZeroGPU hardware for a
6
+ Docker-SDK Space returns "ZeroGPU Spaces only work with Gradio SDK") -- the Gradio UI itself is
7
+ just a minimal landing page; a custom FastAPI route is mounted on Gradio's own underlying app to
8
+ keep the exact same /generate contract chat_remote.py already expects.
9
+
10
+ Cold-start note: first request after idle downloads the GGUF and allocates the GPU
11
+ (~60-120 s). Subsequent requests within the same GPU lease are fast.
12
  """
13
  from __future__ import annotations
14
 
15
  import json
 
 
 
16
 
17
+ import gradio as gr
18
  import spaces
19
+ from fastapi import Request
20
  from fastapi.responses import StreamingResponse
21
  from huggingface_hub import hf_hub_download
22
 
 
 
 
 
23
  REPO_ID = "Dellboy/chatpdb_32b_v1-GGUF"
24
  FILENAME = "chatpdb_32b_v1_q4km.gguf"
 
 
25
  N_CTX = 1536 # matches chatPDB's real training max_seq_length (config/train_config.yaml)
26
  N_GPU_LAYERS = -1 # offload all layers to GPU
27
 
28
+ _model_path: str | None = None
 
 
 
 
 
 
 
 
 
 
29
 
 
 
30
 
31
+ def _get_model_path() -> str:
32
+ global _model_path
33
+ if _model_path is None:
34
+ _model_path = hf_hub_download(repo_id=REPO_ID, filename=FILENAME)
35
+ return _model_path
 
36
 
37
 
38
+ @spaces.GPU(duration=180)
39
  def _generate_tokens(
40
  prompt: str,
41
  max_tokens: int,
42
  temperature: float,
43
  repeat_penalty: float,
44
+ ) -> list[str]:
45
+ """Run inside the ZeroGPU lease; collect all tokens and return.
46
+
47
+ ZeroGPU-decorated functions run in a bounded lease and can't hold a live generator open
48
+ across the function boundary, so tokens are collected here and streamed out afterward by the
49
+ /generate route below -- the client sees compute-then-deliver rather than true live
50
+ token-by-token latency, a real trade-off of the ZeroGPU model.
51
+ """
52
+ from llama_cpp import Llama
53
+
54
+ llm = Llama(
55
+ model_path=_get_model_path(),
56
+ n_ctx=N_CTX,
57
+ n_gpu_layers=N_GPU_LAYERS,
58
+ verbose=False,
59
+ )
60
+ tokens: list[str] = []
61
+ for chunk in llm(
62
  prompt,
63
  max_tokens=max_tokens,
64
  temperature=temperature,
65
  repeat_penalty=repeat_penalty,
66
  stream=True,
67
+ ):
68
+ tok = chunk["choices"][0]["text"]
69
+ if tok:
70
+ tokens.append(tok)
71
+ return tokens
72
+
73
+
74
+ # -- Gradio UI (minimal -- required for Gradio SDK / ZeroGPU) --
75
+
76
+ with gr.Blocks(title="chatPDB API") as demo:
77
+ gr.Markdown(
78
+ "## 🧬 chatPDB Inference API\n\n"
79
+ "Internal endpoint for [chatpdb.mdeller.com](https://chatpdb.mdeller.com). "
80
+ "Use `POST /generate` — returns `text/event-stream` of token chunks.\n\n"
81
+ "**Cold start:** first request after idle takes ~60-120 s (GGUF download + GPU alloc)."
82
  )
 
 
 
 
83
 
84
 
85
+ # -- Custom FastAPI route mounted on Gradio's app --
86
+
87
+ @demo.app.post("/generate")
88
+ async def generate(request: Request):
89
+ body = await request.json()
90
+ prompt = body.get("prompt", "")
91
+ max_tokens = int(body.get("max_tokens", 512))
92
+ temperature = float(body.get("temperature", 0.15))
93
+ repeat_penalty = float(body.get("repeat_penalty", 1.15))
94
+
95
+ tokens = _generate_tokens(prompt, max_tokens, temperature, repeat_penalty)
96
 
97
  def event_stream():
98
+ for tok in tokens:
99
+ yield f"data: {json.dumps({'token': tok})}\n\n"
100
  yield "data: [DONE]\n\n"
101
 
102
  return StreamingResponse(event_stream(), media_type="text/event-stream")
103
 
104
 
105
+ @demo.app.get("/health")
106
+ async def health():
107
  return {"status": "ok"}
108
+
109
+
110
+ if __name__ == "__main__":
111
+ demo.launch()
requirements.txt CHANGED
@@ -1,5 +1,5 @@
1
- fastapi
2
- uvicorn
3
- llama-cpp-python[cuda]
4
- huggingface_hub
5
  spaces
 
 
1
+ --extra-index-url https://abetlen.github.io/llama-cpp-python/whl/cu121
2
+ llama-cpp-python
3
+ gradio>=4.0
 
4
  spaces
5
+ huggingface_hub