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.
Files changed (84) hide show
  1. lemonade/__init__.py +5 -0
  2. lemonade/api.py +180 -0
  3. lemonade/cache.py +92 -0
  4. lemonade/cli.py +173 -0
  5. lemonade/common/__init__.py +0 -0
  6. lemonade/common/build.py +176 -0
  7. lemonade/common/cli_helpers.py +139 -0
  8. lemonade/common/exceptions.py +98 -0
  9. lemonade/common/filesystem.py +368 -0
  10. lemonade/common/inference_engines.py +408 -0
  11. lemonade/common/network.py +93 -0
  12. lemonade/common/printing.py +110 -0
  13. lemonade/common/status.py +471 -0
  14. lemonade/common/system_info.py +1411 -0
  15. lemonade/common/test_helpers.py +28 -0
  16. lemonade/profilers/__init__.py +1 -0
  17. lemonade/profilers/agt_power.py +437 -0
  18. lemonade/profilers/hwinfo_power.py +429 -0
  19. lemonade/profilers/memory_tracker.py +259 -0
  20. lemonade/profilers/profiler.py +58 -0
  21. lemonade/sequence.py +363 -0
  22. lemonade/state.py +159 -0
  23. lemonade/tools/__init__.py +1 -0
  24. lemonade/tools/accuracy.py +432 -0
  25. lemonade/tools/adapter.py +114 -0
  26. lemonade/tools/bench.py +302 -0
  27. lemonade/tools/flm/__init__.py +1 -0
  28. lemonade/tools/flm/utils.py +305 -0
  29. lemonade/tools/huggingface/bench.py +187 -0
  30. lemonade/tools/huggingface/load.py +235 -0
  31. lemonade/tools/huggingface/utils.py +359 -0
  32. lemonade/tools/humaneval.py +264 -0
  33. lemonade/tools/llamacpp/bench.py +255 -0
  34. lemonade/tools/llamacpp/load.py +222 -0
  35. lemonade/tools/llamacpp/utils.py +1260 -0
  36. lemonade/tools/management_tools.py +319 -0
  37. lemonade/tools/mmlu.py +319 -0
  38. lemonade/tools/oga/__init__.py +0 -0
  39. lemonade/tools/oga/bench.py +120 -0
  40. lemonade/tools/oga/load.py +804 -0
  41. lemonade/tools/oga/migration.py +403 -0
  42. lemonade/tools/oga/utils.py +462 -0
  43. lemonade/tools/perplexity.py +147 -0
  44. lemonade/tools/prompt.py +263 -0
  45. lemonade/tools/report/__init__.py +0 -0
  46. lemonade/tools/report/llm_report.py +203 -0
  47. lemonade/tools/report/table.py +899 -0
  48. lemonade/tools/server/__init__.py +0 -0
  49. lemonade/tools/server/flm.py +133 -0
  50. lemonade/tools/server/llamacpp.py +320 -0
  51. lemonade/tools/server/serve.py +2123 -0
  52. lemonade/tools/server/static/favicon.ico +0 -0
  53. lemonade/tools/server/static/index.html +279 -0
  54. lemonade/tools/server/static/js/chat.js +1059 -0
  55. lemonade/tools/server/static/js/model-settings.js +183 -0
  56. lemonade/tools/server/static/js/models.js +1395 -0
  57. lemonade/tools/server/static/js/shared.js +556 -0
  58. lemonade/tools/server/static/logs.html +191 -0
  59. lemonade/tools/server/static/styles.css +2654 -0
  60. lemonade/tools/server/static/webapp.html +321 -0
  61. lemonade/tools/server/tool_calls.py +153 -0
  62. lemonade/tools/server/tray.py +664 -0
  63. lemonade/tools/server/utils/macos_tray.py +226 -0
  64. lemonade/tools/server/utils/port.py +77 -0
  65. lemonade/tools/server/utils/thread.py +85 -0
  66. lemonade/tools/server/utils/windows_tray.py +408 -0
  67. lemonade/tools/server/webapp.py +34 -0
  68. lemonade/tools/server/wrapped_server.py +559 -0
  69. lemonade/tools/tool.py +374 -0
  70. lemonade/version.py +1 -0
  71. lemonade_install/__init__.py +1 -0
  72. lemonade_install/install.py +239 -0
  73. lemonade_sdk-9.1.1.dist-info/METADATA +276 -0
  74. lemonade_sdk-9.1.1.dist-info/RECORD +84 -0
  75. lemonade_sdk-9.1.1.dist-info/WHEEL +5 -0
  76. lemonade_sdk-9.1.1.dist-info/entry_points.txt +5 -0
  77. lemonade_sdk-9.1.1.dist-info/licenses/LICENSE +201 -0
  78. lemonade_sdk-9.1.1.dist-info/licenses/NOTICE.md +47 -0
  79. lemonade_sdk-9.1.1.dist-info/top_level.txt +3 -0
  80. lemonade_server/cli.py +805 -0
  81. lemonade_server/model_manager.py +758 -0
  82. lemonade_server/pydantic_models.py +159 -0
  83. lemonade_server/server_models.json +643 -0
  84. 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