diff --git a/Dockerfile b/Dockerfile index 9509e77..776b2cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,6 +36,7 @@ RUN uv venv && source .venv/bin/activate && uv pip install -e . ENV PATH="/app/.venv/bin:$PATH" \ PYTHONUNBUFFERED=1 \ PYTHONDONTWRITEBYTECODE=1 \ + RERANKER_BACKEND_TYPE=pytorch \ RERANKER_HOST=0.0.0.0 \ RERANKER_PORT=8010 diff --git a/README.md b/README.md index 0168c8e..1a06080 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ This project provides a FastAPI-based web service that implements a reranking AP * **Jina Compatible API**: Implements `/v1/rerank` endpoint structure * **Local Hosting**: Run reranker model entirely on your own infrastructure -* **Multiple Backends**: Supports both PyTorch and MLX backends for optimal performance -* **Apple Silicon Optimization**: MLX backend provides optimized performance for M1/M2/M3 chips +* **Multiple Backends**: Supports PyTorch everywhere and MLX on native Apple Silicon +* **Apple Silicon Optimization**: MLX backend is automatically installed on macOS arm64 (M1/M2/M3) * **MLX Fallback Reranker**: Automatically wraps MLX-converted Hugging Face models that do not ship a `rerank.py` helper * **Sentence Transformers**: Uses powerful `sentence-transformers` library for PyTorch backend * **Configurable Model**: Easily switch between different reranker models and backends @@ -29,10 +29,11 @@ This project provides a FastAPI-based web service that implements a reranking AP **PyTorch Backend:** * PyTorch 2.5+ (automatically installed) * CUDA/MPS support for GPU acceleration (optional) +* Recommended backend for Linux and Docker deployments **MLX Backend (Apple Silicon only):** * Apple Silicon (M1/M2/M3) Mac -* MLX and MLX-LM libraries (automatically installed) +* MLX and MLX-LM are installed automatically on native macOS arm64 * Optimized for memory efficiency and performance on Apple chips ## Installation @@ -50,6 +51,10 @@ source .venv/bin/activate uv pip install -e ".[dev]" ``` +On native macOS Apple Silicon, MLX dependencies are installed automatically. + +On Linux, including Docker, the default backend is `pytorch`. MLX is not supported there because Docker containers run a Linux userspace/kernel interface even on Apple hosts, and MLX requires Apple-native runtime libraries. + ## Usage The CLI supports both modern subcommands and legacy arguments for backward compatibility. @@ -74,7 +79,16 @@ cli --backend --model --host --port ### Available Backends * `pytorch`: PyTorch-based reranker (default, cross-platform) -* `mlx`: MLX-based reranker (Apple Silicon optimized) +* `mlx`: MLX-based reranker (native macOS Apple Silicon only) + +### Backend Support By Platform + +| Platform | Default backend | MLX support | +| --- | --- | --- | +| Linux / Docker | `pytorch` | Not supported | +| macOS Intel | `pytorch` | Not supported | +| macOS Apple Silicon (native) | `pytorch` | Auto-installed and supported | +| Docker Desktop on Apple Silicon | `pytorch` | Not supported (still Linux container runtime) | ### Command Options @@ -99,6 +113,48 @@ cli serve --backend pytorch --model jinaai/jina-reranker-v2-base-multilingual cli serve --backend mlx --model jinaai/jina-reranker-v3-mlx ``` +If you explicitly select `--backend mlx` on Linux or non-Apple hardware, startup fails fast with an actionable error telling you to switch back to `pytorch`. + +### Backend Selection Via Environment + +```bash +# Default for Docker/Linux +export RERANKER_BACKEND_TYPE=pytorch + +# Native macOS Apple Silicon (MLX auto-installed) +export RERANKER_BACKEND_TYPE=mlx +``` + +Command-line flags still override the defaults: + +```bash +cli serve --backend pytorch +cli serve --backend mlx +``` + +### Docker + +Build and run the container with the default PyTorch backend: + +```bash +docker build -t local-reranker . +docker run --rm -p 8010:8010 \ + -e RERANKER_BACKEND_TYPE=pytorch \ + local-reranker +``` + +Minimal `docker compose` example: + +```yaml +services: + local-reranker: + build: . + ports: + - "8010:8010" + environment: + RERANKER_BACKEND_TYPE: pytorch +``` + **Development Mode:** ```bash @@ -211,8 +267,8 @@ uv run ruff check && uv run mypy src/ # Ensure you're on Apple Silicon uname -m # Should show arm64 -# Install MLX dependencies -uv add mlx mlx-lm safetensors +# Reinstall project dependencies (MLX auto-installs on native macOS arm64) +uv pip install -e ".[dev]" ``` **Model download fails:** @@ -271,8 +327,8 @@ top -o mem | grep python The application uses pydantic-settings for configuration management. You can set the following environment variables to override defaults: ```bash -# Force MLX backend -export RERANKER_RERANKER_TYPE=mlx +# Force backend selection +export RERANKER_BACKEND_TYPE=mlx # Custom model name export RERANKER_MODEL_NAME=custom-mlx-model @@ -290,6 +346,9 @@ export RERANKER_RELOAD=true **Note**: Using the CLI command line options is recommended over environment variables for clarity. +On Linux containers, keep `RERANKER_BACKEND_TYPE=pytorch`. Selecting `mlx` there will fail because Docker/Linux cannot provide the Apple MLX shared libraries required by `mlx.core`. +On Docker Desktop running on Apple Silicon, containers are still Linux runtime environments, so MLX is still unsupported there. + ## Project Structure ``` diff --git a/pyproject.toml b/pyproject.toml index 93836fb..976643d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,12 +29,12 @@ dependencies = [ "pydantic-settings>=2.6.0", "sentence-transformers>=5.0.0", "torch>=2.5.0", - "transformers>=4.57.0", + "transformers>=4.57.0,<5", "tokenizers>=0.21.0", "accelerate>=1.2.0", "einops>=0.8.0", - "mlx>=0.30.0", - "mlx-lm>=0.28.0", + "mlx>=0.30.0; sys_platform == 'darwin' and platform_machine == 'arm64'", + "mlx-lm>=0.28.0; sys_platform == 'darwin' and platform_machine == 'arm64'", "safetensors>=0.5.0", "psutil>=7.0.0", ] diff --git a/src/local_reranker/__init__.py b/src/local_reranker/__init__.py index b0f7089..4df8b2b 100644 --- a/src/local_reranker/__init__.py +++ b/src/local_reranker/__init__.py @@ -6,7 +6,21 @@ __version__ = "0.0.1" # Placeholder version -from .cli import main -from .jina_mlx_reranker import JinaMLXReranker - __all__ = ["main", "JinaMLXReranker"] + + +def __getattr__(name: str): + """Lazily expose package entrypoints and optional MLX symbols.""" + if name == "main": + from .cli import main + + return main + + if name == "JinaMLXReranker": + # Keep MLX optional so importing the package on Linux does not try to + # load Apple-only shared libraries during process startup. + from .jina_mlx_reranker import JinaMLXReranker + + return JinaMLXReranker + + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/src/local_reranker/api.py b/src/local_reranker/api.py index 452e61f..8f2f441 100644 --- a/src/local_reranker/api.py +++ b/src/local_reranker/api.py @@ -11,8 +11,7 @@ from .models import RerankRequest, RerankResponse from .reranker import Reranker as RerankerProtocol from .reranker_pytorch import Reranker as PyTorchReranker -from .reranker_mlx import Reranker as MLXReranker -from .config import Settings, get_effective_model_name +from .config import Settings, get_effective_model_name, validate_backend_type # --- Logging Setup --- logger = logging.getLogger(__name__) @@ -34,12 +33,16 @@ async def lifespan(app: FastAPI): logger.info(f"Loading reranker type: {settings.backend_type}") logger.info(f"Loading model: {model_name}") + validate_backend_type(settings.backend_type) + # Initialize reranker based on type if settings.backend_type == "pytorch": reranker_instance = PyTorchReranker( model_name=model_name, disable_batching=settings.disable_batching ) elif settings.backend_type == "mlx": + from .reranker_mlx import Reranker as MLXReranker + reranker_instance = MLXReranker( model_name=model_name, disable_batching=settings.disable_batching ) diff --git a/src/local_reranker/cli.py b/src/local_reranker/cli.py index bfd9fc0..2845688 100644 --- a/src/local_reranker/cli.py +++ b/src/local_reranker/cli.py @@ -7,7 +7,12 @@ import sys import uvicorn -from .config import Settings, get_available_backends, get_effective_model_name +from .config import ( + Settings, + get_available_backends, + get_effective_model_name, + validate_backend_type, +) logger = logging.getLogger(__name__) @@ -188,6 +193,11 @@ def main() -> None: disable_batching=args.disable_batching, ) + try: + validate_backend_type(settings.backend_type) + except ValueError as exc: + parser.error(str(exc)) + logger.info( f"Starting Local Reranker API server on {settings.host}:{settings.port}" ) diff --git a/src/local_reranker/config.py b/src/local_reranker/config.py index 66c6a54..1cbbc99 100644 --- a/src/local_reranker/config.py +++ b/src/local_reranker/config.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- """Configuration management for the local reranker service.""" +import platform from typing import Dict, Optional + from pydantic_settings import BaseSettings @@ -56,3 +58,40 @@ def get_available_backends() -> Dict[str, str]: # "onnx": "ONNX-based reranker for CPU optimization", # "tensorflow": "TensorFlow-based reranker", } + + +def get_mlx_platform_error() -> Optional[str]: + """Return an actionable MLX platform error, if any.""" + system = platform.system() + machine = platform.machine().lower() + + if system != "Darwin": + return ( + "MLX is only supported on macOS Apple Silicon. " + f"Detected {system} on {machine or 'unknown'}, and Linux containers " + "cannot load the Apple MLX runtime libraries." + ) + + if machine not in {"arm64", "aarch64"}: + return ( + "MLX requires Apple Silicon arm64 hardware. " + f"Detected macOS on {machine or 'unknown'}." + ) + + return None + + +def validate_backend_type(backend_type: str) -> None: + """Validate that the requested backend can run on this platform.""" + if backend_type != "mlx": + return + + platform_error = get_mlx_platform_error() + if platform_error is None: + return + + raise ValueError( + f"{platform_error} Use '--backend pytorch' or set " + "RERANKER_BACKEND_TYPE=pytorch. Install the optional MLX extras only " + "on macOS Apple Silicon with `pip install 'local-reranker[mlx]'`." + ) diff --git a/src/local_reranker/reranker_mlx.py b/src/local_reranker/reranker_mlx.py index 58d9963..169c923 100644 --- a/src/local_reranker/reranker_mlx.py +++ b/src/local_reranker/reranker_mlx.py @@ -8,7 +8,7 @@ from .models import RerankRequest, RerankResult, RerankDocument from .batch_manager import BatchManager from .batch_processor import BatchProcessor, DocumentTextExtractor -from .jina_mlx_reranker import JinaMLXReranker +from .config import get_mlx_platform_error logger = logging.getLogger(__name__) @@ -36,6 +36,14 @@ def __init__( ImportError: If MLX dependencies are not installed. RuntimeError: If model loading fails. """ + platform_error = get_mlx_platform_error() + if platform_error is not None: + raise RuntimeError( + f"{platform_error} Use '--backend pytorch' or set " + "RERANKER_BACKEND_TYPE=pytorch. Install `local-reranker[mlx]` " + "only on macOS Apple Silicon." + ) + self.model_name = model_name self.device = device @@ -48,9 +56,12 @@ def __init__( self.model = self._load_mlx_reranker(model_path) logger.info(f"Successfully loaded MLX reranker with batching: {model_name}") - except ImportError as e: + except (ImportError, OSError) as e: raise ImportError( - "MLX dependencies not found. Install with: pip install mlx mlx-lm safetensors" + "MLX backend requested, but the optional MLX dependencies could " + "not be imported. This is expected on Linux containers because " + "Apple's MLX runtime is unavailable there. Use '--backend pytorch' " + "or install `local-reranker[mlx]` on macOS Apple Silicon." ) from e except Exception as e: raise RuntimeError(f"Failed to load MLX model '{model_name}': {e}") from e @@ -85,6 +96,16 @@ def _load_mlx_reranker(self, model_path: str): Raises: RuntimeError: If model or projector loading fails. """ + try: + from .jina_mlx_reranker import JinaMLXReranker + except (ImportError, OSError) as exc: + raise ImportError( + "MLX backend requested, but the MLX runtime could not be loaded. " + "On Docker/Linux this usually means the platform does not support " + "MLX shared libraries such as libmlx.so. Use '--backend pytorch' " + "or install `local-reranker[mlx]` on macOS Apple Silicon." + ) from exc + projector_path = f"{model_path}/projector.safetensors" return JinaMLXReranker( diff --git a/src/local_reranker/reranker_pytorch.py b/src/local_reranker/reranker_pytorch.py index 18fd708..764a681 100644 --- a/src/local_reranker/reranker_pytorch.py +++ b/src/local_reranker/reranker_pytorch.py @@ -2,6 +2,7 @@ """PyTorch implementation of the reranker protocol.""" import logging +import warnings from typing import List, Optional, Tuple, Union import torch @@ -12,6 +13,9 @@ from .batch_manager import BatchManager from .batch_processor import BatchProcessor, DocumentTextExtractor +# Suppress torch_dtype deprecation warning from Jina model files +warnings.filterwarnings("ignore", message=".*torch_dtype.*is deprecated.*Use `dtype` instead") + logger = logging.getLogger(__name__) DEFAULT_MODEL_NAME = "jinaai/jina-reranker-v2-base-multilingual" diff --git a/tests/test_cli.py b/tests/test_cli.py index 1bbea49..3de4920 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -213,6 +213,26 @@ def test_main_invalid_port(self, mock_run_server): assert exc_info.value.code == 2 # argparse error code mock_run_server.assert_not_called() + @patch("local_reranker.cli.validate_backend_type") + @patch("local_reranker.cli.run_server") + @patch("sys.argv", ["local-reranker", "--backend", "mlx"]) + def test_main_unsupported_mlx_backend( + self, mock_run_server, mock_validate_backend_type + ): + """Test main function with unsupported MLX backend.""" + mock_validate_backend_type.side_effect = ValueError("MLX unavailable") + + with pytest.raises(SystemExit) as exc_info: + main() + + assert exc_info.value.code == 2 + mock_run_server.assert_not_called() + + +def test_package_import_keeps_mlx_lazy(): + """Importing the package should not eagerly import MLX modules.""" + assert "local_reranker.jina_mlx_reranker" not in sys.modules + class TestArgumentParser: """Test cases for argument parsing logic.""" diff --git a/uv.lock b/uv.lock index b79c9d6..4e37057 100644 --- a/uv.lock +++ b/uv.lock @@ -336,8 +336,6 @@ dependencies = [ { name = "accelerate" }, { name = "einops" }, { name = "fastapi" }, - { name = "mlx" }, - { name = "mlx-lm" }, { name = "psutil" }, { name = "pydantic" }, { name = "pydantic-settings" }, @@ -349,6 +347,12 @@ dependencies = [ { name = "uvicorn", extra = ["standard"] }, ] +[package.optional-dependencies] +mlx = [ + { name = "mlx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, + { name = "mlx-lm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin'" }, +] + [package.dev-dependencies] dev = [ { name = "httpx" }, @@ -363,8 +367,8 @@ requires-dist = [ { name = "accelerate", specifier = ">=1.2.0" }, { name = "einops", specifier = ">=0.8.0" }, { name = "fastapi", specifier = ">=0.115.0" }, - { name = "mlx", specifier = ">=0.30.0" }, - { name = "mlx-lm", specifier = ">=0.28.0" }, + { name = "mlx", marker = "platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'mlx'", specifier = ">=0.30.0" }, + { name = "mlx-lm", marker = "platform_machine == 'arm64' and sys_platform == 'darwin' and extra == 'mlx'", specifier = ">=0.28.0" }, { name = "psutil", specifier = ">=7.0.0" }, { name = "pydantic", specifier = ">=2.10.0" }, { name = "pydantic-settings", specifier = ">=2.6.0" }, @@ -372,9 +376,10 @@ requires-dist = [ { name = "sentence-transformers", specifier = ">=5.0.0" }, { name = "tokenizers", specifier = ">=0.21.0" }, { name = "torch", specifier = ">=2.5.0" }, - { name = "transformers", specifier = ">=4.57.0" }, + { name = "transformers", specifier = ">=4.57.0,<5" }, { name = "uvicorn", extras = ["standard"], specifier = ">=0.38.0" }, ] +provides-extras = ["mlx"] [package.metadata.requires-dev] dev = [ @@ -434,13 +439,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/a3/32c4c05d8967591e2a1a1e7e3fc9cece8821f5aea8ac8f3bcfdb203f4722/mlx-0.30.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:11fbae58b1e992afdec6709d5e281932871a0138582a794cdcc82ff895a28670", size = 554567, upload-time = "2025-11-20T16:45:12.73Z" }, { url = "https://files.pythonhosted.org/packages/aa/b3/b6143f1c078fbc873e40e624dc428a3ada240721001414955f584afa866d/mlx-0.30.0-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:fc1994a4c9e60ccfe2ab9211c846be2ef7abc6fc3e53e1addb2cdf7468f61e7b", size = 554566, upload-time = "2025-11-20T01:16:41.877Z" }, { url = "https://files.pythonhosted.org/packages/96/cb/aec36297ef76b4b190cc6d4cea1ed995b458bbc21cb91c58b0862f8cae7d/mlx-0.30.0-cp312-cp312-macosx_26_0_arm64.whl", hash = "sha256:a9f30c5c94b30b65f026162e91256473187e1de57c13dcc2aedb2fc33c07f2c1", size = 554593, upload-time = "2025-11-20T05:33:25.312Z" }, - { url = "https://files.pythonhosted.org/packages/40/d3/575eee4ef4b5f3dad9076a78f287affe046fd32b3bdba7a2e0af31f0d9d3/mlx-0.30.0-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:f88d11e2719fdab08dbe68c9666003c8644ccd01dff973bc86e949cc16195eac", size = 612086, upload-time = "2025-12-01T15:23:42.317Z" }, - { url = "https://files.pythonhosted.org/packages/23/37/b5dd68da0e79e258f5d6a0e9f5fd4f9e5452e92c20b13b64948a965ea429/mlx-0.30.0-cp312-cp312-manylinux_2_35_x86_64.whl", hash = "sha256:1c4a2f5285bdd585aa6485a4fb5759ebc2721ba9381404ff867c136e84764e9b", size = 653994, upload-time = "2025-11-20T01:16:43.492Z" }, { url = "https://files.pythonhosted.org/packages/33/a5/e171b2caa69b346bc1abc1bfd0b139f631f68a0ff602862dd255e7dd95ec/mlx-0.30.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f46aaa6c562ba183e2a64a0e6ba15ed54f9027d9b7b1822e9eec7f59b13d610c", size = 554595, upload-time = "2025-11-20T16:45:13.919Z" }, { url = "https://files.pythonhosted.org/packages/3e/f4/aeb8980bbef08fc031ab1a2d043a1d76e60d49bf46728bef66cf25b26dfa/mlx-0.30.0-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:5edee9f1452703c0804e067f212c61d488b3ad7419b9fcacf337ffd35842f576", size = 554593, upload-time = "2025-11-20T01:16:45.033Z" }, { url = "https://files.pythonhosted.org/packages/0b/b3/904235e11610c7cf2ba39eb39f32587d34048f7293d386f19d42d1e3dabc/mlx-0.30.0-cp313-cp313-macosx_26_0_arm64.whl", hash = "sha256:b8ae92b61353d756bbd0668b065a4ee5ee35867b03d63c0c61134d656ed236fe", size = 554330, upload-time = "2025-11-20T05:33:26.653Z" }, - { url = "https://files.pythonhosted.org/packages/de/0f/bb956ec9926596fe771ec67677c049e358c43f0506699b59dbed7ba9fedc/mlx-0.30.0-cp313-cp313-manylinux_2_35_aarch64.whl", hash = "sha256:f3ae4c99308ff4c006c3062ff73108b6f39414bcd90416930b917160070f759b", size = 612083, upload-time = "2025-12-01T15:23:43.369Z" }, - { url = "https://files.pythonhosted.org/packages/5e/87/3f3505d3fbf0f977b2930b3596f590f0079c3a2a253d01349f936c40985a/mlx-0.30.0-cp313-cp313-manylinux_2_35_x86_64.whl", hash = "sha256:431009c531f8bdbf56f46b85d458d5526b40ea43178dbc85f37ed55e03e716be", size = 653971, upload-time = "2025-11-20T01:16:46.292Z" }, ] [[package]] @@ -747,12 +748,7 @@ version = "6.33.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0a/03/a1440979a3f74f16cab3b75b0da1a1a7f922d56a8ddea96092391998edc0/protobuf-6.33.1.tar.gz", hash = "sha256:97f65757e8d09870de6fd973aeddb92f85435607235d20b2dfed93405d00c85b", size = 443432, upload-time = "2025-11-13T16:44:18.895Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/f1/446a9bbd2c60772ca36556bac8bfde40eceb28d9cc7838755bc41e001d8f/protobuf-6.33.1-cp310-abi3-win32.whl", hash = "sha256:f8d3fdbc966aaab1d05046d0240dd94d40f2a8c62856d41eaa141ff64a79de6b", size = 425593, upload-time = "2025-11-13T16:44:06.275Z" }, - { url = "https://files.pythonhosted.org/packages/a6/79/8780a378c650e3df849b73de8b13cf5412f521ca2ff9b78a45c247029440/protobuf-6.33.1-cp310-abi3-win_amd64.whl", hash = "sha256:923aa6d27a92bf44394f6abf7ea0500f38769d4b07f4be41cb52bd8b1123b9ed", size = 436883, upload-time = "2025-11-13T16:44:09.222Z" }, { url = "https://files.pythonhosted.org/packages/cd/93/26213ff72b103ae55bb0d73e7fb91ea570ef407c3ab4fd2f1f27cac16044/protobuf-6.33.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:fe34575f2bdde76ac429ec7b570235bf0c788883e70aee90068e9981806f2490", size = 427522, upload-time = "2025-11-13T16:44:10.475Z" }, - { url = "https://files.pythonhosted.org/packages/c2/32/df4a35247923393aa6b887c3b3244a8c941c32a25681775f96e2b418f90e/protobuf-6.33.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:f8adba2e44cde2d7618996b3fc02341f03f5bc3f2748be72dc7b063319276178", size = 324445, upload-time = "2025-11-13T16:44:11.869Z" }, - { url = "https://files.pythonhosted.org/packages/8e/d0/d796e419e2ec93d2f3fa44888861c3f88f722cde02b7c3488fcc6a166820/protobuf-6.33.1-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:0f4cf01222c0d959c2b399142deb526de420be8236f22c71356e2a544e153c53", size = 339161, upload-time = "2025-11-13T16:44:12.778Z" }, - { url = "https://files.pythonhosted.org/packages/1d/2a/3c5f05a4af06649547027d288747f68525755de692a26a7720dced3652c0/protobuf-6.33.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:8fd7d5e0eb08cd5b87fd3df49bc193f5cfd778701f47e11d127d0afc6c39f1d1", size = 323171, upload-time = "2025-11-13T16:44:14.035Z" }, { url = "https://files.pythonhosted.org/packages/08/b4/46310463b4f6ceef310f8348786f3cff181cea671578e3d9743ba61a459e/protobuf-6.33.1-py3-none-any.whl", hash = "sha256:d595a9fd694fdeb061a62fbe10eb039cc1e444df81ec9bb70c7fc59ebcb1eafa", size = 170477, upload-time = "2025-11-13T16:44:17.633Z" }, ] @@ -1093,29 +1089,11 @@ source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/15/15/2e7a025fc62d764b151ae6d0f2a92f8081755ebe8d4a64099accc6f77ba6/sentencepiece-0.2.1.tar.gz", hash = "sha256:8138cec27c2f2282f4a34d9a016e3374cd40e5c6e9cb335063db66a0a3b71fad", size = 3228515, upload-time = "2025-08-12T07:00:51.718Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/4a/be/32ce495aa1d0e0c323dcb1ba87096037358edee539cac5baf8755a6bd396/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:57cae326c8727de58c85977b175af132a7138d84c764635d7e71bbee7e774133", size = 1943152, upload-time = "2025-08-12T06:59:40.048Z" }, - { url = "https://files.pythonhosted.org/packages/88/7e/ff23008899a58678e98c6ff592bf4d368eee5a71af96d0df6b38a039dd4f/sentencepiece-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:56dd39a3c4d6493db3cdca7e8cc68c6b633f0d4195495cbadfcf5af8a22d05a6", size = 1325651, upload-time = "2025-08-12T06:59:41.536Z" }, { url = "https://files.pythonhosted.org/packages/19/84/42eb3ce4796777a1b5d3699dfd4dca85113e68b637f194a6c8d786f16a04/sentencepiece-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9381351182ff9888cc80e41c632e7e274b106f450de33d67a9e8f6043da6f76", size = 1253645, upload-time = "2025-08-12T06:59:42.903Z" }, - { url = "https://files.pythonhosted.org/packages/89/fa/d3d5ebcba3cb9e6d3775a096251860c41a6bc53a1b9461151df83fe93255/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99f955df238021bf11f0fc37cdb54fd5e5b5f7fd30ecc3d93fb48b6815437167", size = 1316273, upload-time = "2025-08-12T06:59:44.476Z" }, - { url = "https://files.pythonhosted.org/packages/04/88/14f2f4a2b922d8b39be45bf63d79e6cd3a9b2f248b2fcb98a69b12af12f5/sentencepiece-0.2.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0cdfecef430d985f1c2bcbfff3defd1d95dae876fbd0173376012d2d7d24044b", size = 1387881, upload-time = "2025-08-12T06:59:46.09Z" }, - { url = "https://files.pythonhosted.org/packages/fd/b8/903e5ccb77b4ef140605d5d71b4f9e0ad95d456d6184688073ed11712809/sentencepiece-0.2.1-cp312-cp312-win32.whl", hash = "sha256:a483fd29a34c3e34c39ac5556b0a90942bec253d260235729e50976f5dba1068", size = 999540, upload-time = "2025-08-12T06:59:48.023Z" }, - { url = "https://files.pythonhosted.org/packages/2d/81/92df5673c067148c2545b1bfe49adfd775bcc3a169a047f5a0e6575ddaca/sentencepiece-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4cdc7c36234fda305e85c32949c5211faaf8dd886096c7cea289ddc12a2d02de", size = 1054671, upload-time = "2025-08-12T06:59:49.895Z" }, - { url = "https://files.pythonhosted.org/packages/fe/02/c5e3bc518655d714622bec87d83db9cdba1cd0619a4a04e2109751c4f47f/sentencepiece-0.2.1-cp312-cp312-win_arm64.whl", hash = "sha256:daeb5e9e9fcad012324807856113708614d534f596d5008638eb9b40112cd9e4", size = 1033923, upload-time = "2025-08-12T06:59:51.952Z" }, { url = "https://files.pythonhosted.org/packages/ba/4a/85fbe1706d4d04a7e826b53f327c4b80f849cf1c7b7c5e31a20a97d8f28b/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dcd8161eee7b41aae57ded06272905dbd680a0a04b91edd0f64790c796b2f706", size = 1943150, upload-time = "2025-08-12T06:59:53.588Z" }, - { url = "https://files.pythonhosted.org/packages/c2/83/4cfb393e287509fc2155480b9d184706ef8d9fa8cbf5505d02a5792bf220/sentencepiece-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c6c8f42949f419ff8c7e9960dbadcfbc982d7b5efc2f6748210d3dd53a7de062", size = 1325651, upload-time = "2025-08-12T06:59:55.073Z" }, { url = "https://files.pythonhosted.org/packages/8d/de/5a007fb53b1ab0aafc69d11a5a3dd72a289d5a3e78dcf2c3a3d9b14ffe93/sentencepiece-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:097f3394e99456e9e4efba1737c3749d7e23563dd1588ce71a3d007f25475fff", size = 1253641, upload-time = "2025-08-12T06:59:56.562Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d2/f552be5928105588f4f4d66ee37dd4c61460d8097e62d0e2e0eec41bc61d/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7b670879c370d350557edabadbad1f6561a9e6968126e6debca4029e5547820", size = 1316271, upload-time = "2025-08-12T06:59:58.109Z" }, - { url = "https://files.pythonhosted.org/packages/96/df/0cfe748ace5485be740fed9476dee7877f109da32ed0d280312c94ec259f/sentencepiece-0.2.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c7f0fd2f2693309e6628aeeb2e2faf6edd221134dfccac3308ca0de01f8dab47", size = 1387882, upload-time = "2025-08-12T07:00:00.701Z" }, - { url = "https://files.pythonhosted.org/packages/ac/dd/f7774d42a881ced8e1739f393ab1e82ece39fc9abd4779e28050c2e975b5/sentencepiece-0.2.1-cp313-cp313-win32.whl", hash = "sha256:92b3816aa2339355fda2c8c4e021a5de92180b00aaccaf5e2808972e77a4b22f", size = 999541, upload-time = "2025-08-12T07:00:02.709Z" }, - { url = "https://files.pythonhosted.org/packages/dd/e9/932b9eae6fd7019548321eee1ab8d5e3b3d1294df9d9a0c9ac517c7b636d/sentencepiece-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:10ed3dab2044c47f7a2e7b4969b0c430420cdd45735d78c8f853191fa0e3148b", size = 1054669, upload-time = "2025-08-12T07:00:04.915Z" }, - { url = "https://files.pythonhosted.org/packages/c9/3a/76488a00ea7d6931689cda28726a1447d66bf1a4837943489314593d5596/sentencepiece-0.2.1-cp313-cp313-win_arm64.whl", hash = "sha256:ac650534e2251083c5f75dde4ff28896ce7c8904133dc8fef42780f4d5588fcd", size = 1033922, upload-time = "2025-08-12T07:00:06.496Z" }, { url = "https://files.pythonhosted.org/packages/4a/b6/08fe2ce819e02ccb0296f4843e3f195764ce9829cbda61b7513f29b95718/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:8dd4b477a7b069648d19363aad0cab9bad2f4e83b2d179be668efa672500dc94", size = 1946052, upload-time = "2025-08-12T07:00:08.136Z" }, - { url = "https://files.pythonhosted.org/packages/ab/d9/1ea0e740591ff4c6fc2b6eb1d7510d02f3fb885093f19b2f3abd1363b402/sentencepiece-0.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0c0f672da370cc490e4c59d89e12289778310a0e71d176c541e4834759e1ae07", size = 1327408, upload-time = "2025-08-12T07:00:09.572Z" }, { url = "https://files.pythonhosted.org/packages/99/7e/1fb26e8a21613f6200e1ab88824d5d203714162cf2883248b517deb500b7/sentencepiece-0.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad8493bea8432dae8d6830365352350f3b4144415a1d09c4c8cb8d30cf3b6c3c", size = 1254857, upload-time = "2025-08-12T07:00:11.021Z" }, - { url = "https://files.pythonhosted.org/packages/bc/85/c72fd1f3c7a6010544d6ae07f8ddb38b5e2a7e33bd4318f87266c0bbafbf/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b81a24733726e3678d2db63619acc5a8dccd074f7aa7a54ecd5ca33ca6d2d596", size = 1315722, upload-time = "2025-08-12T07:00:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/4a/e8/661e5bd82a8aa641fd6c1020bd0e890ef73230a2b7215ddf9c8cd8e941c2/sentencepiece-0.2.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0a81799d0a68d618e89063fb423c3001a034c893069135ffe51fee439ae474d6", size = 1387452, upload-time = "2025-08-12T07:00:15.088Z" }, - { url = "https://files.pythonhosted.org/packages/99/5e/ae66c361023a470afcbc1fbb8da722c72ea678a2fcd9a18f1a12598c7501/sentencepiece-0.2.1-cp313-cp313t-win32.whl", hash = "sha256:89a3ea015517c42c0341d0d962f3e6aaf2cf10d71b1932d475c44ba48d00aa2b", size = 1002501, upload-time = "2025-08-12T07:00:16.966Z" }, - { url = "https://files.pythonhosted.org/packages/c1/03/d332828c4ff764e16c1b56c2c8f9a33488bbe796b53fb6b9c4205ddbf167/sentencepiece-0.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:33f068c9382dc2e7c228eedfd8163b52baa86bb92f50d0488bf2b7da7032e484", size = 1057555, upload-time = "2025-08-12T07:00:18.573Z" }, - { url = "https://files.pythonhosted.org/packages/88/14/5aee0bf0864df9bd82bd59e7711362908e4935e3f9cdc1f57246b5d5c9b9/sentencepiece-0.2.1-cp313-cp313t-win_arm64.whl", hash = "sha256:b3616ad246f360e52c85781e47682d31abfb6554c779e42b65333d4b5f44ecc0", size = 1036042, upload-time = "2025-08-12T07:00:20.209Z" }, ] [[package]]