AlokBharadwaj's picture
top_logprobs=1
0bda52c verified
Raw
History Blame Contribute Delete
5.36 kB
import os
import math
import time
import logging
from collections import deque
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from huggingface_hub import InferenceClient
# ── Logging ──────────────────────────────────────────────────────────────────
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# ── App setup ─────────────────────────────────────────────────────────────────
app = FastAPI(title="NB4170 LLM Proxy")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Binder + local notebooks need this
allow_methods=["*"],
allow_headers=["*"],
)
# ── HF client (token lives only here, in a Space Secret) ──────────────────────
HF_TOKEN = os.environ.get("HF_TOKEN")
if not HF_TOKEN:
raise RuntimeError("HF_TOKEN environment variable is not set")
client = InferenceClient(token=HF_TOKEN)
# ── Allowed models ─────────────────────────────────────────────────────────────
ALLOWED_MODELS = {
"openai-community/gpt2": "openai-community/gpt2",
"llama-1b": "meta-llama/Llama-3.2-1B-Instruct",
"meta-llama/Llama-3.2-1B-Instruct": "meta-llama/Llama-3.2-1B-Instruct",
"llama-8b": "meta-llama/Llama-3.1-8B-Instruct",
"meta-llama/Llama-3.1-8B-Instruct": "meta-llama/Llama-3.1-8B-Instruct",
}
# ── Simple rate limiter: max 60 requests per minute across all students ────────
request_times = deque()
RATE_LIMIT = 60
RATE_WINDOW = 60 # seconds
def check_rate_limit():
now = time.time()
while request_times and request_times[0] < now - RATE_WINDOW:
request_times.popleft()
if len(request_times) >= RATE_LIMIT:
raise HTTPException(
status_code=429,
detail=f"Rate limit reached ({RATE_LIMIT} requests/min). Wait a moment and try again."
)
request_times.append(now)
# ── Request schema ─────────────────────────────────────────────────────────────
class GenerateRequest(BaseModel):
system_prompt: str = "" # added system prompt
prompt: str
model: str = "gpt2"
max_tokens: int = 50
# ── Health check ───────────────────────────────────────────────────────────────
@app.get("/")
def health():
return {"status": "ok", "message": "NB4170 LLM Proxy is running"}
# ── Main endpoint ──────────────────────────────────────────────────────────────
@app.post("/generate")
def generate(req: GenerateRequest):
# Rate limit
check_rate_limit()
# Validate model
model_id = ALLOWED_MODELS.get(req.model)
if not model_id:
raise HTTPException(
status_code=400,
detail=f"Model '{req.model}' not allowed. Choose from: {list(ALLOWED_MODELS.keys())}"
)
# Clamp max_tokens to avoid runaway costs
max_tokens = min(req.max_tokens, 1000)
logger.info(f"Request: model={model_id}, max_tokens={max_tokens}, prompt_len={len(req.prompt)}")
try:
messages = []
# add system prompt to messages if provided
if req.system_prompt:
messages.append({"role": "system", "content": req.system_prompt})
messages.append({"role": "user", "content": req.prompt})
response = client.chat.completions.create(
model=model_id,
messages=messages,
max_tokens=max_tokens,
logprobs=True,
top_logprobs=1,
)
except Exception as e:
logger.error(f"HF API error: {e}")
raise HTTPException(status_code=502, detail=f"HF Inference API error: {str(e)}")
# ── Post-process response ──────────────────────────────────────────────────
try:
answer = response.choices[0].message.content
# Extract logprobs β€” same logic as your original notebook
logprobs_content = response.choices[0].logprobs.content
logprobs_dict = {x.token: x.logprob for x in logprobs_content}
token_probs_dict = {token: math.exp(lp) for token, lp in logprobs_dict.items()}
except Exception as e:
logger.error(traceback.format_exc())
raise HTTPException(status_code=500, detail=f"Post-processing error: {e}")
return {
"answer": answer,
"logprobs": logprobs_dict,
"token_probs": token_probs_dict,
"logprobs_content": logprobs_content,
}