doc_rag / embedder.py
TytonTerrapin's picture
Update embedder.py
d66c09b verified
Raw
History Blame Contribute Delete
3.15 kB
"""
embedder.py — BGE-small-en-v1.5 via pure onnxruntime (no torch, no optimum).
ONNX weights downloaded from Xenova/bge-small-en-v1.5 at build time.
"""
from __future__ import annotations
import logging
import os
from typing import List
import numpy as np
from config import EMBED_BATCH_SIZE, EMBED_MODEL_NAME, EMBED_ONNX_DIR
logger = logging.getLogger(__name__)
_BGE_QUERY_PREFIX = "Represent this sentence: "
class Embedder:
def __init__(self):
self._session = None
self._tokenizer = None
self._ready = False
def load(self) -> None:
if self._ready:
return
import onnxruntime as ort
from transformers import AutoTokenizer
# model.onnx is placed flat in EMBED_ONNX_DIR by download_models.py
model_path = os.path.join(EMBED_ONNX_DIR, "model.onnx")
if not os.path.isfile(model_path):
raise FileNotFoundError(
f"ONNX model not found at {model_path}. "
"Run download_models.py first."
)
logger.info("Loading ONNX embedder from %s", model_path)
opts = ort.SessionOptions()
opts.intra_op_num_threads = max(1, os.cpu_count() or 2)
opts.inter_op_num_threads = max(1, os.cpu_count() or 2)
self._session = ort.InferenceSession(
model_path,
sess_options=opts,
providers=["CPUExecutionProvider"],
)
self._tokenizer = AutoTokenizer.from_pretrained(EMBED_ONNX_DIR)
self._ready = True
logger.info("Embedder ready (pure ONNX/onnxruntime, no torch)")
def encode_chunks(self, texts: List[str]) -> np.ndarray:
return self._encode(texts)
def encode_query(self, text: str) -> np.ndarray:
return self._encode([_BGE_QUERY_PREFIX + text])[0]
def _encode(self, texts: List[str]) -> np.ndarray:
if not self._ready:
raise RuntimeError("Embedder.load() has not been called")
all_embeddings = []
for i in range(0, len(texts), EMBED_BATCH_SIZE):
batch = texts[i: i + EMBED_BATCH_SIZE]
enc = self._tokenizer(
batch, padding=True, truncation=True,
max_length=512, return_tensors="np",
)
inputs = {
"input_ids": enc["input_ids"].astype(np.int64),
"attention_mask": enc["attention_mask"].astype(np.int64),
}
if "token_type_ids" in enc:
inputs["token_type_ids"] = enc["token_type_ids"].astype(np.int64)
outputs = self._session.run(None, inputs)
token_emb = outputs[0] # (batch, seq, dim)
mask = enc["attention_mask"][:, :, np.newaxis].astype(np.float32)
summed = np.sum(token_emb * mask, axis=1)
count = np.clip(mask.sum(axis=1), 1e-9, None)
all_embeddings.append((summed / count).astype(np.float32))
return np.vstack(all_embeddings)
@property
def is_ready(self) -> bool:
return self._ready
embedder = Embedder()