Spaces:
Sleeping
Sleeping
Kalpana API ZeroGPU: Qwen 0.5B + RIF Engine on free A10G
Browse files- Single model: Qwen2-0.5B-Instruct on ZeroGPU (free A10G)
- RIF Engine runs on CPU (6.3 MB O(1) constant memory)
- @spaces.GPU decorator for on-demand GPU inference
- Full OpenAI-compatible /v1/chat/completions API
- Knowledge Pack compile/upload/download endpoints
- Cost comparison vs GPT-4o in every response
- README.md +16 -7
- app.py +500 -0
- kalpana/__init__.py +10 -0
- kalpana/core.py +355 -0
- kalpana/integrations.py +131 -0
- requirements.txt +14 -0
README.md
CHANGED
|
@@ -1,13 +1,22 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: "Kalpanā API (ZeroGPU)"
|
| 3 |
+
emoji: "🧠"
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
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)**
|
app.py
ADDED
|
@@ -0,0 +1,500 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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")
|
| 450 |
+
def delete_kp(pack_id: str):
|
| 451 |
+
if pack_id not in ACTIVE_PACKS:
|
| 452 |
+
raise HTTPException(status_code=404, detail="Pack not found.")
|
| 453 |
+
del ACTIVE_PACKS[pack_id]
|
| 454 |
+
for ip_set in _IP_PACKS.values():
|
| 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 ──
|
| 471 |
+
def ui_predict(user_message, max_tokens, bandwidth):
|
| 472 |
+
req = ChatCompletionRequest(
|
| 473 |
+
model=MODEL_ID,
|
| 474 |
+
messages=[ChatMessage(role="user", content=user_message)],
|
| 475 |
+
max_tokens=int(max_tokens), bandwidth=int(bandwidth)
|
| 476 |
+
)
|
| 477 |
+
result = chat_completions(req)
|
| 478 |
+
return result
|
| 479 |
+
|
| 480 |
+
demo = gr.Interface(
|
| 481 |
+
fn=ui_predict,
|
| 482 |
+
inputs=[
|
| 483 |
+
gr.Textbox(label="Message", value="Explain quantum computing in simple terms."),
|
| 484 |
+
gr.Slider(minimum=10, maximum=512, value=100, step=1, label="Max Tokens"),
|
| 485 |
+
gr.Number(label="RIF Bandwidth", value=2048),
|
| 486 |
+
],
|
| 487 |
+
outputs=gr.JSON(label="Response + Cost Comparison"),
|
| 488 |
+
title="Kalpanā AI — Qwen 0.5B + RIF Engine (ZeroGPU)",
|
| 489 |
+
description="Free GPU inference via ZeroGPU. **[API Docs →](/docs)**"
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
app = gr.mount_gradio_app(app, demo, path="/ui")
|
| 493 |
+
|
| 494 |
+
@app.get("/", include_in_schema=False)
|
| 495 |
+
def root_redirect():
|
| 496 |
+
return RedirectResponse(url="/docs")
|
| 497 |
+
|
| 498 |
+
if __name__ == "__main__":
|
| 499 |
+
import uvicorn
|
| 500 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
kalpana/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,355 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.44.0
|
| 2 |
+
fastapi
|
| 3 |
+
pydantic
|
| 4 |
+
transformers
|
| 5 |
+
accelerate
|
| 6 |
+
uvicorn
|
| 7 |
+
scipy
|
| 8 |
+
tiktoken
|
| 9 |
+
sentencepiece
|
| 10 |
+
protobuf
|
| 11 |
+
pypdf
|
| 12 |
+
python-multipart
|
| 13 |
+
torch
|
| 14 |
+
spaces
|