lemonade-sdk 8.1.3__py3-none-any.whl → 8.1.5__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/api.py CHANGED
@@ -36,6 +36,7 @@ def _make_state(recipe, checkpoint) -> Dict:
36
36
  def from_pretrained(
37
37
  checkpoint: str,
38
38
  recipe: str = "hf-cpu",
39
+ do_not_upgrade: bool = True,
39
40
  ) -> Tuple[ModelAdapter, TokenizerAdapter]:
40
41
  """
41
42
  Load an LLM and the corresponding tokenizer using a lemonade recipe.
@@ -43,6 +44,9 @@ def from_pretrained(
43
44
  Args:
44
45
  - checkpoint: huggingface checkpoint that defines the LLM
45
46
  - recipe: defines the implementation and hardware used for the LLM
47
+ - do_not_upgrade: prioritize the local copy of the model, if available,
48
+ even if an upgraded copy is available on the server (note: only applies
49
+ for oga-* recipes)
46
50
 
47
51
  Recipe choices:
48
52
  - hf-cpu: Huggingface Transformers implementation for CPU with max-perf settings
@@ -118,6 +122,7 @@ def from_pretrained(
118
122
  input=checkpoint,
119
123
  device=user_backend,
120
124
  dtype=backend_to_dtype[user_backend],
125
+ do_not_upgrade=do_not_upgrade,
121
126
  )
122
127
 
123
128
  return state.model, state.tokenizer
@@ -2,6 +2,7 @@ import os
2
2
  from typing import Optional
3
3
  import socket
4
4
  from huggingface_hub import model_info, snapshot_download
5
+ from huggingface_hub.errors import LocalEntryNotFoundError
5
6
 
6
7
 
7
8
  def is_offline():
@@ -50,10 +51,11 @@ def get_base_model(checkpoint: str) -> Optional[str]:
50
51
  return None
51
52
 
52
53
 
53
- def custom_snapshot_download(repo_id, **kwargs):
54
+ def _symlink_safe_snapshot_download(repo_id, **kwargs):
54
55
  """
55
56
  Custom snapshot download with retry logic for Windows symlink privilege errors.
56
57
  """
58
+
57
59
  for attempt in range(2):
58
60
  try:
59
61
  return snapshot_download(repo_id=repo_id, **kwargs)
@@ -65,3 +67,27 @@ def custom_snapshot_download(repo_id, **kwargs):
65
67
  ):
66
68
  continue
67
69
  raise
70
+
71
+
72
+ def custom_snapshot_download(repo_id, do_not_upgrade=False, **kwargs):
73
+ """
74
+ Custom snapshot download with:
75
+ 1) retry logic for Windows symlink privilege errors.
76
+ 2) do_not_upgrade allows the caller to prioritize a local copy
77
+ of the model over an upgraded remote copy.
78
+ """
79
+
80
+ if do_not_upgrade:
81
+ try:
82
+ # Prioritize the local model, if available
83
+ return _symlink_safe_snapshot_download(
84
+ repo_id, local_files_only=True, **kwargs
85
+ )
86
+ except LocalEntryNotFoundError:
87
+ # LocalEntryNotFoundError means there was no local model, at this point
88
+ # we'll accept a remote model
89
+ return _symlink_safe_snapshot_download(
90
+ repo_id, local_files_only=False, **kwargs
91
+ )
92
+ else:
93
+ return _symlink_safe_snapshot_download(repo_id, **kwargs)
@@ -585,7 +585,7 @@ def identify_gguf_models(
585
585
  return core_files, sharded_files
586
586
 
587
587
 
588
- def download_gguf(config_checkpoint, config_mmproj=None) -> dict:
588
+ def download_gguf(config_checkpoint, config_mmproj=None, do_not_upgrade=False) -> dict:
589
589
  """
590
590
  Downloads the GGUF file for the given model configuration.
591
591
 
@@ -605,6 +605,7 @@ def download_gguf(config_checkpoint, config_mmproj=None) -> dict:
605
605
  snapshot_folder = custom_snapshot_download(
606
606
  checkpoint,
607
607
  allow_patterns=list(core_files.values()) + sharded_files,
608
+ do_not_upgrade=do_not_upgrade,
608
609
  )
609
610
 
610
611
  # Ensure we downloaded all expected files
@@ -654,6 +654,7 @@ class OgaLoad(FirstTool):
654
654
  download_only: bool = False,
655
655
  trust_remote_code=False,
656
656
  subfolder: str = None,
657
+ do_not_upgrade: bool = False,
657
658
  ) -> State:
658
659
  from lemonade.common.network import (
659
660
  custom_snapshot_download,
@@ -744,7 +745,7 @@ class OgaLoad(FirstTool):
744
745
  input_model_path = custom_snapshot_download(
745
746
  checkpoint,
746
747
  ignore_patterns=["*.md", "*.txt"],
747
- local_files_only=offline,
748
+ local_files_only=offline or do_not_upgrade,
748
749
  )
749
750
  # Check if model is ONNX or safetensors
750
751
  is_onnx_model = any(
@@ -378,7 +378,11 @@ def _launch_llama_subprocess(
378
378
 
379
379
 
380
380
  def server_load(
381
- model_config: PullConfig, telemetry: LlamaTelemetry, backend: str, ctx_size: int
381
+ model_config: PullConfig,
382
+ telemetry: LlamaTelemetry,
383
+ backend: str,
384
+ ctx_size: int,
385
+ do_not_upgrade: bool = False,
382
386
  ):
383
387
  # Install and/or update llama.cpp if needed
384
388
  try:
@@ -389,7 +393,9 @@ def server_load(
389
393
  )
390
394
 
391
395
  # Download the gguf to the hugging face cache
392
- snapshot_files = download_gguf(model_config.checkpoint, model_config.mmproj)
396
+ snapshot_files = download_gguf(
397
+ model_config.checkpoint, model_config.mmproj, do_not_upgrade=do_not_upgrade
398
+ )
393
399
  logging.debug(f"GGUF file paths: {snapshot_files}")
394
400
 
395
401
  # Check if model supports embeddings
@@ -67,7 +67,9 @@ from lemonade_server.pydantic_models import (
67
67
  ResponsesRequest,
68
68
  PullConfig,
69
69
  DeleteConfig,
70
+ LogLevelConfig,
70
71
  )
72
+ from lemonade_server.settings import save_setting
71
73
 
72
74
  # Set to a high number to allow for interesting experiences in real apps
73
75
  # Tests should use the max_new_tokens argument to set a lower value
@@ -249,6 +251,7 @@ class Server:
249
251
  self.app.get(f"{prefix}/system-info")(self.get_system_info)
250
252
  self.app.post(f"{prefix}/completions")(self.completions)
251
253
  self.app.post(f"{prefix}/responses")(self.responses)
254
+ self.app.post(f"{prefix}/log-level")(self.set_log_level)
252
255
 
253
256
  # OpenAI-compatible routes
254
257
  self.app.post(f"{prefix}/chat/completions")(self.chat_completions)
@@ -259,6 +262,38 @@ class Server:
259
262
  self.app.post(f"{prefix}/reranking")(self.reranking)
260
263
  self.app.post(f"{prefix}/rerank")(self.reranking)
261
264
 
265
+ async def set_log_level(self, config: LogLevelConfig):
266
+ """
267
+ Set the logging level of the server.
268
+ """
269
+ try:
270
+ log_level_upper = config.level.upper()
271
+ numeric_level = getattr(logging, log_level_upper, None)
272
+ if not isinstance(numeric_level, int):
273
+ raise ValueError(f"Invalid log level: {config.level}")
274
+
275
+ # Get the root logger
276
+ logger = logging.getLogger()
277
+ logger.setLevel(numeric_level)
278
+
279
+ # Update all handlers
280
+ for handler in logger.handlers:
281
+ handler.setLevel(numeric_level)
282
+
283
+ logging.getLogger("uvicorn.error").setLevel(numeric_level)
284
+ self.debug_logging_enabled = numeric_level <= logging.DEBUG
285
+
286
+ # Save the setting
287
+ save_setting("log_level", config.level)
288
+
289
+ logging.info(f"Log level changed to: {config.level}")
290
+ return {"status": "success", "message": f"Log level set to {config.level}"}
291
+ except Exception as e:
292
+ raise HTTPException(
293
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
294
+ detail=f"Failed to set log level: {str(e)}",
295
+ )
296
+
262
297
  def _log_request_parameters(self, request, endpoint_name: str):
263
298
  """
264
299
  Log request parameters excluding content fields like messages, prompt, or input.
@@ -367,7 +402,9 @@ class Server:
367
402
 
368
403
  # Open lemonade server in tray mode
369
404
  # lambda function used for deferred instantiation and thread safety
370
- LemonadeTray(self.log_file, self.port, lambda: self).run()
405
+ LemonadeTray(
406
+ self.log_file, self.port, lambda: self, log_level=self.log_level
407
+ ).run()
371
408
  sys.exit(0)
372
409
 
373
410
  if self.debug_logging_enabled:
@@ -1351,6 +1388,9 @@ class Server:
1351
1388
  recipe=config.recipe,
1352
1389
  reasoning=config.reasoning,
1353
1390
  mmproj=config.mmproj,
1391
+ # The pull endpoint will download an upgraded model if available, even
1392
+ # if we already have a local copy of the model
1393
+ do_not_upgrade=False,
1354
1394
  )
1355
1395
 
1356
1396
  # Refresh the list of downloaded models, to ensure it
@@ -1449,6 +1489,8 @@ class Server:
1449
1489
  telemetry=self.llama_telemetry,
1450
1490
  backend=self.llamacpp_backend,
1451
1491
  ctx_size=self.ctx_size,
1492
+ # Models should only upgrade when using the pull endpoint
1493
+ do_not_upgrade=True,
1452
1494
  )
1453
1495
 
1454
1496
  else:
@@ -1836,7 +1836,7 @@ body::before {
1836
1836
  background: #fff;
1837
1837
  border-radius: 8px;
1838
1838
  border: 1px solid #e0e0e0;
1839
- overflow: hidden;
1839
+ overflow: visible;
1840
1840
  }
1841
1841
 
1842
1842
  .model-browser-sidebar {
@@ -1974,7 +1974,7 @@ body::before {
1974
1974
  flex: 1;
1975
1975
  display: flex;
1976
1976
  flex-direction: column;
1977
- overflow: hidden;
1977
+ overflow: visible;
1978
1978
  }
1979
1979
 
1980
1980
  .model-browser-header {
@@ -43,7 +43,7 @@ class LemonadeTray(SystemTray):
43
43
  Lemonade-specific system tray implementation.
44
44
  """
45
45
 
46
- def __init__(self, log_file, port, server_factory):
46
+ def __init__(self, log_file, port, server_factory, log_level="info"):
47
47
  # Find the icon path
48
48
  icon_path = Path(__file__).resolve().parents[0] / "static" / "favicon.ico"
49
49
 
@@ -58,6 +58,7 @@ class LemonadeTray(SystemTray):
58
58
  self.log_file = log_file
59
59
  self.port = port
60
60
  self.server_factory = server_factory
61
+ self.debug_logs_enabled = log_level == "debug"
61
62
 
62
63
  # Get current and latest version
63
64
  self.current_version = __version__
@@ -281,11 +282,27 @@ class LemonadeTray(SystemTray):
281
282
  self.logger.error(f"Error changing port: {str(e)}")
282
283
  self.show_balloon_notification("Error", f"Failed to change port: {str(e)}")
283
284
 
285
+ def _using_installer(self):
286
+ """
287
+ Check if the user is using the NSIS installer by checking for embeddable python
288
+ """
289
+ py_home = Path(sys.executable).parent
290
+ pth_file = (
291
+ py_home / f"python{sys.version_info.major}{sys.version_info.minor}._pth"
292
+ )
293
+ return pth_file.exists()
294
+
284
295
  def upgrade_to_latest(self, _, __):
285
296
  """
286
- Download and launch the Lemonade Server installer
297
+ Download and launch the Lemonade Server installer if the user is using the NSIS installer
298
+ Otherwise, simply open the browser to the release page
287
299
  """
288
300
 
301
+ # If the user installed from source, simple open their browser to the release page
302
+ if not self._using_installer():
303
+ webbrowser.open("https://github.com/lemonade-sdk/lemonade/releases/latest")
304
+ return
305
+
289
306
  # Show notification that download is starting
290
307
  self.show_balloon_notification(
291
308
  "Upgrading Lemonade",
@@ -325,6 +342,26 @@ class LemonadeTray(SystemTray):
325
342
 
326
343
  # No need to quit the application, the installer will handle it
327
344
 
345
+ def toggle_debug_logs(self, _, __):
346
+ """
347
+ Toggle debug logs on and off.
348
+ """
349
+ try:
350
+ new_level = "debug" if not self.debug_logs_enabled else "info"
351
+ response = requests.post(
352
+ f"http://localhost:{self.port}/api/v1/log-level",
353
+ json={"level": new_level},
354
+ )
355
+ response.raise_for_status()
356
+ self.debug_logs_enabled = not self.debug_logs_enabled
357
+ self.show_balloon_notification(
358
+ "Debug Logs",
359
+ f"Debug logs {'enabled' if self.debug_logs_enabled else 'disabled'}",
360
+ )
361
+ except (FileNotFoundError, ValueError) as e:
362
+ self.logger.error(f"Error toggling debug logs: {str(e)}")
363
+ self.show_balloon_notification("Error", "Failed to toggle debug logs.")
364
+
328
365
  def create_menu(self):
329
366
  """
330
367
  Create the Lemonade-specific context menu.
@@ -401,6 +438,17 @@ class LemonadeTray(SystemTray):
401
438
 
402
439
  port_submenu = Menu(*port_menu_items)
403
440
 
441
+ # Create the Logs submenu
442
+ debug_log_text = "Enable Debug Logs"
443
+ debug_log_item = MenuItem(debug_log_text, self.toggle_debug_logs)
444
+ debug_log_item.checked = self.debug_logs_enabled
445
+
446
+ logs_submenu = Menu(
447
+ MenuItem("Show Logs", self.show_logs),
448
+ Menu.SEPARATOR,
449
+ debug_log_item,
450
+ )
451
+
404
452
  if status_successfully_checked:
405
453
  items.append(MenuItem("Load Model", None, submenu=load_submenu))
406
454
  items.append(MenuItem("Port", None, submenu=port_submenu))
@@ -417,7 +465,7 @@ class LemonadeTray(SystemTray):
417
465
  items.append(MenuItem("Documentation", self.open_documentation))
418
466
  items.append(MenuItem("LLM Chat", self.open_llm_chat))
419
467
  items.append(MenuItem("Model Manager", self.open_model_manager))
420
- items.append(MenuItem("Show Logs", self.show_logs))
468
+ items.append(MenuItem("Logs", None, submenu=logs_submenu))
421
469
  items.append(Menu.SEPARATOR)
422
470
  items.append(MenuItem("Quit Lemonade", self.exit_app))
423
471
  return Menu(*items)
lemonade/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "8.1.3"
1
+ __version__ = "8.1.5"
@@ -162,7 +162,7 @@ def _get_ryzenai_version_info(device=None):
162
162
 
163
163
  if Version(og.__version__) >= Version("0.7.0"):
164
164
  oga_path = os.path.dirname(og.__file__)
165
- if og.__version__ == "0.7.0.2":
165
+ if og.__version__ in ("0.7.0.2.1", "0.7.0.2"):
166
166
  return "1.5.0", oga_path
167
167
  else:
168
168
  return "1.4.0", oga_path
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lemonade-sdk
3
- Version: 8.1.3
3
+ Version: 8.1.5
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
@@ -30,7 +30,7 @@ Requires-Dist: sentencepiece
30
30
  Requires-Dist: huggingface-hub[hf_xet]==0.33.0
31
31
  Requires-Dist: python-dotenv
32
32
  Provides-Extra: oga-ryzenai
33
- Requires-Dist: onnxruntime-genai-directml-ryzenai==0.7.0.2; extra == "oga-ryzenai"
33
+ Requires-Dist: onnxruntime-genai-directml-ryzenai==0.7.0.2.1; extra == "oga-ryzenai"
34
34
  Requires-Dist: protobuf>=6.30.1; extra == "oga-ryzenai"
35
35
  Provides-Extra: oga-cpu
36
36
  Requires-Dist: onnxruntime-genai==0.8.2; extra == "oga-cpu"
@@ -200,48 +200,11 @@ You can also import custom GGUF and ONNX models from Hugging Face by using our [
200
200
 
201
201
  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).
202
202
 
203
- <table>
204
- <thead>
205
- <tr>
206
- <th rowspan="2">Hardware</th>
207
- <th colspan="3" align="center">🛠️ Engine Support</th>
208
- <th colspan="2" align="center">🖥️ OS (x86/x64)</th>
209
- </tr>
210
- <tr>
211
- <th align="center">OGA</th>
212
- <th align="center">llamacpp</th>
213
- <th align="center">HF</th>
214
- <th align="center">Windows</th>
215
- <th align="center">Linux</th>
216
- </tr>
217
- </thead>
218
- <tbody>
219
- <tr>
220
- <td><strong>🧠 CPU</strong></td>
221
- <td align="center">All platforms</td>
222
- <td align="center">All platforms</td>
223
- <td align="center">All platforms</td>
224
- <td align="center">✅</td>
225
- <td align="center">✅</td>
226
- </tr>
227
- <tr>
228
- <td><strong>🎮 GPU</strong></td>
229
- <td align="center">—</td>
230
- <td align="center">Vulkan: All platforms<br>ROCm: Selected AMD platforms*</td>
231
- <td align="center">—</td>
232
- <td align="center">✅</td>
233
- <td align="center">✅</td>
234
- </tr>
235
- <tr>
236
- <td><strong>🤖 NPU</strong></td>
237
- <td align="center">AMD Ryzen™ AI 300 series</td>
238
- <td align="center">—</td>
239
- <td align="center">—</td>
240
- <td align="center">✅</td>
241
- <td align="center">—</td>
242
- </tr>
243
- </tbody>
244
- </table>
203
+ | Hardware | Engine: OGA | Engine: llamacpp | Engine: HF | Windows | Linux |
204
+ |----------|-------------|------------------|------------|---------|-------|
205
+ | **🧠 CPU** | All platforms | All platforms | All platforms | ✅ | ✅ |
206
+ | **🎮 GPU** | — | Vulkan: All platforms<br>ROCm: Selected AMD platforms* | — | ✅ | ✅ |
207
+ | **🤖 NPU** | AMD Ryzen™ AI 300 series | — | — | ✅ | — |
245
208
 
246
209
  <details>
247
210
  <summary><small><i>* See supported AMD ROCm platforms</i></small></summary>
@@ -337,9 +300,19 @@ New contributors can find beginner-friendly issues tagged with "Good First Issue
337
300
 
338
301
  This project is sponsored by AMD. It is maintained by @danielholanda @jeremyfowers @ramkrishna @vgodsoe in equal measure. You can reach us by filing an [issue](https://github.com/lemonade-sdk/lemonade/issues), emailing [lemonade@amd.com](mailto:lemonade@amd.com), or joining our [Discord](https://discord.gg/5xXzkMu8Zk).
339
302
 
340
- ## License
341
-
342
- This project is licensed under the [Apache 2.0 License](https://github.com/lemonade-sdk/lemonade/blob/main/LICENSE). Portions of the project are licensed as described in [NOTICE.md](./NOTICE.md).
303
+ ## License and Attribution
304
+
305
+ This project is:
306
+ - [Built with Python](https://www.amd.com/en/developer/resources/technical-articles/2025/rethinking-local-ai-lemonade-servers-python-advantage.html) with ❤️ for the open source community,
307
+ - Standing on the shoulders of great tools from:
308
+ - [ggml/llama.cpp](https://github.com/ggml-org/llama.cpp)
309
+ - [OnnxRuntime GenAI](https://github.com/microsoft/onnxruntime-genai)
310
+ - [Hugging Face Hub](https://github.com/huggingface/huggingface_hub)
311
+ - [OpenAI API](https://github.com/openai/openai-python)
312
+ - and more...
313
+ - Accelerated by mentorship from the OCV Catalyst program.
314
+ - Licensed under the [Apache 2.0 License](https://github.com/lemonade-sdk/lemonade/blob/main/LICENSE).
315
+ - Portions of the project are licensed as described in [NOTICE.md](./NOTICE.md).
343
316
 
344
317
  <!--This file was originally licensed under Apache 2.0. It has been modified.
345
318
  Modifications Copyright (c) 2025 AMD-->
@@ -1,17 +1,17 @@
1
1
  lemonade/__init__.py,sha256=W1Qk7r0rnQqFhPNHp6BIBT_q-OH3s-8Q_POoVfAmKW0,117
2
- lemonade/api.py,sha256=kGz8N_9TuN3peFG8fES0odN0bWR9itLNomlR-FC2z8k,5515
2
+ lemonade/api.py,sha256=Oc4yBA3LZg8FrTsbuDq1p9-XE74pqNnIEUhXyKa7qg8,5786
3
3
  lemonade/cache.py,sha256=5iZbk273TiTMqK_vdzPOPYTo6VsWW2gNByOISA9zi1w,3002
4
4
  lemonade/cli.py,sha256=9Pcs3PcrWC2F8_pcBaz09xHUICIJTvpemBdPGyXkjIk,4395
5
5
  lemonade/sequence.py,sha256=KSH7BPsiyDKsOsg_ziQKEGsDwMmuO_YbgPRBxkZd0pw,13267
6
6
  lemonade/state.py,sha256=sdSezla7Cd7KYL90xY3p9kcNV4ndSyN6UvNLOr3vBMA,5261
7
- lemonade/version.py,sha256=gnc1sclqzDLnQB9vbqA0LgSMz4H-bYCuu--_P-HWhAc,22
7
+ lemonade/version.py,sha256=UE2byv3TrjFSpmrlMMLljjtWoisXIAZ0MoNAF9Lc36k,22
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
11
11
  lemonade/common/exceptions.py,sha256=w83sVKmL1QXoJlGjj_bRyjIBMhlMqdVQy_FEOTu2YQI,2050
12
12
  lemonade/common/filesystem.py,sha256=QV3cHhKNu-7W2rr8wZ4JQfD2rP_5T2Js7jiDQBYWHVQ,12142
13
13
  lemonade/common/inference_engines.py,sha256=pJxn0zOf3gEmjGAIWXNdCibfzarzc7LRbZjoQyygkcU,12591
14
- lemonade/common/network.py,sha256=p1lWJkN0H5hCpb4rKi3Zc47W_BRrrm-7ghdTALJLGqU,1944
14
+ lemonade/common/network.py,sha256=qXpUjDYQEYM_gH3JwTtU-pu_yCKcaa1IeohJRPy91-A,2903
15
15
  lemonade/common/printing.py,sha256=GFFzrXIineIOMa9yu0lo5sL4j6A5BBg_T9aUCdP-juw,3229
16
16
  lemonade/common/status.py,sha256=xSOZN508cdRtrs1HVyr9zmASYg69EsZBLSs0lroLoCM,16519
17
17
  lemonade/common/system_info.py,sha256=pn-k3zMQCbt5cu3aHXa4cENgrubOK97gs9PYdGPsFXA,28405
@@ -34,10 +34,10 @@ lemonade/tools/huggingface/load.py,sha256=KsSGOBBD-tNEIfYC8mCWV_jpnkjHMhN3juVmC1
34
34
  lemonade/tools/huggingface/utils.py,sha256=j1S-IgjDsznUIVwkHSqqChmFyqIx9f3WcEelzohWwvU,13955
35
35
  lemonade/tools/llamacpp/bench.py,sha256=1fkE02ecg-jRk92i5dTAXz6re14WH8bd-Z9l-m3lbDA,4844
36
36
  lemonade/tools/llamacpp/load.py,sha256=DFCvQN548Ch9H8U_rHOiYviinzw6vixb5-V7xLj7XE4,6499
37
- lemonade/tools/llamacpp/utils.py,sha256=Auid9FepxwLIgDahaDNIxwz8kP_ap8Opd3eSF6t637g,32336
37
+ lemonade/tools/llamacpp/utils.py,sha256=LZ0xae7tTQG9nP55DLm90PJS8UQEwGJmMIb_96pWDKE,32397
38
38
  lemonade/tools/oga/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
39
  lemonade/tools/oga/bench.py,sha256=PJXv4UchcS2YPwijNzef8DY4DSAKYxIYY1ycHuH3T34,5005
40
- lemonade/tools/oga/load.py,sha256=6Pf_QrHpIXDbfpTwFNRj4RmWTxI-RImhYuqRvmTVgmY,33722
40
+ lemonade/tools/oga/load.py,sha256=BH5ChYbZgeP_ZN4E6HoboJD3kZcUIAPgPEVbgUZpVjQ,33778
41
41
  lemonade/tools/oga/utils.py,sha256=F8UVLKlfYcLa2SUqlehar8-jaX2Aw4u58DjHNNvLdOA,17675
42
42
  lemonade/tools/quark/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
43
43
  lemonade/tools/quark/quark_load.py,sha256=FJ4LJKTToZbHHWVEOBLadae1a3jCnnY4KvXySHbkJMA,5589
@@ -46,13 +46,13 @@ lemonade/tools/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
46
46
  lemonade/tools/report/llm_report.py,sha256=bVHhwCINA-Ok2EdSwAsLubsc83N3KWOVuwTguw7jDcE,6676
47
47
  lemonade/tools/report/table.py,sha256=ssqy1bZqF-wptNzKEOj6_9REtCNZyXO8R5vakAtg3R4,27973
48
48
  lemonade/tools/server/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- lemonade/tools/server/llamacpp.py,sha256=jVkaPx1ZbHYiJll3wnDR0fh-e0yfg7UB0BXlLWPx4dE,20998
50
- lemonade/tools/server/serve.py,sha256=3wnB19YThQLHkjbzy7PCWppQY_j5xKB24GcqM8IybxI,58857
49
+ lemonade/tools/server/llamacpp.py,sha256=9OneTx78UgvTzvZbdQidiihAN4F-JyfmhwD0bnj5_IU,21090
50
+ lemonade/tools/server/serve.py,sha256=qA0BqYEeRKXtEoS-hG20M_b1WXiiDmyvfEAk72s6XTc,60573
51
51
  lemonade/tools/server/tool_calls.py,sha256=xrAlQwKG-nv2xLlf8f9CDSaUbyMn8ZtHkds9iZLG9K8,5230
52
- lemonade/tools/server/tray.py,sha256=YJ4-vJlM6tJ0ojY_wVM6COuNscETFkQPt-BaNqYa9YQ,17640
52
+ lemonade/tools/server/tray.py,sha256=a9z6hdqlfj91H00j6hAExRPQkzWHhE3dnqSumzEgq0U,19599
53
53
  lemonade/tools/server/webapp.py,sha256=8Das5yXOaSBLZmSZ_eddJajQFxBhvl5D6GI_hHlGbE0,1040
54
54
  lemonade/tools/server/static/favicon.ico,sha256=hMmP9qGJNeZ0mFS86JIqPbZstXMZn0Z76_HfHQpREAU,126745
55
- lemonade/tools/server/static/styles.css,sha256=X_mqf3XCOo_pZEqkDiVzMGCy8ARseEBq5DdGnAdfVk0,43383
55
+ lemonade/tools/server/static/styles.css,sha256=5HQQCpm8N_fzLcolPiDuhyZw_5nbO8aIl60xAn4RKmg,43385
56
56
  lemonade/tools/server/static/webapp.html,sha256=FX2MZUsljfgxxuF12KBdgvNkso_z-sHewWc0SEGGcGM,18138
57
57
  lemonade/tools/server/static/js/chat.js,sha256=BTvREuEt0NrN8qhAuda5tTAoUN6tbsoukevA-zyTrwQ,27193
58
58
  lemonade/tools/server/static/js/model-settings.js,sha256=JXHeG7xVrRU181Hj7CZflERAi1Z6t-qwYFR4aH5nf5I,5820
@@ -62,15 +62,16 @@ lemonade/tools/server/utils/port.py,sha256=J7-g-Aqygb50jNoHLhhRfBZVM-uhGlcB5-oYB
62
62
  lemonade/tools/server/utils/system_tray.py,sha256=b9lvNv9chJKQxvmH7qzAuUe6H9HsLu7pdHFqGlAJaL0,12654
63
63
  lemonade/tools/server/utils/thread.py,sha256=Z-PDzGcpgfN2qxTmtlROWqrUN0B2fXdPrqo_J10fR_w,2772
64
64
  lemonade_install/__init__.py,sha256=26zohKg2jgr_5y7tObduWMYQg8zCTWMZHL8lfi2zZVQ,40
65
- lemonade_install/install.py,sha256=Zl_JtEIhbqZZTvxcqtq895IomEN-JNxp9xOZEtahMHQ,28289
66
- lemonade_sdk-8.1.3.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
67
- lemonade_sdk-8.1.3.dist-info/licenses/NOTICE.md,sha256=B8lEqi4QE41J9ljz4Riv2JgHD1v8GCZE6nNBHO3KIA0,2135
68
- lemonade_server/cli.py,sha256=-haIK4Q9cYFwna5-m6vgxW9qMaGW-_lDFB49zXxDk2A,18755
69
- lemonade_server/model_manager.py,sha256=cFaHJVOsabwekAPryXAPdo6qrXYBD_yht7XPg2QImqc,10791
70
- lemonade_server/pydantic_models.py,sha256=oTFnDVCax2Gerz7RBJOJF0FVQjKoUPJZbBo-EgogQyk,3161
65
+ lemonade_install/install.py,sha256=onndA2a-ygyLtDfupI8JQFhU_XpK8McGZtGujFasXww,28304
66
+ lemonade_sdk-8.1.5.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
67
+ lemonade_sdk-8.1.5.dist-info/licenses/NOTICE.md,sha256=RSca9LE5e6pvdWA_LXAUCcACIHPmINKqkRX-AVRqBGo,3499
68
+ lemonade_server/cli.py,sha256=-oRbLRlOX6SRH1bZURmlkDujsUBwMVprm83MtVR3DEY,18819
69
+ lemonade_server/model_manager.py,sha256=8FsD234ODtqVF3k4GIjo6UnZKSpii11UU_zoIf-kvSU,11133
70
+ lemonade_server/pydantic_models.py,sha256=49MyOlb5feLUlKsGcI75tWaflWckrItqcSVkdCY4e3A,3269
71
71
  lemonade_server/server_models.json,sha256=DAdG4ebIt5Dy5MM3kmXn1pO0XbNMph1gdpzbacBDVuc,11664
72
- lemonade_sdk-8.1.3.dist-info/METADATA,sha256=3As4CPILSkJVZMKsyqHZX6o9P8aBsixEJuQTtOas25w,17086
73
- lemonade_sdk-8.1.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
74
- lemonade_sdk-8.1.3.dist-info/entry_points.txt,sha256=7sRvpNhi1E7amnM7RZo57e8yFF9iA5uuRaIeJ1Xre6w,193
75
- lemonade_sdk-8.1.3.dist-info/top_level.txt,sha256=10ap5GNiPhalO4V50LRoxA1FqRT9g3Xkia6BITu880k,42
76
- lemonade_sdk-8.1.3.dist-info/RECORD,,
72
+ lemonade_server/settings.py,sha256=6nsmPLFJD-UokQDmlx9ZBYMbpnn48So_PuBGWP7Fmfg,1299
73
+ lemonade_sdk-8.1.5.dist-info/METADATA,sha256=DqkuNfUnA3CgSvDSVkz6cQJnQBw75AE9FmbAAoRdrso,16852
74
+ lemonade_sdk-8.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
75
+ lemonade_sdk-8.1.5.dist-info/entry_points.txt,sha256=7sRvpNhi1E7amnM7RZo57e8yFF9iA5uuRaIeJ1Xre6w,193
76
+ lemonade_sdk-8.1.5.dist-info/top_level.txt,sha256=10ap5GNiPhalO4V50LRoxA1FqRT9g3Xkia6BITu880k,42
77
+ lemonade_sdk-8.1.5.dist-info/RECORD,,
@@ -1,7 +1,33 @@
1
1
  PORTIONS LICENSED AS FOLLOWS
2
2
 
3
+ ## llama.cpp
4
+
5
+ Binaries for llama.cpp are downloaded under the MIT license from https://github.com/ggml-org/llama.cpp, as well as https://github.com/lemonade-sdk/llamacpp-rocm (which uses https://github.com/ggml-org/llama.cpp to build them.)
6
+
3
7
  Lemonade SDK used the [ONNX TurnkeyML](https://github.com/onnx/turnkeyml) project as a starting point under the [Apache 2.0 license](./LICENSE).
4
8
 
9
+ > MIT License
10
+ >
11
+ > Copyright (c) 2023-2024 The ggml authors
12
+ >
13
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ > of this software and associated documentation files (the "Software"), to deal
15
+ > in the Software without restriction, including without limitation the rights
16
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ > copies of the Software, and to permit persons to whom the Software is
18
+ > furnished to do so, subject to the following conditions:
19
+ >
20
+ > The above copyright notice and this permission notice shall be included in all
21
+ > copies or substantial portions of the Software.
22
+ >
23
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ > SOFTWARE.
30
+
5
31
  ## TurnkeyML Attribution
6
32
 
7
33
  TurnkeyML used code from other open source projects as a starting point (see [NOTICE.md](NOTICE.md)). Thank you Philip Colangelo, Derek Elkins, Jeremy Fowers, Dan Gard, Victoria Godsoe, Mark Heaps, Daniel Holanda, Brian Kurtz, Mariah Larwood, Philip Lassen, Andrew Ling, Adrian Macias, Gary Malik, Sarah Massengill, Ashwin Murthy, Hatice Ozen, Tim Sears, Sean Settle, Krishna Sivakumar, Aviv Weinstein, Xueli Xao, Bill Xing, and Lev Zlotnik for your contributions to that work.
@@ -18,4 +44,4 @@ TurnkeyML used code from other open source projects as a starting point (see [NO
18
44
  >
19
45
  >The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
20
46
  >
21
- >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
47
+ >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
lemonade_server/cli.py CHANGED
@@ -11,6 +11,7 @@ from lemonade_server.pydantic_models import (
11
11
  DEFAULT_LLAMACPP_BACKEND,
12
12
  DEFAULT_CTX_SIZE,
13
13
  )
14
+ from lemonade_server.settings import load_setting
14
15
 
15
16
 
16
17
  # Error codes for different CLI scenarios
@@ -390,12 +391,7 @@ def get_server_info() -> Tuple[int | None, int | None]:
390
391
  connections = psutil.net_connections(kind="tcp4")
391
392
 
392
393
  for conn in connections:
393
- if (
394
- conn.status == "LISTEN"
395
- and conn.laddr
396
- and conn.laddr.ip in ["127.0.0.1"]
397
- and conn.pid is not None
398
- ):
394
+ if conn.status == "LISTEN" and conn.laddr and conn.pid is not None:
399
395
  if is_lemonade_server(conn.pid):
400
396
  return conn.pid, conn.laddr.port
401
397
 
@@ -472,6 +468,9 @@ def developer_entrypoint():
472
468
  def _add_server_arguments(parser):
473
469
  """Add common server arguments to a parser"""
474
470
 
471
+ # Load the persisted log level to use as a default
472
+ persisted_log_level = load_setting("log_level", DEFAULT_LOG_LEVEL)
473
+
475
474
  parser.add_argument(
476
475
  "--port",
477
476
  type=int,
@@ -489,7 +488,7 @@ def _add_server_arguments(parser):
489
488
  type=str,
490
489
  help="Log level for the server",
491
490
  choices=["critical", "error", "warning", "info", "debug", "trace"],
492
- default=DEFAULT_LOG_LEVEL,
491
+ default=persisted_log_level,
493
492
  )
494
493
  parser.add_argument(
495
494
  "--llamacpp",
@@ -103,9 +103,13 @@ class ModelManager:
103
103
  recipe: Optional[str] = None,
104
104
  reasoning: bool = False,
105
105
  mmproj: str = "",
106
+ do_not_upgrade: bool = False,
106
107
  ):
107
108
  """
108
109
  Downloads the specified models from Hugging Face.
110
+
111
+ do_not_upgrade: prioritize any local copy of the model over any updated copy
112
+ from the Hugging Face Hub.
109
113
  """
110
114
  for model in models:
111
115
  if model not in self.supported_models:
@@ -174,9 +178,15 @@ class ModelManager:
174
178
  print(f"Downloading {model} ({checkpoint_to_download})")
175
179
 
176
180
  if "gguf" in checkpoint_to_download.lower():
177
- download_gguf(gguf_model_config.checkpoint, gguf_model_config.mmproj)
181
+ download_gguf(
182
+ gguf_model_config.checkpoint,
183
+ gguf_model_config.mmproj,
184
+ do_not_upgrade=do_not_upgrade,
185
+ )
178
186
  else:
179
- custom_snapshot_download(checkpoint_to_download)
187
+ custom_snapshot_download(
188
+ checkpoint_to_download, do_not_upgrade=do_not_upgrade
189
+ )
180
190
 
181
191
  # Register the model in user_models.json, creating that file if needed
182
192
  # We do this registration after the download so that we don't register
@@ -122,3 +122,11 @@ class DeleteConfig(BaseModel):
122
122
  """
123
123
 
124
124
  model_name: str
125
+
126
+
127
+ class LogLevelConfig(BaseModel):
128
+ """
129
+ Configuration for log-level setting.
130
+ """
131
+
132
+ level: str
@@ -0,0 +1,39 @@
1
+ import json
2
+ import os
3
+ from lemonade.cache import DEFAULT_CACHE_DIR
4
+
5
+ # Define the path for the user settings file, placing it in the cache directory
6
+ USER_SETTINGS_FILE = os.path.join(DEFAULT_CACHE_DIR, "user_settings.json")
7
+
8
+
9
+ def save_setting(key, value):
10
+ """Save a setting to the user_settings.json file."""
11
+ # Ensure the cache directory exists
12
+ os.makedirs(DEFAULT_CACHE_DIR, exist_ok=True)
13
+
14
+ settings = {}
15
+ if os.path.exists(USER_SETTINGS_FILE):
16
+ with open(USER_SETTINGS_FILE, "r") as f:
17
+ try:
18
+ settings = json.load(f)
19
+ except json.JSONDecodeError:
20
+ # If the file is empty or corrupt, start with a fresh dictionary
21
+ pass
22
+
23
+ settings[key] = value
24
+ with open(USER_SETTINGS_FILE, "w") as f:
25
+ json.dump(settings, f, indent=4)
26
+
27
+
28
+ def load_setting(key, default=None):
29
+ """Load a setting from the user_settings.json file."""
30
+ if not os.path.exists(USER_SETTINGS_FILE):
31
+ return default
32
+
33
+ with open(USER_SETTINGS_FILE, "r") as f:
34
+ try:
35
+ settings = json.load(f)
36
+ return settings.get(key, default)
37
+ except json.JSONDecodeError:
38
+ # Return default if the file is empty or corrupt
39
+ return default