Download api.py from Angshul/SpliNet-Embedder: direct link, hf CLI and curl.
- Browser
- Download file 3.46 kB
-
https://huggingface.co/Angshul/SpliNet-Embedder/resolve/main/api.py
- Command line
-
hf download hf://Angshul/SpliNet-Embedder/api.py
-
curl -L -o api.py https://huggingface.co/Angshul/SpliNet-Embedder/resolve/main/api.py
3.46 kB
| from pathlib import Path | |
| import os | |
| import json | |
| import unicodedata | |
| import numpy as np | |
| import sentencepiece as spm | |
| import torch | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel | |
| ROOT = Path(__file__).resolve().parent | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| MODEL = torch.jit.load(str(ROOT / "splinet_embedder.ts"), map_location=DEVICE).eval() | |
| SP = spm.SentencePieceProcessor(model_file=str(ROOT / "spiece.model")) | |
| PAD_ID = 3 | |
| CLS_ID = 4 | |
| SEP_ID = 5 | |
| MAX_LENGTH = 512 | |
| DEFAULT_BATCH = int(os.environ.get("SPLINET_BATCH_SIZE", "256" if DEVICE.type == "cuda" else "8")) | |
| def normalize_text(text): | |
| text = unicodedata.normalize("NFKC", text or "").lower() | |
| return " ".join(text.split()) | |
| def tokenize(texts): | |
| ids = torch.full((len(texts), MAX_LENGTH), PAD_ID, dtype=torch.long) | |
| mask = torch.zeros((len(texts), MAX_LENGTH), dtype=torch.long) | |
| for i, text in enumerate(texts): | |
| pieces = list(SP.encode(normalize_text(text), out_type=int))[:MAX_LENGTH - 2] | |
| seq = [CLS_ID] + pieces + [SEP_ID] | |
| n = len(seq) | |
| ids[i, :n] = torch.tensor(seq, dtype=torch.long) | |
| mask[i, :n] = 1 | |
| return ids, mask | |
| def encode(texts, batch_size=None): | |
| if isinstance(texts, str): | |
| texts = [texts] | |
| if not texts: | |
| return np.empty((0, 768), dtype=np.float32) | |
| bs = max(1, int(batch_size or DEFAULT_BATCH)) | |
| output = [] | |
| start = 0 | |
| while start < len(texts): | |
| current = min(bs, len(texts) - start) | |
| try: | |
| ids, mask = tokenize(texts[start:start + current]) | |
| ids = ids.to(DEVICE) | |
| mask = mask.to(DEVICE) | |
| with torch.inference_mode(): | |
| if DEVICE.type == "cuda": | |
| with torch.autocast(device_type="cuda", dtype=torch.float16): | |
| z = MODEL(ids, mask) | |
| else: | |
| z = MODEL(ids, mask) | |
| output.append(z.float().cpu().numpy()) | |
| start += current | |
| except torch.OutOfMemoryError: | |
| if DEVICE.type != "cuda" or current == 1: | |
| raise | |
| torch.cuda.empty_cache() | |
| bs = max(1, current // 2) | |
| return np.concatenate(output, axis=0).astype(np.float32) | |
| def similarity(text_a, text_b): | |
| z = encode([text_a, text_b]) | |
| return float(np.dot(z[0], z[1])) | |
| class EmbedRequest(BaseModel): | |
| texts: list[str] | |
| batch_size: int | None = None | |
| class SimilarityRequest(BaseModel): | |
| text_a: str | |
| text_b: str | |
| app = FastAPI(title="SpliNet Embedder API", version="1.0.0") | |
| def health(): | |
| return {"status": "ok", "device": str(DEVICE)} | |
| def model_info(): | |
| with open(ROOT / "model_info.json", "r", encoding="utf-8") as f: | |
| return json.load(f) | |
| def embed_endpoint(req: EmbedRequest): | |
| try: | |
| z = encode(req.texts, req.batch_size) | |
| return {"count": len(req.texts), "dimension": 768, "embeddings": z.tolist()} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| def similarity_endpoint(req: SimilarityRequest): | |
| try: | |
| return {"cosine_similarity": similarity(req.text_a, req.text_b)} | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", "8000"))) | |