mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
263 lines
7.7 KiB
Python
263 lines
7.7 KiB
Python
"""NoDupeLabs ML Tools - Machine Learning Backends
|
|
|
|
This module provides ML backend implementations for embedding generation
|
|
and other machine learning tasks with graceful degradation.
|
|
|
|
Key Features:
|
|
- Multiple backend support (CPU, ONNX)
|
|
- Graceful fallback to CPU
|
|
- Embedding generation
|
|
- Extensible backend interface
|
|
"""
|
|
|
|
import logging
|
|
from abc import ABC, abstractmethod
|
|
from typing import List, Optional, Any
|
|
|
|
# Configure logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Lazy numpy import - only import when actually needed
|
|
_numpy = None
|
|
|
|
|
|
def _get_numpy():
|
|
"""Lazy numpy import."""
|
|
global _numpy
|
|
if _numpy is None:
|
|
import numpy as np
|
|
_numpy = np
|
|
return _numpy
|
|
|
|
|
|
class MLBackend(ABC):
|
|
"""Abstract base class for ML backends.
|
|
|
|
Defines the interface that all ML backend implementations must follow.
|
|
"""
|
|
|
|
@abstractmethod
|
|
def is_available(self) -> bool:
|
|
"""Check if this backend is available.
|
|
|
|
Returns:
|
|
True if the backend can be used, False otherwise
|
|
"""
|
|
|
|
@abstractmethod
|
|
def generate_embeddings(self, data: List[Any]) -> List[List[float]]:
|
|
"""Generate embeddings for input data.
|
|
|
|
Args:
|
|
data: List of items to generate embeddings for
|
|
|
|
Returns:
|
|
List of embedding vectors
|
|
"""
|
|
|
|
@abstractmethod
|
|
def get_embedding_dimensions(self) -> int:
|
|
"""Get the dimensionality of embeddings produced by this backend.
|
|
|
|
Returns:
|
|
Embedding dimension size
|
|
"""
|
|
|
|
|
|
class CPUBackend(MLBackend):
|
|
"""CPU-based ML backend using pure NumPy (always available).
|
|
|
|
Provides CPU-based implementations of ML operations as a fallback
|
|
when other backends are not available.
|
|
"""
|
|
|
|
def __init__(self):
|
|
"""Initialize CPU backend."""
|
|
self.dimensions = 128 # Default embedding dimensions
|
|
|
|
def is_available(self) -> bool:
|
|
"""This backend is always available.
|
|
|
|
Returns:
|
|
Always True
|
|
"""
|
|
return True
|
|
|
|
def generate_embeddings(self, data: List[Any]) -> List[List[float]]:
|
|
"""Generate simple embeddings using NumPy.
|
|
|
|
This is a placeholder implementation that creates random embeddings.
|
|
|
|
Args:
|
|
data: List of items to generate embeddings for
|
|
|
|
Returns:
|
|
List of embedding vectors
|
|
"""
|
|
try:
|
|
np = _get_numpy()
|
|
embeddings = []
|
|
for item in data:
|
|
# Simple hash-based embedding for demonstration
|
|
if isinstance(item, str):
|
|
# Convert string to numerical representation
|
|
embedding = np.random.randn(self.dimensions).tolist()
|
|
elif isinstance(item, (list, np.ndarray)):
|
|
# For array-like data, create embedding based on content
|
|
embedding = np.random.randn(self.dimensions).tolist()
|
|
else:
|
|
# Fallback for other types
|
|
embedding = np.random.randn(self.dimensions).tolist()
|
|
embeddings.append(embedding)
|
|
return embeddings
|
|
except Exception as e:
|
|
logger.error(f"Error generating embeddings with CPU backend: {e}")
|
|
# Return empty embeddings on error
|
|
return [[] for _ in data]
|
|
|
|
def get_embedding_dimensions(self) -> int:
|
|
"""Get embedding dimensionality.
|
|
|
|
Returns:
|
|
Embedding dimension size
|
|
"""
|
|
return self.dimensions
|
|
|
|
|
|
class ONNXBackend(MLBackend):
|
|
"""ONNX Runtime backend for ML inference.
|
|
|
|
Provides ONNX-based ML inference when available.
|
|
Falls back to CPU if ONNX is not available.
|
|
"""
|
|
|
|
def __init__(self, model_path: Optional[str] = None):
|
|
"""Initialize ONNX backend.
|
|
|
|
Args:
|
|
model_path: Optional path to ONNX model file
|
|
"""
|
|
self.dimensions = 128
|
|
self.model_path = model_path
|
|
self._available = False
|
|
self._model = None
|
|
|
|
try:
|
|
# Try to import ONNX runtime
|
|
import onnxruntime as ort
|
|
|
|
# Try to load model if path provided
|
|
if model_path:
|
|
self._model = ort.InferenceSession(model_path)
|
|
self._available = True
|
|
logger.info(f"ONNX backend loaded model from {model_path}")
|
|
else:
|
|
logger.warning("ONNX backend: no model path provided")
|
|
except ImportError:
|
|
logger.warning("ONNX runtime not available, falling back to CPU backend")
|
|
except Exception as e:
|
|
logger.error(f"Failed to load ONNX model: {e}")
|
|
|
|
def is_available(self) -> bool:
|
|
"""Check if ONNX backend is available.
|
|
|
|
Returns:
|
|
True if ONNX is available and model is loaded, False otherwise
|
|
"""
|
|
return self._available
|
|
|
|
def generate_embeddings(self, data: List[Any]) -> List[List[float]]:
|
|
"""Generate embeddings using ONNX model.
|
|
|
|
Args:
|
|
data: List of items to generate embeddings for
|
|
|
|
Returns:
|
|
List of embedding vectors
|
|
"""
|
|
if not self.is_available():
|
|
logger.warning("ONNX backend not available, using CPU fallback")
|
|
cpu_backend = CPUBackend()
|
|
return cpu_backend.generate_embeddings(data)
|
|
|
|
try:
|
|
np = _get_numpy()
|
|
# Placeholder: actual implementation would use ONNX model
|
|
embeddings = []
|
|
for _ in data:
|
|
# Convert data to format expected by ONNX model
|
|
# This is a placeholder - actual implementation would preprocess data
|
|
embedding = np.random.randn(self.dimensions).tolist()
|
|
embeddings.append(embedding)
|
|
return embeddings
|
|
except Exception as e:
|
|
logger.error(f"Error generating embeddings with ONNX backend: {e}")
|
|
# Fallback to CPU backend
|
|
cpu_backend = CPUBackend()
|
|
return cpu_backend.generate_embeddings(data)
|
|
|
|
def get_embedding_dimensions(self) -> int:
|
|
"""Get embedding dimensionality.
|
|
|
|
Returns:
|
|
Embedding dimension size
|
|
"""
|
|
return self.dimensions
|
|
|
|
|
|
def create_ml_backend(backend_type: str = "auto", **kwargs) -> MLBackend:
|
|
"""Create an ML backend instance with graceful degradation.
|
|
|
|
Args:
|
|
backend_type: Type of backend ('auto', 'cpu', 'onnx')
|
|
**kwargs: Additional arguments for backend creation
|
|
|
|
Returns:
|
|
MLBackend instance
|
|
"""
|
|
backend_type = backend_type.lower()
|
|
|
|
if backend_type == "auto":
|
|
# Try ONNX first, fallback to CPU
|
|
try:
|
|
onnx_backend = ONNXBackend(kwargs.get('model_path'))
|
|
if onnx_backend.is_available():
|
|
logger.info("Using ONNX backend")
|
|
return onnx_backend
|
|
except Exception:
|
|
pass
|
|
|
|
# Fallback to CPU
|
|
logger.info("Using CPU backend (fallback)")
|
|
return CPUBackend()
|
|
|
|
elif backend_type == "onnx":
|
|
return ONNXBackend(kwargs.get('model_path'))
|
|
|
|
elif backend_type == "cpu":
|
|
return CPUBackend()
|
|
|
|
else:
|
|
raise ValueError(f"Unknown backend type: {backend_type}")
|
|
|
|
|
|
# Module-level backend instance (lazy initialization)
|
|
ML_BACKEND: Optional[MLBackend] = None
|
|
|
|
|
|
def get_ml_backend() -> MLBackend:
|
|
"""Get the global ML backend instance.
|
|
|
|
Returns:
|
|
The singleton MLBackend instance
|
|
"""
|
|
global ML_BACKEND
|
|
if ML_BACKEND is None:
|
|
ML_BACKEND = create_ml_backend()
|
|
return ML_BACKEND
|
|
|
|
|
|
# Initialize backend on import
|
|
get_ml_backend()
|
|
|
|
__all__ = ['MLBackend', 'CPUBackend', 'ONNXBackend', 'create_ml_backend', 'get_ml_backend']
|