lemonade-sdk 8.1.10__py3-none-any.whl → 8.1.11__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.
Potentially problematic release.
This version of lemonade-sdk might be problematic. Click here for more details.
- lemonade/tools/flm/__init__.py +1 -0
- lemonade/tools/flm/utils.py +255 -0
- lemonade/tools/llamacpp/utils.py +58 -10
- lemonade/tools/server/flm.py +137 -0
- lemonade/tools/server/llamacpp.py +23 -5
- lemonade/tools/server/serve.py +260 -135
- lemonade/tools/server/static/js/chat.js +165 -82
- lemonade/tools/server/static/js/models.js +87 -54
- lemonade/tools/server/static/js/shared.js +5 -3
- lemonade/tools/server/static/logs.html +47 -0
- lemonade/tools/server/static/styles.css +159 -8
- lemonade/tools/server/static/webapp.html +28 -10
- lemonade/tools/server/tray.py +94 -38
- lemonade/tools/server/utils/macos_tray.py +226 -0
- lemonade/tools/server/utils/{system_tray.py → windows_tray.py} +13 -0
- lemonade/tools/server/webapp.py +4 -1
- lemonade/tools/server/wrapped_server.py +91 -25
- lemonade/version.py +1 -1
- lemonade_install/install.py +25 -2
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/METADATA +9 -6
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/RECORD +30 -25
- lemonade_server/cli.py +103 -14
- lemonade_server/model_manager.py +186 -45
- lemonade_server/pydantic_models.py +25 -1
- lemonade_server/server_models.json +162 -62
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/WHEEL +0 -0
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/entry_points.txt +0 -0
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/licenses/LICENSE +0 -0
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/licenses/NOTICE.md +0 -0
- {lemonade_sdk-8.1.10.dist-info → lemonade_sdk-8.1.11.dist-info}/top_level.txt +0 -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()
|
|
@@ -25,12 +25,14 @@ class MenuItem:
|
|
|
25
25
|
enabled: bool = True,
|
|
26
26
|
submenu=None,
|
|
27
27
|
bitmap_path: Optional[str] = None,
|
|
28
|
+
checked: bool = False,
|
|
28
29
|
):
|
|
29
30
|
self.text = text
|
|
30
31
|
self.callback = callback
|
|
31
32
|
self.enabled = enabled
|
|
32
33
|
self.submenu = submenu
|
|
33
34
|
self.bitmap_path = bitmap_path
|
|
35
|
+
self.checked = checked
|
|
34
36
|
self.id = None # Will be set when menu is created
|
|
35
37
|
self.bitmap_handle = None
|
|
36
38
|
|
|
@@ -358,9 +360,20 @@ class SystemTray:
|
|
|
358
360
|
self.register_window_class()
|
|
359
361
|
self.create_window()
|
|
360
362
|
|
|
363
|
+
# Set up Windows console control handler for CTRL+C
|
|
364
|
+
self.console_handler = self.setup_console_control_handler(self.logger)
|
|
365
|
+
|
|
361
366
|
# Add tray icon
|
|
362
367
|
self.add_tray_icon()
|
|
363
368
|
|
|
369
|
+
# Notify subclasses that the tray is ready (hwnd and icon created)
|
|
370
|
+
# Allows showing initial notifications after initialization
|
|
371
|
+
try:
|
|
372
|
+
if hasattr(self, "on_ready") and callable(getattr(self, "on_ready")):
|
|
373
|
+
self.on_ready()
|
|
374
|
+
except Exception:
|
|
375
|
+
pass
|
|
376
|
+
|
|
364
377
|
# Run the message loop in the main thread
|
|
365
378
|
self.message_loop()
|
|
366
379
|
|
lemonade/tools/server/webapp.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pathlib import Path
|
|
2
2
|
import json
|
|
3
|
+
import platform
|
|
3
4
|
from fastapi.responses import HTMLResponse
|
|
4
5
|
from lemonade_server.model_manager import ModelManager
|
|
5
6
|
|
|
@@ -14,10 +15,11 @@ def get_webapp_html(port=8000):
|
|
|
14
15
|
# Use shared filter function from model_manager.py
|
|
15
16
|
filtered_models = ModelManager().filter_models_by_backend(server_models)
|
|
16
17
|
|
|
17
|
-
# Pass filtered server_models to JS
|
|
18
|
+
# Pass filtered server_models and platform info to JS
|
|
18
19
|
server_models_js = (
|
|
19
20
|
f"<script>window.SERVER_MODELS = {json.dumps(filtered_models)};</script>"
|
|
20
21
|
)
|
|
22
|
+
platform_js = f"<script>window.PLATFORM = '{platform.system()}';</script>"
|
|
21
23
|
|
|
22
24
|
# Load HTML template
|
|
23
25
|
template_path = Path(__file__).parent / "static" / "webapp.html"
|
|
@@ -27,5 +29,6 @@ def get_webapp_html(port=8000):
|
|
|
27
29
|
# Replace template variables
|
|
28
30
|
html_content = html_template.replace("{{SERVER_PORT}}", str(port))
|
|
29
31
|
html_content = html_content.replace("{{SERVER_MODELS_JS}}", server_models_js)
|
|
32
|
+
html_content = html_content.replace("{{PLATFORM_JS}}", platform_js)
|
|
30
33
|
|
|
31
34
|
return HTMLResponse(content=html_content)
|
|
@@ -58,8 +58,22 @@ class WrappedServerTelemetry(ABC):
|
|
|
58
58
|
telemetry = [
|
|
59
59
|
["Input tokens", self.input_tokens],
|
|
60
60
|
["Output tokens", self.output_tokens],
|
|
61
|
-
[
|
|
62
|
-
|
|
61
|
+
[
|
|
62
|
+
"TTFT (s)",
|
|
63
|
+
(
|
|
64
|
+
f"{self.time_to_first_token:.2f}"
|
|
65
|
+
if self.time_to_first_token is not None
|
|
66
|
+
else "N/A"
|
|
67
|
+
),
|
|
68
|
+
],
|
|
69
|
+
[
|
|
70
|
+
"TPS",
|
|
71
|
+
(
|
|
72
|
+
f"{self.tokens_per_second:.2f}"
|
|
73
|
+
if self.tokens_per_second is not None
|
|
74
|
+
else "N/A"
|
|
75
|
+
),
|
|
76
|
+
],
|
|
63
77
|
]
|
|
64
78
|
|
|
65
79
|
table = tabulate(
|
|
@@ -83,7 +97,7 @@ class WrappedServer(ABC):
|
|
|
83
97
|
self.telemetry: WrappedServerTelemetry = telemetry
|
|
84
98
|
self.log_thread_exception = None
|
|
85
99
|
|
|
86
|
-
def
|
|
100
|
+
def _choose_port(self):
|
|
87
101
|
"""
|
|
88
102
|
Users probably don't care what port we start the wrapped server on, so let's
|
|
89
103
|
search for an empty port
|
|
@@ -318,18 +332,44 @@ class WrappedServer(ABC):
|
|
|
318
332
|
if chat_completion_request.stream:
|
|
319
333
|
|
|
320
334
|
def event_stream():
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
for chunk in client.chat.completions.create(**openai_client_params):
|
|
325
|
-
yield f"data: {chunk.model_dump_json()}\n\n"
|
|
326
|
-
yield "data: [DONE]\n\n"
|
|
327
|
-
|
|
328
|
-
# Show telemetry after completion
|
|
329
|
-
self.telemetry.show_telemetry()
|
|
335
|
+
# Ensure streaming is enabled in params
|
|
336
|
+
stream_params = dict(openai_client_params)
|
|
337
|
+
stream_params["stream"] = True
|
|
330
338
|
|
|
331
|
-
|
|
332
|
-
|
|
339
|
+
# Use streaming context so we can explicitly close on cancellation
|
|
340
|
+
with client.chat.completions.with_streaming_response.create(
|
|
341
|
+
# pylint: disable=missing-kwoa
|
|
342
|
+
**stream_params,
|
|
343
|
+
) as response:
|
|
344
|
+
try:
|
|
345
|
+
for line in response.iter_lines():
|
|
346
|
+
# Preserve SSE event boundaries: blank line separates events
|
|
347
|
+
if line == b"" or line == "":
|
|
348
|
+
yield "\n"
|
|
349
|
+
continue
|
|
350
|
+
if isinstance(line, bytes):
|
|
351
|
+
try:
|
|
352
|
+
line = line.decode("utf-8", errors="ignore")
|
|
353
|
+
except (UnicodeDecodeError, LookupError):
|
|
354
|
+
# Skip lines that fail decoding due to encoding issues
|
|
355
|
+
continue
|
|
356
|
+
# Forward SSE lines as-is
|
|
357
|
+
if not line.endswith("\n"):
|
|
358
|
+
line += "\n"
|
|
359
|
+
yield line
|
|
360
|
+
|
|
361
|
+
# Show telemetry after completion
|
|
362
|
+
self.telemetry.show_telemetry()
|
|
363
|
+
|
|
364
|
+
except GeneratorExit:
|
|
365
|
+
# Client disconnected/cancelled; close upstream stream and stop
|
|
366
|
+
try:
|
|
367
|
+
response.close()
|
|
368
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
369
|
+
pass
|
|
370
|
+
raise
|
|
371
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
372
|
+
yield f'data: {{"error": "{str(e)}"}}\n\n'
|
|
333
373
|
|
|
334
374
|
return StreamingResponse(
|
|
335
375
|
event_stream(),
|
|
@@ -387,18 +427,44 @@ class WrappedServer(ABC):
|
|
|
387
427
|
if completion_request.stream:
|
|
388
428
|
|
|
389
429
|
def event_stream():
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
for chunk in client.completions.create(**openai_client_params):
|
|
394
|
-
yield f"data: {chunk.model_dump_json()}\n\n"
|
|
395
|
-
yield "data: [DONE]\n\n"
|
|
396
|
-
|
|
397
|
-
# Show telemetry after completion
|
|
398
|
-
self.telemetry.show_telemetry()
|
|
430
|
+
# Ensure streaming is enabled in params
|
|
431
|
+
stream_params = dict(openai_client_params)
|
|
432
|
+
stream_params["stream"] = True
|
|
399
433
|
|
|
400
|
-
|
|
401
|
-
|
|
434
|
+
# Use streaming context so we can explicitly close on cancellation
|
|
435
|
+
with client.completions.with_streaming_response.create(
|
|
436
|
+
# pylint: disable=missing-kwoa
|
|
437
|
+
**stream_params,
|
|
438
|
+
) as response:
|
|
439
|
+
try:
|
|
440
|
+
for line in response.iter_lines():
|
|
441
|
+
# Preserve SSE event boundaries: blank line separates events
|
|
442
|
+
if line == b"" or line == "":
|
|
443
|
+
yield "\n"
|
|
444
|
+
continue
|
|
445
|
+
if isinstance(line, bytes):
|
|
446
|
+
try:
|
|
447
|
+
line = line.decode("utf-8", errors="ignore")
|
|
448
|
+
except (UnicodeDecodeError, LookupError):
|
|
449
|
+
# Skip lines that fail decoding due to encoding issues
|
|
450
|
+
continue
|
|
451
|
+
# Forward SSE lines as-is
|
|
452
|
+
if not line.endswith("\n"):
|
|
453
|
+
line += "\n"
|
|
454
|
+
yield line
|
|
455
|
+
|
|
456
|
+
# Show telemetry after completion
|
|
457
|
+
self.telemetry.show_telemetry()
|
|
458
|
+
|
|
459
|
+
except GeneratorExit:
|
|
460
|
+
# Client disconnected/cancelled; close upstream stream and stop
|
|
461
|
+
try:
|
|
462
|
+
response.close()
|
|
463
|
+
except Exception: # pylint: disable=broad-exception-caught
|
|
464
|
+
pass
|
|
465
|
+
raise
|
|
466
|
+
except Exception as e: # pylint: disable=broad-exception-caught
|
|
467
|
+
yield f'data: {{"error": "{str(e)}"}}\n\n'
|
|
402
468
|
|
|
403
469
|
return StreamingResponse(
|
|
404
470
|
event_stream(),
|
lemonade/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "8.1.
|
|
1
|
+
__version__ = "8.1.11"
|
lemonade_install/install.py
CHANGED
|
@@ -447,6 +447,12 @@ class Install:
|
|
|
447
447
|
choices=["rocm", "vulkan"],
|
|
448
448
|
)
|
|
449
449
|
|
|
450
|
+
parser.add_argument(
|
|
451
|
+
"--flm",
|
|
452
|
+
action="store_true",
|
|
453
|
+
help="Install FLM (FastFlowLM) for running local language models",
|
|
454
|
+
)
|
|
455
|
+
|
|
450
456
|
parser.add_argument(
|
|
451
457
|
"--override",
|
|
452
458
|
action="store_true",
|
|
@@ -727,19 +733,33 @@ class Install:
|
|
|
727
733
|
|
|
728
734
|
install_llamacpp(backend)
|
|
729
735
|
|
|
736
|
+
@staticmethod
|
|
737
|
+
def _install_flm():
|
|
738
|
+
"""
|
|
739
|
+
Install FLM (FastFlowLM) for running local language models.
|
|
740
|
+
"""
|
|
741
|
+
|
|
742
|
+
# Check if the processor is supported before proceeding
|
|
743
|
+
check_ryzen_ai_processor()
|
|
744
|
+
|
|
745
|
+
from lemonade.tools.flm.utils import install_flm
|
|
746
|
+
|
|
747
|
+
install_flm()
|
|
748
|
+
|
|
730
749
|
def run(
|
|
731
750
|
self,
|
|
732
751
|
ryzenai: Optional[str] = None,
|
|
733
752
|
build_model: Optional[str] = None,
|
|
734
753
|
llamacpp: Optional[str] = None,
|
|
754
|
+
flm: Optional[bool] = None,
|
|
735
755
|
yes: bool = False,
|
|
736
756
|
token: Optional[str] = None,
|
|
737
757
|
override: bool = False,
|
|
738
758
|
):
|
|
739
|
-
if ryzenai is None and llamacpp is None:
|
|
759
|
+
if ryzenai is None and llamacpp is None and flm is None:
|
|
740
760
|
raise ValueError(
|
|
741
761
|
"You must select something to install, "
|
|
742
|
-
"for example `--
|
|
762
|
+
"for example `--llamacpp`, `--flm`, or `--ryzenai`"
|
|
743
763
|
)
|
|
744
764
|
|
|
745
765
|
if ryzenai is not None:
|
|
@@ -748,6 +768,9 @@ class Install:
|
|
|
748
768
|
if llamacpp is not None:
|
|
749
769
|
self._install_llamacpp(llamacpp)
|
|
750
770
|
|
|
771
|
+
if flm:
|
|
772
|
+
self._install_flm()
|
|
773
|
+
|
|
751
774
|
|
|
752
775
|
def main():
|
|
753
776
|
installer = Install()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lemonade-sdk
|
|
3
|
-
Version: 8.1.
|
|
3
|
+
Version: 8.1.11
|
|
4
4
|
Summary: Lemonade SDK: Your LLM Aide for Validation and Deployment
|
|
5
5
|
Author-email: lemonade@amd.com
|
|
6
6
|
Requires-Python: >=3.10, <3.14
|
|
@@ -29,6 +29,7 @@ Requires-Dist: tabulate
|
|
|
29
29
|
Requires-Dist: sentencepiece
|
|
30
30
|
Requires-Dist: huggingface-hub[hf_xet]==0.33.0
|
|
31
31
|
Requires-Dist: python-dotenv
|
|
32
|
+
Requires-Dist: rumps>=0.4.0; sys_platform == "darwin"
|
|
32
33
|
Provides-Extra: oga-ryzenai
|
|
33
34
|
Requires-Dist: onnxruntime-genai-directml-ryzenai==0.7.0.2.1; extra == "oga-ryzenai"
|
|
34
35
|
Requires-Dist: protobuf>=6.30.1; extra == "oga-ryzenai"
|
|
@@ -65,6 +66,8 @@ Dynamic: summary
|
|
|
65
66
|
<img src="https://img.shields.io/badge/Windows-11-0078D6?logo=windows&logoColor=white" alt="Windows 11" /></a>
|
|
66
67
|
<a href="https://lemonade-server.ai/#linux" title="Ubuntu 24.04 & 25.04 Supported">
|
|
67
68
|
<img src="https://img.shields.io/badge/Ubuntu-24.04%20%7C%2025.04-E95420?logo=ubuntu&logoColor=white" alt="Ubuntu 24.04 | 25.04" /></a>
|
|
69
|
+
<a href="https://lemonade-server.ai/" title="macOS 14+ with Apple Silicon">
|
|
70
|
+
<img src="https://img.shields.io/badge/macOS-14%2B-000000?logo=apple&logoColor=white" alt="macOS 14+" /></a>
|
|
68
71
|
<a href="docs/README.md#installation" title="Check out our instructions">
|
|
69
72
|
<img src="https://img.shields.io/badge/Python-3.10--3.13-blue?logo=python&logoColor=white" alt="Made with Python" /></a>
|
|
70
73
|
<a href="https://github.com/lemonade-sdk/lemonade/blob/main/docs/contribute.md" title="Contribution Guide">
|
|
@@ -152,11 +155,11 @@ You can also import custom GGUF and ONNX models from Hugging Face by using our [
|
|
|
152
155
|
|
|
153
156
|
Lemonade supports the following configurations, while also making it easy to switch between them at runtime. Find more information about it [here](./docs/README.md#software-and-hardware-overview).
|
|
154
157
|
|
|
155
|
-
| Hardware | Engine: OGA | Engine: llamacpp | Engine:
|
|
156
|
-
|
|
157
|
-
| **🧠 CPU** | All platforms | All platforms |
|
|
158
|
-
| **🎮 GPU** | — | Vulkan: All platforms<br>ROCm: Selected AMD platforms
|
|
159
|
-
| **🤖 NPU** | AMD Ryzen™ AI 300 series | — |
|
|
158
|
+
| Hardware | Engine: OGA | Engine: llamacpp | Engine: FLM | Windows | Linux | macOS |
|
|
159
|
+
|----------|-------------|------------------|------------|---------|-------|-------|
|
|
160
|
+
| **🧠 CPU** | All platforms | All platforms | - | ✅ | ✅ | ✅ |
|
|
161
|
+
| **🎮 GPU** | — | Vulkan: All platforms<br>ROCm: Selected AMD platforms*<br>Metal: Apple Silicon | — | ✅ | ✅ | ✅ |
|
|
162
|
+
| **🤖 NPU** | AMD Ryzen™ AI 300 series | — | Ryzen™ AI 300 series | ✅ | — | — |
|
|
160
163
|
|
|
161
164
|
<details>
|
|
162
165
|
<summary><small><i>* See supported AMD ROCm platforms</i></small></summary>
|
|
@@ -4,7 +4,7 @@ lemonade/cache.py,sha256=5iZbk273TiTMqK_vdzPOPYTo6VsWW2gNByOISA9zi1w,3002
|
|
|
4
4
|
lemonade/cli.py,sha256=qU5bW7RQAUKNSpvrhVyzn68NMxyi-336Ke_JU4bsv1Q,5708
|
|
5
5
|
lemonade/sequence.py,sha256=KSH7BPsiyDKsOsg_ziQKEGsDwMmuO_YbgPRBxkZd0pw,13267
|
|
6
6
|
lemonade/state.py,sha256=sdSezla7Cd7KYL90xY3p9kcNV4ndSyN6UvNLOr3vBMA,5261
|
|
7
|
-
lemonade/version.py,sha256=
|
|
7
|
+
lemonade/version.py,sha256=et6OH4dSRF6oHhfToZjlMBObl75tflM6mBJXEWJArps,23
|
|
8
8
|
lemonade/common/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
lemonade/common/build.py,sha256=zTb0m1-kuUx6zw5QHp2SNnVuN6jOTMQ2FCdj9iH374U,6140
|
|
10
10
|
lemonade/common/cli_helpers.py,sha256=hjBfXrTtFl8gmCFlL-ksviXR0mOcdPtTWVNKoEp3PG4,4993
|
|
@@ -31,12 +31,14 @@ lemonade/tools/mmlu.py,sha256=c2QaIMDzjqxCvgHlMXmy_dP1sAFkwkDxL7RO2nogI6s,11071
|
|
|
31
31
|
lemonade/tools/perplexity.py,sha256=eiaTZ3yhqF2pfwOffVbKKJLwjSri7Im2pC-tBJr7LLU,5638
|
|
32
32
|
lemonade/tools/prompt.py,sha256=PyLksp1k8jsZsU7XBRK61k1DUHhbdLa20h-AP8Noh3w,9011
|
|
33
33
|
lemonade/tools/tool.py,sha256=UsxVYukfm_iM3BfeGYPZxQlTK5UfDfDOl3RIyLr8A1Y,13256
|
|
34
|
+
lemonade/tools/flm/__init__.py,sha256=NQ4CEzJZGS_VvxPMlfrK4Dcx48bQSoUR4iG8e7yZjas,46
|
|
35
|
+
lemonade/tools/flm/utils.py,sha256=hHjSiRlkw239n03CyZeRQomxtmsJptM7m5M3CNnPlqo,8126
|
|
34
36
|
lemonade/tools/huggingface/bench.py,sha256=-mTfldCtquL4mspq8ykVwDc9Mut5Ecv_jHJnSb0CYGE,6734
|
|
35
37
|
lemonade/tools/huggingface/load.py,sha256=KsSGOBBD-tNEIfYC8mCWV_jpnkjHMhN3juVmC1Ln4uQ,7745
|
|
36
38
|
lemonade/tools/huggingface/utils.py,sha256=j1S-IgjDsznUIVwkHSqqChmFyqIx9f3WcEelzohWwvU,13955
|
|
37
39
|
lemonade/tools/llamacpp/bench.py,sha256=1fkE02ecg-jRk92i5dTAXz6re14WH8bd-Z9l-m3lbDA,4844
|
|
38
40
|
lemonade/tools/llamacpp/load.py,sha256=DFCvQN548Ch9H8U_rHOiYviinzw6vixb5-V7xLj7XE4,6499
|
|
39
|
-
lemonade/tools/llamacpp/utils.py,sha256=
|
|
41
|
+
lemonade/tools/llamacpp/utils.py,sha256=JpI9McEYbrZXQXb0Wo7EoQ8-0LLmmZuwbgGSuuYTiyQ,35221
|
|
40
42
|
lemonade/tools/oga/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
43
|
lemonade/tools/oga/bench.py,sha256=PJXv4UchcS2YPwijNzef8DY4DSAKYxIYY1ycHuH3T34,5005
|
|
42
44
|
lemonade/tools/oga/load.py,sha256=x-A-nhoni-WyDpVCLcWRAMfs5ouac9MJzxT-rsnLPw8,34226
|
|
@@ -45,33 +47,36 @@ lemonade/tools/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
45
47
|
lemonade/tools/report/llm_report.py,sha256=bVHhwCINA-Ok2EdSwAsLubsc83N3KWOVuwTguw7jDcE,6676
|
|
46
48
|
lemonade/tools/report/table.py,sha256=Kv_Epd8a6KIrdzSC2EgIl6uTKw7E5eMq10Tg16O0WxM,27996
|
|
47
49
|
lemonade/tools/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
-
lemonade/tools/server/
|
|
49
|
-
lemonade/tools/server/
|
|
50
|
+
lemonade/tools/server/flm.py,sha256=lErpSYLIB6vyVavDd5c-XOz-85m8yPPlyHhiceKWf6c,4119
|
|
51
|
+
lemonade/tools/server/llamacpp.py,sha256=dSab9hR_CcctmU8HeQYQ7U0XCbAQNd_QGZUt6q7cxHA,9952
|
|
52
|
+
lemonade/tools/server/serve.py,sha256=ONdONSN0Noh2c65Z1m9zfQuHIDgb1c42Do0ao2MG534,68771
|
|
50
53
|
lemonade/tools/server/tool_calls.py,sha256=xrAlQwKG-nv2xLlf8f9CDSaUbyMn8ZtHkds9iZLG9K8,5230
|
|
51
|
-
lemonade/tools/server/tray.py,sha256=
|
|
52
|
-
lemonade/tools/server/webapp.py,sha256=
|
|
53
|
-
lemonade/tools/server/wrapped_server.py,sha256=
|
|
54
|
+
lemonade/tools/server/tray.py,sha256=EFnSc2Ra4owiHVz6ykoMhxi2fYqZAK1g21AynAYBiyk,24426
|
|
55
|
+
lemonade/tools/server/webapp.py,sha256=GGSVIzN19C2ZaadOEPBg_D7Lt0PuF339NuWwjMPfZu8,1225
|
|
56
|
+
lemonade/tools/server/wrapped_server.py,sha256=uh7ifrRX1Hx0IuRwZRCGPyQOukitE7kKQipCCz0bSGA,19844
|
|
54
57
|
lemonade/tools/server/static/favicon.ico,sha256=hMmP9qGJNeZ0mFS86JIqPbZstXMZn0Z76_HfHQpREAU,126745
|
|
55
|
-
lemonade/tools/server/static/
|
|
56
|
-
lemonade/tools/server/static/
|
|
57
|
-
lemonade/tools/server/static/
|
|
58
|
+
lemonade/tools/server/static/logs.html,sha256=BSpdRJ8XJLStpD7XijXLbeeDaVW-FrJbI29zHNBxvYM,1321
|
|
59
|
+
lemonade/tools/server/static/styles.css,sha256=GYJgRtlZSgz3pShDeuatu-J9TpVwGgOjOcIWwNnWYck,49100
|
|
60
|
+
lemonade/tools/server/static/webapp.html,sha256=QQRMMMf8fbtJfGZYqBaRVd0-bENmdPfkVo8sdc4092Q,19151
|
|
61
|
+
lemonade/tools/server/static/js/chat.js,sha256=jxyMyu4MfvI2YmsMbJQ8ZwDNBnLzu2nbjm-qLfgWSNI,42182
|
|
58
62
|
lemonade/tools/server/static/js/model-settings.js,sha256=JXHeG7xVrRU181Hj7CZflERAi1Z6t-qwYFR4aH5nf5I,5820
|
|
59
|
-
lemonade/tools/server/static/js/models.js,sha256=
|
|
60
|
-
lemonade/tools/server/static/js/shared.js,sha256=
|
|
63
|
+
lemonade/tools/server/static/js/models.js,sha256=es3LwrU49UtoC59e-AusUpdSXQnIRNsZvw8f05keAM0,37856
|
|
64
|
+
lemonade/tools/server/static/js/shared.js,sha256=NVu7lXotLnVVh1x_eXqxX1bLzYyW_eMQttOZ9f0RqUQ,17591
|
|
65
|
+
lemonade/tools/server/utils/macos_tray.py,sha256=xwHW44ZN5hDVlJcwIpHHfqn4VRXWxXHuDACaT-ZqdO8,7095
|
|
61
66
|
lemonade/tools/server/utils/port.py,sha256=J7-g-Aqygb50jNoHLhhRfBZVM-uhGlcB5-oYBAehvgw,2263
|
|
62
|
-
lemonade/tools/server/utils/system_tray.py,sha256=b9lvNv9chJKQxvmH7qzAuUe6H9HsLu7pdHFqGlAJaL0,12654
|
|
63
67
|
lemonade/tools/server/utils/thread.py,sha256=Z-PDzGcpgfN2qxTmtlROWqrUN0B2fXdPrqo_J10fR_w,2772
|
|
68
|
+
lemonade/tools/server/utils/windows_tray.py,sha256=2z5aTmUPlkT-QfkcfwHsyA6dv6nSNBT0gXUErarhac8,13170
|
|
64
69
|
lemonade_install/__init__.py,sha256=26zohKg2jgr_5y7tObduWMYQg8zCTWMZHL8lfi2zZVQ,40
|
|
65
|
-
lemonade_install/install.py,sha256=
|
|
66
|
-
lemonade_sdk-8.1.
|
|
67
|
-
lemonade_sdk-8.1.
|
|
68
|
-
lemonade_server/cli.py,sha256=
|
|
69
|
-
lemonade_server/model_manager.py,sha256=
|
|
70
|
-
lemonade_server/pydantic_models.py,sha256=
|
|
71
|
-
lemonade_server/server_models.json,sha256=
|
|
70
|
+
lemonade_install/install.py,sha256=p3pYqhUnLQ9JJMcbjlSYDYqN-amnU_535O9Oj1yPbyM,27608
|
|
71
|
+
lemonade_sdk-8.1.11.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
72
|
+
lemonade_sdk-8.1.11.dist-info/licenses/NOTICE.md,sha256=RSca9LE5e6pvdWA_LXAUCcACIHPmINKqkRX-AVRqBGo,3499
|
|
73
|
+
lemonade_server/cli.py,sha256=mzlIqLeGkU96KHuib8lmZn0snU7XjQ9lspOy83dVplo,23401
|
|
74
|
+
lemonade_server/model_manager.py,sha256=6W6_nQea6hLD82Il2o_EgQ7oNaLfICXPKNjZiY9Y1Xk,26331
|
|
75
|
+
lemonade_server/pydantic_models.py,sha256=5U3PZ__UqcWQh-dNXVBc-vyJc6-In2vngZXP9VmiScM,3954
|
|
76
|
+
lemonade_server/server_models.json,sha256=_GxymNW7gBkJcTjzncYUvPUJ8kc-I3qqWwHqPztzYcA,14644
|
|
72
77
|
lemonade_server/settings.py,sha256=JOlZmirUXO9rA6BCODVFwyXrrHtYoH_LiKYm49lGm_c,1260
|
|
73
|
-
lemonade_sdk-8.1.
|
|
74
|
-
lemonade_sdk-8.1.
|
|
75
|
-
lemonade_sdk-8.1.
|
|
76
|
-
lemonade_sdk-8.1.
|
|
77
|
-
lemonade_sdk-8.1.
|
|
78
|
+
lemonade_sdk-8.1.11.dist-info/METADATA,sha256=uLq8pbAD_uriJbpPT25i5gk-Ukq_RbWzzEckgtceFDo,15334
|
|
79
|
+
lemonade_sdk-8.1.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
80
|
+
lemonade_sdk-8.1.11.dist-info/entry_points.txt,sha256=7sRvpNhi1E7amnM7RZo57e8yFF9iA5uuRaIeJ1Xre6w,193
|
|
81
|
+
lemonade_sdk-8.1.11.dist-info/top_level.txt,sha256=10ap5GNiPhalO4V50LRoxA1FqRT9g3Xkia6BITu880k,42
|
|
82
|
+
lemonade_sdk-8.1.11.dist-info/RECORD,,
|