MaduRox commited on
Commit
9111b37
·
1 Parent(s): d8b891e

v4.0: External RIF + Any LLM Provider

Browse files

Architecture:
- RIF Engine: Pure CPU, O(1) memory, TF-IDF retrieval
- LLM: Any provider (HF free default, Groq, OpenAI, etc.)
- No GPU, no torch, no model downloads

Endpoints:
- POST /v1/providers/register — BYO LLM (Groq, OpenAI, etc.)
- GET /v1/providers — list known providers
- GET /v1/models — list default + custom models
- POST /v1/chat/completions — RIF retrieval + LLM inference
- Knowledge Pack CRUD (compile, upload, download)

Default models (free, no signup):
- Qwen 2.5 72B, Llama 3.1 8B, Mistral 7B via HF Inference

Files changed (6) hide show
  1. README.md +7 -8
  2. app.py +520 -407
  3. kalpana/__init__.py +0 -10
  4. kalpana/core.py +0 -355
  5. kalpana/integrations.py +0 -131
  6. requirements.txt +3 -8
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: "Kalpanā API (ZeroGPU)"
3
  emoji: "🧠"
4
  colorFrom: blue
5
  colorTo: purple
@@ -7,16 +7,15 @@ sdk: gradio
7
  sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
10
- suggested_hardware: zero-a10g
11
  ---
12
 
13
- # Kalpanā AI API — ZeroGPU Edition
14
 
15
- OpenAI-compatible API powered by **Qwen 0.5B** + **Kalpanā RIF Engine**.
16
 
17
- - 🚀 **Free GPU inference** via HuggingFace ZeroGPU (A10G)
18
- - 🧠 **RIF Engine** on CPU 6.3 MB constant O(1) memory
19
- - 📦 **Knowledge Packs** — compile documents into portable `.kp` files
20
- - 💰 **Cost comparison** vs GPT-4o included in every response
21
 
22
  **[View API Docs →](/docs)**
 
1
  ---
2
+ title: "Kalpanā API"
3
  emoji: "🧠"
4
  colorFrom: blue
5
  colorTo: purple
 
7
  sdk_version: "4.44.0"
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
+ # Kalpanā AI API — External RIF + Any LLM
13
 
14
+ RIF Engine runs on free CPU. LLM inference via any provider (HF, Groq, OpenAI, etc.)
15
 
16
+ - 🧠 **RIF Engine** 6.3 MB O(1) constant memory, unlimited context
17
+ - 🔌 **Bring Your Own LLM** — register any OpenAI-compatible provider
18
+ - 📦 **Knowledge Packs** — compile documents into portable .kp files
19
+ - 💰 **Token reduction** 125x fewer tokens sent to LLM
20
 
21
  **[View API Docs →](/docs)**
app.py CHANGED
@@ -1,449 +1,611 @@
1
  import os
2
- import pwd
3
-
4
- # Fix Hugging Face Spaces Permission & UID Errors
5
- os.environ["HOME"] = "/tmp"
6
- os.environ["HF_HOME"] = "/tmp/huggingface_cache"
7
- os.environ["TORCH_EXTENSIONS_DIR"] = "/tmp/torch_extensions"
8
- os.environ["TRITON_CACHE_DIR"] = "/tmp/triton_cache"
9
-
10
- # ── HuggingFace login ──
11
- _hf_token = os.environ.get("HF_TOKEN", "")
12
- if _hf_token:
13
- try:
14
- from huggingface_hub import login
15
- login(token=_hf_token, add_to_git_credential=False)
16
- print("✅ HuggingFace login successful")
17
- except Exception as _e:
18
- print(f"⚠️ HuggingFace login failed: {_e}")
19
-
20
- # Monkey-patch getpwuid for HF Spaces
21
- _original_getpwuid = pwd.getpwuid
22
- def _mock_getpwuid(uid):
23
- try:
24
- return _original_getpwuid(uid)
25
- except KeyError:
26
- import collections
27
- MockUser = collections.namedtuple('struct_passwd', ['pw_name', 'pw_passwd', 'pw_uid', 'pw_gid', 'pw_gecos', 'pw_dir', 'pw_shell'])
28
- return MockUser('user', 'x', uid, uid, '', '/tmp', '/bin/bash')
29
- pwd.getpwuid = _mock_getpwuid
30
-
31
- import spaces
32
- from fastapi import FastAPI, HTTPException, Request, Header, Depends
33
- from fastapi.responses import StreamingResponse, JSONResponse, RedirectResponse, Response
34
- from pydantic import BaseModel, Field
35
- import gradio as gr
36
  import time
37
  import uuid
38
- import torch
39
  import json
40
- import threading
 
 
41
  import collections
42
  import traceback
43
- import io
44
- from typing import List, Optional
45
- from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
46
- from kalpana.integrations import KalpanaHuggingFaceCache
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
- # ── Device: CPU for loading, ZeroGPU for inference ──
49
- DEVICE = "cpu"
50
- DTYPE = torch.float32
 
 
51
 
52
- # ── Cost Constants ──
53
- OPENAI_INPUT_PER_1M = 2.50
54
  OPENAI_OUTPUT_PER_1M = 10.00
55
  KALPANA_SALE_PER_1M = 3.00
56
 
57
- # ── Pydantic Models ──
58
  class ChatMessage(BaseModel):
59
- role: str = Field(..., example="user")
60
- content: str = Field(..., example="Explain quantum computing.")
61
 
62
- class ChatCompletionRequest(BaseModel):
63
- model: str = Field("kalpana-qwen-0.5b-rif", description="Model identifier")
64
  messages: List[ChatMessage] = Field(...)
65
- max_tokens: int = Field(512, ge=1, le=2048)
66
  temperature: float = Field(0.7, ge=0.0, le=2.0)
67
  bandwidth: int = Field(2048)
68
- active_pack_id: Optional[str] = Field(None)
69
 
70
- class CompileKpRequest(BaseModel):
71
- model: str = "kalpana-qwen-0.5b-rif"
72
- text: str
73
- bandwidth: int = 2048
 
74
 
75
- class ExportKpRequest(BaseModel):
76
- model: str = Field("kalpana-qwen-0.5b-rif")
77
- messages: List[ChatMessage] = Field(...)
78
  bandwidth: int = Field(2048)
79
 
80
- class ModelLoadRequest(BaseModel):
81
- model: str
82
-
83
- # ── Initialize FastAPI ──
84
  app = FastAPI(
85
  title="Kalpanā AI API",
86
  description=(
87
- "## Kalpanā AI — OpenAI-Compatible API (ZeroGPU Edition)\n\n"
88
- "Drop-in replacement for `/v1/chat/completions` with unlimited context via RIF.\n\n"
89
- "**Model:** Qwen 0.5B on free ZeroGPU (A10G)\n"
90
- "**RIF Engine:** O(1) constant memory on CPU\n\n"
 
 
 
 
 
 
 
91
  "### Pricing\n"
92
  "| Plan | Tokens | Knowledge Packs |\n"
93
  "|---|---|---|\n"
94
- "| **Free** | 3M tokens | 10 packs |\n"
95
- "| **Pay-As-You-Go** | $9 / 3M tokens | Unlimited |\n\n"
96
- "**Every response includes `cost_comparison` vs GPT-4o.**"
97
  ),
98
- version="3.1.0",
99
  contact={"name": "Vijñāna AI", "url": "https://huggingface.co/MaduRox"},
100
  openapi_tags=[
101
- {"name": "Chat", "description": "Generate AI responses OpenAI compatible"},
 
 
102
  {"name": "Knowledge Packs", "description": "Compile documents into portable .kp files"},
103
- {"name": "Models", "description": "List and manage models"},
104
  {"name": "System", "description": "Health check"},
105
  ]
106
  )
107
 
108
  @app.exception_handler(Exception)
109
  async def global_exception_handler(request: Request, exc: Exception):
110
- return JSONResponse(status_code=500, content={"message": str(exc), "trace": traceback.format_exc()})
111
 
112
- from fastapi.middleware.cors import CORSMiddleware
113
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
114
 
115
- # ── Rate Limiting ──
116
- _RATE_WINDOWS = collections.defaultdict(list)
117
- RATE_LIMIT_REQUESTS = 60
118
- RATE_LIMIT_WINDOW_SEC = 3600
119
- MAX_PACKS_FREE = 50
120
- _IP_PACKS = collections.defaultdict(set)
121
-
122
  def _get_client_ip(request: Request) -> str:
123
  forwarded = request.headers.get("x-forwarded-for")
124
  return forwarded.split(",")[0].strip() if forwarded else (request.client.host if request.client else "unknown")
125
 
126
- def check_rate_limit(request: Request, authorization: Optional[str] = Header(default=None)):
127
  ip = _get_client_ip(request)
128
  now = time.time()
129
- _RATE_WINDOWS[ip] = [t for t in _RATE_WINDOWS[ip] if now - t < RATE_LIMIT_WINDOW_SEC]
130
- if len(_RATE_WINDOWS[ip]) >= RATE_LIMIT_REQUESTS:
131
- raise HTTPException(status_code=429, detail="Rate limit exceeded: 60 requests/hour on free tier.")
132
  _RATE_WINDOWS[ip].append(now)
133
- return {"tier": "free", "ip": ip}
134
-
135
- # ── Single Model: Qwen 0.5B ──
136
- MODEL_ID = "kalpana-qwen-0.5b-rif"
137
- HF_MODEL_NAME = "Qwen/Qwen2-0.5B-Instruct"
138
- MODELS = {MODEL_ID: HF_MODEL_NAME}
139
-
140
- print(f"📦 Loading {HF_MODEL_NAME} on CPU (will use ZeroGPU for inference)...")
141
- tokenizer = AutoTokenizer.from_pretrained(HF_MODEL_NAME, token=_hf_token)
142
- model = AutoModelForCausalLM.from_pretrained(
143
- HF_MODEL_NAME,
144
- torch_dtype=torch.float32,
145
- device_map="cpu",
146
- token=_hf_token
147
- )
148
- print(f"✅ {HF_MODEL_NAME} loaded on CPU")
149
-
150
- # ── Knowledge Packs Storage ──
151
- ACTIVE_PACKS = {}
152
-
153
- # ── ZeroGPU Inference Function ──
154
- @spaces.GPU(duration=60)
155
- def gpu_generate(input_ids, attention_mask, kalpana_cache, max_new_tokens, temperature, eos_token_ids):
156
- """Run model.generate on ZeroGPU. Model auto-moves to GPU."""
157
- model.to("cuda")
158
- input_ids = input_ids.to("cuda")
159
- attention_mask = attention_mask.to("cuda")
160
-
161
- # Move cache to GPU
162
- if hasattr(kalpana_cache, 'key_cache'):
163
- kalpana_cache.key_cache = [k.to("cuda") if k is not None else None for k in kalpana_cache.key_cache]
164
- kalpana_cache.value_cache = [v.to("cuda") if v is not None else None for v in kalpana_cache.value_cache]
165
-
166
- with torch.no_grad():
167
- output = model.generate(
168
- input_ids=input_ids,
169
- attention_mask=attention_mask,
170
- max_new_tokens=max_new_tokens,
171
- temperature=temperature,
172
- do_sample=temperature > 0,
173
- top_p=0.9 if temperature > 0 else 1.0,
174
- repetition_penalty=1.1,
175
- past_key_values=kalpana_cache,
176
- use_cache=True,
177
- pad_token_id=tokenizer.eos_token_id,
178
- eos_token_id=eos_token_ids,
179
- )
180
-
181
- # Move cache back to CPU for storage
182
- if hasattr(kalpana_cache, 'key_cache'):
183
- kalpana_cache.key_cache = [k.to("cpu") if k is not None else None for k in kalpana_cache.key_cache]
184
- kalpana_cache.value_cache = [v.to("cpu") if v is not None else None for v in kalpana_cache.value_cache]
185
-
186
- result = output.to("cpu")
187
- model.to("cpu")
188
- torch.cuda.empty_cache()
189
- return result, kalpana_cache
190
-
191
- @spaces.GPU(duration=60)
192
- def gpu_forward_pass(input_ids, attention_mask, kalpana_cache):
193
- """Run a forward pass on GPU to build KV cache from document text."""
194
- model.to("cuda")
195
- input_ids = input_ids.to("cuda")
196
- attention_mask = attention_mask.to("cuda")
197
-
198
- if hasattr(kalpana_cache, 'key_cache'):
199
- kalpana_cache.key_cache = [k.to("cuda") if k is not None else None for k in kalpana_cache.key_cache]
200
- kalpana_cache.value_cache = [v.to("cuda") if v is not None else None for v in kalpana_cache.value_cache]
201
-
202
- with torch.no_grad():
203
- model(input_ids=input_ids, attention_mask=attention_mask, past_key_values=kalpana_cache, use_cache=True)
204
-
205
- if hasattr(kalpana_cache, 'key_cache'):
206
- kalpana_cache.key_cache = [k.to("cpu") if k is not None else None for k in kalpana_cache.key_cache]
207
- kalpana_cache.value_cache = [v.to("cpu") if v is not None else None for v in kalpana_cache.value_cache]
208
 
209
- model.to("cpu")
210
- torch.cuda.empty_cache()
211
- return kalpana_cache
212
 
213
- # ── API Endpoints ──
214
-
215
- @app.get("/v1/models", tags=["Models"], summary="List Available Models")
216
- def list_models():
217
- return {"object": "list", "data": [{"id": MODEL_ID, "object": "model", "owned_by": "vijnana-ai", "engine": "kalpana-rif", "context_window": "Unlimited (O(1) memory)"}]}
 
 
 
 
 
218
 
219
- @app.post("/v1/models/load", tags=["Models"], summary="Pre-warm Model")
220
- def load_model_endpoint(req: ModelLoadRequest):
221
- return {"status": "success", "model": MODEL_ID, "loaded": True, "note": "Qwen 0.5B already loaded on CPU, GPU allocated on-demand via ZeroGPU"}
 
 
 
 
 
 
 
 
 
 
 
222
 
223
- @app.post("/v1/chat/completions", tags=["Chat"], summary="Chat Completions",
224
- description="Generate AI response — OpenAI compatible. Includes cost comparison vs GPT-4o.")
225
- def chat_completions(req: ChatCompletionRequest, request: Request = None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  try:
227
  t0 = time.perf_counter()
228
-
229
- prompt = tokenizer.apply_chat_template(
230
- [{"role": m.role, "content": m.content} for m in req.messages],
231
- tokenize=False, add_generation_prompt=True
232
- )
233
- if hasattr(req, "active_pack_id") and req.active_pack_id and req.active_pack_id in ACTIVE_PACKS:
234
- prompt = prompt.replace("<|begin_of_text|>", "")
235
-
236
- inputs = tokenizer(prompt, return_tensors="pt", add_special_tokens=False)
237
-
238
- # Paradigm B: Bounded Prompt (≤ 1000 tokens)
239
- MAX_INPUT_BOUND = 1000
240
- if inputs["input_ids"].shape[1] > MAX_INPUT_BOUND:
241
- inputs["input_ids"] = inputs["input_ids"][:, -MAX_INPUT_BOUND:]
242
- if "attention_mask" in inputs:
243
- inputs["attention_mask"] = inputs["attention_mask"][:, -MAX_INPUT_BOUND:]
244
-
245
- # Create or load KalpanaHuggingFaceCache
246
- auto_created_pack_id = None
247
- if hasattr(req, "active_pack_id") and req.active_pack_id and req.active_pack_id in ACTIVE_PACKS:
248
- pack_data = ACTIVE_PACKS[req.active_pack_id]
249
- kalpana_cache = KalpanaHuggingFaceCache(
250
- config=model.config,
251
- bandwidth=pack_data.get("metadata", {}).get("bandwidth", req.bandwidth),
252
- device="cpu"
253
- )
254
- if hasattr(kalpana_cache, "key_cache"):
255
- kalpana_cache.key_cache = [k.clone() for k in pack_data["state"]["key_cache"]]
256
- kalpana_cache.value_cache = [v.clone() for v in pack_data["state"]["value_cache"]]
257
- if len(kalpana_cache.key_cache) > 0 and kalpana_cache.key_cache[0] is not None:
258
- pack_len = kalpana_cache.key_cache[0].shape[2]
259
- kalpana_cache._seen_tokens = [pack_len] * kalpana_cache.num_layers
260
- if "attention_mask" in inputs:
261
- past_mask = torch.ones((inputs["input_ids"].shape[0], pack_len), dtype=inputs["attention_mask"].dtype)
262
- inputs["attention_mask"] = torch.cat([past_mask, inputs["attention_mask"]], dim=1)
263
- else:
264
- kalpana_cache = KalpanaHuggingFaceCache(config=model.config, bandwidth=req.bandwidth, device="cpu")
265
- auto_created_pack_id = f"kp_{uuid.uuid4().hex[:8]}"
266
- ACTIVE_PACKS[auto_created_pack_id] = {
267
- "metadata": {"tokenCount": 0, "model": MODEL_ID, "bandwidth": req.bandwidth, "created_at": int(time.time()), "auto_created": True},
268
- "state": {}
269
- }
270
- if request:
271
- ip = _get_client_ip(request)
272
- _IP_PACKS[ip].add(auto_created_pack_id)
273
-
274
- effective_pack_id = req.active_pack_id if (req.active_pack_id and req.active_pack_id in ACTIVE_PACKS) else auto_created_pack_id
275
-
276
- # EOS tokens
277
- eos_tokens = [tokenizer.eos_token_id]
278
- if "<|eot_id|>" in tokenizer.vocab:
279
- eos_tokens.append(tokenizer.convert_tokens_to_ids("<|eot_id|>"))
280
-
281
- # Run inference on ZeroGPU
282
- output, kalpana_cache = gpu_generate(
283
- inputs["input_ids"], inputs["attention_mask"],
284
- kalpana_cache, req.max_tokens, req.temperature, eos_tokens
285
  )
286
-
287
- generated_text = tokenizer.decode(output[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
288
  t1 = time.perf_counter()
289
-
290
- prompt_tokens = int(inputs["input_ids"].shape[1])
291
- completion_tokens = len(tokenizer(generated_text).input_ids)
292
- total_tokens = prompt_tokens + completion_tokens
293
- generation_time = round(t1 - t0, 3)
294
-
295
- # Cost calculations
296
  openai_cost = (prompt_tokens / 1e6) * OPENAI_INPUT_PER_1M + (completion_tokens / 1e6) * OPENAI_OUTPUT_PER_1M
297
  kalpana_cost = (total_tokens / 1e6) * KALPANA_SALE_PER_1M
298
  you_saved = max(openai_cost - kalpana_cost, 0)
299
  savings_pct = round((you_saved / openai_cost) * 100, 1) if openai_cost > 0 else 0.0
300
-
301
- head_dim = model.config.hidden_size // getattr(model.config, "num_key_value_heads", getattr(model.config, "num_attention_heads", 16))
302
- rif_mb = round((2 * req.bandwidth * head_dim * 4) / (1024 ** 2), 2)
303
- num_layers = getattr(model.config, "num_hidden_layers", 32)
304
- num_kv_heads = getattr(model.config, "num_key_value_heads", 8)
305
- std_kv_gb = round((2 * 2 * num_layers * num_kv_heads * head_dim * 1_000_000) / (1024 ** 3), 2)
306
-
307
  response = {
308
  "id": f"chatcmpl-{uuid.uuid4().hex[:12]}",
309
  "object": "chat.completion",
310
  "created": int(time.time()),
311
- "model": MODEL_ID,
312
- "choices": [{"index": 0, "message": {"role": "assistant", "content": generated_text}, "finish_reason": "stop"}],
313
- "usage": {"prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": total_tokens},
314
- "generation_time_sec": generation_time,
 
 
 
 
 
 
 
 
 
 
 
 
315
  "cost_comparison": {
316
- "request_economics": {
317
- "openai_gpt4o_cost": f"${openai_cost:.6f}",
318
- "kalpana_cost": f"${kalpana_cost:.6f}",
319
- "you_saved": f"${you_saved:.6f}",
320
- "savings_percentage": f"{savings_pct}%"
321
- }
322
  },
323
- "kalpana_rif_state": f"{rif_mb} MB (O(1) constant)",
324
- "standard_kv_cache_1m_ctx": f"{std_kv_gb} GB (O(N) — linear growth)"
325
  }
326
-
327
- # Save updated cache back
328
- if effective_pack_id and effective_pack_id in ACTIVE_PACKS:
329
- try:
330
- ACTIVE_PACKS[effective_pack_id]["state"] = {
331
- "key_cache": [k.clone().to("cpu") if k is not None else None for k in kalpana_cache.key_cache],
332
- "value_cache": [v.clone().to("cpu") if v is not None else None for v in kalpana_cache.value_cache]
333
- }
334
- new_len = kalpana_cache.key_cache[0].shape[2] if kalpana_cache.key_cache and kalpana_cache.key_cache[0] is not None else 0
335
- ACTIVE_PACKS[effective_pack_id]["metadata"]["tokenCount"] = new_len
336
- response["active_pack_id"] = effective_pack_id
337
- except Exception as save_err:
338
- print(f"[WARN] Could not save cache: {save_err}")
339
-
340
- if auto_created_pack_id:
341
- response["auto_created_pack_id"] = auto_created_pack_id
342
-
343
  return response
344
-
345
  except Exception as e:
346
- raise HTTPException(status_code=500, detail=f"Inference Error: {str(e)}\n\n{traceback.format_exc()}")
347
 
348
- # ── Knowledge Pack Endpoints ──
349
-
350
- def _build_kp_payload(text: str, bandwidth: int):
351
- """Compile text into a knowledge pack using GPU forward pass."""
352
- system_msg = {"role": "system", "content": f"Document Context:\n{text}\nUse this to answer questions."}
353
- formatted_prompt = tokenizer.apply_chat_template([system_msg], tokenize=False, add_generation_prompt=False)
354
- inputs = tokenizer(formatted_prompt, return_tensors="pt", add_special_tokens=False)
355
- kalpana_cache = KalpanaHuggingFaceCache(config=model.config, bandwidth=bandwidth, device="cpu")
356
-
357
- # Forward pass on GPU
358
- kalpana_cache = gpu_forward_pass(inputs["input_ids"], inputs["attention_mask"], kalpana_cache)
359
-
360
- return {
361
- "metadata": {"tokenCount": int(inputs["input_ids"].shape[1]), "model": MODEL_ID, "bandwidth": bandwidth, "created_at": int(time.time())},
362
- "state": {
363
- "key_cache": [k.clone() if k is not None else None for k in kalpana_cache.key_cache],
364
- "value_cache": [v.clone() if v is not None else None for v in kalpana_cache.value_cache]
365
- }
366
- }
367
 
368
- @app.post("/v1/knowledge_packs/compile", tags=["Knowledge Packs"], summary="Compile Text into Knowledge Pack")
 
 
369
  def compile_kp(req: CompileKpRequest, request: Request = None, _rate: dict = Depends(check_rate_limit)):
370
  try:
371
  ip = _rate["ip"]
372
- if len(_IP_PACKS[ip]) >= MAX_PACKS_FREE:
373
- raise HTTPException(status_code=429, detail=f"Free tier: max {MAX_PACKS_FREE} packs.")
374
- kp_payload = _build_kp_payload(req.text, req.bandwidth)
 
 
 
375
  pack_id = f"kp_{uuid.uuid4().hex[:8]}"
376
- ACTIVE_PACKS[pack_id] = kp_payload
377
  _IP_PACKS[ip].add(pack_id)
378
- return {"pack_id": pack_id, "token_count": kp_payload["metadata"]["tokenCount"], "model": MODEL_ID}
 
 
 
 
 
 
 
379
  except HTTPException:
380
  raise
381
  except Exception as e:
382
  raise HTTPException(status_code=500, detail=f"Failed: {str(e)}\n{traceback.format_exc()}")
383
 
384
- from fastapi import File, UploadFile
385
-
386
- @app.post("/v1/knowledge_packs/compile_file", tags=["Knowledge Packs"], summary="Compile PDF/TXT into Knowledge Pack")
387
- async def compile_kp_file(model: str, file: UploadFile = File(...), request: Request = None, _rate: dict = Depends(check_rate_limit)):
388
  try:
389
  ip = _rate["ip"]
390
- if len(_IP_PACKS[ip]) >= MAX_PACKS_FREE:
391
- raise HTTPException(status_code=429, detail=f"Free tier: max {MAX_PACKS_FREE} packs.")
 
392
  contents = await file.read()
393
  filename = file.filename or ""
 
394
  if filename.lower().endswith(".pdf"):
395
  from pypdf import PdfReader
396
  reader = PdfReader(io.BytesIO(contents))
397
  text = "\n".join(page.extract_text() or "" for page in reader.pages)
398
  else:
399
  text = contents.decode("utf-8", errors="ignore")
 
400
  if not text.strip():
401
  raise HTTPException(status_code=422, detail="File is empty.")
402
- kp_payload = _build_kp_payload(text, 2048)
403
- kp_payload["metadata"]["source_file"] = filename
 
 
404
  pack_id = f"kp_{uuid.uuid4().hex[:8]}"
405
- ACTIVE_PACKS[pack_id] = kp_payload
406
  _IP_PACKS[ip].add(pack_id)
407
- return {"pack_id": pack_id, "token_count": kp_payload["metadata"]["tokenCount"], "model": MODEL_ID, "source_file": filename}
 
 
 
 
 
 
 
408
  except HTTPException:
409
  raise
410
  except Exception as e:
411
  raise HTTPException(status_code=500, detail=f"Failed: {str(e)}\n{traceback.format_exc()}")
412
 
413
- @app.post("/v1/knowledge_packs/upload", tags=["Knowledge Packs"], summary="Import a .kp File")
 
414
  async def upload_kp(file: UploadFile = File(...), request: Request = None, _rate: dict = Depends(check_rate_limit)):
415
  try:
416
  ip = _rate["ip"]
417
- if len(_IP_PACKS[ip]) >= MAX_PACKS_FREE:
418
- raise HTTPException(status_code=429, detail=f"Free tier: max {MAX_PACKS_FREE} packs.")
419
  contents = await file.read()
420
- kp_payload = torch.load(io.BytesIO(contents), map_location="cpu", weights_only=False)
421
  pack_id = f"kp_{uuid.uuid4().hex[:8]}"
422
- if "metadata" not in kp_payload:
423
- kp_payload["metadata"] = {}
424
- kp_payload["metadata"]["created_at"] = int(time.time())
425
- ACTIVE_PACKS[pack_id] = kp_payload
426
  _IP_PACKS[ip].add(pack_id)
427
- return {"pack_id": pack_id, "metadata": kp_payload.get("metadata", {})}
428
- except HTTPException:
429
- raise
430
  except Exception as e:
431
  raise HTTPException(status_code=400, detail=f"Invalid .kp file: {str(e)}")
432
 
433
- @app.get("/v1/knowledge_packs/{pack_id}/download", tags=["Knowledge Packs"], summary="Download .kp File")
 
434
  def download_kp(pack_id: str, name: Optional[str] = None):
435
  if pack_id not in ACTIVE_PACKS:
436
  raise HTTPException(status_code=404, detail=f"Pack '{pack_id}' not found.")
437
- buffer = io.BytesIO()
438
- torch.save(ACTIVE_PACKS[pack_id], buffer)
439
  safe_name = "".join(c if c.isalnum() or c in ('-', '_', ' ') else '_' for c in (name or pack_id)).strip()[:60]
440
- return Response(content=buffer.getvalue(), media_type="application/octet-stream",
441
  headers={"Content-Disposition": f'attachment; filename="{safe_name}.kp"'})
442
 
443
  @app.get("/v1/knowledge_packs", tags=["Knowledge Packs"], summary="List Active Packs")
444
  def list_kp():
445
- result = [{"pack_id": pid, "token_count": p.get("metadata", {}).get("tokenCount", 0), "model": p.get("metadata", {}).get("model", "unknown")}
446
- for pid, p in ACTIVE_PACKS.items()]
447
  return {"packs": result, "count": len(result)}
448
 
449
  @app.delete("/v1/knowledge_packs/{pack_id}", tags=["Knowledge Packs"], summary="Delete Pack")
@@ -455,92 +617,45 @@ def delete_kp(pack_id: str):
455
  ip_set.discard(pack_id)
456
  return {"status": "deleted", "pack_id": pack_id}
457
 
458
- # ── Health ──
459
  @app.get("/health", tags=["System"], summary="Health Check")
460
- def read_root():
461
  return {
462
  "status": "ok",
463
- "model": MODEL_ID,
464
- "hf_model": HF_MODEL_NAME,
465
- "device": "ZeroGPU (A10G on-demand, model on CPU)",
466
  "active_packs": len(ACTIVE_PACKS),
467
- "version": "3.1.0-zerogpu"
 
468
  }
469
 
470
- # ── Gradio UI (must use @spaces.GPU decorated function) ──
471
- @spaces.GPU(duration=60)
472
- def gradio_inference(user_message, max_tokens, bandwidth):
473
- """Gradio wrapper that triggers ZeroGPU."""
474
- req = ChatCompletionRequest(
475
- model=MODEL_ID,
476
- messages=[ChatMessage(role="user", content=user_message)],
477
- max_tokens=int(max_tokens), bandwidth=int(bandwidth)
478
- )
479
- # Inline the inference here so @spaces.GPU wraps it
480
- t0 = time.perf_counter()
481
- prompt = tokenizer.apply_chat_template(
482
- [{"role": "user", "content": user_message}],
483
- tokenize=False, add_generation_prompt=True
484
- )
485
- inputs = tokenizer(prompt, return_tensors="pt", add_special_tokens=False)
486
- if inputs["input_ids"].shape[1] > 1000:
487
- inputs["input_ids"] = inputs["input_ids"][:, -1000:]
488
- if "attention_mask" in inputs:
489
- inputs["attention_mask"] = inputs["attention_mask"][:, -1000:]
490
-
491
- kalpana_cache = KalpanaHuggingFaceCache(config=model.config, bandwidth=int(bandwidth), device="cuda")
492
-
493
- model.to("cuda")
494
- input_ids = inputs["input_ids"].to("cuda")
495
- attention_mask = inputs["attention_mask"].to("cuda")
496
-
497
- eos_tokens = [tokenizer.eos_token_id]
498
-
499
- with torch.no_grad():
500
- output = model.generate(
501
- input_ids=input_ids, attention_mask=attention_mask,
502
- max_new_tokens=int(max_tokens), temperature=0.7, do_sample=True,
503
- top_p=0.9, repetition_penalty=1.1,
504
- past_key_values=kalpana_cache, use_cache=True,
505
- pad_token_id=tokenizer.eos_token_id, eos_token_id=eos_tokens
506
  )
507
-
508
- generated_text = tokenizer.decode(output[0][input_ids.shape[1]:], skip_special_tokens=True)
509
- t1 = time.perf_counter()
510
-
511
- model.to("cpu")
512
- torch.cuda.empty_cache()
513
-
514
- prompt_tokens = int(input_ids.shape[1])
515
- completion_tokens = len(tokenizer(generated_text).input_ids)
516
- total_tokens = prompt_tokens + completion_tokens
517
-
518
- openai_cost = (prompt_tokens / 1e6) * OPENAI_INPUT_PER_1M + (completion_tokens / 1e6) * OPENAI_OUTPUT_PER_1M
519
- kalpana_cost = (total_tokens / 1e6) * KALPANA_SALE_PER_1M
520
- savings_pct = round(((openai_cost - kalpana_cost) / openai_cost) * 100, 1) if openai_cost > 0 else 0.0
521
-
522
- return {
523
- "model": MODEL_ID,
524
- "response": generated_text,
525
- "generation_time_sec": round(t1 - t0, 3),
526
- "usage": {"prompt_tokens": prompt_tokens, "completion_tokens": completion_tokens, "total_tokens": total_tokens},
527
- "cost_comparison": {
528
- "openai_gpt4o_cost": f"${openai_cost:.6f}",
529
- "kalpana_cost": f"${kalpana_cost:.6f}",
530
- "savings": f"{savings_pct}%"
531
- }
532
- }
533
 
534
  demo = gr.Interface(
535
- fn=gradio_inference,
536
  inputs=[
537
  gr.Textbox(label="Message", value="Explain quantum computing in simple terms."),
538
- gr.Slider(minimum=10, maximum=512, value=100, step=1, label="Max Tokens"),
539
- gr.Number(label="RIF Bandwidth", value=2048),
540
  ],
541
- outputs=gr.JSON(label="Response + Cost Comparison"),
542
- title="Kalpanā AI — Qwen 0.5B + RIF Engine (ZeroGPU)",
543
- description="Free GPU inference via ZeroGPU. **[API Docs →](/docs)**"
544
  )
545
 
546
  app = gr.mount_gradio_app(app, demo, path="/ui")
@@ -549,7 +664,5 @@ app = gr.mount_gradio_app(app, demo, path="/ui")
549
  def root_redirect():
550
  return RedirectResponse(url="/docs")
551
 
552
- # ZeroGPU REQUIRES demo.launch() — not uvicorn.run()
553
  if __name__ == "__main__":
554
  demo.launch(server_name="0.0.0.0", server_port=7860)
555
-
 
1
  import os
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  import time
3
  import uuid
 
4
  import json
5
+ import io
6
+ import re
7
+ import pickle
8
  import collections
9
  import traceback
10
+ import numpy as np
11
+ from typing import List, Optional, Dict
12
+ from sklearn.feature_extraction.text import TfidfVectorizer
13
+ from sklearn.metrics.pairwise import cosine_similarity
14
+
15
+ import gradio as gr
16
+ from fastapi import FastAPI, HTTPException, Request, Header, Depends, File, UploadFile
17
+ from fastapi.responses import JSONResponse, RedirectResponse, Response
18
+ from fastapi.middleware.cors import CORSMiddleware
19
+ from pydantic import BaseModel, Field
20
+
21
+ # ── RIF Engine (Pure CPU, O(1) Memory) ──────────────────────────
22
+ class RIFEngine:
23
+ """
24
+ Resonant Interference Field Engine — External semantic retrieval.
25
+ Absorbs documents into O(1) state. Retrieves relevant context on query.
26
+ Pure CPU, no GPU needed. Fixed memory footprint regardless of input size.
27
+ """
28
+
29
+ def __init__(self, bandwidth=2048, max_chunks=4096):
30
+ self.bandwidth = bandwidth
31
+ self.max_chunks = max_chunks
32
+ self.chunks = []
33
+ self.vectorizer = TfidfVectorizer(
34
+ max_features=bandwidth, # Fixed vocabulary = O(1) state
35
+ stop_words='english',
36
+ ngram_range=(1, 2),
37
+ sublinear_tf=True
38
+ )
39
+ self.tfidf_matrix = None
40
+ self.is_fitted = False
41
+ self.token_count = 0
42
+
43
+ def absorb(self, text: str):
44
+ """Absorb text into RIF state. State size stays constant."""
45
+ # Split into sentences/chunks
46
+ sentences = re.split(r'(?<=[.!?])\s+', text)
47
+ new_chunks = [s.strip() for s in sentences if len(s.strip()) > 10]
48
+
49
+ if not new_chunks:
50
+ return 0
51
+
52
+ # If we have too many chunks, keep most recent (sliding window)
53
+ self.chunks.extend(new_chunks)
54
+ if len(self.chunks) > self.max_chunks:
55
+ self.chunks = self.chunks[-self.max_chunks:]
56
+
57
+ # Rebuild TF-IDF matrix (O(1) in vocabulary dimension = bandwidth)
58
+ self.vectorizer = TfidfVectorizer(
59
+ max_features=self.bandwidth,
60
+ stop_words='english',
61
+ ngram_range=(1, 2),
62
+ sublinear_tf=True
63
+ )
64
+ self.tfidf_matrix = self.vectorizer.fit_transform(self.chunks)
65
+ self.is_fitted = True
66
+ self.token_count += sum(len(s.split()) for s in new_chunks)
67
+
68
+ return len(new_chunks)
69
+
70
+ def retrieve(self, query: str, top_k=5, max_tokens=800) -> str:
71
+ """Phase-conjugate retrieval: extract relevant context from RIF state."""
72
+ if not self.is_fitted or len(self.chunks) == 0:
73
+ return ""
74
+
75
+ query_vec = self.vectorizer.transform([query])
76
+ similarities = cosine_similarity(query_vec, self.tfidf_matrix).flatten()
77
+
78
+ # Get top-k most relevant chunks
79
+ top_indices = similarities.argsort()[-top_k * 2:][::-1] # Get extras for token budget
80
+
81
+ retrieved = []
82
+ token_count = 0
83
+ for idx in top_indices:
84
+ if similarities[idx] < 0.01: # Skip irrelevant
85
+ continue
86
+ chunk = self.chunks[idx]
87
+ chunk_tokens = len(chunk.split())
88
+ if token_count + chunk_tokens > max_tokens:
89
+ break
90
+ retrieved.append(chunk)
91
+ token_count += chunk_tokens
92
+
93
+ return "\n".join(retrieved)
94
+
95
+ def get_state_size_mb(self) -> float:
96
+ """Returns current RIF state size in MB."""
97
+ if self.tfidf_matrix is not None:
98
+ # Sparse matrix size + vocabulary
99
+ data_size = self.tfidf_matrix.data.nbytes + self.tfidf_matrix.indices.nbytes + self.tfidf_matrix.indptr.nbytes
100
+ vocab_size = len(self.vectorizer.vocabulary_) * 50 # ~50 bytes per vocab entry
101
+ return round((data_size + vocab_size) / (1024 * 1024), 2)
102
+ return 0.0
103
+
104
+ def serialize(self) -> bytes:
105
+ """Serialize RIF state to bytes (.kp format)."""
106
+ state = {
107
+ "chunks": self.chunks,
108
+ "bandwidth": self.bandwidth,
109
+ "token_count": self.token_count,
110
+ "max_chunks": self.max_chunks
111
+ }
112
+ return pickle.dumps(state)
113
+
114
+ @classmethod
115
+ def deserialize(cls, data: bytes) -> 'RIFEngine':
116
+ """Load RIF state from bytes (.kp format)."""
117
+ state = pickle.loads(data)
118
+ engine = cls(bandwidth=state["bandwidth"], max_chunks=state.get("max_chunks", 4096))
119
+ engine.chunks = state["chunks"]
120
+ engine.token_count = state.get("token_count", 0)
121
+ if engine.chunks:
122
+ engine.vectorizer = TfidfVectorizer(
123
+ max_features=engine.bandwidth,
124
+ stop_words='english',
125
+ ngram_range=(1, 2),
126
+ sublinear_tf=True
127
+ )
128
+ engine.tfidf_matrix = engine.vectorizer.fit_transform(engine.chunks)
129
+ engine.is_fitted = True
130
+ return engine
131
+
132
+ # ── LLM Provider Manager ────────────────────────────────────────
133
+ class LLMProvider:
134
+ """Manages LLM API calls to any OpenAI-compatible provider."""
135
+
136
+ # Default models via HuggingFace Inference API (free, no signup)
137
+ DEFAULT_MODELS = {
138
+ "qwen2.5-72b": {"provider": "huggingface", "hf_model": "Qwen/Qwen2.5-72B-Instruct", "name": "Qwen 2.5 72B (HF Free)"},
139
+ "llama-3.1-8b": {"provider": "huggingface", "hf_model": "meta-llama/Llama-3.1-8B-Instruct", "name": "Llama 3.1 8B (HF Free)"},
140
+ "mistral-7b": {"provider": "huggingface", "hf_model": "mistralai/Mistral-7B-Instruct-v0.3", "name": "Mistral 7B (HF Free)"},
141
+ "qwen2-0.5b": {"provider": "huggingface", "hf_model": "Qwen/Qwen2-0.5B-Instruct", "name": "Qwen 2 0.5B (HF Free)"},
142
+ "phi-3-mini": {"provider": "huggingface", "hf_model": "microsoft/Phi-3-mini-4k-instruct", "name": "Phi-3 Mini (HF Free)"},
143
+ }
144
+
145
+ def __init__(self):
146
+ self.custom_providers = {} # session_id -> {provider, api_key, base_url, model}
147
+ self.hf_token = os.environ.get("HF_TOKEN", "")
148
+
149
+ def register_provider(self, session_id: str, provider: str, api_key: str,
150
+ model: str, base_url: str = None) -> dict:
151
+ """Register a custom LLM provider for a session."""
152
+ known_bases = {
153
+ "groq": "https://api.groq.com/openai/v1",
154
+ "openai": "https://api.openai.com/v1",
155
+ "together": "https://api.together.xyz/v1",
156
+ "cerebras": "https://api.cerebras.ai/v1",
157
+ "openrouter": "https://openrouter.ai/api/v1",
158
+ }
159
+
160
+ if not base_url:
161
+ base_url = known_bases.get(provider, base_url)
162
+
163
+ if not base_url:
164
+ raise ValueError(f"Unknown provider '{provider}'. Provide a base_url or use: {list(known_bases.keys())}")
165
+
166
+ self.custom_providers[session_id] = {
167
+ "provider": provider,
168
+ "api_key": api_key,
169
+ "base_url": base_url.rstrip("/"),
170
+ "model": model
171
+ }
172
+ return {"status": "registered", "provider": provider, "model": model}
173
+
174
+ def generate(self, model_id: str, messages: list, max_tokens: int = 512,
175
+ temperature: float = 0.7, session_id: str = None) -> dict:
176
+ """Generate a response from the configured LLM."""
177
+ import requests as req
178
+
179
+ # Check custom provider first
180
+ if session_id and session_id in self.custom_providers:
181
+ config = self.custom_providers[session_id]
182
+ return self._call_openai_compatible(
183
+ config["base_url"], config["api_key"], config["model"],
184
+ messages, max_tokens, temperature
185
+ )
186
+
187
+ # Use default HF Inference API
188
+ if model_id in self.DEFAULT_MODELS:
189
+ model_config = self.DEFAULT_MODELS[model_id]
190
+ if model_config["provider"] == "huggingface":
191
+ return self._call_hf_inference(model_config["hf_model"], messages, max_tokens, temperature)
192
+
193
+ # Fallback to first default
194
+ first = list(self.DEFAULT_MODELS.values())[0]
195
+ return self._call_hf_inference(first["hf_model"], messages, max_tokens, temperature)
196
+
197
+ def _call_hf_inference(self, model_name: str, messages: list,
198
+ max_tokens: int, temperature: float) -> dict:
199
+ """Call HuggingFace Inference API (free)."""
200
+ from huggingface_hub import InferenceClient
201
+
202
+ client = InferenceClient(model=model_name, token=self.hf_token)
203
+
204
+ t0 = time.perf_counter()
205
+ response = client.chat_completion(
206
+ messages=messages,
207
+ max_tokens=max_tokens,
208
+ temperature=temperature
209
+ )
210
+ t1 = time.perf_counter()
211
+
212
+ return {
213
+ "content": response.choices[0].message.content,
214
+ "prompt_tokens": response.usage.prompt_tokens if response.usage else 0,
215
+ "completion_tokens": response.usage.completion_tokens if response.usage else 0,
216
+ "total_tokens": response.usage.total_tokens if response.usage else 0,
217
+ "generation_time": round(t1 - t0, 3),
218
+ "model": model_name,
219
+ "provider": "huggingface"
220
+ }
221
+
222
+ def _call_openai_compatible(self, base_url: str, api_key: str, model: str,
223
+ messages: list, max_tokens: int, temperature: float) -> dict:
224
+ """Call any OpenAI-compatible API (Groq, Together, OpenAI, etc.)."""
225
+ import requests as req
226
+
227
+ t0 = time.perf_counter()
228
+ resp = req.post(
229
+ f"{base_url}/chat/completions",
230
+ headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
231
+ json={"model": model, "messages": messages, "max_tokens": max_tokens, "temperature": temperature},
232
+ timeout=60
233
+ )
234
+ t1 = time.perf_counter()
235
+
236
+ if resp.status_code != 200:
237
+ raise Exception(f"LLM API error ({resp.status_code}): {resp.text[:500]}")
238
+
239
+ data = resp.json()
240
+ choice = data["choices"][0]["message"]["content"]
241
+ usage = data.get("usage", {})
242
+
243
+ return {
244
+ "content": choice,
245
+ "prompt_tokens": usage.get("prompt_tokens", 0),
246
+ "completion_tokens": usage.get("completion_tokens", 0),
247
+ "total_tokens": usage.get("total_tokens", 0),
248
+ "generation_time": round(t1 - t0, 3),
249
+ "model": model,
250
+ "provider": base_url.split("//")[1].split("/")[0] if "//" in base_url else "custom"
251
+ }
252
 
253
+ # ── Initialize ───────────────────────────────────────────────────
254
+ llm = LLMProvider()
255
+ ACTIVE_PACKS: Dict[str, RIFEngine] = {}
256
+ _IP_PACKS = collections.defaultdict(set)
257
+ _RATE_WINDOWS = collections.defaultdict(list)
258
 
259
+ # Cost constants
260
+ OPENAI_INPUT_PER_1M = 2.50
261
  OPENAI_OUTPUT_PER_1M = 10.00
262
  KALPANA_SALE_PER_1M = 3.00
263
 
264
+ # ── Pydantic Models ──────────────────────────────────────────────
265
  class ChatMessage(BaseModel):
266
+ role: str
267
+ content: str
268
 
269
+ class ChatRequest(BaseModel):
270
+ model: str = Field("qwen2.5-72b", description="Model ID from /v1/models or your registered provider")
271
  messages: List[ChatMessage] = Field(...)
272
+ max_tokens: int = Field(512, ge=1, le=4096)
273
  temperature: float = Field(0.7, ge=0.0, le=2.0)
274
  bandwidth: int = Field(2048)
275
+ active_pack_id: Optional[str] = Field(None, description="Knowledge Pack ID for context")
276
 
277
+ class RegisterProviderRequest(BaseModel):
278
+ provider: str = Field(..., description="Provider name: groq, openai, together, cerebras, openrouter, or custom")
279
+ api_key: str = Field(..., description="Your API key for the provider")
280
+ model: str = Field(..., description="Model name (e.g. llama-3.1-8b-instant for Groq)")
281
+ base_url: Optional[str] = Field(None, description="Custom API base URL (auto-detected for known providers)")
282
 
283
+ class CompileKpRequest(BaseModel):
284
+ text: str = Field(..., description="Document text to compile")
 
285
  bandwidth: int = Field(2048)
286
 
287
+ # ── FastAPI ──────────────────────────────────────────────────────
 
 
 
288
  app = FastAPI(
289
  title="Kalpanā AI API",
290
  description=(
291
+ "## Kalpanā AI — RIF-Powered API with Any LLM\n\n"
292
+ "**RIF Engine** runs on CPU with O(1) memory. **LLM** is any provider you choose.\n\n"
293
+ "### How it works\n"
294
+ "1. Upload documents RIF absorbs into constant-size state\n"
295
+ "2. Ask questions → RIF retrieves relevant context (~800 tokens)\n"
296
+ "3. Context + question sent to LLM → response generated\n"
297
+ "4. **125x fewer tokens** sent to LLM vs raw document\n\n"
298
+ "### Default Models (Free, No Signup)\n"
299
+ "Qwen 2.5 72B, Llama 3.1 8B, Mistral 7B — all via HuggingFace Inference API\n\n"
300
+ "### Bring Your Own LLM\n"
301
+ "Register Groq, OpenAI, Together, or any OpenAI-compatible provider via `/v1/providers/register`\n\n"
302
  "### Pricing\n"
303
  "| Plan | Tokens | Knowledge Packs |\n"
304
  "|---|---|---|\n"
305
+ "| **Free** | Unlimited (your LLM key) | 50 packs |\n"
306
+ "| **Enterprise** | Priority RIF + SLA | Unlimited |\n"
 
307
  ),
308
+ version="4.0.0",
309
  contact={"name": "Vijñāna AI", "url": "https://huggingface.co/MaduRox"},
310
  openapi_tags=[
311
+ {"name": "Chat", "description": "Generate AI responses with RIF context retrieval"},
312
+ {"name": "Providers", "description": "Register your own LLM provider (Groq, OpenAI, etc.)"},
313
+ {"name": "Models", "description": "List available models"},
314
  {"name": "Knowledge Packs", "description": "Compile documents into portable .kp files"},
 
315
  {"name": "System", "description": "Health check"},
316
  ]
317
  )
318
 
319
  @app.exception_handler(Exception)
320
  async def global_exception_handler(request: Request, exc: Exception):
321
+ return JSONResponse(status_code=500, content={"error": str(exc), "trace": traceback.format_exc()})
322
 
 
323
  app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
324
 
325
+ # ── Helpers ──
 
 
 
 
 
 
326
  def _get_client_ip(request: Request) -> str:
327
  forwarded = request.headers.get("x-forwarded-for")
328
  return forwarded.split(",")[0].strip() if forwarded else (request.client.host if request.client else "unknown")
329
 
330
+ def check_rate_limit(request: Request):
331
  ip = _get_client_ip(request)
332
  now = time.time()
333
+ _RATE_WINDOWS[ip] = [t for t in _RATE_WINDOWS[ip] if now - t < 3600]
334
+ if len(_RATE_WINDOWS[ip]) >= 60:
335
+ raise HTTPException(status_code=429, detail="Rate limit: 60 requests/hour.")
336
  _RATE_WINDOWS[ip].append(now)
337
+ return {"ip": ip}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
+ # ── Provider Endpoints ───────────────────────────────────────────
 
 
340
 
341
+ @app.post("/v1/providers/register", tags=["Providers"],
342
+ summary="Register Your LLM Provider",
343
+ description="Bring your own API key for Groq, OpenAI, Together, Cerebras, or any OpenAI-compatible provider.")
344
+ def register_provider(req: RegisterProviderRequest, request: Request = None):
345
+ try:
346
+ session_id = _get_client_ip(request) if request else "default"
347
+ result = llm.register_provider(session_id, req.provider, req.api_key, req.model, req.base_url)
348
+ return result
349
+ except Exception as e:
350
+ raise HTTPException(status_code=400, detail=str(e))
351
 
352
+ @app.get("/v1/providers", tags=["Providers"],
353
+ summary="List Known Providers",
354
+ description="Shows provider names and their auto-detected base URLs.")
355
+ def list_providers():
356
+ return {
357
+ "known_providers": {
358
+ "groq": {"base_url": "https://api.groq.com/openai/v1", "example_models": ["llama-3.1-8b-instant", "gemma2-9b-it", "mixtral-8x7b-32768"]},
359
+ "openai": {"base_url": "https://api.openai.com/v1", "example_models": ["gpt-4o-mini", "gpt-4o"]},
360
+ "together": {"base_url": "https://api.together.xyz/v1", "example_models": ["meta-llama/Llama-3-8b-chat-hf"]},
361
+ "cerebras": {"base_url": "https://api.cerebras.ai/v1", "example_models": ["llama3.1-8b"]},
362
+ "openrouter": {"base_url": "https://openrouter.ai/api/v1", "example_models": ["meta-llama/llama-3.1-8b-instruct:free"]},
363
+ },
364
+ "note": "Use /v1/providers/register with your API key to connect any provider."
365
+ }
366
 
367
+ # ── Model Endpoints ──────────────────────────────────────────────
368
+
369
+ @app.get("/v1/models", tags=["Models"],
370
+ summary="List Available Models",
371
+ description="Shows default free models (HF Inference) and any custom-registered models.")
372
+ def list_models(request: Request = None):
373
+ models = []
374
+ for model_id, config in llm.DEFAULT_MODELS.items():
375
+ models.append({
376
+ "id": model_id,
377
+ "name": config["name"],
378
+ "provider": config["provider"],
379
+ "hf_model": config.get("hf_model"),
380
+ "cost": "Free (HuggingFace Inference API)",
381
+ "object": "model",
382
+ "owned_by": "vijnana-ai",
383
+ "engine": "kalpana-rif-external"
384
+ })
385
+
386
+ # Show custom provider if registered
387
+ if request:
388
+ ip = _get_client_ip(request)
389
+ if ip in llm.custom_providers:
390
+ custom = llm.custom_providers[ip]
391
+ models.append({
392
+ "id": "custom",
393
+ "name": f"{custom['provider']}/{custom['model']}",
394
+ "provider": custom["provider"],
395
+ "cost": "Your API key",
396
+ "object": "model",
397
+ "owned_by": "user",
398
+ "engine": "kalpana-rif-external"
399
+ })
400
+
401
+ return {"object": "list", "data": models}
402
+
403
+ # ── Chat Endpoint ────────────────────────────────────────────────
404
+
405
+ @app.post("/v1/chat/completions", tags=["Chat"],
406
+ summary="Chat with RIF Context Retrieval",
407
+ description=(
408
+ "Send a message and get a response powered by RIF context retrieval.\n\n"
409
+ "**With a Knowledge Pack:** RIF retrieves relevant chunks (~800 tokens) from the pack "
410
+ "and injects them into the prompt before sending to the LLM.\n\n"
411
+ "**Without a pack:** Messages sent directly to the LLM.\n\n"
412
+ "**Model selection:** Use a default model ID (e.g. `qwen2.5-72b`) or `custom` "
413
+ "if you registered your own provider via `/v1/providers/register`."
414
+ ))
415
+ def chat_completions(req: ChatRequest, request: Request = None, _rate: dict = Depends(check_rate_limit)):
416
  try:
417
  t0 = time.perf_counter()
418
+ session_id = _rate["ip"]
419
+
420
+ # Build messages with RIF context
421
+ rif_context = ""
422
+ rif_retrieval_time = 0
423
+ rif_state_mb = 0
424
+
425
+ if req.active_pack_id and req.active_pack_id in ACTIVE_PACKS:
426
+ rif_engine = ACTIVE_PACKS[req.active_pack_id]
427
+ user_query = req.messages[-1].content if req.messages else ""
428
+
429
+ rt0 = time.perf_counter()
430
+ rif_context = rif_engine.retrieve(user_query, top_k=10, max_tokens=800)
431
+ rt1 = time.perf_counter()
432
+ rif_retrieval_time = round((rt1 - rt0) * 1000, 1) # ms
433
+ rif_state_mb = rif_engine.get_state_size_mb()
434
+
435
+ # Build final messages for LLM
436
+ llm_messages = []
437
+ if rif_context:
438
+ llm_messages.append({
439
+ "role": "system",
440
+ "content": f"Use the following retrieved context to answer the user's question. "
441
+ f"Only use information from this context.\n\n--- CONTEXT ---\n{rif_context}\n--- END CONTEXT ---"
442
+ })
443
+
444
+ for msg in req.messages:
445
+ llm_messages.append({"role": msg.role, "content": msg.content})
446
+
447
+ # Call LLM
448
+ result = llm.generate(
449
+ model_id=req.model,
450
+ messages=llm_messages,
451
+ max_tokens=req.max_tokens,
452
+ temperature=req.temperature,
453
+ session_id=session_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
  )
455
+
 
456
  t1 = time.perf_counter()
457
+
458
+ # Token counts
459
+ prompt_tokens = result["prompt_tokens"]
460
+ completion_tokens = result["completion_tokens"]
461
+ total_tokens = result["total_tokens"]
462
+
463
+ # Cost comparison
464
  openai_cost = (prompt_tokens / 1e6) * OPENAI_INPUT_PER_1M + (completion_tokens / 1e6) * OPENAI_OUTPUT_PER_1M
465
  kalpana_cost = (total_tokens / 1e6) * KALPANA_SALE_PER_1M
466
  you_saved = max(openai_cost - kalpana_cost, 0)
467
  savings_pct = round((you_saved / openai_cost) * 100, 1) if openai_cost > 0 else 0.0
468
+
469
+ # Without RIF, user would send entire document
470
+ pack_token_count = ACTIVE_PACKS[req.active_pack_id].token_count if req.active_pack_id and req.active_pack_id in ACTIVE_PACKS else 0
471
+ tokens_without_rif = pack_token_count + completion_tokens if pack_token_count > 0 else total_tokens
472
+ token_reduction = round((1 - total_tokens / tokens_without_rif) * 100, 1) if tokens_without_rif > 0 and pack_token_count > 0 else 0
473
+
 
474
  response = {
475
  "id": f"chatcmpl-{uuid.uuid4().hex[:12]}",
476
  "object": "chat.completion",
477
  "created": int(time.time()),
478
+ "model": result["model"],
479
+ "provider": result["provider"],
480
+ "choices": [{"index": 0, "message": {"role": "assistant", "content": result["content"]}, "finish_reason": "stop"}],
481
+ "usage": {
482
+ "prompt_tokens": prompt_tokens,
483
+ "completion_tokens": completion_tokens,
484
+ "total_tokens": total_tokens
485
+ },
486
+ "kalpana_rif": {
487
+ "retrieval_time_ms": rif_retrieval_time,
488
+ "context_tokens_sent": len(rif_context.split()) if rif_context else 0,
489
+ "document_tokens_absorbed": pack_token_count,
490
+ "token_reduction": f"{token_reduction}%" if token_reduction > 0 else "N/A (no pack)",
491
+ "rif_state_mb": rif_state_mb,
492
+ "rif_state_scaling": "O(1) — constant"
493
+ },
494
  "cost_comparison": {
495
+ "openai_gpt4o_cost": f"${openai_cost:.6f}",
496
+ "kalpana_cost": f"${kalpana_cost:.6f}",
497
+ "savings": f"{savings_pct}%"
 
 
 
498
  },
499
+ "generation_time_sec": round(t1 - t0, 3)
 
500
  }
501
+
502
+ if req.active_pack_id:
503
+ response["active_pack_id"] = req.active_pack_id
504
+
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  return response
506
+
507
  except Exception as e:
508
+ raise HTTPException(status_code=500, detail=f"Error: {str(e)}\n\n{traceback.format_exc()}")
509
 
510
+ # ── Knowledge Pack Endpoints ─────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
 
512
+ @app.post("/v1/knowledge_packs/compile", tags=["Knowledge Packs"],
513
+ summary="Compile Text into Knowledge Pack",
514
+ description="Absorb text into a RIF Knowledge Pack. Returns a pack_id for use in chat.")
515
  def compile_kp(req: CompileKpRequest, request: Request = None, _rate: dict = Depends(check_rate_limit)):
516
  try:
517
  ip = _rate["ip"]
518
+ if len(_IP_PACKS[ip]) >= 50:
519
+ raise HTTPException(status_code=429, detail="Max 50 active packs on free tier.")
520
+
521
+ engine = RIFEngine(bandwidth=req.bandwidth)
522
+ chunks_absorbed = engine.absorb(req.text)
523
+
524
  pack_id = f"kp_{uuid.uuid4().hex[:8]}"
525
+ ACTIVE_PACKS[pack_id] = engine
526
  _IP_PACKS[ip].add(pack_id)
527
+
528
+ return {
529
+ "pack_id": pack_id,
530
+ "chunks_absorbed": chunks_absorbed,
531
+ "token_count": engine.token_count,
532
+ "rif_state_mb": engine.get_state_size_mb(),
533
+ "bandwidth": req.bandwidth
534
+ }
535
  except HTTPException:
536
  raise
537
  except Exception as e:
538
  raise HTTPException(status_code=500, detail=f"Failed: {str(e)}\n{traceback.format_exc()}")
539
 
540
+ @app.post("/v1/knowledge_packs/compile_file", tags=["Knowledge Packs"],
541
+ summary="Compile PDF/TXT File into Knowledge Pack")
542
+ async def compile_kp_file(file: UploadFile = File(...), bandwidth: int = 2048,
543
+ request: Request = None, _rate: dict = Depends(check_rate_limit)):
544
  try:
545
  ip = _rate["ip"]
546
+ if len(_IP_PACKS[ip]) >= 50:
547
+ raise HTTPException(status_code=429, detail="Max 50 active packs on free tier.")
548
+
549
  contents = await file.read()
550
  filename = file.filename or ""
551
+
552
  if filename.lower().endswith(".pdf"):
553
  from pypdf import PdfReader
554
  reader = PdfReader(io.BytesIO(contents))
555
  text = "\n".join(page.extract_text() or "" for page in reader.pages)
556
  else:
557
  text = contents.decode("utf-8", errors="ignore")
558
+
559
  if not text.strip():
560
  raise HTTPException(status_code=422, detail="File is empty.")
561
+
562
+ engine = RIFEngine(bandwidth=bandwidth)
563
+ chunks_absorbed = engine.absorb(text)
564
+
565
  pack_id = f"kp_{uuid.uuid4().hex[:8]}"
566
+ ACTIVE_PACKS[pack_id] = engine
567
  _IP_PACKS[ip].add(pack_id)
568
+
569
+ return {
570
+ "pack_id": pack_id,
571
+ "source_file": filename,
572
+ "chunks_absorbed": chunks_absorbed,
573
+ "token_count": engine.token_count,
574
+ "rif_state_mb": engine.get_state_size_mb()
575
+ }
576
  except HTTPException:
577
  raise
578
  except Exception as e:
579
  raise HTTPException(status_code=500, detail=f"Failed: {str(e)}\n{traceback.format_exc()}")
580
 
581
+ @app.post("/v1/knowledge_packs/upload", tags=["Knowledge Packs"],
582
+ summary="Import a .kp File")
583
  async def upload_kp(file: UploadFile = File(...), request: Request = None, _rate: dict = Depends(check_rate_limit)):
584
  try:
585
  ip = _rate["ip"]
 
 
586
  contents = await file.read()
587
+ engine = RIFEngine.deserialize(contents)
588
  pack_id = f"kp_{uuid.uuid4().hex[:8]}"
589
+ ACTIVE_PACKS[pack_id] = engine
 
 
 
590
  _IP_PACKS[ip].add(pack_id)
591
+ return {"pack_id": pack_id, "token_count": engine.token_count, "chunks": len(engine.chunks)}
 
 
592
  except Exception as e:
593
  raise HTTPException(status_code=400, detail=f"Invalid .kp file: {str(e)}")
594
 
595
+ @app.get("/v1/knowledge_packs/{pack_id}/download", tags=["Knowledge Packs"],
596
+ summary="Download Knowledge Pack as .kp File")
597
  def download_kp(pack_id: str, name: Optional[str] = None):
598
  if pack_id not in ACTIVE_PACKS:
599
  raise HTTPException(status_code=404, detail=f"Pack '{pack_id}' not found.")
600
+ data = ACTIVE_PACKS[pack_id].serialize()
 
601
  safe_name = "".join(c if c.isalnum() or c in ('-', '_', ' ') else '_' for c in (name or pack_id)).strip()[:60]
602
+ return Response(content=data, media_type="application/octet-stream",
603
  headers={"Content-Disposition": f'attachment; filename="{safe_name}.kp"'})
604
 
605
  @app.get("/v1/knowledge_packs", tags=["Knowledge Packs"], summary="List Active Packs")
606
  def list_kp():
607
+ result = [{"pack_id": pid, "token_count": e.token_count, "chunks": len(e.chunks), "rif_state_mb": e.get_state_size_mb()}
608
+ for pid, e in ACTIVE_PACKS.items()]
609
  return {"packs": result, "count": len(result)}
610
 
611
  @app.delete("/v1/knowledge_packs/{pack_id}", tags=["Knowledge Packs"], summary="Delete Pack")
 
617
  ip_set.discard(pack_id)
618
  return {"status": "deleted", "pack_id": pack_id}
619
 
620
+ # ── Health ───────────────────────────────────────────────────────
621
  @app.get("/health", tags=["System"], summary="Health Check")
622
+ def health():
623
  return {
624
  "status": "ok",
625
+ "engine": "Kalpanā RIF External",
626
+ "device": "CPU (RIF) + Remote LLM API",
 
627
  "active_packs": len(ACTIVE_PACKS),
628
+ "default_models": list(llm.DEFAULT_MODELS.keys()),
629
+ "version": "4.0.0"
630
  }
631
 
632
+ # ── Gradio UI ────────────────────────────────────────────────────
633
+ def gradio_chat(user_message, model_id, max_tokens):
634
+ try:
635
+ req = ChatRequest(
636
+ model=model_id,
637
+ messages=[ChatMessage(role="user", content=user_message)],
638
+ max_tokens=int(max_tokens)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
  )
640
+ # Simulate request for rate limiting
641
+ class FakeRequest:
642
+ headers = {}
643
+ client = None
644
+ result = chat_completions(req, request=FakeRequest(), _rate={"ip": "gradio"})
645
+ return result
646
+ except Exception as e:
647
+ return {"error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
648
 
649
  demo = gr.Interface(
650
+ fn=gradio_chat,
651
  inputs=[
652
  gr.Textbox(label="Message", value="Explain quantum computing in simple terms."),
653
+ gr.Dropdown(choices=list(llm.DEFAULT_MODELS.keys()), value="qwen2.5-72b", label="Model"),
654
+ gr.Slider(minimum=10, maximum=1024, value=200, step=1, label="Max Tokens"),
655
  ],
656
+ outputs=gr.JSON(label="Response"),
657
+ title="Kalpanā AI — RIF Engine + Any LLM",
658
+ description="RIF retrieves context on CPU. LLM generates on remote API. **[API Docs →](/docs)**"
659
  )
660
 
661
  app = gr.mount_gradio_app(app, demo, path="/ui")
 
664
  def root_redirect():
665
  return RedirectResponse(url="/docs")
666
 
 
667
  if __name__ == "__main__":
668
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
kalpana/__init__.py DELETED
@@ -1,10 +0,0 @@
1
- """
2
- Kalpanā SDK
3
- The O(1) Memory Engine for AI.
4
- """
5
-
6
- from .core import KalpanaEngineTensor, KalpanaRIFTensor
7
- from .integrations import KalpanaCache, KalpanaHuggingFaceCache
8
-
9
- __version__ = "1.0.0"
10
- __all__ = ["KalpanaEngineTensor", "KalpanaRIFTensor", "KalpanaCache", "KalpanaHuggingFaceCache"]
 
 
 
 
 
 
 
 
 
 
 
kalpana/core.py DELETED
@@ -1,355 +0,0 @@
1
- import os
2
- import torch
3
- import torch.nn as nn
4
- import math
5
-
6
- CUDA_AVAILABLE = False
7
- try:
8
- if (torch.cuda.is_available() or os.environ.get("FORCE_CUDA_COMPILE", "0") == "1") and os.environ.get("DISABLE_CUDA_COMPILE", "0") != "1":
9
- from torch.utils.cpp_extension import load_inline
10
- cuda_source = """
11
- #include <torch/extension.h>
12
- #include <cuda.h>
13
- #include <cuda_runtime.h>
14
-
15
- __global__ void reconstruct_kernel(
16
- const float* __restrict__ state_re,
17
- const float* __restrict__ state_im,
18
- const float* __restrict__ o3,
19
- const float* __restrict__ p4,
20
- float* __restrict__ out,
21
- int batch, int heads, int max_t, int bands, int dim, float kappa
22
- ) {
23
- int d = blockIdx.x * blockDim.x + threadIdx.x;
24
- int t = blockIdx.y;
25
- int h = blockIdx.z % heads;
26
- int b = blockIdx.z / heads;
27
-
28
- if (d >= dim) return;
29
-
30
- float sum = 0.0f;
31
- for (int w = 0; w < bands; w++) {
32
- float angle = kappa * o3[w] * t + p4[w];
33
- float cr = cosf(angle);
34
- float ci = sinf(angle);
35
- int state_idx = ((b * heads + h) * bands + w) * dim + d;
36
- sum += state_re[state_idx] * cr + state_im[state_idx] * ci;
37
- }
38
-
39
- int out_idx = ((b * heads + h) * max_t + t) * dim + d;
40
- out[out_idx] = sum / bands;
41
- }
42
-
43
- __global__ void write_rif_kernel(
44
- float* __restrict__ state_re,
45
- float* __restrict__ state_im,
46
- const float* __restrict__ vector,
47
- const float* __restrict__ o3,
48
- const float* __restrict__ p4,
49
- int batch, int heads, int seq_len, int bands, int dim, float kappa, int start_t
50
- ) {
51
- int d = blockIdx.x * blockDim.x + threadIdx.x;
52
- int w = blockIdx.y;
53
- int h = blockIdx.z % heads;
54
- int b = blockIdx.z / heads;
55
-
56
- if (d >= dim) return;
57
-
58
- float sum_re = 0.0f;
59
- float sum_im = 0.0f;
60
-
61
- for (int s = 0; s < seq_len; s++) {
62
- int t = start_t + s;
63
- float angle = kappa * o3[w] * t + p4[w];
64
- float cr = cosf(angle);
65
- float ci = sinf(angle);
66
-
67
- int vec_idx = ((b * heads + h) * seq_len + s) * dim + d;
68
- float v = vector[vec_idx];
69
-
70
- sum_re += v * cr;
71
- sum_im += v * ci;
72
- }
73
-
74
- int state_idx = ((b * heads + h) * bands + w) * dim + d;
75
- state_re[state_idx] += sum_re;
76
- state_im[state_idx] += sum_im;
77
- }
78
-
79
- torch::Tensor reconstruct_cuda(
80
- torch::Tensor state_re, torch::Tensor state_im,
81
- torch::Tensor o3, torch::Tensor p4,
82
- int max_t, float kappa)
83
- {
84
- int batch = state_re.size(0);
85
- int heads = state_re.size(1);
86
- int bands = state_re.size(2);
87
- int dim = state_re.size(3);
88
-
89
- auto out = torch::empty({batch, heads, max_t, dim}, state_re.options());
90
-
91
- dim3 threads(min(dim, 1024));
92
- dim3 blocks((dim + threads.x - 1) / threads.x, max_t, batch * heads);
93
-
94
- reconstruct_kernel<<<blocks, threads>>>(
95
- state_re.data_ptr<float>(), state_im.data_ptr<float>(),
96
- o3.data_ptr<float>(), p4.data_ptr<float>(),
97
- out.data_ptr<float>(),
98
- batch, heads, max_t, bands, dim, kappa
99
- );
100
-
101
- return out;
102
- }
103
-
104
- void write_rif_cuda(
105
- torch::Tensor state_re, torch::Tensor state_im,
106
- torch::Tensor vector,
107
- torch::Tensor o3, torch::Tensor p4,
108
- int start_t, float kappa)
109
- {
110
- int batch = vector.size(0);
111
- int heads = vector.size(1);
112
- int seq_len = vector.size(2);
113
- int dim = vector.size(3);
114
- int bands = state_re.size(2);
115
-
116
- dim3 threads(min(dim, 1024));
117
- dim3 blocks((dim + threads.x - 1) / threads.x, bands, batch * heads);
118
-
119
- write_rif_kernel<<<blocks, threads>>>(
120
- state_re.data_ptr<float>(), state_im.data_ptr<float>(),
121
- vector.data_ptr<float>(),
122
- o3.data_ptr<float>(), p4.data_ptr<float>(),
123
- batch, heads, seq_len, bands, dim, kappa, start_t
124
- );
125
- }
126
- """
127
- cpp_source = """
128
- torch::Tensor reconstruct_cuda(torch::Tensor state_re, torch::Tensor state_im, torch::Tensor o3, torch::Tensor p4, int max_t, float kappa);
129
- void write_rif_cuda(torch::Tensor state_re, torch::Tensor state_im, torch::Tensor vector, torch::Tensor o3, torch::Tensor p4, int start_t, float kappa);
130
- """
131
- print("Compiling Kalpanā C++ CUDA kernel. This takes ~60 seconds on first run...")
132
- kalpana_cuda = load_inline(
133
- name="kalpana_cuda_ext",
134
- cpp_sources=cpp_source,
135
- cuda_sources=cuda_source,
136
- functions=["reconstruct_cuda", "write_rif_cuda"],
137
- with_cuda=True,
138
- extra_cflags=["-O3"],
139
- extra_cuda_cflags=["-O3"]
140
- )
141
- CUDA_AVAILABLE = True
142
- print("Kalpanā C++ CUDA Kernel compiled successfully!")
143
- except Exception as e:
144
- import traceback
145
- CUDA_ERROR = traceback.format_exc()
146
- print("Failed to compile CUDA kernels, using PyTorch fallback.", CUDA_ERROR)
147
- CUDA_AVAILABLE = False
148
- class KalpanaEngineTensor(nn.Module):
149
- """
150
- Kalpanā Resonant Interference Field (RIF) Memory Engine
151
- Maintains an O(1) memory footprint for storing an infinite stream of vectors.
152
- """
153
- def __init__(self, *args, **kwargs):
154
- super().__init__()
155
-
156
- # 1. Parse arguments to support both positional and keyword initializations
157
- # Pattern A: KalpanaEngineTensor(shape=(1, 8, 128), bandwidth=2048)
158
- # Pattern B: KalpanaEngineTensor(batch_size, num_heads, bandwidth, dim)
159
-
160
- shape = kwargs.get('shape', None)
161
- bandwidth = kwargs.get('bandwidth', kwargs.get('bands', 2048))
162
- kappa = kwargs.get('kappa', 1.0)
163
- min_freq = kwargs.get('min_freq', 0.1)
164
- max_freq = kwargs.get('max_freq', 10.0)
165
- device = kwargs.get('device', 'cpu')
166
-
167
- batch_size = 1
168
- num_heads = 8
169
- dim = 128
170
-
171
- if len(args) > 0:
172
- if isinstance(args[0], (tuple, list)):
173
- shape = args[0]
174
- if len(args) > 1:
175
- bandwidth = args[1]
176
- else:
177
- if len(args) == 4:
178
- # Positional compatibility: batch_size, num_heads, bands, dim
179
- batch_size, num_heads, bandwidth, dim = args
180
- elif len(args) == 3:
181
- # Alternative positional: batch_size, num_heads, dim
182
- batch_size, num_heads, dim = args
183
- else:
184
- batch_size = args[0] if len(args) > 0 else 1
185
- num_heads = args[1] if len(args) > 1 else 8
186
- bandwidth = args[2] if len(args) > 2 else 2048
187
- dim = args[3] if len(args) > 3 else 128
188
- else:
189
- if shape is not None:
190
- batch_size = shape[0]
191
- num_heads = shape[1]
192
- dim = shape[2]
193
- else:
194
- batch_size = kwargs.get('batch_size', kwargs.get('batch', 1))
195
- num_heads = kwargs.get('num_heads', kwargs.get('heads', 8))
196
- dim = kwargs.get('dim', kwargs.get('dimensions', kwargs.get('dimension', 128)))
197
-
198
- self.batch_size = batch_size
199
- self.num_heads = num_heads
200
- self.bands = bandwidth
201
- self.dim = dim
202
- self.kappa = kappa
203
- self.device = device
204
- self.current_t = 0
205
-
206
- # State tensors for Single-Vector RIF
207
- self.state_re = torch.zeros(batch_size, num_heads, bandwidth, dim, device=device)
208
- self.state_im = torch.zeros(batch_size, num_heads, bandwidth, dim, device=device)
209
-
210
- # State tensors for Dual-Vector RIF (Keys & Values combined)
211
- self.state_re_v = torch.zeros(batch_size, num_heads, bandwidth, dim, device=device)
212
- self.state_im_v = torch.zeros(batch_size, num_heads, bandwidth, dim, device=device)
213
- self._is_dual = False
214
-
215
- # Frequencies and Phases
216
- bands_f = float(bandwidth - 1) if bandwidth > 1 else 1.0
217
- step = (max_freq - min_freq) / bands_f
218
-
219
- o3 = min_freq + torch.arange(bandwidth, device=device).float() * step
220
- self.o3 = o3.view(1, 1, bandwidth, 1)
221
-
222
- p4 = 2 * math.pi * torch.rand(bandwidth, device=device)
223
- self.p4 = p4.view(1, 1, bandwidth, 1)
224
-
225
- def write_rif(self, start_t, vector, is_value=False):
226
- """
227
- Optimized write_rif using C++ CUDA kernel or parallel cuBLAS.
228
- """
229
- batch, heads, seq_len, dim = vector.shape
230
- if seq_len == 0:
231
- return
232
-
233
- state_re = self.state_re_v if is_value else self.state_re
234
- state_im = self.state_im_v if is_value else self.state_im
235
-
236
- if CUDA_AVAILABLE and self.device == "cuda" and state_re.dtype == torch.float32:
237
- kalpana_cuda.write_rif_cuda(
238
- state_re, state_im, vector.to(torch.float32).contiguous(),
239
- self.o3.view(-1).contiguous(), self.p4.view(-1).contiguous(), start_t, self.kappa
240
- )
241
- if is_value:
242
- self._is_dual = True
243
- return
244
-
245
- t_range = start_t + torch.arange(0, seq_len, device=self.device).float()
246
- o3_flat = self.o3.view(-1)
247
- p4_flat = self.p4.view(-1)
248
-
249
- # angle shape: (seq_len, bandwidth)
250
- angle = self.kappa * o3_flat.unsqueeze(0) * t_range.unsqueeze(1) + p4_flat.unsqueeze(0)
251
-
252
- cr = torch.cos(angle) # (S, W)
253
- ci = torch.sin(angle) # (S, W)
254
-
255
- # vector is (B, H, S, D) -> transpose to (B, H, D, S)
256
- vector_fp32 = vector.float()
257
- # Matmul gives (B, H, D, W)
258
- delta_re = vector_fp32.transpose(2, 3) @ cr
259
- delta_im = vector_fp32.transpose(2, 3) @ ci
260
-
261
- # Transpose back to (B, H, W, D) and accumulate
262
- if is_value:
263
- self.state_re_v += delta_re.transpose(2, 3)
264
- self.state_im_v += delta_im.transpose(2, 3)
265
- self._is_dual = True
266
- else:
267
- self.state_re += delta_re.transpose(2, 3)
268
- self.state_im += delta_im.transpose(2, 3)
269
-
270
- def reconstruct_all(self, max_t, is_value=False):
271
- """
272
- Optimized reconstruct_all using C++ CUDA kernel or highly parallel cuBLAS.
273
- """
274
- if max_t == 0:
275
- return torch.zeros((self.batch_size, self.num_heads, 0, self.dim), device=self.device)
276
-
277
- state_re = self.state_re_v if is_value else self.state_re
278
- state_im = self.state_im_v if is_value else self.state_im
279
-
280
- if CUDA_AVAILABLE and self.device == "cuda" and state_re.dtype == torch.float32:
281
- rv = kalpana_cuda.reconstruct_cuda(
282
- state_re, state_im, self.o3.view(-1).contiguous(), self.p4.view(-1).contiguous(), max_t, self.kappa
283
- )
284
- return rv
285
-
286
- t_range = torch.arange(0, max_t, device=self.device).float()
287
- o3_flat = self.o3.view(-1)
288
- p4_flat = self.p4.view(-1)
289
-
290
- # angle shape: (T, W)
291
- angle = self.kappa * o3_flat.unsqueeze(0) * t_range.unsqueeze(1) + p4_flat.unsqueeze(0)
292
-
293
- cr = torch.cos(angle) # (T, W)
294
- ci = torch.sin(angle) # (T, W)
295
-
296
- state_re = self.state_re_v if is_value else self.state_re
297
- state_im = self.state_im_v if is_value else self.state_im
298
-
299
- # state_re is (B, H, W, D) -> transpose to (B, H, D, W)
300
- # cr is (T, W) -> transpose to (W, T)
301
- # Matmul gives (B, H, D, T)
302
- rv_re = state_re.transpose(2, 3) @ cr.t()
303
- rv_im = state_im.transpose(2, 3) @ ci.t()
304
-
305
- # Sum and divide by bandwidth
306
- rv = (rv_re + rv_im) / self.bands
307
-
308
- # Transpose back to (B, H, T, D) to match HF KV Cache expectations
309
- return rv.transpose(2, 3)
310
-
311
- def update(self, key, value=None):
312
- """
313
- Dual-integration update API as documented in the README.
314
- If key and value are both provided, updates dual state.
315
- If value is None, updates single-vector state.
316
- """
317
- # Expose shape matching to write_rif
318
- if len(key.shape) == 3:
319
- key_unsqueezed = key.unsqueeze(2)
320
- else:
321
- key_unsqueezed = key
322
-
323
- if value is not None:
324
- if len(value.shape) == 3:
325
- value_unsqueezed = value.unsqueeze(2)
326
- else:
327
- value_unsqueezed = value
328
-
329
- self.write_rif(self.current_t, key_unsqueezed, is_value=False)
330
- self.write_rif(self.current_t, value_unsqueezed, is_value=True)
331
- self.current_t += key_unsqueezed.shape[2]
332
- else:
333
- self.write_rif(self.current_t, key_unsqueezed, is_value=False)
334
- self.current_t += key_unsqueezed.shape[2]
335
-
336
- def retrieve(self, t=None):
337
- """
338
- Dual-integration retrieve API as documented in the README.
339
- Returns (reconstructed_k, reconstructed_v) for dual state, or reconstructed_k for single.
340
- """
341
- max_t = t if t is not None else self.current_t
342
- if max_t == 0:
343
- k_shape = (self.batch_size, self.num_heads, 0, self.dim)
344
- if self._is_dual:
345
- return torch.zeros(k_shape, device=self.device), torch.zeros(k_shape, device=self.device)
346
- return torch.zeros(k_shape, device=self.device)
347
-
348
- recon_k = self.reconstruct_all(max_t, is_value=False)
349
- if self._is_dual:
350
- recon_v = self.reconstruct_all(max_t, is_value=True)
351
- return recon_k.squeeze(2), recon_v.squeeze(2)
352
- return recon_k.squeeze(2)
353
-
354
- # Backward Compatibility Alias
355
- KalpanaRIFTensor = KalpanaEngineTensor
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
kalpana/integrations.py DELETED
@@ -1,131 +0,0 @@
1
- import torch
2
- from transformers import Cache
3
- from .core import KalpanaEngineTensor
4
-
5
- class KalpanaCache(Cache):
6
- """
7
- Overrides the default O(N) HuggingFace DynamicCache with the O(1) Kalpanā RIF!
8
- """
9
- def __init__(self, config=None, batch_size=1, device='cpu', bandwidth=2048, **kwargs):
10
- # We intentionally do not call super().__init__() to bypass HuggingFace's
11
- # aggressive base-class requirements in newer versions.
12
-
13
- # Parse optional bandwidth and batch size options
14
- bandwidth = kwargs.get('bandwidth', kwargs.get('bands', bandwidth))
15
- batch_size = kwargs.get('batch_size', kwargs.get('batch', batch_size))
16
-
17
- # If config is None, we fall back to defaults that fit standard configurations like LLaMA-3 8B
18
- if config is not None:
19
- self.num_layers = getattr(config, "num_hidden_layers", getattr(config, "n_layer", 32))
20
- self.num_key_value_heads = getattr(config, "num_key_value_heads", getattr(config, "num_attention_heads", getattr(config, "n_head", 8)))
21
-
22
- if hasattr(config, "head_dim"):
23
- self.head_dim = config.head_dim
24
- else:
25
- hidden_size = getattr(config, "hidden_size", 4096)
26
- num_attention_heads = getattr(config, "num_attention_heads", getattr(config, "n_head", 32))
27
- self.head_dim = hidden_size // num_attention_heads
28
- else:
29
- self.num_layers = kwargs.get('num_layers', 32)
30
- self.num_key_value_heads = kwargs.get('num_key_value_heads', kwargs.get('heads', 8))
31
- self.head_dim = kwargs.get('head_dim', kwargs.get('dimensions', kwargs.get('dimension', kwargs.get('dim', 128))))
32
-
33
- self.device = device
34
- self._seen_tokens = [0] * self.num_layers
35
- self.bandwidth = bandwidth
36
-
37
- # Compatibility hacks for HuggingFace Cache interface
38
- # In transformers v4.45+, __len__ uses len(self.layers)
39
- self.layers = [None] * self.num_layers
40
-
41
- self.key_rifs = [
42
- KalpanaEngineTensor(
43
- batch_size=batch_size,
44
- num_heads=self.num_key_value_heads,
45
- bandwidth=bandwidth,
46
- dim=self.head_dim,
47
- device=device
48
- ) for _ in range(self.num_layers)
49
- ]
50
- self.val_rifs = [
51
- KalpanaEngineTensor(
52
- batch_size=batch_size,
53
- num_heads=self.num_key_value_heads,
54
- bandwidth=bandwidth,
55
- dim=self.head_dim,
56
- device=device
57
- ) for _ in range(self.num_layers)
58
- ]
59
-
60
- # Fast running cache for active generation sessions
61
- self.reconstructed_keys = [None] * self.num_layers
62
- self.reconstructed_values = [None] * self.num_layers
63
-
64
- @property
65
- def is_compileable(self):
66
- return False
67
-
68
- def update(self, key_states, value_states, layer_idx, cache_kwargs=None):
69
- seq_len = key_states.shape[2]
70
-
71
- # If we have pre-loaded knowledge pack caches, initialize reconstructed_keys with them!
72
- if self.reconstructed_keys[layer_idx] is None:
73
- if len(self.key_cache) > layer_idx and self.key_cache[layer_idx] is not None:
74
- self.reconstructed_keys[layer_idx] = self.key_cache[layer_idx].to(key_states.device)
75
- self.reconstructed_values[layer_idx] = self.value_cache[layer_idx].to(value_states.device)
76
- self._seen_tokens[layer_idx] = self.reconstructed_keys[layer_idx].shape[2]
77
-
78
- current_t = self._seen_tokens[layer_idx]
79
-
80
- self.key_rifs[layer_idx].write_rif(current_t, key_states)
81
- self.val_rifs[layer_idx].write_rif(current_t, value_states)
82
-
83
- self._seen_tokens[layer_idx] += seq_len
84
-
85
- if self.reconstructed_keys[layer_idx] is None:
86
- # Prefill phase (first execution): keep cloned full precision state to avoid in-place corruption
87
- self.reconstructed_keys[layer_idx] = key_states.clone()
88
- self.reconstructed_values[layer_idx] = value_states.clone()
89
- else:
90
- # Autoregressive generation phase: append new token key/value states directly
91
- self.reconstructed_keys[layer_idx] = torch.cat([self.reconstructed_keys[layer_idx], key_states], dim=2)
92
- self.reconstructed_values[layer_idx] = torch.cat([self.reconstructed_values[layer_idx], value_states], dim=2)
93
-
94
- return self.reconstructed_keys[layer_idx], self.reconstructed_values[layer_idx]
95
-
96
- @property
97
- def key_cache(self):
98
- return self.reconstructed_keys
99
-
100
- @key_cache.setter
101
- def key_cache(self, value):
102
- self.reconstructed_keys = value
103
-
104
- @property
105
- def value_cache(self):
106
- return self.reconstructed_values
107
-
108
- @value_cache.setter
109
- def value_cache(self, value):
110
- self.reconstructed_values = value
111
-
112
- def get_seq_length(self, layer_idx=0):
113
- if layer_idx is None:
114
- layer_idx = 0
115
- return self._seen_tokens[layer_idx]
116
-
117
- def get_mask_sizes(self, cache_position, layer_idx=None):
118
- if layer_idx is None:
119
- layer_idx = 0
120
- if isinstance(cache_position, torch.Tensor):
121
- query_length = cache_position.shape[0]
122
- else:
123
- query_length = cache_position
124
- past_length = self.get_seq_length(layer_idx)
125
- return past_length + query_length, 0
126
-
127
- def get_max_length(self):
128
- return None
129
-
130
- # Backward Compatibility Alias
131
- KalpanaHuggingFaceCache = KalpanaCache
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,14 +1,9 @@
1
  fastapi
2
  pydantic
3
- transformers
4
- accelerate
5
  uvicorn
6
- scipy
7
- tiktoken
8
- sentencepiece
9
- protobuf
10
  pypdf
11
  python-multipart
12
- torch
13
- ninja
14
  huggingface_hub<0.25
 
 
1
  fastapi
2
  pydantic
 
 
3
  uvicorn
4
+ numpy
5
+ scikit-learn
 
 
6
  pypdf
7
  python-multipart
 
 
8
  huggingface_hub<0.25
9
+ requests