studyhub-app / studyhub /ingest /embedder.py
parhamkhoshsolat's picture
StudyHub web — custom Docker frontend (FastAPI + SPA)
8db761b verified
Raw
History Blame Contribute Delete
1.97 kB
from __future__ import annotations
import hashlib
from typing import Protocol, runtime_checkable
import numpy as np
@runtime_checkable
class Embedder(Protocol):
dim: int
def encode(self, texts: list[str]) -> np.ndarray: # (n, dim), L2-normalized float32
...
class FakeEmbedder:
"""Deterministic, dependency-free embedder for tests."""
def __init__(self, dim: int = 32):
self.dim = dim
def encode(self, texts: list[str]) -> np.ndarray:
out = np.zeros((len(texts), self.dim), dtype="float32")
for i, t in enumerate(texts):
digest = hashlib.sha256(t.encode("utf-8")).digest()
reps = (self.dim // len(digest)) + 1
raw = np.frombuffer((digest * reps)[: self.dim], dtype=np.uint8).astype("float32")
v = raw - raw.mean()
norm = float(np.linalg.norm(v))
out[i] = v / norm if norm > 0 else v
return out
class BGEM3Embedder:
"""Real embedder (BAAI/bge-m3). Lazy import so tests need no model download.
Used both for offline index-building and for query embedding on the Space
(weights are downloaded from the Hub on first use). An int8-ONNX build to
speed cold starts is a documented follow-up, not yet implemented.
"""
def __init__(self, model_name: str = "BAAI/bge-m3"):
from FlagEmbedding import BGEM3FlagModel
import torch
# fp16 only on GPU — fp16 matmul isn't implemented for CPU inference, which
# is what CI runners and the free Space use; fp32 is correct (just more RAM).
self._model = BGEM3FlagModel(model_name, use_fp16=torch.cuda.is_available())
self.dim = 1024
def encode(self, texts: list[str]) -> np.ndarray:
dense = self._model.encode(texts, return_dense=True)["dense_vecs"]
v = np.asarray(dense, dtype="float32")
norms = np.linalg.norm(v, axis=1, keepdims=True)
norms[norms == 0] = 1.0
return v / norms