From fea1c0fc29e691dbcd6c6aabd6f0da73c0ed01fe Mon Sep 17 00:00:00 2001 From: josh Date: Mon, 18 Nov 2024 08:47:17 -0800 Subject: [PATCH] clean branch --- .gitignore | 2 +- docs/exo-rounded.png | Bin 0 -> 28651 bytes exo/__init__.py | 2 +- exo/api/chatgpt_api.py | 27 +++++++++++--- exo/download/hf/hf_helpers.py | 10 +++++- exo/inference/mlx/sharded_utils.py | 3 +- exo/main.py | 9 +++-- scripts/build_exo.py | 56 +++++++++++++++++++++++++++++ setup.py | 5 +-- 9 files changed, 102 insertions(+), 12 deletions(-) create mode 100644 docs/exo-rounded.png create mode 100644 scripts/build_exo.py diff --git a/.gitignore b/.gitignore index 814af48c..93227e3c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ test_weights.npz .exo_used_ports .exo_node_id .idea +.DS_Store # Byte-compiled / optimized / DLL files __pycache__/ @@ -15,7 +16,6 @@ __pycache__/ # Distribution / packaging /.Python -/build/ /develop-eggs/ /dist/ /downloads/ diff --git a/docs/exo-rounded.png b/docs/exo-rounded.png new file mode 100644 index 0000000000000000000000000000000000000000..f64b45ae1e02be7fac8901a5f4faa8516c48b8d6 GIT binary patch literal 28651 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 KKQ5C;1pFUW<`dHZ literal 0 HcmV?d00001 diff --git a/exo/__init__.py b/exo/__init__.py index e802d331..596559f3 100644 --- a/exo/__init__.py +++ b/exo/__init__.py @@ -1 +1 @@ -from exo.helpers import DEBUG as DEBUG, DEBUG_DISCOVERY as DEBUG_DISCOVERY, VERSION as VERSION +from exo.helpers import DEBUG as DEBUG, DEBUG_DISCOVERY as DEBUG_DISCOVERY, VERSION as VERSION \ No newline at end of file diff --git a/exo/api/chatgpt_api.py b/exo/api/chatgpt_api.py index 7c22a38c..fa6e6cee 100644 --- a/exo/api/chatgpt_api.py +++ b/exo/api/chatgpt_api.py @@ -8,6 +8,9 @@ from typing import List, Literal, Union, Dict from aiohttp import web import aiohttp_cors import traceback +import os +import signal +import sys from exo import DEBUG, VERSION from exo.download.download_progress import RepoProgressEvent from exo.helpers import PrefixDict @@ -18,7 +21,6 @@ from exo.orchestration import Node from exo.models import build_base_shard, model_cards, get_repo, pretty_name from typing import Callable - class Message: def __init__(self, role: str, content: Union[str, List[Dict[str, Union[str, Dict[str, str]]]]]): self.role = role @@ -28,6 +30,7 @@ class Message: return {"role": self.role, "content": self.content} + class ChatCompletionRequest: def __init__(self, model: str, messages: List[Message], temperature: float): self.model = model @@ -145,6 +148,10 @@ class PromptSession: self.timestamp = timestamp self.prompt = prompt +def is_frozen(): + return getattr(sys, 'frozen', False) or os.path.basename(sys.executable) == "exo" \ + or ('Contents/MacOS' in str(os.path.dirname(sys.executable))) \ + or '__nuitka__' in globals() or getattr(sys, '__compiled__', False) class ChatGPTAPI: def __init__(self, node: Node, inference_engine_classname: str, response_timeout: int = 90, on_chat_completion_request: Callable[[str, ChatCompletionRequest, str], None] = None): @@ -174,13 +181,25 @@ class ChatGPTAPI: cors.add(self.app.router.add_get("/v1/download/progress", self.handle_get_download_progress), {"*": cors_options}) cors.add(self.app.router.add_get("/modelpool", self.handle_model_support), {"*": cors_options}) cors.add(self.app.router.add_get("/healthcheck", self.handle_healthcheck), {"*": cors_options}) + cors.add(self.app.router.add_post("/quit", self.handle_quit), {"*": cors_options}) - self.static_dir = Path(__file__).parent.parent/"tinychat" - self.app.router.add_get("/", self.handle_root) - self.app.router.add_static("/", self.static_dir, name="static") + if "__compiled__" not in globals(): + self.static_dir = Path(__file__).parent.parent/"tinychat" + self.app.router.add_get("/", self.handle_root) + self.app.router.add_static("/", self.static_dir, name="static") self.app.middlewares.append(self.timeout_middleware) self.app.middlewares.append(self.log_request) + + async def handle_quit(self, request): + print("Received quit signal") + from exo.main import shutdown + response = web.json_response({"detail": "Quit signal received"}, status=200) + await response.prepare(request) + await response.write_eof() + + await shutdown(signal.SIGINT, asyncio.get_event_loop()) + return response async def timeout_middleware(self, app, handler): async def middleware(request): diff --git a/exo/download/hf/hf_helpers.py b/exo/download/hf/hf_helpers.py index e0087da0..a1af9789 100644 --- a/exo/download/hf/hf_helpers.py +++ b/exo/download/hf/hf_helpers.py @@ -2,6 +2,7 @@ import asyncio import aiohttp import json import os +import sys from urllib.parse import urljoin from typing import Callable, Optional, Coroutine, Any, Dict, List, Union, Literal from datetime import datetime, timedelta @@ -17,6 +18,10 @@ from aiofiles import os as aios T = TypeVar("T") +def is_frozen(): + return getattr(sys, 'frozen', False) or os.path.basename(sys.executable) == "exo" \ + or ('Contents/MacOS' in str(os.path.dirname(sys.executable))) \ + or ('__compiled__' in globals()) async def get_local_snapshot_dir(repo_id: str, revision: str = "main") -> Optional[Path]: refs_dir = get_repo_root(repo_id)/"refs" @@ -99,7 +104,10 @@ async def get_auth_headers(): def get_repo_root(repo_id: str) -> Path: """Get the root directory for a given repo ID in the Hugging Face cache.""" - sanitized_repo_id = repo_id.replace("/", "--") + sanitized_repo_id = str(repo_id).replace("/", "--") + if "Qwen2.5-0.5B-Instruct-4bit" in str(repo_id) and is_frozen(): + repo_root = Path(sys.argv[0]).parent/f"models--{sanitized_repo_id}" + return repo_root return get_hf_home()/"hub"/f"models--{sanitized_repo_id}" diff --git a/exo/inference/mlx/sharded_utils.py b/exo/inference/mlx/sharded_utils.py index 1a6343e9..9818bb25 100644 --- a/exo/inference/mlx/sharded_utils.py +++ b/exo/inference/mlx/sharded_utils.py @@ -21,6 +21,7 @@ from transformers import AutoProcessor from mlx_lm.tokenizer_utils import load_tokenizer, TokenizerWrapper from exo import DEBUG +from exo.inference.tokenizers import resolve_tokenizer from ..shard import Shard @@ -183,7 +184,7 @@ async def load_shard( processor.encode = processor.tokenizer.encode return model, processor else: - tokenizer = load_tokenizer(model_path, tokenizer_config) + tokenizer = await resolve_tokenizer(model_path) return model, tokenizer diff --git a/exo/main.py b/exo/main.py index a07eaa14..7bd41221 100644 --- a/exo/main.py +++ b/exo/main.py @@ -3,7 +3,11 @@ import asyncio import signal import json import logging +import platform +import os +import sys import time +import subprocess import traceback import uuid from exo.networking.manual.manual_discovery import ManualDiscovery @@ -210,8 +214,9 @@ async def main(): def handle_exit(): asyncio.ensure_future(shutdown(signal.SIGTERM, loop)) - for s in [signal.SIGINT, signal.SIGTERM]: - loop.add_signal_handler(s, handle_exit) + if platform.system() != "Windows": + for s in [signal.SIGINT, signal.SIGTERM]: + loop.add_signal_handler(s, handle_exit) await node.start(wait_for_peers=args.wait_for_peers) diff --git a/scripts/build_exo.py b/scripts/build_exo.py new file mode 100644 index 00000000..a2513b03 --- /dev/null +++ b/scripts/build_exo.py @@ -0,0 +1,56 @@ +import site +import subprocess +import sys +import os +def run(): + site_packages = site.getsitepackages()[0] + command = [ + f"{sys.executable}", "-m", "nuitka", "exo/main.py", + "--company-name=exolabs", + "--product-name=exo", + "--output-dir=dist", + "--follow-imports", + "--standalone", + "--output-filename=exo", + "--onefile", + "--python-flag=no_site" + ] + + if sys.platform == "darwin": + command.extend([ + "--macos-app-name=exo", + "--macos-app-mode=gui", + "--macos-app-version=0.0.1", + "--include-module=exo.inference.mlx.models.llama", + "--include-module=exo.inference.mlx.models.deepseek_v2", + "--include-module=exo.inference.mlx.models.base", + "--include-module=exo.inference.mlx.models.llava", + "--include-module=exo.inference.mlx.models.qwen2", + "--include-distribution-meta=mlx", + "--include-module=mlx._reprlib_fix", + "--include-module=mlx._os_warning", + f"--include-data-files={site_packages}/mlx/lib/mlx.metallib=mlx/lib/mlx.metallib", + f"--include-data-files={site_packages}/mlx/lib/mlx.metallib=./mlx.metallib", + "--include-distribution-meta=pygments", + "--nofollow-import-to=tinygrad" + ]) + elif sys.platform == "win32": + command.extend([ + "--windows-icon-from-ico=docs/exo-logo-win.ico", + "--file-version=0.0.1", + "--product-version=0.0.1" + ]) + elif sys.platform.startswith("linux"): + command.extend([ + "--include-distribution-metadata=pygments", + "--linux-icon=docs/exo-rounded.png" + ]) + + try: + subprocess.run(command, check=True) + print("Build completed!") + except subprocess.CalledProcessError as e: + print(f"An error occurred: {e}") + +if __name__ == "__main__": + run() \ No newline at end of file diff --git a/setup.py b/setup.py index b84d8f66..da137fde 100644 --- a/setup.py +++ b/setup.py @@ -13,6 +13,7 @@ install_requires = [ "Jinja2==3.1.4", "netifaces==0.11.0", "numpy==2.0.0", + "nuitka==2.4.10", "nvidia-ml-py==12.560.30", "pillow==10.4.0", "prometheus-client==0.20.0", @@ -34,8 +35,8 @@ extras_require = { "yapf==0.40.2", ], "apple_silicon": [ - "mlx==0.20.0", - "mlx-lm==0.19.3", + "mlx==0.18.0", + "mlx-lm==0.18.2", ], }