MaduRox commited on
Commit
c8f0400
·
1 Parent(s): 1e0f5c6

feat: add FastAPI server with Live Swagger UI, OpenAI endpoints, and CORS

Browse files
app.py CHANGED
@@ -2,21 +2,27 @@ import os
2
  import time
3
  import traceback
4
  import base64
 
 
5
 
6
  # Set HF token
7
  _VALID_HF_TOKEN = base64.b64decode('aGZfU09rZ0JjR1NvdXZRRVNEZ09Xbnl5dk9BRWFablREeFZX').decode('utf-8')
8
  os.environ["HF_TOKEN"] = _VALID_HF_TOKEN
9
 
10
  import spaces
11
- import gradio as gr
12
  import torch
 
 
 
 
 
 
13
  from transformers import AutoModelForCausalLM, AutoTokenizer
14
 
15
  import sys
16
  sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
17
  from kalpana_embed_to_kv import KalpanaDynamicCache
18
 
19
- import threading
20
  _MODEL_LOCK = threading.Lock()
21
  _LOCAL_QWEN_MODEL = None
22
  _LOCAL_TOKENIZER = None
@@ -45,11 +51,7 @@ def _load_model(device, dtype):
45
  def kalpana_generate(prompt: str, max_tokens: int = 256, temperature: float = 0.7) -> dict:
46
  """
47
  Core generation function — KalpanaDynamicCache O(1) KV memory on NVIDIA A100.
48
- Returns a dict with: response, latency_s, memory_mb, layers_intercepted.
49
  """
50
- import transformers as _tf
51
- print(f"[API] transformers=={_tf.__version__}, torch=={torch.__version__}")
52
-
53
  device = "cuda" if torch.cuda.is_available() else "cpu"
54
  dtype = torch.float16 if device == "cuda" else torch.float32
55
 
@@ -60,9 +62,7 @@ def kalpana_generate(prompt: str, max_tokens: int = 256, temperature: float = 0.
60
  inputs = _LOCAL_TOKENIZER(fmt, return_tensors="pt").to(device)
61
 
62
  num_layers = getattr(_LOCAL_QWEN_MODEL.config, "num_hidden_layers", 24)
63
- # 2048 bands for high-fidelity holographic resolution (4x quality)
64
  cache = KalpanaDynamicCache(num_layers=num_layers, bands=2048)
65
- print(f"[API] Cache: {num_layers} layers, bands=2048, is_sliding[:3]={cache.is_sliding[:3]}")
66
 
67
  t0 = time.perf_counter()
68
  with torch.inference_mode():
@@ -79,67 +79,197 @@ def kalpana_generate(prompt: str, max_tokens: int = 256, temperature: float = 0.
79
  resp = _LOCAL_TOKENIZER.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
80
  mem_mb = cache.get_total_memory_mb()
81
 
82
- print(f"[API] Done in {t1-t0:.2f}s | mem={mem_mb:.1f}MB | response={resp[:80]!r}")
83
  return {
84
  "response": resp,
85
  "latency_s": round(t1 - t0, 3),
86
  "memory_mb": round(mem_mb, 2),
87
  "layers_intercepted": num_layers,
 
 
88
  }
89
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
- def _run_inference(prompt: str, max_tokens: int, temperature: float):
92
- """UI wrapper — calls kalpana_generate and unpacks for Gradio outputs."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  if not prompt or not prompt.strip():
94
  return "Enter a prompt above.", "0.0s", "0.0 MB", "0 Layers"
95
  try:
96
- result = kalpana_generate(prompt.strip(), int(max_tokens), float(temperature))
97
  return (
98
- result["response"],
99
- f"{result['latency_s']}s",
100
- f"{result['memory_mb']:.2f} MB",
101
- f"{result['layers_intercepted']}/24 Layers",
102
  )
103
  except Exception as e:
104
  tb = traceback.format_exc()
105
- print("=== ERROR ===\n" + tb)
106
- return f"Error: {type(e).__name__}: {e}\n\n{tb}", "Error", "Error", "Error"
107
 
108
-
109
- # ── Minimal Gradio UI (backend test console only) ────────────────────────────
110
- with gr.Blocks(title="Kalpanā API — ZeroGPU Backend") as demo:
111
  gr.Markdown(
112
- "## ⚡ Kalpanā API ZeroGPU NVIDIA A100 Backend\n"
113
- "Pure O(1) KV Cache (`KalpanaDynamicCache`) running on `zero-a10g`.\n\n"
114
- "👉 **Visual Studio:** [Kalpana RIF Studio](https://huggingface.co/spaces/MaduRox/Kalpana-RIF-Studio)"
115
  )
116
- with gr.Row():
117
- prompt_box = gr.Textbox(label="Prompt", lines=2, value="What is cricket?")
118
- max_tok = gr.Slider(32, 512, value=128, step=32, label="Max Tokens")
119
- temp = gr.Slider(0.0, 1.5, value=0.7, step=0.05, label="Temperature")
120
- btn = gr.Button("▶ Run on A100", variant="primary")
121
- with gr.Row():
122
- out_text = gr.Textbox(label="Response", lines=5, interactive=False)
123
- out_lat = gr.Textbox(label="Latency", interactive=False)
124
- out_mem = gr.Textbox(label="VRAM (O(1))", interactive=False)
125
- out_lay = gr.Textbox(label="Layers", interactive=False)
126
-
127
- btn.click(_run_inference, inputs=[prompt_box, max_tok, temp],
128
- outputs=[out_text, out_lat, out_mem, out_lay])
129
-
130
- # ── Programmatic API endpoint (no Gradio form needed) ───────────────────
131
- gr.Interface(
132
- fn=kalpana_generate,
133
- inputs=[
134
- gr.Textbox(label="prompt"),
135
- gr.Number(label="max_tokens", value=256),
136
- gr.Number(label="temperature", value=0.7),
137
- ],
138
- outputs=gr.JSON(label="result"),
139
- title="Kalpanā API",
140
- description="POST /run/predict with JSON body to call programmatically.",
141
- ).queue()
142
-
143
- demo.queue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  if __name__ == "__main__":
145
- demo.launch()
 
2
  import time
3
  import traceback
4
  import base64
5
+ import threading
6
+ from typing import List, Optional, Dict, Any
7
 
8
  # Set HF token
9
  _VALID_HF_TOKEN = base64.b64decode('aGZfU09rZ0JjR1NvdXZRRVNEZ09Xbnl5dk9BRWFablREeFZX').decode('utf-8')
10
  os.environ["HF_TOKEN"] = _VALID_HF_TOKEN
11
 
12
  import spaces
 
13
  import torch
14
+ import uvicorn
15
+ from fastapi import FastAPI, Request, HTTPException
16
+ from fastapi.middleware.cors import CORSMiddleware
17
+ from fastapi.responses import JSONResponse
18
+ from pydantic import BaseModel, Field
19
+ import gradio as gr
20
  from transformers import AutoModelForCausalLM, AutoTokenizer
21
 
22
  import sys
23
  sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
24
  from kalpana_embed_to_kv import KalpanaDynamicCache
25
 
 
26
  _MODEL_LOCK = threading.Lock()
27
  _LOCAL_QWEN_MODEL = None
28
  _LOCAL_TOKENIZER = None
 
51
  def kalpana_generate(prompt: str, max_tokens: int = 256, temperature: float = 0.7) -> dict:
52
  """
53
  Core generation function — KalpanaDynamicCache O(1) KV memory on NVIDIA A100.
 
54
  """
 
 
 
55
  device = "cuda" if torch.cuda.is_available() else "cpu"
56
  dtype = torch.float16 if device == "cuda" else torch.float32
57
 
 
62
  inputs = _LOCAL_TOKENIZER(fmt, return_tensors="pt").to(device)
63
 
64
  num_layers = getattr(_LOCAL_QWEN_MODEL.config, "num_hidden_layers", 24)
 
65
  cache = KalpanaDynamicCache(num_layers=num_layers, bands=2048)
 
66
 
67
  t0 = time.perf_counter()
68
  with torch.inference_mode():
 
79
  resp = _LOCAL_TOKENIZER.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
80
  mem_mb = cache.get_total_memory_mb()
81
 
 
82
  return {
83
  "response": resp,
84
  "latency_s": round(t1 - t0, 3),
85
  "memory_mb": round(mem_mb, 2),
86
  "layers_intercepted": num_layers,
87
+ "model": MODEL_NAME,
88
+ "bands": 2048
89
  }
90
 
91
+ # ── FastAPI App with Live Swagger at /docs ────────────────────────────────────
92
+ app = FastAPI(
93
+ title="⚡ Kalpanā RIF O(1) Memory API",
94
+ description=(
95
+ "Production REST & Swagger API for **Kalpanā Resonant Interference Field (RIF)** KV Cache.\n\n"
96
+ "Runs on an **NVIDIA A100 80GB GPU** on Hugging Face ZeroGPU with strictly constant O(1) memory.\n\n"
97
+ "👉 **Visual Studio:** [Kalpana RIF Studio](https://huggingface.co/spaces/MaduRox/Kalpana-RIF-Studio)"
98
+ ),
99
+ version="4.2.0",
100
+ docs_url="/docs",
101
+ redoc_url="/redoc"
102
+ )
103
+
104
+ # Enable CORS for Studio and external callers
105
+ app.add_middleware(
106
+ CORSMiddleware,
107
+ allow_origins=["*"],
108
+ allow_credentials=True,
109
+ allow_methods=["*"],
110
+ allow_headers=["*"],
111
+ )
112
+
113
+ class GenerateRequest(BaseModel):
114
+ prompt: str = Field(..., example="What is quantum entanglement and how does it relate to waves?")
115
+ max_tokens: Optional[int] = Field(256, example=256, ge=16, le=1024)
116
+ temperature: Optional[float] = Field(0.7, example=0.7, ge=0.0, le=1.5)
117
+
118
+ class GenerateResponse(BaseModel):
119
+ response: str
120
+ latency_s: float
121
+ memory_mb: float
122
+ layers_intercepted: int
123
+ model: str
124
+ bands: int
125
 
126
+ class ChatMessage(BaseModel):
127
+ role: str
128
+ content: str
129
+
130
+ class ChatCompletionRequest(BaseModel):
131
+ model: Optional[str] = "kalpana-qwen2.5-0.5b"
132
+ messages: List[ChatMessage]
133
+ max_tokens: Optional[int] = 256
134
+ temperature: Optional[float] = 0.7
135
+
136
+ @app.get("/api/health", tags=["System"])
137
+ def health_check():
138
+ return {
139
+ "status": "healthy",
140
+ "gpu_available": torch.cuda.is_available(),
141
+ "device": torch.cuda.get_device_name(0) if torch.cuda.is_available() else "CPU",
142
+ "model": MODEL_NAME,
143
+ "bands": 2048,
144
+ "architecture": "KalpanaDynamicCache O(1)"
145
+ }
146
+
147
+ @app.post("/api/generate", response_model=GenerateResponse, tags=["Inference"])
148
+ def api_generate(req: GenerateRequest):
149
+ """
150
+ Standard generation endpoint. Intercepts all 24 layers of Qwen2.5 with KalpanaDynamicCache.
151
+ """
152
+ if not req.prompt.strip():
153
+ raise HTTPException(status_code=400, detail="Prompt cannot be empty")
154
+ try:
155
+ res = kalpana_generate(req.prompt.strip(), req.max_tokens, req.temperature)
156
+ return res
157
+ except Exception as e:
158
+ tb = traceback.format_exc()
159
+ print("API ERROR:\n" + tb)
160
+ raise HTTPException(status_code=500, detail=str(e))
161
+
162
+ @app.post("/v1/chat/completions", tags=["OpenAI Compatibility"])
163
+ def openai_chat_completions(req: ChatCompletionRequest):
164
+ """
165
+ OpenAI-compatible chat completions endpoint for seamless drop-in integration.
166
+ """
167
+ # Extract user prompt from last message
168
+ prompt = req.messages[-1].content if req.messages else ""
169
+ if not prompt.strip():
170
+ raise HTTPException(status_code=400, detail="Messages cannot be empty")
171
+ try:
172
+ res = kalpana_generate(prompt.strip(), req.max_tokens or 256, req.temperature or 0.7)
173
+ return {
174
+ "id": f"chatcmpl-kalpana-{int(time.time())}",
175
+ "object": "chat.completion",
176
+ "created": int(time.time()),
177
+ "model": req.model,
178
+ "choices": [{
179
+ "index": 0,
180
+ "message": {
181
+ "role": "assistant",
182
+ "content": res["response"]
183
+ },
184
+ "finish_reason": "stop"
185
+ }],
186
+ "usage": {
187
+ "prompt_tokens": len(prompt.split()),
188
+ "completion_tokens": len(res["response"].split()),
189
+ "total_tokens": len(prompt.split()) + len(res["response"].split())
190
+ },
191
+ "kalpana_telemetry": {
192
+ "latency_s": res["latency_s"],
193
+ "vram_mb": res["memory_mb"],
194
+ "layers_intercepted": res["layers_intercepted"],
195
+ "bands": res["bands"]
196
+ }
197
+ }
198
+ except Exception as e:
199
+ tb = traceback.format_exc()
200
+ print("CHAT ERROR:\n" + tb)
201
+ raise HTTPException(status_code=500, detail=str(e))
202
+
203
+
204
+ # ── Gradio Interactive Console & Live Swagger Docs ───────────────────────────
205
+ def _run_ui(prompt: str, max_tokens: int, temperature: float):
206
  if not prompt or not prompt.strip():
207
  return "Enter a prompt above.", "0.0s", "0.0 MB", "0 Layers"
208
  try:
209
+ res = kalpana_generate(prompt.strip(), int(max_tokens), float(temperature))
210
  return (
211
+ res["response"],
212
+ f"{res['latency_s']}s",
213
+ f"{res['memory_mb']:.2f} MB",
214
+ f"{res['layers_intercepted']}/24 Layers",
215
  )
216
  except Exception as e:
217
  tb = traceback.format_exc()
218
+ print("UI ERROR:\n" + tb)
219
+ return f"Error: {e}\n\n{tb}", "Error", "Error", "Error"
220
 
221
+ with gr.Blocks(title="Kalpanā API — ZeroGPU Backend", theme=gr.themes.Soft()) as demo:
 
 
222
  gr.Markdown(
223
+ "# ⚡ Kalpanā RIF O(1) Memory API & Server\n"
224
+ "High-performance constant-memory LLM inference engine running on an **NVIDIA A100 80GB GPU** (`zero-a10g`).\n\n"
225
+ "👉 **Interactive Live Swagger Docs:** [`/docs`](/docs) • **Visual Studio:** [Kalpana RIF Studio](https://huggingface.co/spaces/MaduRox/Kalpana-RIF-Studio)"
226
  )
227
+ with gr.Tabs():
228
+ with gr.TabItem(" Live Interactive Testbench"):
229
+ with gr.Row():
230
+ prompt_box = gr.Textbox(label="Prompt", lines=3, value="Explain how neural networks learn in simple terms.")
231
+ with gr.Column():
232
+ max_tok = gr.Slider(32, 512, value=128, step=32, label="Max Tokens")
233
+ temp = gr.Slider(0.0, 1.5, value=0.7, step=0.05, label="Temperature")
234
+ btn = gr.Button("🚀 Run on NVIDIA A100", variant="primary")
235
+
236
+ with gr.Row():
237
+ out_text = gr.Textbox(label="Generated Response", lines=6, interactive=False)
238
+ with gr.Row():
239
+ out_lat = gr.Textbox(label="Latency", interactive=False)
240
+ out_mem = gr.Textbox(label="O(1) VRAM Footprint", interactive=False)
241
+ out_lay = gr.Textbox(label="Intercepted Layers", interactive=False)
242
+
243
+ btn.click(_run_ui, inputs=[prompt_box, max_tok, temp], outputs=[out_text, out_lat, out_mem, out_lay])
244
+
245
+ with gr.TabItem("📖 Live Swagger / REST API Documentation"):
246
+ gr.Markdown(
247
+ "### 🌐 REST API Endpoints\n\n"
248
+ "You can call this space directly from any programming language via standard HTTP requests.\n\n"
249
+ "#### 1. Standard REST Endpoint (`POST /api/generate`)\n"
250
+ "```bash\n"
251
+ "curl -X POST https://madurox-kalpana-api-gpu.hf.space/api/generate \\\n"
252
+ " -H 'Content-Type: application/json' \\\n"
253
+ " -d '{\"prompt\": \"What is cricket?\", \"max_tokens\": 128, \"temperature\": 0.7}'\n"
254
+ "```\n\n"
255
+ "#### 2. OpenAI-Compatible Endpoint (`POST /v1/chat/completions`)\n"
256
+ "```python\n"
257
+ "from openai import OpenAI\n\n"
258
+ "client = OpenAI(\n"
259
+ " base_url='https://madurox-kalpana-api-gpu.hf.space/v1',\n"
260
+ " api_key='not-needed'\n"
261
+ ")\n\n"
262
+ "response = client.chat.completions.create(\n"
263
+ " model='kalpana-qwen2.5-0.5b',\n"
264
+ " messages=[{'role': 'user', 'content': 'What is RIF memory?'}]\n"
265
+ ")\n"
266
+ "print(response.choices[0].message.content)\n"
267
+ "```\n\n"
268
+ "👉 **Access the Full Interactive OpenAPI Swagger UI at [`/docs`](/docs)**"
269
+ )
270
+
271
+ # Mount Gradio onto FastAPI
272
+ app = gr.mount_gradio_app(app, demo, path="/")
273
+
274
  if __name__ == "__main__":
275
+ uvicorn.run(app, host="0.0.0.0", port=7860)
app.py.metadata.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
- "summary": "Minimal Gradio-free FastAPI-style backend for ZeroGPU - no UI, just a hidden API endpoint",
3
- "updatedAt": "2026-08-21T06:31:09.299251400Z"
4
  }
 
1
  {
2
+ "summary": "FastAPI + Gradio + Live Swagger backend for Kalpana-API-GPU",
3
+ "updatedAt": "2026-08-22T06:19:25.314359700Z"
4
  }
requirements.txt CHANGED
@@ -1,5 +1,8 @@
1
  transformers==5.8.0
2
  accelerate==1.8.1
3
  gradio>=5.0.0
4
- numpy
 
 
5
  requests
 
 
1
  transformers==5.8.0
2
  accelerate==1.8.1
3
  gradio>=5.0.0
4
+ fastapi
5
+ uvicorn
6
+ pydantic
7
  requests
8
+ numpy
requirements.txt.metadata.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
- "summary": "Clean requirements.txt without failing release URL",
3
- "updatedAt": "2026-08-21T08:31:04.897131800Z"
4
  }
 
1
  {
2
+ "summary": "Requirements with fastapi and uvicorn for Swagger support",
3
+ "updatedAt": "2026-08-22T06:19:34.353996200Z"
4
  }