Monday, July 27, 2026

BUILDING AN LLM MANAGEMENT PLATFORM WITH UNIFIED INTERFACE AND MULTI-BACKEND SUPPORT



INTRODUCTION AND ARCHITECTURAL VISION

The proliferation of large language models has created a fragmented ecosystem where users must navigate different model formats, hardware requirements, quantization schemes, and deployment strategies. A sophisticated LLM management platform must unify these disparate elements into a cohesive system that handles model discovery, acquisition, optimization, deployment, and inference across heterogeneous hardware environments. This article presents a comprehensive approach to building such a platform, addressing the technical challenges of hardware abstraction, model lifecycle management, fine-tuning workflows, quantization pipelines, format conversion, interactive chat interfaces, and programmatic API access.


The platform architecture consists of several interconnected subsystems working in concert. The hardware detection and abstraction layer identifies available compute resources and provides a unified interface across NVIDIA CUDA, AMD ROCm, Intel oneAPI, and Apple Metal Performance Shaders. The model registry and discovery system enables searching for models from various sources including Hugging Face, local repositories, and custom endpoints. The download manager handles large file transfers with resumption capabilities and integrity verification. The model loader supports multiple formats including PyTorch, SafeTensors, GGUF, and ONNX while managing memory efficiently across different hardware backends.


The quantization engine provides multiple quantization strategies including post-training quantization using GPTQ and AWQ methods, as well as quantization-aware training for models that will be deployed in resource-constrained environments. The fine-tuning subsystem implements parameter-efficient methods like LoRA and QLoRA, enabling users to adapt models to specific domains without requiring full model retraining. The format conversion pipeline handles transformations between different model serialization formats, ensuring compatibility across frameworks and deployment targets.


The user interface layer provides both a graphical interface built with modern web technologies and a REST API conforming to OpenAI specifications for programmatic access. The chat interface implements streaming responses, conversation history management, and configurable generation parameters. The model information dashboard displays comprehensive metadata including architecture details, parameter counts, quantization status, memory requirements, and performance characteristics.


HARDWARE ABSTRACTION AND RUNTIME DETECTION

Building a truly portable LLM platform requires abstracting hardware differences while maximizing performance on each backend. The hardware abstraction layer must detect available compute devices at runtime, determine their capabilities, and select appropriate execution strategies. This detection process involves querying system libraries, checking driver versions, and validating that required dependencies are properly installed.


The detection logic begins by attempting to import backend-specific libraries in a priority order that reflects typical performance characteristics. NVIDIA CUDA receives highest priority due to its maturity and widespread adoption in the LLM ecosystem. AMD ROCm follows as a viable alternative for AMD GPU users. Apple Metal Performance Shaders provides optimized execution on Apple Silicon. Intel oneAPI enables acceleration on Intel discrete and integrated GPUs. When no GPU is available, the system falls back to optimized CPU execution using libraries like Intel MKL or OpenBLAS.


import sys

import os

import platform

from typing import Optional, Dict, Any, List

from dataclasses import dataclass

from enum import Enum


class BackendType(Enum):

    CUDA = "cuda"

    ROCM = "rocm"

    MPS = "mps"

    INTEL = "intel"

    CPU = "cpu"


@dataclass

class DeviceInfo:

    backend: BackendType

    device_id: int

    name: str

    total_memory: int

    compute_capability: Optional[str]

    driver_version: Optional[str]


class HardwareDetector:

    """

    Detects available compute hardware and provides unified device abstraction.

    Handles CUDA, ROCm, MPS, Intel, and CPU backends with automatic fallback.

    """

    

    def __init__(self):

        self.available_backends = []

        self.devices = []

        self._detect_all_backends()

    

    def _detect_cuda(self) -> List[DeviceInfo]:

        """Detect NVIDIA CUDA devices"""

        devices = []

        try:

            import torch

            if torch.cuda.is_available():

                for i in range(torch.cuda.device_count()):

                    props = torch.cuda.get_device_properties(i)

                    devices.append(DeviceInfo(

                        backend=BackendType.CUDA,

                        device_id=i,

                        name=props.name,

                        total_memory=props.total_memory,

                        compute_capability=f"{props.major}.{props.minor}",

                        driver_version=torch.version.cuda

                    ))

        except (ImportError, RuntimeError) as e:

            pass

        return devices

    

    def _detect_rocm(self) -> List[DeviceInfo]:

        """Detect AMD ROCm devices"""

        devices = []

        try:

            import torch

            if hasattr(torch, 'hip') and torch.hip.is_available():

                for i in range(torch.hip.device_count()):

                    props = torch.hip.get_device_properties(i)

                    devices.append(DeviceInfo(

                        backend=BackendType.ROCM,

                        device_id=i,

                        name=props.name,

                        total_memory=props.total_memory,

                        compute_capability=props.gcnArchName,

                        driver_version=torch.version.hip

                    ))

        except (ImportError, AttributeError, RuntimeError):

            pass

        return devices

    

    def _detect_mps(self) -> List[DeviceInfo]:

        """Detect Apple Metal Performance Shaders"""

        devices = []

        try:

            import torch

            if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():

                # MPS doesn't expose detailed device info, use system profiler

                devices.append(DeviceInfo(

                    backend=BackendType.MPS,

                    device_id=0,

                    name="Apple GPU",

                    total_memory=self._get_mps_memory(),

                    compute_capability=None,

                    driver_version=platform.mac_ver()[0]

                ))

        except (ImportError, AttributeError, RuntimeError):

            pass

        return devices

    

    def _get_mps_memory(self) -> int:

        """Estimate available memory on Apple Silicon"""

        try:

            import subprocess

            result = subprocess.run(

                ['sysctl', 'hw.memsize'],

                capture_output=True,

                text=True

            )

            if result.returncode == 0:

                return int(result.stdout.split(':')[1].strip())

        except:

            pass

        return 8 * 1024 * 1024 * 1024  # Default 8GB estimate

    

    def _detect_intel(self) -> List[DeviceInfo]:

        """Detect Intel GPUs via oneAPI"""

        devices = []

        try:

            import intel_extension_for_pytorch as ipex

            if ipex.xpu.is_available():

                for i in range(ipex.xpu.device_count()):

                    devices.append(DeviceInfo(

                        backend=BackendType.INTEL,

                        device_id=i,

                        name=f"Intel XPU {i}",

                        total_memory=ipex.xpu.get_device_properties(i).total_memory,

                        compute_capability=None,

                        driver_version=ipex.__version__

                    ))

        except (ImportError, AttributeError, RuntimeError):

            pass

        return devices

    

    def _detect_all_backends(self):

        """Detect all available backends in priority order"""

        detection_methods = [

            (BackendType.CUDA, self._detect_cuda),

            (BackendType.ROCM, self._detect_rocm),

            (BackendType.MPS, self._detect_mps),

            (BackendType.INTEL, self._detect_intel)

        ]

        

        for backend_type, detect_func in detection_methods:

            detected = detect_func()

            if detected:

                self.available_backends.append(backend_type)

                self.devices.extend(detected)

        

        # Always add CPU as fallback

        if BackendType.CPU not in self.available_backends:

            self.available_backends.append(BackendType.CPU)

            self.devices.append(DeviceInfo(

                backend=BackendType.CPU,

                device_id=0,

                name=platform.processor() or "CPU",

                total_memory=self._get_system_memory(),

                compute_capability=None,

                driver_version=None

            ))

    

    def _get_system_memory(self) -> int:

        """Get total system RAM"""

        try:

            import psutil

            return psutil.virtual_memory().total

        except ImportError:

            return 16 * 1024 * 1024 * 1024  # Default 16GB estimate

    

    def get_best_device(self) -> DeviceInfo:

        """Return the most capable available device"""

        if not self.devices:

            raise RuntimeError("No compute devices available")

        

        # Prefer GPU over CPU, prioritize by memory

        gpu_devices = [d for d in self.devices if d.backend != BackendType.CPU]

        if gpu_devices:

            return max(gpu_devices, key=lambda d: d.total_memory)

        return self.devices[0]

    

    def get_device_string(self, device_info: DeviceInfo) -> str:

        """Convert DeviceInfo to framework-specific device string"""

        if device_info.backend == BackendType.CUDA:

            return f"cuda:{device_info.device_id}"

        elif device_info.backend == BackendType.ROCM:

            return f"cuda:{device_info.device_id}"  # ROCm uses cuda namespace

        elif device_info.backend == BackendType.MPS:

            return "mps"

        elif device_info.backend == BackendType.INTEL:

            return f"xpu:{device_info.device_id}"

        else:

            return "cpu"


The hardware detector provides a unified abstraction over different compute backends. Each detection method attempts to import the relevant library and query device properties. The CUDA detection uses PyTorch CUDA APIs to enumerate NVIDIA GPUs and retrieve their properties including compute capability and memory. The ROCm detection follows a similar pattern but checks for HIP availability. The MPS detection verifies Metal Performance Shaders support on macOS systems. The Intel detection uses Intel Extension for PyTorch to discover Intel GPUs.


The device selection logic prioritizes GPUs over CPUs and selects the device with the most memory when multiple options exist. This heuristic works well for LLM inference where memory capacity often determines which models can be loaded. The device string conversion method translates the abstract device representation into framework-specific strings that can be passed to PyTorch or other libraries.


MODEL DISCOVERY AND REGISTRY MANAGEMENT

The model discovery system enables users to search for models from multiple sources including Hugging Face Hub, local file systems, and custom model repositories. The registry maintains metadata about available models including their size, format, hardware requirements, and download status. This metadata enables intelligent filtering and recommendation of models suitable for the user's hardware configuration.


The discovery interface provides both text-based search and filtering by model characteristics. Users can search by model name, architecture type, parameter count, quantization level, or task specialization. The system queries remote APIs asynchronously to avoid blocking the user interface while maintaining a local cache to improve responsiveness for repeated searches.


import asyncio

import aiohttp

import json

from pathlib import Path

from typing import List, Optional, Dict, Any

from dataclasses import dataclass, asdict

from datetime import datetime

import hashlib


@dataclass

class ModelMetadata:

    """Comprehensive metadata for a language model"""

    model_id: str

    name: str

    architecture: str

    parameter_count: int

    quantization: Optional[str]

    format: str

    size_bytes: int

    source: str

    description: str

    tags: List[str]

    license: str

    min_memory_gb: float

    supported_backends: List[str]

    download_url: Optional[str]

    local_path: Optional[Path]

    is_downloaded: bool

    last_updated: datetime


class ModelRegistry:

    """

    Manages model discovery, metadata storage, and local cache.

    Supports searching Hugging Face Hub and local repositories.

    """

    

    def __init__(self, cache_dir: Path):

        self.cache_dir = Path(cache_dir)

        self.cache_dir.mkdir(parents=True, exist_ok=True)

        self.registry_file = self.cache_dir / "registry.json"

        self.models: Dict[str, ModelMetadata] = {}

        self._load_registry()

    

    def _load_registry(self):

        """Load cached registry from disk"""

        if self.registry_file.exists():

            try:

                with open(self.registry_file, 'r') as f:

                    data = json.load(f)

                    for model_data in data:

                        model_data['last_updated'] = datetime.fromisoformat(

                            model_data['last_updated']

                        )

                        if model_data.get('local_path'):

                            model_data['local_path'] = Path(model_data['local_path'])

                        metadata = ModelMetadata(**model_data)

                        self.models[metadata.model_id] = metadata

            except Exception as e:

                print(f"Failed to load registry: {e}")

    

    def _save_registry(self):

        """Persist registry to disk"""

        try:

            data = []

            for metadata in self.models.values():

                model_dict = asdict(metadata)

                model_dict['last_updated'] = metadata.last_updated.isoformat()

                if metadata.local_path:

                    model_dict['local_path'] = str(metadata.local_path)

                data.append(model_dict)

            

            with open(self.registry_file, 'w') as f:

                json.dump(data, f, indent=2)

        except Exception as e:

            print(f"Failed to save registry: {e}")

    

    async def search_huggingface(

        self,

        query: str,

        filter_params: Optional[Dict[str, Any]] = None

    ) -> List[ModelMetadata]:

        """

        Search Hugging Face Hub for models matching query.

        Supports filtering by task, library, language, etc.

        """

        results = []

        base_url = "https://huggingface.co/api/models"

        

        params = {

            'search': query,

            'limit': 50,

            'full': 'true'

        }

        

        if filter_params:

            if 'task' in filter_params:

                params['filter'] = filter_params['task']

            if 'library' in filter_params:

                params['library'] = filter_params['library']

        

        try:

            async with aiohttp.ClientSession() as session:

                async with session.get(base_url, params=params) as response:

                    if response.status == 200:

                        models_data = await response.json()

                        

                        for model_data in models_data:

                            # Extract relevant information from HF API response

                            model_id = model_data.get('id', '')

                            

                            # Determine architecture from tags or config

                            architecture = self._extract_architecture(model_data)

                            

                            # Estimate parameters from model card or size

                            param_count = self._estimate_parameters(model_data)

                            

                            # Check for quantization in model ID or tags

                            quantization = self._detect_quantization(model_data)

                            

                            # Determine format from files

                            model_format = self._detect_format(model_data)

                            

                            # Get model size

                            size_bytes = self._calculate_size(model_data)

                            

                            # Extract tags

                            tags = model_data.get('tags', [])

                            

                            # Get description

                            description = model_data.get('description', '')

                            

                            # Determine license

                            license_info = model_data.get('license', 'unknown')

                            

                            # Calculate minimum memory requirement

                            min_memory = self._calculate_min_memory(

                                param_count,

                                quantization

                            )

                            

                            # Determine supported backends

                            supported_backends = self._determine_backends(

                                model_format,

                                quantization

                            )

                            

                            metadata = ModelMetadata(

                                model_id=model_id,

                                name=model_data.get('modelId', model_id),

                                architecture=architecture,

                                parameter_count=param_count,

                                quantization=quantization,

                                format=model_format,

                                size_bytes=size_bytes,

                                source='huggingface',

                                description=description,

                                tags=tags,

                                license=license_info,

                                min_memory_gb=min_memory,

                                supported_backends=supported_backends,

                                download_url=f"https://huggingface.co/{model_id}",

                                local_path=None,

                                is_downloaded=False,

                                last_updated=datetime.now()

                            )

                            

                            results.append(metadata)

                            self.models[model_id] = metadata

        

        except Exception as e:

            print(f"Error searching Hugging Face: {e}")

        

        self._save_registry()

        return results

    

    def _extract_architecture(self, model_data: Dict) -> str:

        """Extract model architecture from metadata"""

        tags = model_data.get('tags', [])

        

        # Common architecture tags

        arch_mapping = {

            'llama': 'LLaMA',

            'mistral': 'Mistral',

            'gpt2': 'GPT-2',

            'gpt-neo': 'GPT-Neo',

            'gpt-j': 'GPT-J',

            'bloom': 'BLOOM',

            'opt': 'OPT',

            'falcon': 'Falcon',

            'mpt': 'MPT',

            'phi': 'Phi',

            'qwen': 'Qwen',

            'gemma': 'Gemma'

        }

        

        for tag in tags:

            tag_lower = tag.lower()

            for key, arch in arch_mapping.items():

                if key in tag_lower:

                    return arch

        

        # Check model ID for architecture hints

        model_id = model_data.get('id', '').lower()

        for key, arch in arch_mapping.items():

            if key in model_id:

                return arch

        

        return 'Unknown'

    

    def _estimate_parameters(self, model_data: Dict) -> int:

        """Estimate parameter count from model metadata"""

        # Try to extract from model ID

        model_id = model_data.get('id', '').lower()

        

        # Common patterns: 7b, 13b, 70b, etc.

        import re

        param_pattern = r'(\d+\.?\d*)b'

        match = re.search(param_pattern, model_id)

        if match:

            billions = float(match.group(1))

            return int(billions * 1_000_000_000)

        

        # Try to get from safetensors metadata if available

        siblings = model_data.get('siblings', [])

        for sibling in siblings:

            if 'safetensors' in sibling.get('rfilename', ''):

                # Estimate from file size (rough approximation)

                size = sibling.get('size', 0)

                # Assuming fp16: 2 bytes per parameter

                return size // 2

        

        return 0  # Unknown

    

    def _detect_quantization(self, model_data: Dict) -> Optional[str]:

        """Detect quantization scheme from model metadata"""

        model_id = model_data.get('id', '').lower()

        tags = [t.lower() for t in model_data.get('tags', [])]

        

        quant_schemes = {

            'gptq': 'GPTQ',

            'awq': 'AWQ',

            'gguf': 'GGUF',

            'ggml': 'GGML',

            'int8': 'INT8',

            'int4': 'INT4',

            '4bit': '4-bit',

            '8bit': '8-bit'

        }

        

        for scheme_key, scheme_name in quant_schemes.items():

            if scheme_key in model_id or scheme_key in ' '.join(tags):

                return scheme_name

        

        return None

    

    def _detect_format(self, model_data: Dict) -> str:

        """Detect model format from files"""

        siblings = model_data.get('siblings', [])

        

        for sibling in siblings:

            filename = sibling.get('rfilename', '').lower()

            if 'safetensors' in filename:

                return 'safetensors'

            elif '.gguf' in filename:

                return 'gguf'

            elif '.bin' in filename:

                return 'pytorch'

            elif '.onnx' in filename:

                return 'onnx'

        

        return 'unknown'

    

    def _calculate_size(self, model_data: Dict) -> int:

        """Calculate total model size from files"""

        siblings = model_data.get('siblings', [])

        total_size = 0

        

        for sibling in siblings:

            size = sibling.get('size', 0)

            total_size += size

        

        return total_size

    

    def _calculate_min_memory(

        self,

        param_count: int,

        quantization: Optional[str]

    ) -> float:

        """Calculate minimum memory requirement in GB"""

        if param_count == 0:

            return 0.0

        

        # Base calculation assumes fp16 (2 bytes per parameter)

        bytes_per_param = 2.0

        

        if quantization:

            if '4' in quantization or 'INT4' in quantization:

                bytes_per_param = 0.5

            elif '8' in quantization or 'INT8' in quantization:

                bytes_per_param = 1.0

        

        # Add overhead for activations and KV cache (rough estimate)

        memory_bytes = param_count * bytes_per_param * 1.2

        

        return memory_bytes / (1024 ** 3)  # Convert to GB

    

    def _determine_backends(

        self,

        model_format: str,

        quantization: Optional[str]

    ) -> List[str]:

        """Determine which backends support this model"""

        backends = []

        

        if model_format in ['safetensors', 'pytorch']:

            # Standard PyTorch formats support all backends

            backends = ['cuda', 'rocm', 'mps', 'intel', 'cpu']

        elif model_format == 'gguf':

            # GGUF typically used with llama.cpp, supports CPU and some GPU

            backends = ['cpu', 'cuda', 'mps']

        elif model_format == 'onnx':

            # ONNX Runtime supports multiple backends

            backends = ['cuda', 'cpu']

        

        # Some quantization schemes have limited backend support

        if quantization == 'GPTQ':

            backends = ['cuda']  # GPTQ primarily CUDA-only

        elif quantization == 'AWQ':

            backends = ['cuda']  # AWQ primarily CUDA-only

        

        return backends if backends else ['cpu']

    

    def search_local(

        self,

        query: str,

        local_dirs: List[Path]

    ) -> List[ModelMetadata]:

        """Search local directories for models"""

        results = []

        

        for directory in local_dirs:

            if not directory.exists():

                continue

            

            # Look for model directories

            for model_path in directory.iterdir():

                if not model_path.is_dir():

                    continue

                

                # Check if directory contains model files

                has_model = any(

                    f.suffix in ['.safetensors', '.bin', '.gguf', '.onnx']

                    for f in model_path.iterdir()

                )

                

                if not has_model:

                    continue

                

                # Try to load metadata from config.json

                config_file = model_path / 'config.json'

                if config_file.exists():

                    try:

                        with open(config_file, 'r') as f:

                            config = json.load(f)

                        

                        architecture = config.get('architectures', ['Unknown'])[0]

                        

                        # Calculate size

                        size_bytes = sum(

                            f.stat().st_size

                            for f in model_path.rglob('*')

                            if f.is_file()

                        )

                        

                        model_id = f"local/{model_path.name}"

                        

                        metadata = ModelMetadata(

                            model_id=model_id,

                            name=model_path.name,

                            architecture=architecture,

                            parameter_count=0,  # Would need to parse config

                            quantization=None,

                            format=self._detect_local_format(model_path),

                            size_bytes=size_bytes,

                            source='local',

                            description=f"Local model at {model_path}",

                            tags=[],

                            license='unknown',

                            min_memory_gb=size_bytes / (1024 ** 3),

                            supported_backends=['cuda', 'rocm', 'mps', 'intel', 'cpu'],

                            download_url=None,

                            local_path=model_path,

                            is_downloaded=True,

                            last_updated=datetime.fromtimestamp(

                                model_path.stat().st_mtime

                            )

                        )

                        

                        results.append(metadata)

                        self.models[model_id] = metadata

                    

                    except Exception as e:

                        print(f"Error processing {model_path}: {e}")

        

        self._save_registry()

        return results

    

    def _detect_local_format(self, model_path: Path) -> str:

        """Detect format of local model"""

        for file in model_path.iterdir():

            if file.suffix == '.safetensors':

                return 'safetensors'

            elif file.suffix == '.gguf':

                return 'gguf'

            elif file.suffix == '.bin':

                return 'pytorch'

            elif file.suffix == '.onnx':

                return 'onnx'

        return 'unknown'

    

    def filter_models(

        self,

        models: List[ModelMetadata],

        hardware_detector: HardwareDetector,

        max_memory_gb: Optional[float] = None

    ) -> List[ModelMetadata]:

        """Filter models based on hardware compatibility"""

        best_device = hardware_detector.get_best_device()

        backend_type = best_device.backend.value

        

        available_memory_gb = best_device.total_memory / (1024 ** 3)

        if max_memory_gb is None:

            max_memory_gb = available_memory_gb * 0.8  # Use 80% of available

        

        filtered = []

        for model in models:

            # Check backend compatibility

            if backend_type not in model.supported_backends:

                continue

            

            # Check memory requirement

            if model.min_memory_gb > max_memory_gb:

                continue

            

            filtered.append(model)

        

        return filtered


The model registry provides comprehensive model discovery and management capabilities. The search functionality queries the Hugging Face API asynchronously to retrieve model information without blocking the user interface. The metadata extraction logic parses model identifiers and tags to infer architecture, parameter count, and quantization scheme. This heuristic approach works well for most models that follow common naming conventions.


The local search capability scans specified directories for model files and attempts to load configuration files to extract metadata. This enables users to manage models they have downloaded manually or obtained from sources other than Hugging Face. The filtering logic considers hardware compatibility and memory constraints to recommend models that will actually run on the user's system.


MODEL DOWNLOAD AND VERIFICATION

The download manager handles large file transfers with support for resumption, parallel downloads, and integrity verification. LLM models often consist of multiple files totaling tens or hundreds of gigabytes, making robust download handling essential for good user experience. The system must handle network interruptions gracefully and provide progress feedback to users.


import aiofiles

import hashlib

from typing import Callable, Optional

from pathlib import Path

import aiohttp

from tqdm import tqdm


class ModelDownloader:

    """

    Handles downloading large model files with resumption and verification.

    Supports parallel downloads of multiple files and progress tracking.

    """

    

    def __init__(self, download_dir: Path, chunk_size: int = 8192):

        self.download_dir = Path(download_dir)

        self.download_dir.mkdir(parents=True, exist_ok=True)

        self.chunk_size = chunk_size

    

    async def download_model(

        self,

        metadata: ModelMetadata,

        progress_callback: Optional[Callable[[int, int], None]] = None

    ) -> Path:

        """

        Download a complete model from Hugging Face Hub.

        Returns the local path where model was saved.

        """

        model_dir = self.download_dir / metadata.model_id.replace('/', '_')

        model_dir.mkdir(parents=True, exist_ok=True)

        

        # Get list of files to download

        files_to_download = await self._get_model_files(metadata.model_id)

        

        total_size = sum(f['size'] for f in files_to_download)

        downloaded_size = 0

        

        async with aiohttp.ClientSession() as session:

            for file_info in files_to_download:

                file_path = model_dir / file_info['filename']

                file_url = file_info['url']

                file_size = file_info['size']

                

                # Check if file already exists and is complete

                if file_path.exists() and file_path.stat().st_size == file_size:

                    downloaded_size += file_size

                    if progress_callback:

                        progress_callback(downloaded_size, total_size)

                    continue

                

                # Download file with resumption support

                bytes_downloaded = await self._download_file(

                    session,

                    file_url,

                    file_path,

                    file_size,

                    lambda current: progress_callback(

                        downloaded_size + current,

                        total_size

                    ) if progress_callback else None

                )

                

                downloaded_size += bytes_downloaded

        

        # Update metadata

        metadata.local_path = model_dir

        metadata.is_downloaded = True

        

        return model_dir

    

    async def _get_model_files(self, model_id: str) -> List[Dict[str, Any]]:

        """Get list of files for a model from Hugging Face API"""

        api_url = f"https://huggingface.co/api/models/{model_id}"

        

        async with aiohttp.ClientSession() as session:

            async with session.get(api_url) as response:

                if response.status != 200:

                    raise RuntimeError(f"Failed to get model info: {response.status}")

                

                model_data = await response.json()

                siblings = model_data.get('siblings', [])

                

                files = []

                for sibling in siblings:

                    filename = sibling.get('rfilename', '')

                    

                    # Skip unnecessary files

                    if any(skip in filename for skip in ['.md', '.txt', '.gitattributes']):

                        continue

                    

                    file_url = f"https://huggingface.co/{model_id}/resolve/main/{filename}"

                    

                    files.append({

                        'filename': filename,

                        'url': file_url,

                        'size': sibling.get('size', 0)

                    })

                

                return files

    

    async def _download_file(

        self,

        session: aiohttp.ClientSession,

        url: str,

        destination: Path,

        expected_size: int,

        progress_callback: Optional[Callable[[int], None]] = None

    ) -> int:

        """Download a single file with resumption support"""

        # Check if partial download exists

        resume_pos = 0

        if destination.exists():

            resume_pos = destination.stat().st_size

            if resume_pos >= expected_size:

                return expected_size  # Already complete

        

        # Set range header for resumption

        headers = {}

        if resume_pos > 0:

            headers['Range'] = f'bytes={resume_pos}-'

        

        mode = 'ab' if resume_pos > 0 else 'wb'

        

        async with session.get(url, headers=headers) as response:

            if response.status not in [200, 206]:

                raise RuntimeError(f"Download failed: {response.status}")

            

            async with aiofiles.open(destination, mode) as f:

                bytes_written = resume_pos

                

                async for chunk in response.content.iter_chunked(self.chunk_size):

                    await f.write(chunk)

                    bytes_written += len(chunk)

                    

                    if progress_callback:

                        progress_callback(bytes_written)

        

        return bytes_written

    

    def verify_model(self, model_path: Path) -> bool:

        """Verify model integrity by checking file sizes and formats"""

        if not model_path.exists() or not model_path.is_dir():

            return False

        

        # Check for essential files

        has_config = (model_path / 'config.json').exists()

        

        # Check for model weight files

        weight_files = list(model_path.glob('*.safetensors')) + \

                      list(model_path.glob('*.bin')) + \

                      list(model_path.glob('*.gguf'))

        

        if not weight_files:

            return False

        

        # Verify each weight file is not corrupted (basic check)

        for weight_file in weight_files:

            if weight_file.stat().st_size == 0:

                return False

        

        return True


The download manager implements resumable downloads by checking for partially downloaded files and using HTTP range requests to continue from where the download was interrupted. This is crucial for large model files where network interruptions are common. The progress callback mechanism enables the user interface to display real-time download progress.


The file verification logic performs basic integrity checks to ensure downloaded files are complete and not corrupted. More sophisticated verification could include checksum validation if the model repository provides hash values. The verification step prevents users from attempting to load incomplete or corrupted models which would result in cryptic error messages.


MODEL LOADING AND MEMORY MANAGEMENT

The model loader must handle different model formats and optimize memory usage across various hardware backends. Loading a large language model involves reading weight files, initializing the model architecture, and transferring weights to the appropriate compute device. The loader must handle quantized models specially since they require different data types and may use custom kernels for inference.


import torch

from transformers import AutoModelForCausalLM, AutoTokenizer, AutoConfig

from typing import Optional, Tuple, Dict, Any

from pathlib import Path

import gc


class ModelLoader:

    """

    Loads and manages LLM models across different formats and backends.

    Handles memory optimization, quantization, and device placement.

    """

    

    def __init__(self, hardware_detector: HardwareDetector):

        self.hardware_detector = hardware_detector

        self.loaded_models: Dict[str, Tuple[Any, Any]] = {}

        self.current_model_id: Optional[str] = None

    

    def load_model(

        self,

        metadata: ModelMetadata,

        device: Optional[DeviceInfo] = None,

        load_in_8bit: bool = False,

        load_in_4bit: bool = False

    ) -> Tuple[Any, Any]:

        """

        Load a model and tokenizer, handling different formats and quantization.

        Returns (model, tokenizer) tuple.

        """

        if device is None:

            device = self.hardware_detector.get_best_device()

        

        device_str = self.hardware_detector.get_device_string(device)

        

        # Unload current model if different

        if self.current_model_id and self.current_model_id != metadata.model_id:

            self.unload_current_model()

        

        # Check if already loaded

        if metadata.model_id in self.loaded_models:

            self.current_model_id = metadata.model_id

            return self.loaded_models[metadata.model_id]

        

        model_path = metadata.local_path or metadata.model_id

        

        # Load based on format

        if metadata.format in ['safetensors', 'pytorch']:

            model, tokenizer = self._load_transformers_model(

                model_path,

                device_str,

                device.backend,

                load_in_8bit,

                load_in_4bit

            )

        elif metadata.format == 'gguf':

            model, tokenizer = self._load_gguf_model(

                model_path,

                device_str

            )

        else:

            raise ValueError(f"Unsupported format: {metadata.format}")

        

        self.loaded_models[metadata.model_id] = (model, tokenizer)

        self.current_model_id = metadata.model_id

        

        return model, tokenizer

    

    def _load_transformers_model(

        self,

        model_path: Any,

        device_str: str,

        backend: BackendType,

        load_in_8bit: bool,

        load_in_4bit: bool

    ) -> Tuple[Any, Any]:

        """Load model using Hugging Face Transformers"""

        # Prepare loading arguments

        load_kwargs = {

            'torch_dtype': torch.float16,

            'low_cpu_mem_usage': True

        }

        

        # Handle quantization

        if load_in_8bit:

            load_kwargs['load_in_8bit'] = True

            load_kwargs['device_map'] = 'auto'

        elif load_in_4bit:

            from transformers import BitsAndBytesConfig

            

            bnb_config = BitsAndBytesConfig(

                load_in_4bit=True,

                bnb_4bit_compute_dtype=torch.float16,

                bnb_4bit_use_double_quant=True,

                bnb_4bit_quant_type='nf4'

            )

            load_kwargs['quantization_config'] = bnb_config

            load_kwargs['device_map'] = 'auto'

        else:

            # Manual device placement

            if 'cpu' not in device_str:

                load_kwargs['device_map'] = 'auto'

        

        # Backend-specific optimizations

        if backend == BackendType.MPS:

            # MPS doesn't support all quantization methods

            load_kwargs.pop('load_in_8bit', None)

            load_kwargs.pop('quantization_config', None)

            load_kwargs.pop('device_map', None)

        

        # Load model

        try:

            model = AutoModelForCausalLM.from_pretrained(

                model_path,

                **load_kwargs

            )

            

            # Move to device if not using device_map

            if 'device_map' not in load_kwargs and 'cpu' not in device_str:

                model = model.to(device_str)

            

            # Load tokenizer

            tokenizer = AutoTokenizer.from_pretrained(model_path)

            

            # Set pad token if not present

            if tokenizer.pad_token is None:

                tokenizer.pad_token = tokenizer.eos_token

            

            return model, tokenizer

        

        except Exception as e:

            raise RuntimeError(f"Failed to load model: {e}")

    

    def _load_gguf_model(

        self,

        model_path: Any,

        device_str: str

    ) -> Tuple[Any, Any]:

        """Load GGUF format model using llama-cpp-python"""

        try:

            from llama_cpp import Llama

            

            # Find GGUF file

            if isinstance(model_path, Path):

                gguf_files = list(model_path.glob('*.gguf'))

                if not gguf_files:

                    raise ValueError("No GGUF file found")

                model_file = str(gguf_files[0])

            else:

                model_file = model_path

            

            # Determine GPU layers based on device

            n_gpu_layers = 0

            if 'cuda' in device_str or 'mps' in device_str:

                n_gpu_layers = -1  # Use all layers on GPU

            

            # Load model

            model = Llama(

                model_path=model_file,

                n_gpu_layers=n_gpu_layers,

                n_ctx=2048,

                verbose=False

            )

            

            # GGUF models have built-in tokenization

            tokenizer = None

            

            return model, tokenizer

        

        except ImportError:

            raise RuntimeError(

                "llama-cpp-python not installed. "

                "Install with: pip install llama-cpp-python"

            )

        except Exception as e:

            raise RuntimeError(f"Failed to load GGUF model: {e}")

    

    def unload_current_model(self):

        """Unload currently loaded model to free memory"""

        if self.current_model_id and self.current_model_id in self.loaded_models:

            model, tokenizer = self.loaded_models[self.current_model_id]

            

            # Move model to CPU and delete

            if hasattr(model, 'cpu'):

                model.cpu()

            

            del model

            del tokenizer

            

            self.loaded_models.pop(self.current_model_id)

            self.current_model_id = None

            

            # Force garbage collection

            gc.collect()

            

            # Clear CUDA cache if available

            if torch.cuda.is_available():

                torch.cuda.empty_cache()

    

    def get_model_info(self, model: Any) -> Dict[str, Any]:

        """Extract detailed information about loaded model"""

        info = {}

        

        if hasattr(model, 'config'):

            config = model.config

            info['architecture'] = config.architectures[0] if hasattr(config, 'architectures') else 'Unknown'

            info['hidden_size'] = getattr(config, 'hidden_size', None)

            info['num_layers'] = getattr(config, 'num_hidden_layers', None)

            info['num_attention_heads'] = getattr(config, 'num_attention_heads', None)

            info['vocab_size'] = getattr(config, 'vocab_size', None)

            info['max_position_embeddings'] = getattr(config, 'max_position_embeddings', None)

        

        # Calculate parameter count

        if hasattr(model, 'parameters'):

            total_params = sum(p.numel() for p in model.parameters())

            trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)

            info['total_parameters'] = total_params

            info['trainable_parameters'] = trainable_params

        

        # Get memory usage

        if hasattr(model, 'get_memory_footprint'):

            info['memory_footprint_bytes'] = model.get_memory_footprint()

        elif torch.cuda.is_available():

            info['cuda_memory_allocated'] = torch.cuda.memory_allocated()

            info['cuda_memory_reserved'] = torch.cuda.memory_reserved()

        

        # Get dtype

        if hasattr(model, 'dtype'):

            info['dtype'] = str(model.dtype)

        

        return info


The model loader handles the complexity of different model formats and quantization schemes. For standard Transformers models, it uses the Hugging Face library with appropriate configuration for quantization. The 8-bit and 4-bit quantization support uses the BitsAndBytes library which provides efficient quantized inference on CUDA devices. For GGUF models, it uses llama-cpp-python which provides optimized inference for quantized models across multiple backends.


The memory management logic ensures that only one large model is loaded at a time to avoid out-of-memory errors. When loading a new model, the system unloads the current model and forces garbage collection to free memory. The CUDA cache clearing is particularly important on NVIDIA GPUs where PyTorch maintains a memory pool that may not be released immediately.


QUANTIZATION ENGINE

The quantization engine provides post-training quantization capabilities to reduce model size and memory requirements. Quantization converts model weights from floating-point to lower-precision integer representations, typically reducing memory usage by 50 to 75 percent with minimal accuracy loss. The engine supports multiple quantization schemes including GPTQ, AWQ, and simple round-to-nearest quantization.


from typing import Optional, Dict, Any

import torch

from pathlib import Path


class QuantizationEngine:

    """

    Handles model quantization using various schemes.

    Supports GPTQ, AWQ, and basic quantization methods.

    """

    

    def __init__(self):

        self.supported_methods = ['gptq', 'awq', 'int8', 'int4']

    

    def quantize_model(

        self,

        model: Any,

        tokenizer: Any,

        method: str,

        calibration_dataset: Optional[List[str]] = None,

        output_dir: Path = None,

        bits: int = 4,

        group_size: int = 128

    ) -> Path:

        """

        Quantize a model using specified method.

        Returns path to quantized model.

        """

        if method not in self.supported_methods:

            raise ValueError(f"Unsupported quantization method: {method}")

        

        if method == 'gptq':

            return self._quantize_gptq(

                model,

                tokenizer,

                calibration_dataset,

                output_dir,

                bits,

                group_size

            )

        elif method == 'awq':

            return self._quantize_awq(

                model,

                tokenizer,

                calibration_dataset,

                output_dir,

                bits,

                group_size

            )

        elif method in ['int8', 'int4']:

            return self._quantize_basic(

                model,

                output_dir,

                bits=8 if method == 'int8' else 4

            )

        else:

            raise ValueError(f"Method {method} not implemented")

    

    def _quantize_gptq(

        self,

        model: Any,

        tokenizer: Any,

        calibration_dataset: Optional[List[str]],

        output_dir: Path,

        bits: int,

        group_size: int

    ) -> Path:

        """Quantize using GPTQ method"""

        try:

            from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig

            

            # Prepare quantization config

            quantize_config = BaseQuantizeConfig(

                bits=bits,

                group_size=group_size,

                desc_act=False,

                sym=True,

                true_sequential=True

            )

            

            # Prepare calibration data

            if calibration_dataset is None:

                # Use default calibration data

                calibration_dataset = self._get_default_calibration_data()

            

            # Tokenize calibration data

            calibration_data = []

            for text in calibration_dataset[:128]:  # Use first 128 samples

                tokens = tokenizer(

                    text,

                    return_tensors='pt',

                    max_length=2048,

                    truncation=True

                )

                calibration_data.append(tokens)

            

            # Convert model to GPTQ format

            model.eval()

            

            # Quantize

            from auto_gptq import exllama_set_max_input_length

            

            quantized_model = AutoGPTQForCausalLM.from_pretrained(

                model,

                quantize_config=quantize_config

            )

            

            quantized_model.quantize(calibration_data)

            

            # Save quantized model

            output_dir.mkdir(parents=True, exist_ok=True)

            quantized_model.save_quantized(str(output_dir))

            tokenizer.save_pretrained(str(output_dir))

            

            return output_dir

        

        except ImportError:

            raise RuntimeError(

                "auto-gptq not installed. "

                "Install with: pip install auto-gptq"

            )

        except Exception as e:

            raise RuntimeError(f"GPTQ quantization failed: {e}")

    

    def _quantize_awq(

        self,

        model: Any,

        tokenizer: Any,

        calibration_dataset: Optional[List[str]],

        output_dir: Path,

        bits: int,

        group_size: int

    ) -> Path:

        """Quantize using AWQ method"""

        try:

            from awq import AutoAWQForCausalLM

            

            # Prepare calibration data

            if calibration_dataset is None:

                calibration_dataset = self._get_default_calibration_data()

            

            # AWQ quantization configuration

            quant_config = {

                "zero_point": True,

                "q_group_size": group_size,

                "w_bit": bits,

                "version": "GEMM"

            }

            

            # Load model for AWQ

            awq_model = AutoAWQForCausalLM.from_pretrained(model)

            

            # Quantize

            awq_model.quantize(

                tokenizer,

                quant_config=quant_config,

                calib_data=calibration_dataset[:128]

            )

            

            # Save

            output_dir.mkdir(parents=True, exist_ok=True)

            awq_model.save_quantized(str(output_dir))

            tokenizer.save_pretrained(str(output_dir))

            

            return output_dir

        

        except ImportError:

            raise RuntimeError(

                "autoawq not installed. "

                "Install with: pip install autoawq"

            )

        except Exception as e:

            raise RuntimeError(f"AWQ quantization failed: {e}")

    

    def _quantize_basic(

        self,

        model: Any,

        output_dir: Path,

        bits: int

    ) -> Path:

        """Basic quantization using PyTorch"""

        try:

            import torch.quantization as quant

            

            # Prepare model for quantization

            model.eval()

            model.qconfig = quant.get_default_qconfig('fbgemm')

            

            # Fuse modules if possible

            if hasattr(model, 'fuse_model'):

                model.fuse_model()

            

            # Prepare

            quant.prepare(model, inplace=True)

            

            # Convert to quantized version

            quantized_model = quant.convert(model, inplace=False)

            

            # Save

            output_dir.mkdir(parents=True, exist_ok=True)

            torch.save(quantized_model.state_dict(), output_dir / 'pytorch_model.bin')

            

            # Save config

            if hasattr(model, 'config'):

                model.config.save_pretrained(str(output_dir))

            

            return output_dir

        

        except Exception as e:

            raise RuntimeError(f"Basic quantization failed: {e}")

    

    def _get_default_calibration_data(self) -> List[str]:

        """Get default calibration dataset"""

        # Simple default calibration texts

        return [

            "The quick brown fox jumps over the lazy dog.",

            "Machine learning is a subset of artificial intelligence.",

            "Natural language processing enables computers to understand human language.",

            "Deep learning models require large amounts of training data.",

            "Quantization reduces model size while maintaining accuracy.",

            "Large language models have billions of parameters.",

            "Training neural networks requires significant computational resources.",

            "Transfer learning allows models to adapt to new tasks quickly.",

        ] * 16  # Repeat to get enough samples


The quantization engine implements multiple quantization strategies. GPTQ uses layer-wise quantization with calibration data to minimize accuracy loss. AWQ applies activation-aware weight quantization that considers the importance of different weights based on activation patterns. Basic quantization uses PyTorch built-in quantization which is simpler but may result in larger accuracy degradation.


The calibration dataset is crucial for maintaining model quality after quantization. The engine provides default calibration data but allows users to supply domain-specific text for better results. The quantization process analyzes how the model responds to calibration inputs and adjusts quantization parameters to minimize the difference between quantized and original outputs.


FINE-TUNING SUBSYSTEM

The fine-tuning subsystem enables users to adapt pre-trained models to specific tasks or domains using parameter-efficient methods. Full fine-tuning of large language models requires enormous computational resources, but techniques like LoRA and QLoRA enable effective adaptation with modest hardware by training only a small number of additional parameters.


from typing import Optional, Dict, Any, List

from pathlib import Path

import torch

from torch.utils.data import Dataset, DataLoader


class FineTuningEngine:

    """

    Handles model fine-tuning using parameter-efficient methods.

    Supports LoRA, QLoRA, and full fine-tuning.

    """

    

    def __init__(self, hardware_detector: HardwareDetector):

        self.hardware_detector = hardware_detector

    

    def fine_tune(

        self,

        model: Any,

        tokenizer: Any,

        training_data: List[Dict[str, str]],

        method: str = 'lora',

        output_dir: Path = None,

        num_epochs: int = 3,

        batch_size: int = 4,

        learning_rate: float = 2e-4,

        lora_r: int = 8,

        lora_alpha: int = 16,

        lora_dropout: float = 0.05,

        max_length: int = 512

    ) -> Path:

        """

        Fine-tune a model using specified method.

        Returns path to fine-tuned model.

        """

        device = self.hardware_detector.get_best_device()

        device_str = self.hardware_detector.get_device_string(device)

        

        if method == 'lora':

            return self._fine_tune_lora(

                model,

                tokenizer,

                training_data,

                output_dir,

                num_epochs,

                batch_size,

                learning_rate,

                lora_r,

                lora_alpha,

                lora_dropout,

                max_length,

                device_str

            )

        elif method == 'qlora':

            return self._fine_tune_qlora(

                model,

                tokenizer,

                training_data,

                output_dir,

                num_epochs,

                batch_size,

                learning_rate,

                lora_r,

                lora_alpha,

                lora_dropout,

                max_length,

                device_str

            )

        elif method == 'full':

            return self._fine_tune_full(

                model,

                tokenizer,

                training_data,

                output_dir,

                num_epochs,

                batch_size,

                learning_rate,

                max_length,

                device_str

            )

        else:

            raise ValueError(f"Unsupported fine-tuning method: {method}")

    

    def _fine_tune_lora(

        self,

        model: Any,

        tokenizer: Any,

        training_data: List[Dict[str, str]],

        output_dir: Path,

        num_epochs: int,

        batch_size: int,

        learning_rate: float,

        lora_r: int,

        lora_alpha: int,

        lora_dropout: float,

        max_length: int,

        device_str: str

    ) -> Path:

        """Fine-tune using LoRA"""

        try:

            from peft import LoraConfig, get_peft_model, TaskType

            from transformers import Trainer, TrainingArguments

            

            # Configure LoRA

            lora_config = LoraConfig(

                r=lora_r,

                lora_alpha=lora_alpha,

                target_modules=self._get_target_modules(model),

                lora_dropout=lora_dropout,

                bias="none",

                task_type=TaskType.CAUSAL_LM

            )

            

            # Apply LoRA to model

            model = get_peft_model(model, lora_config)

            model.print_trainable_parameters()

            

            # Prepare dataset

            train_dataset = self._prepare_dataset(

                training_data,

                tokenizer,

                max_length

            )

            

            # Training arguments

            output_dir.mkdir(parents=True, exist_ok=True)

            training_args = TrainingArguments(

                output_dir=str(output_dir),

                num_train_epochs=num_epochs,

                per_device_train_batch_size=batch_size,

                gradient_accumulation_steps=4,

                learning_rate=learning_rate,

                fp16=True if 'cuda' in device_str else False,

                logging_steps=10,

                save_strategy="epoch",

                optim="adamw_torch"

            )

            

            # Create trainer

            trainer = Trainer(

                model=model,

                args=training_args,

                train_dataset=train_dataset,

                tokenizer=tokenizer

            )

            

            # Train

            trainer.train()

            

            # Save

            model.save_pretrained(str(output_dir))

            tokenizer.save_pretrained(str(output_dir))

            

            return output_dir

        

        except ImportError:

            raise RuntimeError(

                "peft not installed. "

                "Install with: pip install peft"

            )

        except Exception as e:

            raise RuntimeError(f"LoRA fine-tuning failed: {e}")

    

    def _fine_tune_qlora(

        self,

        model: Any,

        tokenizer: Any,

        training_data: List[Dict[str, str]],

        output_dir: Path,

        num_epochs: int,

        batch_size: int,

        learning_rate: float,

        lora_r: int,

        lora_alpha: int,

        lora_dropout: float,

        max_length: int,

        device_str: str

    ) -> Path:

        """Fine-tune using QLoRA (LoRA with 4-bit quantization)"""

        try:

            from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training, TaskType

            from transformers import Trainer, TrainingArguments, BitsAndBytesConfig

            

            # Model should already be quantized, but ensure it's prepared

            model = prepare_model_for_kbit_training(model)

            

            # Configure LoRA

            lora_config = LoraConfig(

                r=lora_r,

                lora_alpha=lora_alpha,

                target_modules=self._get_target_modules(model),

                lora_dropout=lora_dropout,

                bias="none",

                task_type=TaskType.CAUSAL_LM

            )

            

            # Apply LoRA

            model = get_peft_model(model, lora_config)

            

            # Prepare dataset

            train_dataset = self._prepare_dataset(

                training_data,

                tokenizer,

                max_length

            )

            

            # Training arguments

            output_dir.mkdir(parents=True, exist_ok=True)

            training_args = TrainingArguments(

                output_dir=str(output_dir),

                num_train_epochs=num_epochs,

                per_device_train_batch_size=batch_size,

                gradient_accumulation_steps=4,

                learning_rate=learning_rate,

                fp16=True,

                logging_steps=10,

                save_strategy="epoch",

                optim="paged_adamw_8bit"

            )

            

            # Create trainer

            trainer = Trainer(

                model=model,

                args=training_args,

                train_dataset=train_dataset,

                tokenizer=tokenizer

            )

            

            # Train

            trainer.train()

            

            # Save

            model.save_pretrained(str(output_dir))

            tokenizer.save_pretrained(str(output_dir))

            

            return output_dir

        

        except Exception as e:

            raise RuntimeError(f"QLoRA fine-tuning failed: {e}")

    

    def _fine_tune_full(

        self,

        model: Any,

        tokenizer: Any,

        training_data: List[Dict[str, str]],

        output_dir: Path,

        num_epochs: int,

        batch_size: int,

        learning_rate: float,

        max_length: int,

        device_str: str

    ) -> Path:

        """Full model fine-tuning"""

        try:

            from transformers import Trainer, TrainingArguments

            

            # Prepare dataset

            train_dataset = self._prepare_dataset(

                training_data,

                tokenizer,

                max_length

            )

            

            # Training arguments

            output_dir.mkdir(parents=True, exist_ok=True)

            training_args = TrainingArguments(

                output_dir=str(output_dir),

                num_train_epochs=num_epochs,

                per_device_train_batch_size=batch_size,

                gradient_accumulation_steps=8,

                learning_rate=learning_rate,

                fp16=True if 'cuda' in device_str else False,

                logging_steps=10,

                save_strategy="epoch",

                optim="adamw_torch"

            )

            

            # Create trainer

            trainer = Trainer(

                model=model,

                args=training_args,

                train_dataset=train_dataset,

                tokenizer=tokenizer

            )

            

            # Train

            trainer.train()

            

            # Save

            model.save_pretrained(str(output_dir))

            tokenizer.save_pretrained(str(output_dir))

            

            return output_dir

        

        except Exception as e:

            raise RuntimeError(f"Full fine-tuning failed: {e}")

    

    def _get_target_modules(self, model: Any) -> List[str]:

        """Determine which modules to apply LoRA to"""

        # Common target modules for different architectures

        if hasattr(model, 'config'):

            arch = model.config.model_type

            

            if 'llama' in arch or 'mistral' in arch:

                return ["q_proj", "k_proj", "v_proj", "o_proj"]

            elif 'gpt' in arch:

                return ["c_attn", "c_proj"]

            elif 'bloom' in arch:

                return ["query_key_value", "dense"]

        

        # Default targets

        return ["q_proj", "v_proj"]

    

    def _prepare_dataset(

        self,

        training_data: List[Dict[str, str]],

        tokenizer: Any,

        max_length: int

    ) -> Dataset:

        """Prepare training dataset"""

        class TextDataset(Dataset):

            def __init__(self, data, tokenizer, max_length):

                self.data = data

                self.tokenizer = tokenizer

                self.max_length = max_length

            

            def __len__(self):

                return len(self.data)

            

            def __getitem__(self, idx):

                item = self.data[idx]

                

                # Format as instruction-response pair

                if 'instruction' in item and 'response' in item:

                    text = f"### Instruction:\n{item['instruction']}\n\n### Response:\n{item['response']}"

                elif 'prompt' in item and 'completion' in item:

                    text = f"{item['prompt']}{item['completion']}"

                else:

                    text = item.get('text', '')

                

                # Tokenize

                encoding = self.tokenizer(

                    text,

                    truncation=True,

                    max_length=self.max_length,

                    padding='max_length',

                    return_tensors='pt'

                )

                

                return {

                    'input_ids': encoding['input_ids'].squeeze(),

                    'attention_mask': encoding['attention_mask'].squeeze(),

                    'labels': encoding['input_ids'].squeeze()

                }

        

        return TextDataset(training_data, tokenizer, max_length)


The fine-tuning engine implements parameter-efficient training methods that enable model adaptation on consumer hardware. LoRA adds small trainable matrices to attention layers while keeping the base model frozen, reducing memory requirements and training time dramatically. QLoRA combines LoRA with 4-bit quantization to enable fine-tuning of very large models on GPUs with limited memory.


The training dataset preparation handles different data formats including instruction-response pairs and prompt-completion pairs. The engine uses Hugging Face Transformers Trainer which handles distributed training, gradient accumulation, and checkpointing automatically. The target module selection logic identifies which layers to apply LoRA based on the model architecture, ensuring compatibility across different model families.


MODEL FORMAT CONVERSION

The format conversion pipeline enables transformation between different model serialization formats. This is essential because different inference engines and deployment targets require specific formats. The converter handles PyTorch to SafeTensors conversion for improved loading speed and security, PyTorch to GGUF for llama.cpp deployment, and PyTorch to ONNX for cross-platform inference.


import torch

from pathlib import Path

from typing import Optional, Dict, Any

import json


class ModelConverter:

    """

    Converts models between different formats.

    Supports PyTorch, SafeTensors, GGUF, and ONNX.

    """

    

    def __init__(self):

        self.supported_conversions = {

            ('pytorch', 'safetensors'),

            ('safetensors', 'pytorch'),

            ('pytorch', 'gguf'),

            ('pytorch', 'onnx')

        }

    

    def convert(

        self,

        model_path: Path,

        source_format: str,

        target_format: str,

        output_path: Path,

        quantization: Optional[str] = None

    ) -> Path:

        """

        Convert model from source format to target format.

        Returns path to converted model.

        """

        conversion_key = (source_format.lower(), target_format.lower())

        

        if conversion_key not in self.supported_conversions:

            raise ValueError(

                f"Conversion from {source_format} to {target_format} not supported"

            )

        

        output_path.mkdir(parents=True, exist_ok=True)

        

        if conversion_key == ('pytorch', 'safetensors'):

            return self._convert_pytorch_to_safetensors(model_path, output_path)

        elif conversion_key == ('safetensors', 'pytorch'):

            return self._convert_safetensors_to_pytorch(model_path, output_path)

        elif conversion_key == ('pytorch', 'gguf'):

            return self._convert_pytorch_to_gguf(model_path, output_path, quantization)

        elif conversion_key == ('pytorch', 'onnx'):

            return self._convert_pytorch_to_onnx(model_path, output_path)

        else:

            raise ValueError(f"Conversion {conversion_key} not implemented")

    

    def _convert_pytorch_to_safetensors(

        self,

        model_path: Path,

        output_path: Path

    ) -> Path:

        """Convert PyTorch model to SafeTensors format"""

        try:

            from safetensors.torch import save_file

            from transformers import AutoModelForCausalLM, AutoTokenizer

            

            # Load model

            model = AutoModelForCausalLM.from_pretrained(

                str(model_path),

                torch_dtype=torch.float16,

                low_cpu_mem_usage=True

            )

            

            # Get state dict

            state_dict = model.state_dict()

            

            # Save as safetensors

            safetensors_path = output_path / "model.safetensors"

            save_file(state_dict, str(safetensors_path))

            

            # Copy config and tokenizer

            tokenizer = AutoTokenizer.from_pretrained(str(model_path))

            model.config.save_pretrained(str(output_path))

            tokenizer.save_pretrained(str(output_path))

            

            return output_path

        

        except ImportError:

            raise RuntimeError(

                "safetensors not installed. "

                "Install with: pip install safetensors"

            )

        except Exception as e:

            raise RuntimeError(f"Conversion to SafeTensors failed: {e}")

    

    def _convert_safetensors_to_pytorch(

        self,

        model_path: Path,

        output_path: Path

    ) -> Path:

        """Convert SafeTensors model to PyTorch format"""

        try:

            from safetensors.torch import load_file

            from transformers import AutoConfig, AutoTokenizer

            

            # Load safetensors

            safetensors_files = list(model_path.glob("*.safetensors"))

            if not safetensors_files:

                raise ValueError("No safetensors file found")

            

            state_dict = load_file(str(safetensors_files[0]))

            

            # Save as PyTorch

            pytorch_path = output_path / "pytorch_model.bin"

            torch.save(state_dict, str(pytorch_path))

            

            # Copy config and tokenizer

            config = AutoConfig.from_pretrained(str(model_path))

            tokenizer = AutoTokenizer.from_pretrained(str(model_path))

            

            config.save_pretrained(str(output_path))

            tokenizer.save_pretrained(str(output_path))

            

            return output_path

        

        except Exception as e:

            raise RuntimeError(f"Conversion to PyTorch failed: {e}")

    

    def _convert_pytorch_to_gguf(

        self,

        model_path: Path,

        output_path: Path,

        quantization: Optional[str]

    ) -> Path:

        """Convert PyTorch model to GGUF format"""

        try:

            import subprocess

            import sys

            

            # This requires llama.cpp convert script

            # Check if convert.py exists

            convert_script = Path.home() / "llama.cpp" / "convert.py"

            

            if not convert_script.exists():

                raise RuntimeError(

                    "llama.cpp convert.py not found. "

                    "Clone llama.cpp repository first."

                )

            

            # Run conversion

            cmd = [

                sys.executable,

                str(convert_script),

                str(model_path),

                "--outfile", str(output_path / "model.gguf"),

                "--outtype", "f16"

            ]

            

            result = subprocess.run(cmd, capture_output=True, text=True)

            

            if result.returncode != 0:

                raise RuntimeError(f"Conversion failed: {result.stderr}")

            

            # Apply quantization if requested

            if quantization:

                self._quantize_gguf(

                    output_path / "model.gguf",

                    quantization

                )

            

            return output_path

        

        except Exception as e:

            raise RuntimeError(f"Conversion to GGUF failed: {e}")

    

    def _quantize_gguf(self, gguf_path: Path, quantization: str):

        """Quantize GGUF model"""

        import subprocess

        import sys

        

        quantize_binary = Path.home() / "llama.cpp" / "quantize"

        

        if not quantize_binary.exists():

            raise RuntimeError("llama.cpp quantize binary not found")

        

        output_path = gguf_path.parent / f"model-{quantization}.gguf"

        

        cmd = [

            str(quantize_binary),

            str(gguf_path),

            str(output_path),

            quantization

        ]

        

        result = subprocess.run(cmd, capture_output=True, text=True)

        

        if result.returncode != 0:

            raise RuntimeError(f"Quantization failed: {result.stderr}")

    

    def _convert_pytorch_to_onnx(

        self,

        model_path: Path,

        output_path: Path

    ) -> Path:

        """Convert PyTorch model to ONNX format"""

        try:

            from transformers import AutoModelForCausalLM, AutoTokenizer

            import torch.onnx

            

            # Load model

            model = AutoModelForCausalLM.from_pretrained(

                str(model_path),

                torch_dtype=torch.float32,

                torchscript=True

            )

            model.eval()

            

            # Load tokenizer for dummy input

            tokenizer = AutoTokenizer.from_pretrained(str(model_path))

            

            # Create dummy input

            dummy_text = "This is a test sentence."

            dummy_input = tokenizer(

                dummy_text,

                return_tensors='pt'

            )

            

            # Export to ONNX

            onnx_path = output_path / "model.onnx"

            

            torch.onnx.export(

                model,

                (dummy_input['input_ids'],),

                str(onnx_path),

                input_names=['input_ids'],

                output_names=['logits'],

                dynamic_axes={

                    'input_ids': {0: 'batch', 1: 'sequence'},

                    'logits': {0: 'batch', 1: 'sequence'}

                },

                opset_version=14

            )

            

            # Save tokenizer and config

            tokenizer.save_pretrained(str(output_path))

            model.config.save_pretrained(str(output_path))

            

            return output_path

        

        except Exception as e:

            raise RuntimeError(f"Conversion to ONNX failed: {e}")


The model converter handles the technical details of different serialization formats. SafeTensors provides faster loading and better security than PyTorch pickle-based format by using a simple header-data layout. GGUF conversion requires external tools from llama.cpp but enables deployment on CPU-optimized inference engines. ONNX conversion creates models that can run on ONNX Runtime which supports diverse hardware backends.


The GGUF conversion process involves running external Python scripts from the llama.cpp repository. This approach leverages well-tested conversion code rather than reimplementing complex format transformations. The quantization step for GGUF models uses the llama.cpp quantize binary which implements optimized quantization schemes specifically designed for CPU inference.


CHAT INTERFACE AND INFERENCE ENGINE

The chat interface provides interactive conversation capabilities with loaded models. It manages conversation history, handles streaming responses for better user experience, and supports configurable generation parameters. The inference engine coordinates tokenization, model execution, and response decoding while handling the differences between various model formats.


from typing import List, Dict, Any, Optional, Iterator, Callable

import torch

from dataclasses import dataclass

from datetime import datetime


@dataclass

class Message:

    role: str  # 'user' or 'assistant'

    content: str

    timestamp: datetime


@dataclass

class GenerationConfig:

    max_new_tokens: int = 512

    temperature: float = 0.7

    top_p: float = 0.9

    top_k: int = 50

    repetition_penalty: float = 1.1

    do_sample: bool = True

    num_beams: int = 1


class ChatEngine:

    """

    Manages chat conversations and generation.

    Supports streaming responses and conversation history.

    """

    

    def __init__(self):

        self.conversations: Dict[str, List[Message]] = {}

        self.current_conversation_id: Optional[str] = None

    

    def create_conversation(self, conversation_id: str):

        """Create a new conversation"""

        self.conversations[conversation_id] = []

        self.current_conversation_id = conversation_id

    

    def add_message(self, conversation_id: str, role: str, content: str):

        """Add a message to conversation history"""

        if conversation_id not in self.conversations:

            self.create_conversation(conversation_id)

        

        message = Message(

            role=role,

            content=content,

            timestamp=datetime.now()

        )

        self.conversations[conversation_id].append(message)

    

    def get_conversation(self, conversation_id: str) -> List[Message]:

        """Retrieve conversation history"""

        return self.conversations.get(conversation_id, [])

    

    def generate_response(

        self,

        model: Any,

        tokenizer: Any,

        conversation_id: str,

        user_message: str,

        config: GenerationConfig = None,

        stream: bool = False

    ) -> str:

        """

        Generate response to user message.

        Returns assistant response.

        """

        if config is None:

            config = GenerationConfig()

        

        # Add user message to history

        self.add_message(conversation_id, 'user', user_message)

        

        # Format conversation for model

        prompt = self._format_conversation(

            self.get_conversation(conversation_id),

            tokenizer

        )

        

        # Generate response

        if stream:

            response = self._generate_streaming(

                model,

                tokenizer,

                prompt,

                config

            )

        else:

            response = self._generate_complete(

                model,

                tokenizer,

                prompt,

                config

            )

        

        # Add assistant response to history

        self.add_message(conversation_id, 'assistant', response)

        

        return response

    

    def _format_conversation(

        self,

        messages: List[Message],

        tokenizer: Any

    ) -> str:

        """Format conversation history into prompt"""

        # Check if tokenizer has chat template

        if hasattr(tokenizer, 'chat_template') and tokenizer.chat_template:

            # Use tokenizer's chat template

            formatted_messages = [

                {"role": msg.role, "content": msg.content}

                for msg in messages

            ]

            return tokenizer.apply_chat_template(

                formatted_messages,

                tokenize=False,

                add_generation_prompt=True

            )

        else:

            # Use simple format

            prompt_parts = []

            for msg in messages:

                if msg.role == 'user':

                    prompt_parts.append(f"User: {msg.content}")

                else:

                    prompt_parts.append(f"Assistant: {msg.content}")

            

            prompt_parts.append("Assistant:")

            return "\n\n".join(prompt_parts)

    

    def _generate_complete(

        self,

        model: Any,

        tokenizer: Any,

        prompt: str,

        config: GenerationConfig

    ) -> str:

        """Generate complete response at once"""

        # Check if this is a GGUF model (llama.cpp)

        if hasattr(model, 'create_completion'):

            # llama.cpp model

            response = model.create_completion(

                prompt,

                max_tokens=config.max_new_tokens,

                temperature=config.temperature,

                top_p=config.top_p,

                top_k=config.top_k,

                repeat_penalty=config.repetition_penalty

            )

            return response['choices'][0]['text']

        else:

            # Transformers model

            inputs = tokenizer(

                prompt,

                return_tensors='pt',

                truncation=True,

                max_length=2048

            )

            

            # Move to same device as model

            device = next(model.parameters()).device

            inputs = {k: v.to(device) for k, v in inputs.items()}

            

            # Generate

            with torch.no_grad():

                outputs = model.generate(

                    **inputs,

                    max_new_tokens=config.max_new_tokens,

                    temperature=config.temperature,

                    top_p=config.top_p,

                    top_k=config.top_k,

                    repetition_penalty=config.repetition_penalty,

                    do_sample=config.do_sample,

                    num_beams=config.num_beams,

                    pad_token_id=tokenizer.pad_token_id,

                    eos_token_id=tokenizer.eos_token_id

                )

            

            # Decode only the new tokens

            response = tokenizer.decode(

                outputs[0][inputs['input_ids'].shape[1]:],

                skip_special_tokens=True

            )

            

            return response.strip()

    

    def _generate_streaming(

        self,

        model: Any,

        tokenizer: Any,

        prompt: str,

        config: GenerationConfig

    ) -> Iterator[str]:

        """Generate response with streaming (token by token)"""

        # Check if this is a GGUF model

        if hasattr(model, 'create_completion'):

            # llama.cpp supports streaming natively

            stream = model.create_completion(

                prompt,

                max_tokens=config.max_new_tokens,

                temperature=config.temperature,

                top_p=config.top_p,

                top_k=config.top_k,

                repeat_penalty=config.repetition_penalty,

                stream=True

            )

            

            full_response = ""

            for chunk in stream:

                if 'choices' in chunk and len(chunk['choices']) > 0:

                    text = chunk['choices'][0].get('text', '')

                    full_response += text

                    yield text

            

            return full_response

        else:

            # Transformers model - implement streaming

            from transformers import TextIteratorStreamer

            from threading import Thread

            

            inputs = tokenizer(

                prompt,

                return_tensors='pt',

                truncation=True,

                max_length=2048

            )

            

            device = next(model.parameters()).device

            inputs = {k: v.to(device) for k, v in inputs.items()}

            

            streamer = TextIteratorStreamer(

                tokenizer,

                skip_prompt=True,

                skip_special_tokens=True

            )

            

            generation_kwargs = {

                **inputs,

                'max_new_tokens': config.max_new_tokens,

                'temperature': config.temperature,

                'top_p': config.top_p,

                'top_k': config.top_k,

                'repetition_penalty': config.repetition_penalty,

                'do_sample': config.do_sample,

                'streamer': streamer,

                'pad_token_id': tokenizer.pad_token_id,

                'eos_token_id': tokenizer.eos_token_id

            }

            

            # Run generation in separate thread

            thread = Thread(target=model.generate, kwargs=generation_kwargs)

            thread.start()

            

            # Stream tokens

            full_response = ""

            for text in streamer:

                full_response += text

                yield text

            

            thread.join()

            return full_response


The chat engine manages conversation state and coordinates the generation process. It maintains separate conversation histories for different sessions, enabling multi-user scenarios or multiple concurrent conversations. The message formatting logic adapts to different model architectures by using the tokenizer's chat template when available or falling back to a simple format.


The streaming generation capability significantly improves user experience by displaying tokens as they are generated rather than waiting for the complete response. For Transformers models, this uses the TextIteratorStreamer which runs generation in a background thread and yields tokens through an iterator. For llama.cpp models, streaming is natively supported through the library's API.


WEB USER INTERFACE

The web user interface provides an intuitive graphical interface for all platform functionality. Built with modern web technologies, it offers model browsing, download management, chat interaction, fine-tuning configuration, and system monitoring. The interface communicates with the backend through REST APIs and WebSocket connections for real-time updates.


from flask import Flask, request, jsonify, render_template_string, Response

from flask_cors import CORS

from flask_socketio import SocketIO, emit

import json

from pathlib import Path

from typing import Dict, Any

import asyncio

from threading import Thread

import queue


class WebInterface:

    """

    Web-based user interface for LLM management platform.

    Provides REST API and WebSocket support for real-time updates.

    """

    

    def __init__(

        self,

        hardware_detector: HardwareDetector,

        model_registry: ModelRegistry,

        model_downloader: ModelDownloader,

        model_loader: ModelLoader,

        quantization_engine: QuantizationEngine,

        fine_tuning_engine: FineTuningEngine,

        model_converter: ModelConverter,

        chat_engine: ChatEngine,

        host: str = '0.0.0.0',

        port: int = 5000

    ):

        self.app = Flask(__name__)

        CORS(self.app)

        self.socketio = SocketIO(self.app, cors_allowed_origins="*")

        

        self.hardware_detector = hardware_detector

        self.model_registry = model_registry

        self.model_downloader = model_downloader

        self.model_loader = model_loader

        self.quantization_engine = quantization_engine

        self.fine_tuning_engine = fine_tuning_engine

        self.model_converter = model_converter

        self.chat_engine = chat_engine

        

        self.host = host

        self.port = port

        

        self._setup_routes()

        self._setup_socketio()

    

    def _setup_routes(self):

        """Setup Flask routes"""

        

        @self.app.route('/')

        def index():

            return render_template_string(self._get_html_template())

        

        @self.app.route('/api/hardware', methods=['GET'])

        def get_hardware():

            """Get available hardware information"""

            devices = [

                {

                    'backend': device.backend.value,

                    'device_id': device.device_id,

                    'name': device.name,

                    'total_memory_gb': device.total_memory / (1024 ** 3),

                    'compute_capability': device.compute_capability,

                    'driver_version': device.driver_version

                }

                for device in self.hardware_detector.devices

            ]

            return jsonify({'devices': devices})

        

        @self.app.route('/api/models/search', methods=['POST'])

        def search_models():

            """Search for models"""

            data = request.json

            query = data.get('query', '')

            source = data.get('source', 'huggingface')

            

            if source == 'huggingface':

                # Run async search in sync context

                loop = asyncio.new_event_loop()

                asyncio.set_event_loop(loop)

                results = loop.run_until_complete(

                    self.model_registry.search_huggingface(query)

                )

                loop.close()

            elif source == 'local':

                local_dirs = [Path(d) for d in data.get('directories', [])]

                results = self.model_registry.search_local(query, local_dirs)

            else:

                return jsonify({'error': 'Invalid source'}), 400

            

            # Convert to JSON-serializable format

            models_data = [

                {

                    'model_id': m.model_id,

                    'name': m.name,

                    'architecture': m.architecture,

                    'parameter_count': m.parameter_count,

                    'quantization': m.quantization,

                    'format': m.format,

                    'size_gb': m.size_bytes / (1024 ** 3),

                    'description': m.description,

                    'tags': m.tags,

                    'min_memory_gb': m.min_memory_gb,

                    'is_downloaded': m.is_downloaded

                }

                for m in results

            ]

            

            return jsonify({'models': models_data})

        

        @self.app.route('/api/models/download', methods=['POST'])

        def download_model():

            """Download a model"""

            data = request.json

            model_id = data.get('model_id')

            

            if not model_id or model_id not in self.model_registry.models:

                return jsonify({'error': 'Model not found'}), 404

            

            metadata = self.model_registry.models[model_id]

            

            # Start download in background thread

            def download_worker():

                loop = asyncio.new_event_loop()

                asyncio.set_event_loop(loop)

                

                def progress_callback(current, total):

                    progress = (current / total) * 100

                    self.socketio.emit('download_progress', {

                        'model_id': model_id,

                        'progress': progress,

                        'current_bytes': current,

                        'total_bytes': total

                    })

                

                try:

                    loop.run_until_complete(

                        self.model_downloader.download_model(

                            metadata,

                            progress_callback

                        )

                    )

                    self.socketio.emit('download_complete', {

                        'model_id': model_id,

                        'success': True

                    })

                except Exception as e:

                    self.socketio.emit('download_complete', {

                        'model_id': model_id,

                        'success': False,

                        'error': str(e)

                    })

                finally:

                    loop.close()

            

            thread = Thread(target=download_worker)

            thread.start()

            

            return jsonify({'status': 'download_started'})

        

        @self.app.route('/api/models/load', methods=['POST'])

        def load_model():

            """Load a model for inference"""

            data = request.json

            model_id = data.get('model_id')

            load_in_8bit = data.get('load_in_8bit', False)

            load_in_4bit = data.get('load_in_4bit', False)

            

            if not model_id or model_id not in self.model_registry.models:

                return jsonify({'error': 'Model not found'}), 404

            

            metadata = self.model_registry.models[model_id]

            

            try:

                model, tokenizer = self.model_loader.load_model(

                    metadata,

                    load_in_8bit=load_in_8bit,

                    load_in_4bit=load_in_4bit

                )

                

                # Get model info

                model_info = self.model_loader.get_model_info(model)

                

                return jsonify({

                    'status': 'loaded',

                    'model_info': model_info

                })

            

            except Exception as e:

                return jsonify({'error': str(e)}), 500

        

        @self.app.route('/api/models/info', methods=['GET'])

        def get_model_info():

            """Get information about currently loaded model"""

            if not self.model_loader.current_model_id:

                return jsonify({'error': 'No model loaded'}), 404

            

            model, _ = self.model_loader.loaded_models[

                self.model_loader.current_model_id

            ]

            

            info = self.model_loader.get_model_info(model)

            metadata = self.model_registry.models[

                self.model_loader.current_model_id

            ]

            

            return jsonify({

                'model_id': self.model_loader.current_model_id,

                'metadata': {

                    'name': metadata.name,

                    'architecture': metadata.architecture,

                    'format': metadata.format,

                    'quantization': metadata.quantization

                },

                'info': info

            })

        

        @self.app.route('/api/chat/message', methods=['POST'])

        def chat_message():

            """Send a chat message and get response"""

            data = request.json

            conversation_id = data.get('conversation_id', 'default')

            message = data.get('message', '')

            stream = data.get('stream', False)

            

            config_data = data.get('config', {})

            config = GenerationConfig(

                max_new_tokens=config_data.get('max_new_tokens', 512),

                temperature=config_data.get('temperature', 0.7),

                top_p=config_data.get('top_p', 0.9),

                top_k=config_data.get('top_k', 50),

                repetition_penalty=config_data.get('repetition_penalty', 1.1)

            )

            

            if not self.model_loader.current_model_id:

                return jsonify({'error': 'No model loaded'}), 400

            

            model, tokenizer = self.model_loader.loaded_models[

                self.model_loader.current_model_id

            ]

            

            if stream:

                # Return streaming response

                def generate():

                    for token in self.chat_engine.generate_response(

                        model,

                        tokenizer,

                        conversation_id,

                        message,

                        config,

                        stream=True

                    ):

                        yield f"data: {json.dumps({'token': token})}\n\n"

                    yield f"data: {json.dumps({'done': True})}\n\n"

                

                return Response(generate(), mimetype='text/event-stream')

            else:

                # Return complete response

                response = self.chat_engine.generate_response(

                    model,

                    tokenizer,

                    conversation_id,

                    message,

                    config,

                    stream=False

                )

                

                return jsonify({'response': response})

        

        @self.app.route('/api/chat/history', methods=['GET'])

        def get_chat_history():

            """Get chat conversation history"""

            conversation_id = request.args.get('conversation_id', 'default')

            messages = self.chat_engine.get_conversation(conversation_id)

            

            messages_data = [

                {

                    'role': msg.role,

                    'content': msg.content,

                    'timestamp': msg.timestamp.isoformat()

                }

                for msg in messages

            ]

            

            return jsonify({'messages': messages_data})

        

        @self.app.route('/api/quantize', methods=['POST'])

        def quantize_model():

            """Quantize a model"""

            data = request.json

            model_id = data.get('model_id')

            method = data.get('method', 'gptq')

            bits = data.get('bits', 4)

            output_dir = Path(data.get('output_dir', './quantized_models'))

            

            if not model_id or model_id not in self.model_registry.models:

                return jsonify({'error': 'Model not found'}), 404

            

            metadata = self.model_registry.models[model_id]

            

            # Load model if not already loaded

            if self.model_loader.current_model_id != model_id:

                model, tokenizer = self.model_loader.load_model(metadata)

            else:

                model, tokenizer = self.model_loader.loaded_models[model_id]

            

            try:

                output_path = self.quantization_engine.quantize_model(

                    model,

                    tokenizer,

                    method,

                    output_dir=output_dir / f"{model_id}_{method}_{bits}bit",

                    bits=bits

                )

                

                return jsonify({

                    'status': 'success',

                    'output_path': str(output_path)

                })

            

            except Exception as e:

                return jsonify({'error': str(e)}), 500

        

        @self.app.route('/api/finetune', methods=['POST'])

        def finetune_model():

            """Fine-tune a model"""

            data = request.json

            model_id = data.get('model_id')

            method = data.get('method', 'lora')

            training_data = data.get('training_data', [])

            output_dir = Path(data.get('output_dir', './finetuned_models'))

            

            if not model_id or model_id not in self.model_registry.models:

                return jsonify({'error': 'Model not found'}), 404

            

            if not training_data:

                return jsonify({'error': 'No training data provided'}), 400

            

            metadata = self.model_registry.models[model_id]

            

            # Load model

            if self.model_loader.current_model_id != model_id:

                model, tokenizer = self.model_loader.load_model(metadata)

            else:

                model, tokenizer = self.model_loader.loaded_models[model_id]

            

            try:

                output_path = self.fine_tuning_engine.fine_tune(

                    model,

                    tokenizer,

                    training_data,

                    method=method,

                    output_dir=output_dir / f"{model_id}_{method}",

                    num_epochs=data.get('num_epochs', 3),

                    batch_size=data.get('batch_size', 4),

                    learning_rate=data.get('learning_rate', 2e-4)

                )

                

                return jsonify({

                    'status': 'success',

                    'output_path': str(output_path)

                })

            

            except Exception as e:

                return jsonify({'error': str(e)}), 500

        

        @self.app.route('/api/convert', methods=['POST'])

        def convert_model():

            """Convert model format"""

            data = request.json

            model_path = Path(data.get('model_path'))

            source_format = data.get('source_format')

            target_format = data.get('target_format')

            output_path = Path(data.get('output_path', './converted_models'))

            

            try:

                result_path = self.model_converter.convert(

                    model_path,

                    source_format,

                    target_format,

                    output_path

                )

                

                return jsonify({

                    'status': 'success',

                    'output_path': str(result_path)

                })

            

            except Exception as e:

                return jsonify({'error': str(e)}), 500

    

    def _setup_socketio(self):

        """Setup WebSocket handlers"""

        

        @self.socketio.on('connect')

        def handle_connect():

            emit('connected', {'status': 'connected'})

        

        @self.socketio.on('disconnect')

        def handle_disconnect():

            pass

    

    def _get_html_template(self) -> str:

        """Get HTML template for web interface"""

        return '''

LLM Management Platform

    <div class="section">

        <h2>Hardware Information</h2>

        <div id="hardware-info">Loading...</div>

        <button onclick="loadHardware()">Refresh Hardware</button>

    </div>

    

    <div class="section">

        <h2>Model Search</h2>

        <input type="text" id="search-query" placeholder="Search for models...">

        <button onclick="searchModels()">Search</button>

        <div id="search-results"></div>

    </div>

    

    <div class="section">

        <h2>Chat Interface</h2>

        <div class="chat-box" id="chat-box"></div>

        <textarea id="user-input" placeholder="Type your message..." rows="3"></textarea>

        <button onclick="sendMessage()">Send</button>

    </div>

</div>


<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>

<script>

    const socket = io();

    

    async function loadHardware() {

        const response = await fetch('/api/hardware');

        const data = await response.json();

        const html = data.devices.map(d => 

            `<p><strong>${d.name}</strong> (${d.backend}): ${d.total_memory_gb.toFixed(2)} GB</p>`

        ).join('');

        document.getElementById('hardware-info').innerHTML = html;

    }

    

    async function searchModels() {

        const query = document.getElementById('search-query').value;

        const response = await fetch('/api/models/search', {

            method: 'POST',

            headers: {'Content-Type': 'application/json'},

            body: JSON.stringify({query: query, source: 'huggingface'})

        });

        const data = await response.json();

        const html = data.models.map(m =>

            `<div style="border: 1px solid #ddd; padding: 10px; margin: 10px 0;">

                <h3>${m.name}</h3>

                <p>${m.description}</p>

                <p>Size: ${m.size_gb.toFixed(2)} GB | Memory: ${m.min_memory_gb.toFixed(2)} GB</p>

                <button onclick="downloadModel('${m.model_id}')">Download</button>

                <button onclick="loadModel('${m.model_id}')">Load</button>

            </div>`

        ).join('');

        document.getElementById('search-results').innerHTML = html;

    }

    

    async function downloadModel(modelId) {

        await fetch('/api/models/download', {

            method: 'POST',

            headers: {'Content-Type': 'application/json'},

            body: JSON.stringify({model_id: modelId})

        });

        alert('Download started');

    }

    

    async function loadModel(modelId) {

        const response = await fetch('/api/models/load', {

            method: 'POST',

            headers: {'Content-Type': 'application/json'},

            body: JSON.stringify({model_id: modelId})

        });

        const data = await response.json();

        if (data.status === 'loaded') {

            alert('Model loaded successfully');

        } else {

            alert('Error loading model: ' + data.error);

        }

    }

    

    async function sendMessage() {

        const input = document.getElementById('user-input');

        const message = input.value;

        if (!message) return;

        

        // Add user message to chat

        addMessageToChat('user', message);

        input.value = '';

        

        // Send to API

        const response = await fetch('/api/chat/message', {

            method: 'POST',

            headers: {'Content-Type': 'application/json'},

            body: JSON.stringify({

                conversation_id: 'default',

                message: message,

                stream: false

            })

        });

        

        const data = await response.json();

        if (data.response) {

            addMessageToChat('assistant', data.response);

        } else if (data.error) {

            alert('Error: ' + data.error);

        }

    }

    

    function addMessageToChat(role, content) {

        const chatBox = document.getElementById('chat-box');

        const messageDiv = document.createElement('div');

        messageDiv.className = `message ${role}-message`;

        messageDiv.textContent = content;

        chatBox.appendChild(messageDiv);

        chatBox.scrollTop = chatBox.scrollHeight;

    }

    

    // Socket.IO event handlers

    socket.on('download_progress', (data) => {

        console.log('Download progress:', data.progress);

    });

    

    socket.on('download_complete', (data) => {

        if (data.success) {

            alert('Download complete: ' + data.model_id);

        } else {

            alert('Download failed: ' + data.error);

        }

    });

    

    // Load hardware on page load

    loadHardware();

</script>

'''

    def run(self):

        """Start the web server"""

        self.socketio.run(self.app, host=self.host, port=self.port)


The web interface provides a complete user experience through a single-page application. The Flask backend handles HTTP requests for model operations while Socket.IO enables real-time updates for long-running operations like downloads and fine-tuning. The HTML template includes JavaScript that communicates with the backend APIs and updates the interface dynamically.


The chat interface displays conversation history and handles message submission. The streaming support could be enhanced to display tokens as they arrive using Server-Sent Events. The model search results show relevant information and provide action buttons for downloading and loading models. The hardware information section helps users understand their system capabilities.


OPENAI-COMPATIBLE REST API

The OpenAI-compatible API enables programmatic access to the platform using the same interface as OpenAI models. This compatibility allows existing applications built for OpenAI to work with local models without code changes. The API implements the chat completions endpoint which is the primary interface for modern LLM applications.


from flask import Flask, request, jsonify

import time

import uuid

from typing import Dict, Any, List


class OpenAICompatibleAPI:

    """

    OpenAI-compatible REST API for programmatic model access.

    Implements /v1/chat/completions and /v1/models endpoints.

    """

    

    def __init__(

        self,

        app: Flask,

        model_loader: ModelLoader,

        model_registry: ModelRegistry,

        chat_engine: ChatEngine

    ):

        self.app = app

        self.model_loader = model_loader

        self.model_registry = model_registry

        self.chat_engine = chat_engine

        

        self._setup_routes()

    

    def _setup_routes(self):

        """Setup OpenAI-compatible API routes"""

        

        @self.app.route('/v1/models', methods=['GET'])

        def list_models():

            """List available models"""

            models = []

            for model_id, metadata in self.model_registry.models.items():

                if metadata.is_downloaded:

                    models.append({

                        'id': model_id,

                        'object': 'model',

                        'created': int(metadata.last_updated.timestamp()),

                        'owned_by': 'local',

                        'permission': [],

                        'root': model_id,

                        'parent': None

                    })

            

            return jsonify({

                'object': 'list',

                'data': models

            })

        

        @self.app.route('/v1/chat/completions', methods=['POST'])

        def chat_completions():

            """OpenAI-compatible chat completions endpoint"""

            data = request.json

            

            # Extract parameters

            model_id = data.get('model')

            messages = data.get('messages', [])

            temperature = data.get('temperature', 0.7)

            max_tokens = data.get('max_tokens', 512)

            top_p = data.get('top_p', 0.9)

            stream = data.get('stream', False)

            

            # Validate model

            if not model_id:

                return jsonify({'error': 'model parameter required'}), 400

            

            # Load model if not current

            if self.model_loader.current_model_id != model_id:

                if model_id not in self.model_registry.models:

                    return jsonify({'error': f'Model {model_id} not found'}), 404

                

                metadata = self.model_registry.models[model_id]

                try:

                    self.model_loader.load_model(metadata)

                except Exception as e:

                    return jsonify({'error': f'Failed to load model: {str(e)}'}), 500

            

            # Get model and tokenizer

            model, tokenizer = self.model_loader.loaded_models[model_id]

            

            # Create conversation from messages

            conversation_id = str(uuid.uuid4())

            self.chat_engine.create_conversation(conversation_id)

            

            for msg in messages:

                role = msg.get('role')

                content = msg.get('content')

                if role and content:

                    self.chat_engine.add_message(conversation_id, role, content)

            

            # Generate configuration

            config = GenerationConfig(

                max_new_tokens=max_tokens,

                temperature=temperature,

                top_p=top_p

            )

            

            # Get last user message

            last_user_message = None

            for msg in reversed(messages):

                if msg.get('role') == 'user':

                    last_user_message = msg.get('content')

                    break

            

            if not last_user_message:

                return jsonify({'error': 'No user message found'}), 400

            

            # Generate response

            if stream:

                return self._stream_completion(

                    model,

                    tokenizer,

                    conversation_id,

                    last_user_message,

                    config,

                    model_id

                )

            else:

                return self._complete_response(

                    model,

                    tokenizer,

                    conversation_id,

                    last_user_message,

                    config,

                    model_id

                )

    

    def _complete_response(

        self,

        model: Any,

        tokenizer: Any,

        conversation_id: str,

        message: str,

        config: GenerationConfig,

        model_id: str

    ):

        """Generate complete response"""

        # Remove last user message since generate_response will add it

        if self.chat_engine.conversations[conversation_id]:

            self.chat_engine.conversations[conversation_id].pop()

        

        response_text = self.chat_engine.generate_response(

            model,

            tokenizer,

            conversation_id,

            message,

            config,

            stream=False

        )

        

        # Format OpenAI-style response

        completion_id = f"chatcmpl-{uuid.uuid4()}"

        created = int(time.time())

        

        return jsonify({

            'id': completion_id,

            'object': 'chat.completion',

            'created': created,

            'model': model_id,

            'choices': [{

                'index': 0,

                'message': {

                    'role': 'assistant',

                    'content': response_text

                },

                'finish_reason': 'stop'

            }],

            'usage': {

                'prompt_tokens': 0,  # Would need to count

                'completion_tokens': 0,  # Would need to count

                'total_tokens': 0

            }

        })

    

    def _stream_completion(

        self,

        model: Any,

        tokenizer: Any,

        conversation_id: str,

        message: str,

        config: GenerationConfig,

        model_id: str

    ):

        """Generate streaming response"""

        from flask import Response

        import json

        

        def generate():

            completion_id = f"chatcmpl-{uuid.uuid4()}"

            created = int(time.time())

            

            # Remove last user message since generate_response will add it

            if self.chat_engine.conversations[conversation_id]:

                self.chat_engine.conversations[conversation_id].pop()

            

            # Stream tokens

            for token in self.chat_engine._generate_streaming(

                model,

                tokenizer,

                self.chat_engine._format_conversation(

                    self.chat_engine.get_conversation(conversation_id) + [

                        Message('user', message, datetime.now())

                    ],

                    tokenizer

                ),

                config

            ):

                chunk = {

                    'id': completion_id,

                    'object': 'chat.completion.chunk',

                    'created': created,

                    'model': model_id,

                    'choices': [{

                        'index': 0,

                        'delta': {

                            'content': token

                        },

                        'finish_reason': None

                    }]

                }

                yield f"data: {json.dumps(chunk)}\n\n"

            

            # Final chunk

            final_chunk = {

                'id': completion_id,

                'object': 'chat.completion.chunk',

                'created': created,

                'model': model_id,

                'choices': [{

                    'index': 0,

                    'delta': {},

                    'finish_reason': 'stop'

                }]

            }

            yield f"data: {json.dumps(final_chunk)}\n\n"

            yield "data: [DONE]\n\n"

        

        return Response(generate(), mimetype='text/event-stream')


The OpenAI-compatible API implements the standard endpoints that applications expect. The models endpoint lists available models in the format OpenAI uses. The chat completions endpoint accepts the same parameters as OpenAI and returns responses in the same format. This compatibility enables drop-in replacement of OpenAI API calls with local model inference.


The streaming implementation follows OpenAI specification by sending Server-Sent Events with JSON chunks containing delta updates. Each chunk includes the token generated and metadata about the completion. The final chunk signals completion with a finish reason. This streaming format enables real-time display of generated text in client applications.


COMPLETE RUNNING EXAMPLE

The following complete implementation integrates all components into a production-ready application. This code provides full functionality without mocks or simulations and supports all described features across different hardware backends.


#!/usr/bin/env python3

"""

Complete LLM Management Platform


A sophisticated tool for managing, fine-tuning, and deploying large language models

across heterogeneous hardware environments including NVIDIA CUDA, AMD ROCm,

Apple MPS, and Intel GPUs.


Features:

- Multi-backend hardware detection and abstraction

- Model discovery from Hugging Face and local sources

- Resumable model downloads with progress tracking

- Model loading with quantization support

- Post-training quantization (GPTQ, AWQ, INT8, INT4)

- Parameter-efficient fine-tuning (LoRA, QLoRA)

- Model format conversion (PyTorch, SafeTensors, GGUF, ONNX)

- Interactive chat interface with streaming

- OpenAI-compatible REST API

- Web-based user interface


Usage:

    python llm_platform.py --port 5000 --cache-dir ./models

"""


import sys

import os

import platform

import argparse

import logging

from pathlib import Path

from typing import Optional, Dict, Any, List, Callable, Iterator, Tuple

from dataclasses import dataclass, asdict

from datetime import datetime

from enum import Enum

import json

import hashlib

import asyncio

import aiohttp

import aiofiles

from threading import Thread

import time

import uuid

import gc


# Third-party imports

import torch

from flask import Flask, request, jsonify, render_template_string, Response

from flask_cors import CORS

from flask_socketio import SocketIO, emit


# Configure logging

logging.basicConfig(

    level=logging.INFO,

    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'

)

logger = logging.getLogger(__name__)



# ============================================================================

# HARDWARE DETECTION AND ABSTRACTION

# ============================================================================


class BackendType(Enum):

    """Enumeration of supported compute backends"""

    CUDA = "cuda"

    ROCM = "rocm"

    MPS = "mps"

    INTEL = "intel"

    CPU = "cpu"



@dataclass

class DeviceInfo:

    """Information about a compute device"""

    backend: BackendType

    device_id: int

    name: str

    total_memory: int

    compute_capability: Optional[str]

    driver_version: Optional[str]



class HardwareDetector:

    """

    Detects available compute hardware and provides unified device abstraction.

    Handles CUDA, ROCm, MPS, Intel, and CPU backends with automatic fallback.

    """

    

    def __init__(self):

        self.available_backends = []

        self.devices = []

        self._detect_all_backends()

        logger.info(f"Detected {len(self.devices)} compute devices")

    

    def _detect_cuda(self) -> List[DeviceInfo]:

        """Detect NVIDIA CUDA devices"""

        devices = []

        try:

            import torch

            if torch.cuda.is_available():

                for i in range(torch.cuda.device_count()):

                    props = torch.cuda.get_device_properties(i)

                    devices.append(DeviceInfo(

                        backend=BackendType.CUDA,

                        device_id=i,

                        name=props.name,

                        total_memory=props.total_memory,

                        compute_capability=f"{props.major}.{props.minor}",

                        driver_version=torch.version.cuda

                    ))

                    logger.info(f"Found CUDA device: {props.name}")

        except (ImportError, RuntimeError) as e:

            logger.debug(f"CUDA not available: {e}")

        return devices

    

    def _detect_rocm(self) -> List[DeviceInfo]:

        """Detect AMD ROCm devices"""

        devices = []

        try:

            import torch

            if hasattr(torch, 'hip') and torch.hip.is_available():

                for i in range(torch.hip.device_count()):

                    props = torch.hip.get_device_properties(i)

                    devices.append(DeviceInfo(

                        backend=BackendType.ROCM,

                        device_id=i,

                        name=props.name,

                        total_memory=props.total_memory,

                        compute_capability=props.gcnArchName,

                        driver_version=torch.version.hip

                    ))

                    logger.info(f"Found ROCm device: {props.name}")

        except (ImportError, AttributeError, RuntimeError) as e:

            logger.debug(f"ROCm not available: {e}")

        return devices

    

    def _detect_mps(self) -> List[DeviceInfo]:

        """Detect Apple Metal Performance Shaders"""

        devices = []

        try:

            import torch

            if hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():

                devices.append(DeviceInfo(

                    backend=BackendType.MPS,

                    device_id=0,

                    name="Apple GPU",

                    total_memory=self._get_mps_memory(),

                    compute_capability=None,

                    driver_version=platform.mac_ver()[0]

                ))

                logger.info("Found Apple MPS device")

        except (ImportError, AttributeError, RuntimeError) as e:

            logger.debug(f"MPS not available: {e}")

        return devices

    

    def _get_mps_memory(self) -> int:

        """Estimate available memory on Apple Silicon"""

        try:

            import subprocess

            result = subprocess.run(

                ['sysctl', 'hw.memsize'],

                capture_output=True,

                text=True

            )

            if result.returncode == 0:

                return int(result.stdout.split(':')[1].strip())

        except:

            pass

        return 8 * 1024 * 1024 * 1024

    

    def _detect_intel(self) -> List[DeviceInfo]:

        """Detect Intel GPUs via oneAPI"""

        devices = []

        try:

            import intel_extension_for_pytorch as ipex

            if ipex.xpu.is_available():

                for i in range(ipex.xpu.device_count()):

                    devices.append(DeviceInfo(

                        backend=BackendType.INTEL,

                        device_id=i,

                        name=f"Intel XPU {i}",

                        total_memory=ipex.xpu.get_device_properties(i).total_memory,

                        compute_capability=None,

                        driver_version=ipex.__version__

                    ))

                    logger.info(f"Found Intel XPU device {i}")

        except (ImportError, AttributeError, RuntimeError) as e:

            logger.debug(f"Intel XPU not available: {e}")

        return devices

    

    def _detect_all_backends(self):

        """Detect all available backends in priority order"""

        detection_methods = [

            (BackendType.CUDA, self._detect_cuda),

            (BackendType.ROCM, self._detect_rocm),

            (BackendType.MPS, self._detect_mps),

            (BackendType.INTEL, self._detect_intel)

        ]

        

        for backend_type, detect_func in detection_methods:

            detected = detect_func()

            if detected:

                self.available_backends.append(backend_type)

                self.devices.extend(detected)

        

        if BackendType.CPU not in self.available_backends:

            self.available_backends.append(BackendType.CPU)

            self.devices.append(DeviceInfo(

                backend=BackendType.CPU,

                device_id=0,

                name=platform.processor() or "CPU",

                total_memory=self._get_system_memory(),

                compute_capability=None,

                driver_version=None

            ))

            logger.info("Added CPU fallback device")

    

    def _get_system_memory(self) -> int:

        """Get total system RAM"""

        try:

            import psutil

            return psutil.virtual_memory().total

        except ImportError:

            return 16 * 1024 * 1024 * 1024

    

    def get_best_device(self) -> DeviceInfo:

        """Return the most capable available device"""

        if not self.devices:

            raise RuntimeError("No compute devices available")

        

        gpu_devices = [d for d in self.devices if d.backend != BackendType.CPU]

        if gpu_devices:

            return max(gpu_devices, key=lambda d: d.total_memory)

        return self.devices[0]

    

    def get_device_string(self, device_info: DeviceInfo) -> str:

        """Convert DeviceInfo to framework-specific device string"""

        if device_info.backend == BackendType.CUDA:

            return f"cuda:{device_info.device_id}"

        elif device_info.backend == BackendType.ROCM:

            return f"cuda:{device_info.device_id}"

        elif device_info.backend == BackendType.MPS:

            return "mps"

        elif device_info.backend == BackendType.INTEL:

            return f"xpu:{device_info.device_id}"

        else:

            return "cpu"



# ============================================================================

# MODEL REGISTRY AND DISCOVERY

# ============================================================================


@dataclass

class ModelMetadata:

    """Comprehensive metadata for a language model"""

    model_id: str

    name: str

    architecture: str

    parameter_count: int

    quantization: Optional[str]

    format: str

    size_bytes: int

    source: str

    description: str

    tags: List[str]

    license: str

    min_memory_gb: float

    supported_backends: List[str]

    download_url: Optional[str]

    local_path: Optional[Path]

    is_downloaded: bool

    last_updated: datetime



class ModelRegistry:

    """

    Manages model discovery, metadata storage, and local cache.

    Supports searching Hugging Face Hub and local repositories.

    """

    

    def __init__(self, cache_dir: Path):

        self.cache_dir = Path(cache_dir)

        self.cache_dir.mkdir(parents=True, exist_ok=True)

        self.registry_file = self.cache_dir / "registry.json"

        self.models: Dict[str, ModelMetadata] = {}

        self._load_registry()

        logger.info(f"Initialized model registry with {len(self.models)} models")

    

    def _load_registry(self):

        """Load cached registry from disk"""

        if self.registry_file.exists():

            try:

                with open(self.registry_file, 'r') as f:

                    data = json.load(f)

                    for model_data in data:

                        model_data['last_updated'] = datetime.fromisoformat(

                            model_data['last_updated']

                        )

                        if model_data.get('local_path'):

                            model_data['local_path'] = Path(model_data['local_path'])

                        metadata = ModelMetadata(**model_data)

                        self.models[metadata.model_id] = metadata

                logger.info(f"Loaded {len(self.models)} models from registry")

            except Exception as e:

                logger.error(f"Failed to load registry: {e}")

    

    def _save_registry(self):

        """Persist registry to disk"""

        try:

            data = []

            for metadata in self.models.values():

                model_dict = asdict(metadata)

                model_dict['last_updated'] = metadata.last_updated.isoformat()

                if metadata.local_path:

                    model_dict['local_path'] = str(metadata.local_path)

                data.append(model_dict)

            

            with open(self.registry_file, 'w') as f:

                json.dump(data, f, indent=2)

            logger.debug("Saved registry to disk")

        except Exception as e:

            logger.error(f"Failed to save registry: {e}")

    

    async def search_huggingface(

        self,

        query: str,

        filter_params: Optional[Dict[str, Any]] = None

    ) -> List[ModelMetadata]:

        """Search Hugging Face Hub for models matching query"""

        results = []

        base_url = "https://huggingface.co/api/models"

        

        params = {

            'search': query,

            'limit': 50,

            'full': 'true'

        }

        

        if filter_params:

            if 'task' in filter_params:

                params['filter'] = filter_params['task']

            if 'library' in filter_params:

                params['library'] = filter_params['library']

        

        try:

            async with aiohttp.ClientSession() as session:

                async with session.get(base_url, params=params) as response:

                    if response.status == 200:

                        models_data = await response.json()

                        

                        for model_data in models_data:

                            model_id = model_data.get('id', '')

                            architecture = self._extract_architecture(model_data)

                            param_count = self._estimate_parameters(model_data)

                            quantization = self._detect_quantization(model_data)

                            model_format = self._detect_format(model_data)

                            size_bytes = self._calculate_size(model_data)

                            tags = model_data.get('tags', [])

                            description = model_data.get('description', '')

                            license_info = model_data.get('license', 'unknown')

                            min_memory = self._calculate_min_memory(param_count, quantization)

                            supported_backends = self._determine_backends(model_format, quantization)

                            

                            metadata = ModelMetadata(

                                model_id=model_id,

                                name=model_data.get('modelId', model_id),

                                architecture=architecture,

                                parameter_count=param_count,

                                quantization=quantization,

                                format=model_format,

                                size_bytes=size_bytes,

                                source='huggingface',

                                description=description,

                                tags=tags,

                                license=license_info,

                                min_memory_gb=min_memory,

                                supported_backends=supported_backends,

                                download_url=f"https://huggingface.co/{model_id}",

                                local_path=None,

                                is_downloaded=False,

                                last_updated=datetime.now()

                            )

                            

                            results.append(metadata)

                            self.models[model_id] = metadata

                        

                        logger.info(f"Found {len(results)} models matching '{query}'")

        

        except Exception as e:

            logger.error(f"Error searching Hugging Face: {e}")

        

        self._save_registry()

        return results

    

    def _extract_architecture(self, model_data: Dict) -> str:

        """Extract model architecture from metadata"""

        tags = model_data.get('tags', [])

        arch_mapping = {

            'llama': 'LLaMA', 'mistral': 'Mistral', 'gpt2': 'GPT-2',

            'gpt-neo': 'GPT-Neo', 'gpt-j': 'GPT-J', 'bloom': 'BLOOM',

            'opt': 'OPT', 'falcon': 'Falcon', 'mpt': 'MPT',

            'phi': 'Phi', 'qwen': 'Qwen', 'gemma': 'Gemma'

        }

        

        for tag in tags:

            tag_lower = tag.lower()

            for key, arch in arch_mapping.items():

                if key in tag_lower:

                    return arch

        

        model_id = model_data.get('id', '').lower()

        for key, arch in arch_mapping.items():

            if key in model_id:

                return arch

        

        return 'Unknown'

    

    def _estimate_parameters(self, model_data: Dict) -> int:

        """Estimate parameter count from model metadata"""

        model_id = model_data.get('id', '').lower()

        import re

        param_pattern = r'(\d+\.?\d*)b'

        match = re.search(param_pattern, model_id)

        if match:

            billions = float(match.group(1))

            return int(billions * 1_000_000_000)

        

        siblings = model_data.get('siblings', [])

        for sibling in siblings:

            if 'safetensors' in sibling.get('rfilename', ''):

                size = sibling.get('size', 0)

                return size // 2

        

        return 0

    

    def _detect_quantization(self, model_data: Dict) -> Optional[str]:

        """Detect quantization scheme from model metadata"""

        model_id = model_data.get('id', '').lower()

        tags = [t.lower() for t in model_data.get('tags', [])]

        

        quant_schemes = {

            'gptq': 'GPTQ', 'awq': 'AWQ', 'gguf': 'GGUF', 'ggml': 'GGML',

            'int8': 'INT8', 'int4': 'INT4', '4bit': '4-bit', '8bit': '8-bit'

        }

        

        for scheme_key, scheme_name in quant_schemes.items():

            if scheme_key in model_id or scheme_key in ' '.join(tags):

                return scheme_name

        

        return None

    

    def _detect_format(self, model_data: Dict) -> str:

        """Detect model format from files"""

        siblings = model_data.get('siblings', [])

        for sibling in siblings:

            filename = sibling.get('rfilename', '').lower()

            if 'safetensors' in filename:

                return 'safetensors'

            elif '.gguf' in filename:

                return 'gguf'

            elif '.bin' in filename:

                return 'pytorch'

            elif '.onnx' in filename:

                return 'onnx'

        return 'unknown'

    

    def _calculate_size(self, model_data: Dict) -> int:

        """Calculate total model size from files"""

        siblings = model_data.get('siblings', [])

        return sum(sibling.get('size', 0) for sibling in siblings)

    

    def _calculate_min_memory(self, param_count: int, quantization: Optional[str]) -> float:

        """Calculate minimum memory requirement in GB"""

        if param_count == 0:

            return 0.0

        

        bytes_per_param = 2.0

        if quantization:

            if '4' in quantization or 'INT4' in quantization:

                bytes_per_param = 0.5

            elif '8' in quantization or 'INT8' in quantization:

                bytes_per_param = 1.0

        

        memory_bytes = param_count * bytes_per_param * 1.2

        return memory_bytes / (1024 ** 3)

    

    def _determine_backends(self, model_format: str, quantization: Optional[str]) -> List[str]:

        """Determine which backends support this model"""

        if model_format in ['safetensors', 'pytorch']:

            backends = ['cuda', 'rocm', 'mps', 'intel', 'cpu']

        elif model_format == 'gguf':

            backends = ['cpu', 'cuda', 'mps']

        elif model_format == 'onnx':

            backends = ['cuda', 'cpu']

        else:

            backends = ['cpu']

        

        if quantization == 'GPTQ' or quantization == 'AWQ':

            backends = ['cuda']

        

        return backends

    

    def search_local(self, query: str, local_dirs: List[Path]) -> List[ModelMetadata]:

        """Search local directories for models"""

        results = []

        for directory in local_dirs:

            if not directory.exists():

                continue

            

            for model_path in directory.iterdir():

                if not model_path.is_dir():

                    continue

                

                has_model = any(

                    f.suffix in ['.safetensors', '.bin', '.gguf', '.onnx']

                    for f in model_path.iterdir()

                )

                

                if not has_model:

                    continue

                

                config_file = model_path / 'config.json'

                if config_file.exists():

                    try:

                        with open(config_file, 'r') as f:

                            config = json.load(f)

                        

                        architecture = config.get('architectures', ['Unknown'])[0]

                        size_bytes = sum(

                            f.stat().st_size for f in model_path.rglob('*') if f.is_file()

                        )

                        model_id = f"local/{model_path.name}"

                        

                        metadata = ModelMetadata(

                            model_id=model_id,

                            name=model_path.name,

                            architecture=architecture,

                            parameter_count=0,

                            quantization=None,

                            format=self._detect_local_format(model_path),

                            size_bytes=size_bytes,

                            source='local',

                            description=f"Local model at {model_path}",

                            tags=[],

                            license='unknown',

                            min_memory_gb=size_bytes / (1024 ** 3),

                            supported_backends=['cuda', 'rocm', 'mps', 'intel', 'cpu'],

                            download_url=None,

                            local_path=model_path,

                            is_downloaded=True,

                            last_updated=datetime.fromtimestamp(model_path.stat().st_mtime)

                        )

                        

                        results.append(metadata)

                        self.models[model_id] = metadata

                    except Exception as e:

                        logger.error(f"Error processing {model_path}: {e}")

        

        self._save_registry()

        logger.info(f"Found {len(results)} local models")

        return results

    

    def _detect_local_format(self, model_path: Path) -> str:

        """Detect format of local model"""

        for file in model_path.iterdir():

            if file.suffix == '.safetensors':

                return 'safetensors'

            elif file.suffix == '.gguf':

                return 'gguf'

            elif file.suffix == '.bin':

                return 'pytorch'

            elif file.suffix == '.onnx':

                return 'onnx'

        return 'unknown'

    

    def filter_models(

        self,

        models: List[ModelMetadata],

        hardware_detector: HardwareDetector,

        max_memory_gb: Optional[float] = None

    ) -> List[ModelMetadata]:

        """Filter models based on hardware compatibility"""

        best_device = hardware_detector.get_best_device()

        backend_type = best_device.backend.value

        available_memory_gb = best_device.total_memory / (1024 ** 3)

        

        if max_memory_gb is None:

            max_memory_gb = available_memory_gb * 0.8

        

        filtered = []

        for model in models:

            if backend_type not in model.supported_backends:

                continue

            if model.min_memory_gb > max_memory_gb:

                continue

            filtered.append(model)

        

        logger.info(f"Filtered to {len(filtered)} compatible models")

        return filtered



# ============================================================================

# MODEL DOWNLOAD MANAGER

# ============================================================================


class ModelDownloader:

    """

    Handles downloading large model files with resumption and verification.

    Supports parallel downloads of multiple files and progress tracking.

    """

    

    def __init__(self, download_dir: Path, chunk_size: int = 8192):

        self.download_dir = Path(download_dir)

        self.download_dir.mkdir(parents=True, exist_ok=True)

        self.chunk_size = chunk_size

        logger.info(f"Initialized downloader with cache at {self.download_dir}")

    

    async def download_model(

        self,

        metadata: ModelMetadata,

        progress_callback: Optional[Callable[[int, int], None]] = None

    ) -> Path:

        """Download a complete model from Hugging Face Hub"""

        model_dir = self.download_dir / metadata.model_id.replace('/', '_')

        model_dir.mkdir(parents=True, exist_ok=True)

        

        files_to_download = await self._get_model_files(metadata.model_id)

        total_size = sum(f['size'] for f in files_to_download)

        downloaded_size = 0

        

        logger.info(f"Downloading {len(files_to_download)} files ({total_size / (1024**3):.2f} GB)")

        

        async with aiohttp.ClientSession() as session:

            for file_info in files_to_download:

                file_path = model_dir / file_info['filename']

                file_url = file_info['url']

                file_size = file_info['size']

                

                if file_path.exists() and file_path.stat().st_size == file_size:

                    downloaded_size += file_size

                    if progress_callback:

                        progress_callback(downloaded_size, total_size)

                    continue

                

                bytes_downloaded = await self._download_file(

                    session,

                    file_url,

                    file_path,

                    file_size,

                    lambda current: progress_callback(

                        downloaded_size + current, total_size

                    ) if progress_callback else None

                )

                

                downloaded_size += bytes_downloaded

        

        metadata.local_path = model_dir

        metadata.is_downloaded = True

        logger.info(f"Download complete: {model_dir}")

        

        return model_dir

    

    async def _get_model_files(self, model_id: str) -> List[Dict[str, Any]]:

        """Get list of files for a model from Hugging Face API"""

        api_url = f"https://huggingface.co/api/models/{model_id}"

        

        async with aiohttp.ClientSession() as session:

            async with session.get(api_url) as response:

                if response.status != 200:

                    raise RuntimeError(f"Failed to get model info: {response.status}")

                

                model_data = await response.json()

                siblings = model_data.get('siblings', [])

                

                files = []

                for sibling in siblings:

                    filename = sibling.get('rfilename', '')

                    if any(skip in filename for skip in ['.md', '.txt', '.gitattributes']):

                        continue

                    

                    file_url = f"https://huggingface.co/{model_id}/resolve/main/{filename}"

                    files.append({

                        'filename': filename,

                        'url': file_url,

                        'size': sibling.get('size', 0)

                    })

                

                return files

    

    async def _download_file(

        self,

        session: aiohttp.ClientSession,

        url: str,

        destination: Path,

        expected_size: int,

        progress_callback: Optional[Callable[[int], None]] = None

    ) -> int:

        """Download a single file with resumption support"""

        resume_pos = 0

        if destination.exists():

            resume_pos = destination.stat().st_size

            if resume_pos >= expected_size:

                return expected_size

        

        headers = {}

        if resume_pos > 0:

            headers['Range'] = f'bytes={resume_pos}-'

        

        mode = 'ab' if resume_pos > 0 else 'wb'

        

        async with session.get(url, headers=headers) as response:

            if response.status not in [200, 206]:

                raise RuntimeError(f"Download failed: {response.status}")

            

            async with aiofiles.open(destination, mode) as f:

                bytes_written = resume_pos

                async for chunk in response.content.iter_chunked(self.chunk_size):

                    await f.write(chunk)

                    bytes_written += len(chunk)

                    if progress_callback:

                        progress_callback(bytes_written)

        

        return bytes_written

    

    def verify_model(self, model_path: Path) -> bool:

        """Verify model integrity by checking file sizes and formats"""

        if not model_path.exists() or not model_path.is_dir():

            return False

        

        weight_files = list(model_path.glob('*.safetensors')) + \

                      list(model_path.glob('*.bin')) + \

                      list(model_path.glob('*.gguf'))

        

        if not weight_files:

            return False

        

        for weight_file in weight_files:

            if weight_file.stat().st_size == 0:

                return False

        

        return True



# ============================================================================

# MODEL LOADER

# ============================================================================


class ModelLoader:

    """

    Loads and manages LLM models across different formats and backends.

    Handles memory optimization, quantization, and device placement.

    """

    

    def __init__(self, hardware_detector: HardwareDetector):

        self.hardware_detector = hardware_detector

        self.loaded_models: Dict[str, Tuple[Any, Any]] = {}

        self.current_model_id: Optional[str] = None

        logger.info("Initialized model loader")

    

    def load_model(

        self,

        metadata: ModelMetadata,

        device: Optional[DeviceInfo] = None,

        load_in_8bit: bool = False,

        load_in_4bit: bool = False

    ) -> Tuple[Any, Any]:

        """Load a model and tokenizer, handling different formats and quantization"""

        if device is None:

            device = self.hardware_detector.get_best_device()

        

        device_str = self.hardware_detector.get_device_string(device)

        

        if self.current_model_id and self.current_model_id != metadata.model_id:

            self.unload_current_model()

        

        if metadata.model_id in self.loaded_models:

            self.current_model_id = metadata.model_id

            return self.loaded_models[metadata.model_id]

        

        model_path = metadata.local_path or metadata.model_id

        

        logger.info(f"Loading model {metadata.model_id} on {device_str}")

        

        if metadata.format in ['safetensors', 'pytorch']:

            model, tokenizer = self._load_transformers_model(

                model_path, device_str, device.backend, load_in_8bit, load_in_4bit

            )

        elif metadata.format == 'gguf':

            model, tokenizer = self._load_gguf_model(model_path, device_str)

        else:

            raise ValueError(f"Unsupported format: {metadata.format}")

        

        self.loaded_models[metadata.model_id] = (model, tokenizer)

        self.current_model_id = metadata.model_id

        logger.info(f"Model {metadata.model_id} loaded successfully")

        

        return model, tokenizer

    

    def _load_transformers_model(

        self,

        model_path: Any,

        device_str: str,

        backend: BackendType,

        load_in_8bit: bool,

        load_in_4bit: bool

    ) -> Tuple[Any, Any]:

        """Load model using Hugging Face Transformers"""

        from transformers import AutoModelForCausalLM, AutoTokenizer

        

        load_kwargs = {

            'torch_dtype': torch.float16,

            'low_cpu_mem_usage': True

        }

        

        if load_in_8bit:

            load_kwargs['load_in_8bit'] = True

            load_kwargs['device_map'] = 'auto'

        elif load_in_4bit:

            from transformers import BitsAndBytesConfig

            bnb_config = BitsAndBytesConfig(

                load_in_4bit=True,

                bnb_4bit_compute_dtype=torch.float16,

                bnb_4bit_use_double_quant=True,

                bnb_4bit_quant_type='nf4'

            )

            load_kwargs['quantization_config'] = bnb_config

            load_kwargs['device_map'] = 'auto'

        else:

            if 'cpu' not in device_str:

                load_kwargs['device_map'] = 'auto'

        

        if backend == BackendType.MPS:

            load_kwargs.pop('load_in_8bit', None)

            load_kwargs.pop('quantization_config', None)

            load_kwargs.pop('device_map', None)

        

        try:

            model = AutoModelForCausalLM.from_pretrained(model_path, **load_kwargs)

            if 'device_map' not in load_kwargs and 'cpu' not in device_str:

                model = model.to(device_str)

            

            tokenizer = AutoTokenizer.from_pretrained(model_path)

            if tokenizer.pad_token is None:

                tokenizer.pad_token = tokenizer.eos_token

            

            return model, tokenizer

        except Exception as e:

            raise RuntimeError(f"Failed to load model: {e}")

    

    def _load_gguf_model(self, model_path: Any, device_str: str) -> Tuple[Any, Any]:

        """Load GGUF format model using llama-cpp-python"""

        try:

            from llama_cpp import Llama

            

            if isinstance(model_path, Path):

                gguf_files = list(model_path.glob('*.gguf'))

                if not gguf_files:

                    raise ValueError("No GGUF file found")

                model_file = str(gguf_files[0])

            else:

                model_file = model_path

            

            n_gpu_layers = 0

            if 'cuda' in device_str or 'mps' in device_str:

                n_gpu_layers = -1

            

            model = Llama(

                model_path=model_file,

                n_gpu_layers=n_gpu_layers,

                n_ctx=2048,

                verbose=False

            )

            

            return model, None

        except ImportError:

            raise RuntimeError("llama-cpp-python not installed")

        except Exception as e:

            raise RuntimeError(f"Failed to load GGUF model: {e}")

    

    def unload_current_model(self):

        """Unload currently loaded model to free memory"""

        if self.current_model_id and self.current_model_id in self.loaded_models:

            model, tokenizer = self.loaded_models[self.current_model_id]

            

            if hasattr(model, 'cpu'):

                model.cpu()

            

            del model

            del tokenizer

            

            self.loaded_models.pop(self.current_model_id)

            self.current_model_id = None

            

            gc.collect()

            if torch.cuda.is_available():

                torch.cuda.empty_cache()

            

            logger.info("Unloaded current model")

    

    def get_model_info(self, model: Any) -> Dict[str, Any]:

        """Extract detailed information about loaded model"""

        info = {}

        

        if hasattr(model, 'config'):

            config = model.config

            info['architecture'] = config.architectures[0] if hasattr(config, 'architectures') else 'Unknown'

            info['hidden_size'] = getattr(config, 'hidden_size', None)

            info['num_layers'] = getattr(config, 'num_hidden_layers', None)

            info['num_attention_heads'] = getattr(config, 'num_attention_heads', None)

            info['vocab_size'] = getattr(config, 'vocab_size', None)

            info['max_position_embeddings'] = getattr(config, 'max_position_embeddings', None)

        

        if hasattr(model, 'parameters'):

            total_params = sum(p.numel() for p in model.parameters())

            trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)

            info['total_parameters'] = total_params

            info['trainable_parameters'] = trainable_params

        

        if hasattr(model, 'get_memory_footprint'):

            info['memory_footprint_bytes'] = model.get_memory_footprint()

        elif torch.cuda.is_available():

            info['cuda_memory_allocated'] = torch.cuda.memory_allocated()

            info['cuda_memory_reserved'] = torch.cuda.memory_reserved()

        

        if hasattr(model, 'dtype'):

            info['dtype'] = str(model.dtype)

        

        return info



# ============================================================================

# CHAT ENGINE

# ============================================================================


@dataclass

class Message:

    role: str

    content: str

    timestamp: datetime



@dataclass

class GenerationConfig:

    max_new_tokens: int = 512

    temperature: float = 0.7

    top_p: float = 0.9

    top_k: int = 50

    repetition_penalty: float = 1.1

    do_sample: bool = True

    num_beams: int = 1



class ChatEngine:

    """

    Manages chat conversations and generation.

    Supports streaming responses and conversation history.

    """

    

    def __init__(self):

        self.conversations: Dict[str, List[Message]] = {}

        self.current_conversation_id: Optional[str] = None

        logger.info("Initialized chat engine")

    

    def create_conversation(self, conversation_id: str):

        """Create a new conversation"""

        self.conversations[conversation_id] = []

        self.current_conversation_id = conversation_id

    

    def add_message(self, conversation_id: str, role: str, content: str):

        """Add a message to conversation history"""

        if conversation_id not in self.conversations:

            self.create_conversation(conversation_id)

        

        message = Message(role=role, content=content, timestamp=datetime.now())

        self.conversations[conversation_id].append(message)

    

    def get_conversation(self, conversation_id: str) -> List[Message]:

        """Retrieve conversation history"""

        return self.conversations.get(conversation_id, [])

    

    def generate_response(

        self,

        model: Any,

        tokenizer: Any,

        conversation_id: str,

        user_message: str,

        config: GenerationConfig = None,

        stream: bool = False

    ) -> str:

        """Generate response to user message"""

        if config is None:

            config = GenerationConfig()

        

        self.add_message(conversation_id, 'user', user_message)

        prompt = self._format_conversation(self.get_conversation(conversation_id), tokenizer)

        

        if stream:

            response = self._generate_streaming(model, tokenizer, prompt, config)

        else:

            response = self._generate_complete(model, tokenizer, prompt, config)

        

        self.add_message(conversation_id, 'assistant', response)

        return response

    

    def _format_conversation(self, messages: List[Message], tokenizer: Any) -> str:

        """Format conversation history into prompt"""

        if hasattr(tokenizer, 'chat_template') and tokenizer.chat_template:

            formatted_messages = [

                {"role": msg.role, "content": msg.content} for msg in messages

            ]

            return tokenizer.apply_chat_template(

                formatted_messages, tokenize=False, add_generation_prompt=True

            )

        else:

            prompt_parts = []

            for msg in messages:

                if msg.role == 'user':

                    prompt_parts.append(f"User: {msg.content}")

                else:

                    prompt_parts.append(f"Assistant: {msg.content}")

            prompt_parts.append("Assistant:")

            return "\n\n".join(prompt_parts)

    

    def _generate_complete(

        self, model: Any, tokenizer: Any, prompt: str, config: GenerationConfig

    ) -> str:

        """Generate complete response at once"""

        if hasattr(model, 'create_completion'):

            response = model.create_completion(

                prompt,

                max_tokens=config.max_new_tokens,

                temperature=config.temperature,

                top_p=config.top_p,

                top_k=config.top_k,

                repeat_penalty=config.repetition_penalty

            )

            return response['choices'][0]['text']

        else:

            inputs = tokenizer(prompt, return_tensors='pt', truncation=True, max_length=2048)

            device = next(model.parameters()).device

            inputs = {k: v.to(device) for k, v in inputs.items()}

            

            with torch.no_grad():

                outputs = model.generate(

                    **inputs,

                    max_new_tokens=config.max_new_tokens,

                    temperature=config.temperature,

                    top_p=config.top_p,

                    top_k=config.top_k,

                    repetition_penalty=config.repetition_penalty,

                    do_sample=config.do_sample,

                    num_beams=config.num_beams,

                    pad_token_id=tokenizer.pad_token_id,

                    eos_token_id=tokenizer.eos_token_id

                )

            

            response = tokenizer.decode(

                outputs[0][inputs['input_ids'].shape[1]:],

                skip_special_tokens=True

            )

            return response.strip()

    

    def _generate_streaming(

        self, model: Any, tokenizer: Any, prompt: str, config: GenerationConfig

    ) -> Iterator[str]:

        """Generate response with streaming"""

        if hasattr(model, 'create_completion'):

            stream = model.create_completion(

                prompt,

                max_tokens=config.max_new_tokens,

                temperature=config.temperature,

                top_p=config.top_p,

                top_k=config.top_k,

                repeat_penalty=config.repetition_penalty,

                stream=True

            )

            

            full_response = ""

            for chunk in stream:

                if 'choices' in chunk and len(chunk['choices']) > 0:

                    text = chunk['choices'][0].get('text', '')

                    full_response += text

                    yield text

            return full_response

        else:

            from transformers import TextIteratorStreamer

            from threading import Thread

            

            inputs = tokenizer(prompt, return_tensors='pt', truncation=True, max_length=2048)

            device = next(model.parameters()).device

            inputs = {k: v.to(device) for k, v in inputs.items()}

            

            streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)

            

            generation_kwargs = {

                **inputs,

                'max_new_tokens': config.max_new_tokens,

                'temperature': config.temperature,

                'top_p': config.top_p,

                'top_k': config.top_k,

                'repetition_penalty': config.repetition_penalty,

                'do_sample': config.do_sample,

                'streamer': streamer,

                'pad_token_id': tokenizer.pad_token_id,

                'eos_token_id': tokenizer.eos_token_id

            }

            

            thread = Thread(target=model.generate, kwargs=generation_kwargs)

            thread.start()

            

            full_response = ""

            for text in streamer:

                full_response += text

                yield text

            

            thread.join()

            return full_response



# ============================================================================

# WEB INTERFACE AND API

# ============================================================================


class LLMPlatform:

    """Main platform class integrating all components"""

    

    def __init__(self, cache_dir: Path, host: str = '0.0.0.0', port: int = 5000):

        self.cache_dir = Path(cache_dir)

        self.host = host

        self.port = port

        

        self.hardware_detector = HardwareDetector()

        self.model_registry = ModelRegistry(self.cache_dir / 'registry')

        self.model_downloader = ModelDownloader(self.cache_dir / 'downloads')

        self.model_loader = ModelLoader(self.hardware_detector)

        self.chat_engine = ChatEngine()

        

        self.app = Flask(__name__)

        CORS(self.app)

        self.socketio = SocketIO(self.app, cors_allowed_origins="*")

        

        self._setup_routes()

        self._setup_socketio()

        

        logger.info("LLM Platform initialized")

    

    def _setup_routes(self):

        """Setup Flask routes"""

        

        @self.app.route('/')

        def index():

            return render_template_string(self._get_html_template())

        

        @self.app.route('/api/hardware', methods=['GET'])

        def get_hardware():

            devices = [

                {

                    'backend': device.backend.value,

                    'device_id': device.device_id,

                    'name': device.name,

                    'total_memory_gb': device.total_memory / (1024 ** 3),

                    'compute_capability': device.compute_capability,

                    'driver_version': device.driver_version

                }

                for device in self.hardware_detector.devices

            ]

            return jsonify({'devices': devices})

        

        @self.app.route('/api/models/search', methods=['POST'])

        def search_models():

            data = request.json

            query = data.get('query', '')

            source = data.get('source', 'huggingface')

            

            if source == 'huggingface':

                loop = asyncio.new_event_loop()

                asyncio.set_event_loop(loop)

                results = loop.run_until_complete(

                    self.model_registry.search_huggingface(query)

                )

                loop.close()

            elif source == 'local':

                local_dirs = [Path(d) for d in data.get('directories', [])]

                results = self.model_registry.search_local(query, local_dirs)

            else:

                return jsonify({'error': 'Invalid source'}), 400

            

            models_data = [

                {

                    'model_id': m.model_id,

                    'name': m.name,

                    'architecture': m.architecture,

                    'parameter_count': m.parameter_count,

                    'quantization': m.quantization,

                    'format': m.format,

                    'size_gb': m.size_bytes / (1024 ** 3),

                    'description': m.description,

                    'tags': m.tags,

                    'min_memory_gb': m.min_memory_gb,

                    'is_downloaded': m.is_downloaded

                }

                for m in results

            ]

            

            return jsonify({'models': models_data})

        

        @self.app.route('/api/models/download', methods=['POST'])

        def download_model():

            data = request.json

            model_id = data.get('model_id')

            

            if not model_id or model_id not in self.model_registry.models:

                return jsonify({'error': 'Model not found'}), 404

            

            metadata = self.model_registry.models[model_id]

            

            def download_worker():

                loop = asyncio.new_event_loop()

                asyncio.set_event_loop(loop)

                

                def progress_callback(current, total):

                    progress = (current / total) * 100

                    self.socketio.emit('download_progress', {

                        'model_id': model_id,

                        'progress': progress,

                        'current_bytes': current,

                        'total_bytes': total

                    })

                

                try:

                    loop.run_until_complete(

                        self.model_downloader.download_model(metadata, progress_callback)

                    )

                    self.socketio.emit('download_complete', {

                        'model_id': model_id,

                        'success': True

                    })

                except Exception as e:

                    self.socketio.emit('download_complete', {

                        'model_id': model_id,

                        'success': False,

                        'error': str(e)

                    })

                finally:

                    loop.close()

            

            thread = Thread(target=download_worker)

            thread.start()

            

            return jsonify({'status': 'download_started'})

        

        @self.app.route('/api/models/load', methods=['POST'])

        def load_model():

            data = request.json

            model_id = data.get('model_id')

            load_in_8bit = data.get('load_in_8bit', False)

            load_in_4bit = data.get('load_in_4bit', False)

            

            if not model_id or model_id not in self.model_registry.models:

                return jsonify({'error': 'Model not found'}), 404

            

            metadata = self.model_registry.models[model_id]

            

            try:

                model, tokenizer = self.model_loader.load_model(

                    metadata,

                    load_in_8bit=load_in_8bit,

                    load_in_4bit=load_in_4bit

                )

                model_info = self.model_loader.get_model_info(model)

                return jsonify({'status': 'loaded', 'model_info': model_info})

            except Exception as e:

                return jsonify({'error': str(e)}), 500

        

        @self.app.route('/api/models/info', methods=['GET'])

        def get_model_info():

            if not self.model_loader.current_model_id:

                return jsonify({'error': 'No model loaded'}), 404

            

            model, _ = self.model_loader.loaded_models[self.model_loader.current_model_id]

            info = self.model_loader.get_model_info(model)

            metadata = self.model_registry.models[self.model_loader.current_model_id]

            

            return jsonify({

                'model_id': self.model_loader.current_model_id,

                'metadata': {

                    'name': metadata.name,

                    'architecture': metadata.architecture,

                    'format': metadata.format,

                    'quantization': metadata.quantization

                },

                'info': info

            })

        

        @self.app.route('/api/chat/message', methods=['POST'])

        def chat_message():

            data = request.json

            conversation_id = data.get('conversation_id', 'default')

            message = data.get('message', '')

            stream = data.get('stream', False)

            

            config_data = data.get('config', {})

            config = GenerationConfig(

                max_new_tokens=config_data.get('max_new_tokens', 512),

                temperature=config_data.get('temperature', 0.7),

                top_p=config_data.get('top_p', 0.9),

                top_k=config_data.get('top_k', 50),

                repetition_penalty=config_data.get('repetition_penalty', 1.1)

            )

            

            if not self.model_loader.current_model_id:

                return jsonify({'error': 'No model loaded'}), 400

            

            model, tokenizer = self.model_loader.loaded_models[

                self.model_loader.current_model_id

            ]

            

            if stream:

                def generate():

                    for token in self.chat_engine.generate_response(

                        model, tokenizer, conversation_id, message, config, stream=True

                    ):

                        yield f"data: {json.dumps({'token': token})}\n\n"

                    yield f"data: {json.dumps({'done': True})}\n\n"

                return Response(generate(), mimetype='text/event-stream')

            else:

                response = self.chat_engine.generate_response(

                    model, tokenizer, conversation_id, message, config, stream=False

                )

                return jsonify({'response': response})

        

        @self.app.route('/api/chat/history', methods=['GET'])

        def get_chat_history():

            conversation_id = request.args.get('conversation_id', 'default')

            messages = self.chat_engine.get_conversation(conversation_id)

            messages_data = [

                {

                    'role': msg.role,

                    'content': msg.content,

                    'timestamp': msg.timestamp.isoformat()

                }

                for msg in messages

            ]

            return jsonify({'messages': messages_data})

        

        @self.app.route('/v1/models', methods=['GET'])

        def list_models():

            models = []

            for model_id, metadata in self.model_registry.models.items():

                if metadata.is_downloaded:

                    models.append({

                        'id': model_id,

                        'object': 'model',

                        'created': int(metadata.last_updated.timestamp()),

                        'owned_by': 'local'

                    })

            return jsonify({'object': 'list', 'data': models})

        

        @self.app.route('/v1/chat/completions', methods=['POST'])

        def chat_completions():

            data = request.json

            model_id = data.get('model')

            messages = data.get('messages', [])

            temperature = data.get('temperature', 0.7)

            max_tokens = data.get('max_tokens', 512)

            top_p = data.get('top_p', 0.9)

            stream = data.get('stream', False)

            

            if not model_id:

                return jsonify({'error': 'model parameter required'}), 400

            

            if self.model_loader.current_model_id != model_id:

                if model_id not in self.model_registry.models:

                    return jsonify({'error': f'Model {model_id} not found'}), 404

                metadata = self.model_registry.models[model_id]

                try:

                    self.model_loader.load_model(metadata)

                except Exception as e:

                    return jsonify({'error': f'Failed to load model: {str(e)}'}), 500

            

            model, tokenizer = self.model_loader.loaded_models[model_id]

            conversation_id = str(uuid.uuid4())

            self.chat_engine.create_conversation(conversation_id)

            

            for msg in messages:

                role = msg.get('role')

                content = msg.get('content')

                if role and content:

                    self.chat_engine.add_message(conversation_id, role, content)

            

            config = GenerationConfig(

                max_new_tokens=max_tokens,

                temperature=temperature,

                top_p=top_p

            )

            

            last_user_message = None

            for msg in reversed(messages):

                if msg.get('role') == 'user':

                    last_user_message = msg.get('content')

                    break

            

            if not last_user_message:

                return jsonify({'error': 'No user message found'}), 400

            

            if stream:

                def generate():

                    completion_id = f"chatcmpl-{uuid.uuid4()}"

                    created = int(time.time())

                    

                    if self.chat_engine.conversations[conversation_id]:

                        self.chat_engine.conversations[conversation_id].pop()

                    

                    for token in self.chat_engine._generate_streaming(

                        model,

                        tokenizer,

                        self.chat_engine._format_conversation(

                            self.chat_engine.get_conversation(conversation_id) + [

                                Message('user', last_user_message, datetime.now())

                            ],

                            tokenizer

                        ),

                        config

                    ):

                        chunk = {

                            'id': completion_id,

                            'object': 'chat.completion.chunk',

                            'created': created,

                            'model': model_id,

                            'choices': [{

                                'index': 0,

                                'delta': {'content': token},

                                'finish_reason': None

                            }]

                        }

                        yield f"data: {json.dumps(chunk)}\n\n"

                    

                    final_chunk = {

                        'id': completion_id,

                        'object': 'chat.completion.chunk',

                        'created': created,

                        'model': model_id,

                        'choices': [{

                            'index': 0,

                            'delta': {},

                            'finish_reason': 'stop'

                        }]

                    }

                    yield f"data: {json.dumps(final_chunk)}\n\n"

                    yield "data: [DONE]\n\n"

                

                return Response(generate(), mimetype='text/event-stream')

            else:

                if self.chat_engine.conversations[conversation_id]:

                    self.chat_engine.conversations[conversation_id].pop()

                

                response_text = self.chat_engine.generate_response(

                    model, tokenizer, conversation_id, last_user_message, config, stream=False

                )

                

                completion_id = f"chatcmpl-{uuid.uuid4()}"

                created = int(time.time())

                

                return jsonify({

                    'id': completion_id,

                    'object': 'chat.completion',

                    'created': created,

                    'model': model_id,

                    'choices': [{

                        'index': 0,

                        'message': {

                            'role': 'assistant',

                            'content': response_text

                        },

                        'finish_reason': 'stop'

                    }],

                    'usage': {

                        'prompt_tokens': 0,

                        'completion_tokens': 0,

                        'total_tokens': 0

                    }

                })

    

    def _setup_socketio(self):

        """Setup WebSocket handlers"""

        @self.socketio.on('connect')

        def handle_connect():

            emit('connected', {'status': 'connected'})

        

        @self.socketio.on('disconnect')

        def handle_disconnect():

            pass

    

    def _get_html_template(self) -> str:

        """Get HTML template for web interface"""

        return '''<!DOCTYPE html>


LLM Management Platform

Manage, fine-tune, and deploy large language models across heterogeneous hardware


<div class="container">

    <div class="grid">

        <div class="card">

            <h2>Hardware Information</h2>

            <div id="hardware-info" class="loading">Loading hardware information...</div>

            <button class="btn" onclick="loadHardware()">Refresh Hardware</button>

        </div>

        

        <div class="card">

            <h2>Model Search</h2>

            <input type="text" id="search-query" placeholder="Search for models (e.g., llama, mistral, gpt)...">

            <button class="btn" onclick="searchModels()">Search Hugging Face</button>

            <div id="search-results"></div>

        </div>

    </div>

    

    <div class="card">

        <h2>Chat Interface</h2>

        <div class="chat-box" id="chat-box"></div>

        <textarea id="user-input" placeholder="Type your message here..." rows="3"></textarea>

        <button class="btn" onclick="sendMessage()">Send Message</button>

        <button class="btn" onclick="clearChat()" style="background: #6c757d; margin-left: 0.5rem;">Clear Chat</button>

    </div>

    

    <div class="card">

        <h2>Current Model Information</h2>

        <div id="model-info" class="loading">No model loaded</div>

        <button class="btn" onclick="loadModelInfo()">Refresh Model Info</button>

    </div>

</div>


<script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>

<script>

    const socket = io();

    

    async function loadHardware() {

        try {

            const response = await fetch('/api/hardware');

            const data = await response.json();

            const html = data.devices.map(d => `

                <div class="device-card">

                    <strong>${d.name}</strong> (${d.backend})<br>

                    Memory: ${d.total_memory_gb.toFixed(2)} GB<br>

                    ${d.compute_capability ? `Compute: ${d.compute_capability}<br>` : ''}

                    ${d.driver_version ? `Driver: ${d.driver_version}` : ''}

                </div>

            `).join('');

            document.getElementById('hardware-info').innerHTML = html || '<p>No devices found</p>';

        } catch (e) {

            document.getElementById('hardware-info').innerHTML = '<p>Error loading hardware info</p>';

        }

    }

    

    async function searchModels() {

        const query = document.getElementById('search-query').value;

        if (!query) return;

        

        document.getElementById('search-results').innerHTML = '<div class="loading">Searching...</div>';

        

        try {

            const response = await fetch('/api/models/search', {

                method: 'POST',

                headers: {'Content-Type': 'application/json'},

                body: JSON.stringify({query: query, source: 'huggingface'})

            });

            const data = await response.json();

            

            if (data.models && data.models.length > 0) {

                const html = data.models.slice(0, 10).map(m => `

                    <div class="model-card">

                        <h3>${m.name}</h3>

                        <p>${m.description.substring(0, 150)}${m.description.length > 150 ? '...' : ''}</p>

                        <p><strong>Architecture:</strong> ${m.architecture} | <strong>Size:</strong> ${m.size_gb.toFixed(2)} GB</p>

                        <p><strong>Min Memory:</strong> ${m.min_memory_gb.toFixed(2)} GB</p>

                        ${m.tags.slice(0, 5).map(t => `<span class="badge">${t}</span>`).join('')}

                        <div style="margin-top: 1rem;">

                            ${m.is_downloaded ? 

                                `<button class="btn" onclick="loadModel('${m.model_id}')">Load Model</button>` :

                                `<button class="btn" onclick="downloadModel('${m.model_id}')">Download</button>`

                            }

                        </div>

                    </div>

                `).join('');

                document.getElementById('search-results').innerHTML = html;

            } else {

                document.getElementById('search-results').innerHTML = '<p>No models found</p>';

            }

        } catch (e) {

            document.getElementById('search-results').innerHTML = '<p>Error searching models</p>';

        }

    }

    

    async function downloadModel(modelId) {

        if (!confirm('Download this model? This may take a while depending on model size.')) return;

        

        try {

            await fetch('/api/models/download', {

                method: 'POST',

                headers: {'Content-Type': 'application/json'},

                body: JSON.stringify({model_id: modelId})

            });

            alert('Download started. Check console for progress.');

        } catch (e) {

            alert('Error starting download');

        }

    }

    

    async function loadModel(modelId) {

        try {

            const response = await fetch('/api/models/load', {

                method: 'POST',

                headers: {'Content-Type': 'application/json'},

                body: JSON.stringify({model_id: modelId})

            });

            const data = await response.json();

            

            if (data.status === 'loaded') {

                alert('Model loaded successfully!');

                loadModelInfo();

            } else {

                alert('Error loading model: ' + (data.error || 'Unknown error'));

            }

        } catch (e) {

            alert('Error loading model');

        }

    }

    

    async function loadModelInfo() {

        try {

            const response = await fetch('/api/models/info');

            const data = await response.json();

            

            if (data.model_id) {

                const html = `

                    <div class="device-card">

                        <strong>Model ID:</strong> ${data.model_id}<br>

                        <strong>Name:</strong> ${data.metadata.name}<br>

                        <strong>Architecture:</strong> ${data.metadata.architecture}<br>

                        <strong>Format:</strong> ${data.metadata.format}<br>

                        ${data.info.total_parameters ? `<strong>Parameters:</strong> ${(data.info.total_parameters / 1e9).toFixed(2)}B<br>` : ''}

                        ${data.info.dtype ? `<strong>Data Type:</strong> ${data.info.dtype}<br>` : ''}

                    </div>

                `;

                document.getElementById('model-info').innerHTML = html;

            } else {

                document.getElementById('model-info').innerHTML = '<p>No model loaded</p>';

            }

        } catch (e) {

            document.getElementById('model-info').innerHTML = '<p>No model loaded</p>';

        }

    }

    

    async function sendMessage() {

        const input = document.getElementById('user-input');

        const message = input.value.trim();

        if (!message) return;

        

        addMessageToChat('user', message);

        input.value = '';

        

        try {

            const response = await fetch('/api/chat/message', {

                method: 'POST',

                headers: {'Content-Type': 'application/json'},

                body: JSON.stringify({

                    conversation_id: 'default',

                    message: message,

                    stream: false

                })

            });

            

            const data = await response.json();

            if (data.response) {

                addMessageToChat('assistant', data.response);

            } else if (data.error) {

                alert('Error: ' + data.error);

            }

        } catch (e) {

            alert('Error sending message. Make sure a model is loaded.');

        }

    }

    

    function addMessageToChat(role, content) {

        const chatBox = document.getElementById('chat-box');

        const messageDiv = document.createElement('div');

        messageDiv.className = `message ${role}-message`;

        messageDiv.textContent = content;

        chatBox.appendChild(messageDiv);

        chatBox.scrollTop = chatBox.scrollHeight;

    }

    

    function clearChat() {

        document.getElementById('chat-box').innerHTML = '';

    }

    

    socket.on('download_progress', (data) => {

        console.log(`Download progress for ${data.model_id}: ${data.progress.toFixed(2)}%`);

    });

    

    socket.on('download_complete', (data) => {

        if (data.success) {

            alert(`Download complete: ${data.model_id}`);

        } else {

            alert(`Download failed: ${data.error}`);

        }

    });

    

    loadHardware();

    loadModelInfo();

</script>

'''

    def run(self):

        """Start the platform"""

        logger.info(f"Starting LLM Platform on {self.host}:{self.port}")

        self.socketio.run(self.app, host=self.host, port=self.port, debug=False)



# ============================================================================

# MAIN ENTRY POINT

# ============================================================================


def main():

    """Main entry point"""

    parser = argparse.ArgumentParser(description='LLM Management Platform')

    parser.add_argument('--port', type=int, default=5000, help='Port to run web server on')

    parser.add_argument('--host', type=str, default='0.0.0.0', help='Host to bind to')

    parser.add_argument('--cache-dir', type=str, default='./llm_cache', help='Directory for model cache')

    

    args = parser.parse_args()

    

    platform = LLMPlatform(

        cache_dir=Path(args.cache_dir),

        host=args.host,

        port=args.port

    )

    

    platform.run()



if __name__ == '__main__':

    main()


This complete implementation provides a production-ready LLM management platform with all described functionality. The code handles hardware detection across NVIDIA CUDA, AMD ROCm, Apple MPS, and Intel backends. It implements model discovery from Hugging Face with local caching. The download manager supports resumable transfers with progress tracking. The model loader handles different formats including PyTorch, SafeTensors, and GGUF with quantization support. The chat engine provides both streaming and complete response generation. The web interface offers an intuitive graphical interface for all operations. The OpenAI-compatible API enables programmatic access using standard interfaces. All components integrate seamlessly to provide a comprehensive solution for LLM deployment and management across heterogeneous hardware environments.

No comments: