lemonade-sdk 9.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lemonade/__init__.py +5 -0
- lemonade/api.py +180 -0
- lemonade/cache.py +92 -0
- lemonade/cli.py +173 -0
- lemonade/common/__init__.py +0 -0
- lemonade/common/build.py +176 -0
- lemonade/common/cli_helpers.py +139 -0
- lemonade/common/exceptions.py +98 -0
- lemonade/common/filesystem.py +368 -0
- lemonade/common/inference_engines.py +408 -0
- lemonade/common/network.py +93 -0
- lemonade/common/printing.py +110 -0
- lemonade/common/status.py +471 -0
- lemonade/common/system_info.py +1411 -0
- lemonade/common/test_helpers.py +28 -0
- lemonade/profilers/__init__.py +1 -0
- lemonade/profilers/agt_power.py +437 -0
- lemonade/profilers/hwinfo_power.py +429 -0
- lemonade/profilers/memory_tracker.py +259 -0
- lemonade/profilers/profiler.py +58 -0
- lemonade/sequence.py +363 -0
- lemonade/state.py +159 -0
- lemonade/tools/__init__.py +1 -0
- lemonade/tools/accuracy.py +432 -0
- lemonade/tools/adapter.py +114 -0
- lemonade/tools/bench.py +302 -0
- lemonade/tools/flm/__init__.py +1 -0
- lemonade/tools/flm/utils.py +305 -0
- lemonade/tools/huggingface/bench.py +187 -0
- lemonade/tools/huggingface/load.py +235 -0
- lemonade/tools/huggingface/utils.py +359 -0
- lemonade/tools/humaneval.py +264 -0
- lemonade/tools/llamacpp/bench.py +255 -0
- lemonade/tools/llamacpp/load.py +222 -0
- lemonade/tools/llamacpp/utils.py +1260 -0
- lemonade/tools/management_tools.py +319 -0
- lemonade/tools/mmlu.py +319 -0
- lemonade/tools/oga/__init__.py +0 -0
- lemonade/tools/oga/bench.py +120 -0
- lemonade/tools/oga/load.py +804 -0
- lemonade/tools/oga/migration.py +403 -0
- lemonade/tools/oga/utils.py +462 -0
- lemonade/tools/perplexity.py +147 -0
- lemonade/tools/prompt.py +263 -0
- lemonade/tools/report/__init__.py +0 -0
- lemonade/tools/report/llm_report.py +203 -0
- lemonade/tools/report/table.py +899 -0
- lemonade/tools/server/__init__.py +0 -0
- lemonade/tools/server/flm.py +133 -0
- lemonade/tools/server/llamacpp.py +320 -0
- lemonade/tools/server/serve.py +2123 -0
- lemonade/tools/server/static/favicon.ico +0 -0
- lemonade/tools/server/static/index.html +279 -0
- lemonade/tools/server/static/js/chat.js +1059 -0
- lemonade/tools/server/static/js/model-settings.js +183 -0
- lemonade/tools/server/static/js/models.js +1395 -0
- lemonade/tools/server/static/js/shared.js +556 -0
- lemonade/tools/server/static/logs.html +191 -0
- lemonade/tools/server/static/styles.css +2654 -0
- lemonade/tools/server/static/webapp.html +321 -0
- lemonade/tools/server/tool_calls.py +153 -0
- lemonade/tools/server/tray.py +664 -0
- lemonade/tools/server/utils/macos_tray.py +226 -0
- lemonade/tools/server/utils/port.py +77 -0
- lemonade/tools/server/utils/thread.py +85 -0
- lemonade/tools/server/utils/windows_tray.py +408 -0
- lemonade/tools/server/webapp.py +34 -0
- lemonade/tools/server/wrapped_server.py +559 -0
- lemonade/tools/tool.py +374 -0
- lemonade/version.py +1 -0
- lemonade_install/__init__.py +1 -0
- lemonade_install/install.py +239 -0
- lemonade_sdk-9.1.1.dist-info/METADATA +276 -0
- lemonade_sdk-9.1.1.dist-info/RECORD +84 -0
- lemonade_sdk-9.1.1.dist-info/WHEEL +5 -0
- lemonade_sdk-9.1.1.dist-info/entry_points.txt +5 -0
- lemonade_sdk-9.1.1.dist-info/licenses/LICENSE +201 -0
- lemonade_sdk-9.1.1.dist-info/licenses/NOTICE.md +47 -0
- lemonade_sdk-9.1.1.dist-info/top_level.txt +3 -0
- lemonade_server/cli.py +805 -0
- lemonade_server/model_manager.py +758 -0
- lemonade_server/pydantic_models.py +159 -0
- lemonade_server/server_models.json +643 -0
- lemonade_server/settings.py +39 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import subprocess
|
|
3
|
+
from typing import Callable, Optional
|
|
4
|
+
|
|
5
|
+
# Check if we're on macOS and import accordingly
|
|
6
|
+
if platform.system() == "Darwin":
|
|
7
|
+
try:
|
|
8
|
+
import rumps
|
|
9
|
+
|
|
10
|
+
RUMPS_AVAILABLE = True
|
|
11
|
+
except ImportError:
|
|
12
|
+
RUMPS_AVAILABLE = False
|
|
13
|
+
print("Warning: rumps not available. Install with: pip install rumps")
|
|
14
|
+
else:
|
|
15
|
+
RUMPS_AVAILABLE = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class MenuItem:
|
|
19
|
+
"""
|
|
20
|
+
Cross-platform menu item representation.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
text: str,
|
|
26
|
+
callback: Optional[Callable] = None,
|
|
27
|
+
enabled: bool = True,
|
|
28
|
+
submenu=None,
|
|
29
|
+
checked: bool = False,
|
|
30
|
+
):
|
|
31
|
+
self.text = text
|
|
32
|
+
self.callback = callback
|
|
33
|
+
self.enabled = enabled
|
|
34
|
+
self.submenu = submenu
|
|
35
|
+
self.checked = checked
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Menu:
|
|
39
|
+
"""
|
|
40
|
+
Cross-platform menu representation.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
SEPARATOR = "SEPARATOR"
|
|
44
|
+
|
|
45
|
+
def __init__(self, *items):
|
|
46
|
+
self.items = list(items)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class MacOSSystemTray:
|
|
50
|
+
"""
|
|
51
|
+
macOS-specific system tray implementation using rumps.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def __init__(self, app_name: str, icon_path: str):
|
|
55
|
+
self._check_rumps_availability()
|
|
56
|
+
|
|
57
|
+
self.app_name = app_name
|
|
58
|
+
self.icon_path = icon_path
|
|
59
|
+
self.app = None
|
|
60
|
+
self.menu_callbacks = {}
|
|
61
|
+
self._menu_update_timer = None
|
|
62
|
+
|
|
63
|
+
def _check_rumps_availability(self):
|
|
64
|
+
"""Check if rumps is available and raise error if not."""
|
|
65
|
+
if not RUMPS_AVAILABLE:
|
|
66
|
+
raise ImportError("rumps library is required for macOS tray support")
|
|
67
|
+
|
|
68
|
+
def create_menu(self):
|
|
69
|
+
"""
|
|
70
|
+
Create the context menu based on current state. Override in subclass.
|
|
71
|
+
"""
|
|
72
|
+
return Menu(MenuItem("Exit", self.exit_app))
|
|
73
|
+
|
|
74
|
+
def build_rumps_menu(self, menu_items):
|
|
75
|
+
"""
|
|
76
|
+
Convert our menu structure to rumps menu items.
|
|
77
|
+
"""
|
|
78
|
+
rumps_items = []
|
|
79
|
+
|
|
80
|
+
for item in menu_items:
|
|
81
|
+
if item == Menu.SEPARATOR:
|
|
82
|
+
rumps_items.append(rumps.separator)
|
|
83
|
+
elif isinstance(item, MenuItem):
|
|
84
|
+
if item.submenu:
|
|
85
|
+
# Create submenu
|
|
86
|
+
submenu_items = self.build_rumps_menu(item.submenu.items)
|
|
87
|
+
submenu = rumps.MenuItem(item.text)
|
|
88
|
+
for sub_item in submenu_items:
|
|
89
|
+
submenu.add(sub_item)
|
|
90
|
+
rumps_items.append(submenu)
|
|
91
|
+
else:
|
|
92
|
+
# Create regular menu item
|
|
93
|
+
menu_item = rumps.MenuItem(
|
|
94
|
+
item.text,
|
|
95
|
+
callback=(
|
|
96
|
+
self._create_callback_wrapper(item)
|
|
97
|
+
if item.callback
|
|
98
|
+
else None
|
|
99
|
+
),
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Set enabled state
|
|
103
|
+
if not item.enabled:
|
|
104
|
+
menu_item.set_callback(None)
|
|
105
|
+
|
|
106
|
+
# Set checked state
|
|
107
|
+
if item.checked:
|
|
108
|
+
menu_item.state = 1
|
|
109
|
+
else:
|
|
110
|
+
menu_item.state = 0
|
|
111
|
+
|
|
112
|
+
rumps_items.append(menu_item)
|
|
113
|
+
|
|
114
|
+
return rumps_items
|
|
115
|
+
|
|
116
|
+
def _create_callback_wrapper(self, item):
|
|
117
|
+
"""Create a callback wrapper that matches our interface."""
|
|
118
|
+
|
|
119
|
+
def wrapper(sender): # pylint: disable=unused-argument
|
|
120
|
+
if item.callback:
|
|
121
|
+
item.callback(None, item)
|
|
122
|
+
|
|
123
|
+
return wrapper
|
|
124
|
+
|
|
125
|
+
def show_balloon_notification(
|
|
126
|
+
self, title, message, timeout=5000
|
|
127
|
+
): # pylint: disable=unused-argument
|
|
128
|
+
"""
|
|
129
|
+
Show a notification on macOS using the Notification Center.
|
|
130
|
+
Falls back to console output if AppleScript fails.
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
# Escape quotes in message and title for AppleScript
|
|
134
|
+
escaped_title = title.replace('"', '\\"')
|
|
135
|
+
escaped_message = message.replace('"', '\\"')
|
|
136
|
+
escaped_app_name = self.app_name.replace('"', '\\"')
|
|
137
|
+
|
|
138
|
+
# Use AppleScript to show notification
|
|
139
|
+
script = (
|
|
140
|
+
f'display notification "{escaped_message}" '
|
|
141
|
+
f'with title "{escaped_title}" subtitle "{escaped_app_name}"'
|
|
142
|
+
)
|
|
143
|
+
subprocess.run(
|
|
144
|
+
["osascript", "-e", script], check=True, capture_output=True, text=True
|
|
145
|
+
)
|
|
146
|
+
except FileNotFoundError:
|
|
147
|
+
# osascript not available, fallback to console
|
|
148
|
+
print(f"[{self.app_name}] {title}: {message}")
|
|
149
|
+
except subprocess.CalledProcessError as e:
|
|
150
|
+
# AppleScript failed, fallback to console
|
|
151
|
+
print(f"[{self.app_name}] {title}: {message}")
|
|
152
|
+
print(f"Warning: Failed to show notification via AppleScript: {e}")
|
|
153
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
154
|
+
# Any other error, fallback to console
|
|
155
|
+
print(f"[{self.app_name}] {title}: {message}")
|
|
156
|
+
print(f"Warning: Failed to show notification: {e}")
|
|
157
|
+
|
|
158
|
+
def exit_app(self, _, __):
|
|
159
|
+
"""Exit the application."""
|
|
160
|
+
if self.app:
|
|
161
|
+
rumps.quit_application()
|
|
162
|
+
|
|
163
|
+
def run(self):
|
|
164
|
+
"""
|
|
165
|
+
Run the tray application.
|
|
166
|
+
"""
|
|
167
|
+
self._check_rumps_availability()
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
# Create the rumps app
|
|
171
|
+
self.app = rumps.App(self.app_name, icon=self.icon_path, quit_button=None)
|
|
172
|
+
|
|
173
|
+
# Build the initial menu
|
|
174
|
+
self.refresh_menu()
|
|
175
|
+
|
|
176
|
+
# Set up a timer to refresh menu periodically (every 3 seconds)
|
|
177
|
+
# This provides a good balance between responsiveness and performance
|
|
178
|
+
self._setup_menu_refresh_timer()
|
|
179
|
+
|
|
180
|
+
# Call the on_ready hook if available (for compatibility with tray.py)
|
|
181
|
+
if hasattr(self, "on_ready") and callable(getattr(self, "on_ready", None)):
|
|
182
|
+
getattr(self, "on_ready")()
|
|
183
|
+
|
|
184
|
+
# Start the app
|
|
185
|
+
self.app.run()
|
|
186
|
+
except Exception as e:
|
|
187
|
+
raise RuntimeError(f"Failed to start macOS tray application: {e}") from e
|
|
188
|
+
|
|
189
|
+
def refresh_menu(self):
|
|
190
|
+
"""
|
|
191
|
+
Refresh the menu by rebuilding it with current state.
|
|
192
|
+
"""
|
|
193
|
+
if not self.app:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Clear existing menu
|
|
197
|
+
self.app.menu.clear()
|
|
198
|
+
|
|
199
|
+
# Build fresh menu with current state
|
|
200
|
+
menu = self.create_menu()
|
|
201
|
+
menu_items = self.build_rumps_menu(menu.items)
|
|
202
|
+
|
|
203
|
+
# Add updated menu items
|
|
204
|
+
for item in menu_items:
|
|
205
|
+
self.app.menu.add(item)
|
|
206
|
+
|
|
207
|
+
def _setup_menu_refresh_timer(self):
|
|
208
|
+
"""
|
|
209
|
+
Set up a timer to periodically refresh the menu.
|
|
210
|
+
"""
|
|
211
|
+
if not self.app:
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
# Create a timer that refreshes the menu every 3 seconds
|
|
215
|
+
@rumps.timer(3)
|
|
216
|
+
def refresh_menu_timer(sender): # pylint: disable=unused-argument
|
|
217
|
+
self.refresh_menu()
|
|
218
|
+
|
|
219
|
+
# Store reference to prevent garbage collection
|
|
220
|
+
self._menu_update_timer = refresh_menu_timer
|
|
221
|
+
|
|
222
|
+
def update_menu(self):
|
|
223
|
+
"""
|
|
224
|
+
Update the menu by rebuilding it.
|
|
225
|
+
"""
|
|
226
|
+
self.refresh_menu()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import socketserver
|
|
2
|
+
import sys
|
|
3
|
+
import logging
|
|
4
|
+
import importlib
|
|
5
|
+
import asyncio
|
|
6
|
+
from contextlib import asynccontextmanager
|
|
7
|
+
from fastapi import FastAPI
|
|
8
|
+
from lemonade.version import __version__
|
|
9
|
+
|
|
10
|
+
_lazy_imports = {
|
|
11
|
+
"TextIteratorStreamer": ("transformers", "TextIteratorStreamer"),
|
|
12
|
+
"StoppingCriteriaList": ("transformers", "StoppingCriteriaList"),
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def find_free_port():
|
|
17
|
+
"""
|
|
18
|
+
Scans for an unoccupied TCP port
|
|
19
|
+
|
|
20
|
+
Returns the port number as an int on success
|
|
21
|
+
Returns None if no port can be found
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
with socketserver.TCPServer(("localhost", 0), None) as s:
|
|
26
|
+
return s.server_address[1]
|
|
27
|
+
# pylint: disable=broad-exception-caught
|
|
28
|
+
except Exception:
|
|
29
|
+
return None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@asynccontextmanager
|
|
33
|
+
async def lifespan(app: FastAPI):
|
|
34
|
+
# Only do minimal setup here so endpoints are available immediately
|
|
35
|
+
try:
|
|
36
|
+
if sys.stdout.encoding:
|
|
37
|
+
"🍋".encode(sys.stdout.encoding)
|
|
38
|
+
use_emojis = True
|
|
39
|
+
except (UnicodeEncodeError, AttributeError):
|
|
40
|
+
use_emojis = False
|
|
41
|
+
|
|
42
|
+
if use_emojis:
|
|
43
|
+
logging.info(
|
|
44
|
+
"\n"
|
|
45
|
+
"\n"
|
|
46
|
+
f"🍋 Lemonade Server v{__version__} Ready!\n"
|
|
47
|
+
f"🍋 Open http://{app.host_}:{app.port} in your browser for:\n"
|
|
48
|
+
"🍋 💬 chat\n"
|
|
49
|
+
"🍋 💻 model management\n"
|
|
50
|
+
"🍋 📄 docs\n"
|
|
51
|
+
)
|
|
52
|
+
else:
|
|
53
|
+
logging.info(
|
|
54
|
+
"\n"
|
|
55
|
+
"\n"
|
|
56
|
+
f"[Lemonade] Lemonade Server v{__version__} Ready!\n"
|
|
57
|
+
f"[Lemonade] Open http://{app.host_}:{app.port} in your browser for:\n"
|
|
58
|
+
"[Lemonade] chat\n"
|
|
59
|
+
"[Lemonade] model management\n"
|
|
60
|
+
"[Lemonade] docs\n"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Start lazy imports in the background, and set app.initialized = True
|
|
64
|
+
# when the imports are available
|
|
65
|
+
async def lazy_imports_bg():
|
|
66
|
+
for object_name, import_info in _lazy_imports.items():
|
|
67
|
+
module_name = import_info[0]
|
|
68
|
+
class_name = import_info[1]
|
|
69
|
+
module = importlib.import_module(module_name)
|
|
70
|
+
obj = getattr(module, class_name)
|
|
71
|
+
globals()[object_name] = obj
|
|
72
|
+
|
|
73
|
+
app.initialized = True
|
|
74
|
+
|
|
75
|
+
asyncio.create_task(lazy_imports_bg())
|
|
76
|
+
|
|
77
|
+
yield
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import logging
|
|
3
|
+
from lemonade.tools.server.serve import Server
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ServerRunner(threading.Thread):
|
|
7
|
+
"""
|
|
8
|
+
Thread class for running the Lemonade Server with a loaded model.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self, model, tokenizer, checkpoint, recipe, host="localhost", port=8000
|
|
13
|
+
):
|
|
14
|
+
threading.Thread.__init__(self)
|
|
15
|
+
self.model = model
|
|
16
|
+
self.tokenizer = tokenizer
|
|
17
|
+
self.checkpoint = checkpoint
|
|
18
|
+
self.recipe = recipe
|
|
19
|
+
self.host = host
|
|
20
|
+
self.port = port
|
|
21
|
+
self.server = None
|
|
22
|
+
self.ready_event = threading.Event()
|
|
23
|
+
self.shutdown_event = threading.Event()
|
|
24
|
+
self.uvicorn_server = None
|
|
25
|
+
|
|
26
|
+
def run(self):
|
|
27
|
+
try:
|
|
28
|
+
# Create the server instance
|
|
29
|
+
self.server = Server(port=self.port, log_level="warning")
|
|
30
|
+
|
|
31
|
+
# Configure the server with model/tokenizer
|
|
32
|
+
self.server.model = self.model
|
|
33
|
+
self.server.tokenizer = self.tokenizer
|
|
34
|
+
self.server.llm_loaded = type(
|
|
35
|
+
"obj",
|
|
36
|
+
(object,),
|
|
37
|
+
{
|
|
38
|
+
"checkpoint": self.checkpoint,
|
|
39
|
+
"recipe": self.recipe,
|
|
40
|
+
"max_prompt_length": None,
|
|
41
|
+
"reasoning": False,
|
|
42
|
+
"model_name": "custom",
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Set up the server for threaded execution
|
|
47
|
+
self.uvicorn_server = self.server.run_in_thread(host=self.host)
|
|
48
|
+
|
|
49
|
+
# Set the ready event
|
|
50
|
+
self.ready_event.set()
|
|
51
|
+
|
|
52
|
+
# Run the server until shutdown is requested
|
|
53
|
+
logging.info(f"Starting server on http://{self.host}:{self.port}")
|
|
54
|
+
self.uvicorn_server.run()
|
|
55
|
+
|
|
56
|
+
except Exception as e:
|
|
57
|
+
logging.error(f"Error starting server: {e}")
|
|
58
|
+
self.ready_event.set()
|
|
59
|
+
raise
|
|
60
|
+
|
|
61
|
+
def shutdown(self):
|
|
62
|
+
"""Shutdown the server"""
|
|
63
|
+
if hasattr(self, "uvicorn_server") and self.uvicorn_server:
|
|
64
|
+
logging.info("Shutting down server...")
|
|
65
|
+
self.uvicorn_server.should_exit = True
|
|
66
|
+
self.shutdown_event.set()
|
|
67
|
+
|
|
68
|
+
# Clean up resources properly to avoid memory leaks
|
|
69
|
+
if hasattr(self, "server") and self.server:
|
|
70
|
+
logging.info("Cleaning up model and tokenizer resources...")
|
|
71
|
+
|
|
72
|
+
if hasattr(self.server, "model"):
|
|
73
|
+
self.server.model = None
|
|
74
|
+
|
|
75
|
+
if hasattr(self.server, "tokenizer"):
|
|
76
|
+
self.server.tokenizer = None
|
|
77
|
+
|
|
78
|
+
if hasattr(self.server, "llm_loaded"):
|
|
79
|
+
self.server.llm_loaded = None
|
|
80
|
+
|
|
81
|
+
# Clean up local references
|
|
82
|
+
if hasattr(self, "model"):
|
|
83
|
+
del self.model
|
|
84
|
+
if hasattr(self, "tokenizer"):
|
|
85
|
+
del self.tokenizer
|