lemonade-sdk 8.1.5__py3-none-any.whl → 8.1.7__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_server/cli.py CHANGED
@@ -144,8 +144,12 @@ def stop():
144
144
  except psutil.NoSuchProcess:
145
145
  pass # Child already terminated
146
146
 
147
- # Wait for main process
148
- process.wait(timeout=10)
147
+ # Wait for main process to terminate gracefully
148
+ # kill if it doesn't terminate gracefully
149
+ try:
150
+ process.wait(timeout=5)
151
+ except psutil.TimeoutExpired:
152
+ process.kill()
149
153
 
150
154
  # Kill llama-server child process if it didn't terminate gracefully
151
155
  for child in children:
@@ -273,6 +277,7 @@ def run(
273
277
  """
274
278
  import webbrowser
275
279
  import time
280
+ import os
276
281
 
277
282
  # Start the server if not running
278
283
  _, running_port = get_server_info()
@@ -299,7 +304,10 @@ def run(
299
304
  # Open the webapp with the specified model
300
305
  url = f"http://{host}:{port}/?model={model_name}#llm-chat"
301
306
  print(f"You can now chat with {model_name} at {url}")
302
- webbrowser.open(url)
307
+
308
+ # Only open browser if not disabled via environment variable
309
+ if not os.environ.get("LEMONADE_DISABLE_BROWSER"):
310
+ webbrowser.open(url)
303
311
 
304
312
  # Keep the server running if we started it
305
313
  if not server_previously_running:
@@ -507,6 +515,13 @@ def _add_server_arguments(parser):
507
515
  default=DEFAULT_CTX_SIZE,
508
516
  )
509
517
 
518
+ if os.name == "nt":
519
+ parser.add_argument(
520
+ "--no-tray",
521
+ action="store_true",
522
+ help="Do not show a tray icon when the server is running",
523
+ )
524
+
510
525
 
511
526
  def main():
512
527
  parser = argparse.ArgumentParser(
@@ -527,12 +542,6 @@ def main():
527
542
  # Serve command
528
543
  serve_parser = subparsers.add_parser("serve", help="Start server")
529
544
  _add_server_arguments(serve_parser)
530
- if os.name == "nt":
531
- serve_parser.add_argument(
532
- "--no-tray",
533
- action="store_true",
534
- help="Do not show a tray icon when the server is running",
535
- )
536
545
 
537
546
  # Status command
538
547
  status_parser = subparsers.add_parser("status", help="Check if server is running")
@@ -77,15 +77,50 @@ class ModelManager:
77
77
  def downloaded_models(self) -> dict:
78
78
  """
79
79
  Returns a dictionary of locally available models.
80
+ For GGUF models with variants, checks if the specific variant files exist.
80
81
  """
81
82
  downloaded_models = {}
82
83
  downloaded_checkpoints = self.downloaded_hf_checkpoints
83
84
  for model in self.supported_models:
84
- base_checkpoint = parse_checkpoint(
85
- self.supported_models[model]["checkpoint"]
86
- )[0]
85
+ model_info = self.supported_models[model]
86
+ checkpoint = model_info["checkpoint"]
87
+ base_checkpoint, variant = parse_checkpoint(checkpoint)
88
+
87
89
  if base_checkpoint in downloaded_checkpoints:
88
- downloaded_models[model] = self.supported_models[model]
90
+ # For GGUF models with variants, verify the specific variant files exist
91
+ if variant and model_info.get("recipe") == "llamacpp":
92
+ try:
93
+ from lemonade.tools.llamacpp.utils import identify_gguf_models
94
+ from lemonade.common.network import custom_snapshot_download
95
+
96
+ # Get the local snapshot path
97
+ snapshot_path = custom_snapshot_download(
98
+ base_checkpoint, local_files_only=True
99
+ )
100
+
101
+ # Check if the specific variant files exist
102
+ core_files, sharded_files = identify_gguf_models(
103
+ base_checkpoint, variant, model_info.get("mmproj", "")
104
+ )
105
+ all_variant_files = list(core_files.values()) + sharded_files
106
+
107
+ # Verify all required files exist locally
108
+ all_files_exist = True
109
+ for file_path in all_variant_files:
110
+ full_file_path = os.path.join(snapshot_path, file_path)
111
+ if not os.path.exists(full_file_path):
112
+ all_files_exist = False
113
+ break
114
+
115
+ if all_files_exist:
116
+ downloaded_models[model] = model_info
117
+
118
+ except Exception:
119
+ # If we can't verify the variant, don't include it
120
+ pass
121
+ else:
122
+ # For non-GGUF models or GGUF without variants, use the original logic
123
+ downloaded_models[model] = model_info
89
124
  return downloaded_models
90
125
 
91
126
  @property
@@ -229,6 +264,7 @@ class ModelManager:
229
264
  def delete_model(self, model_name: str):
230
265
  """
231
266
  Deletes the specified model from local storage.
267
+ For GGUF models with variants, only deletes the specific variant files.
232
268
  """
233
269
  if model_name not in self.supported_models:
234
270
  raise ValueError(
@@ -239,36 +275,134 @@ class ModelManager:
239
275
  checkpoint = self.supported_models[model_name]["checkpoint"]
240
276
  print(f"Deleting {model_name} ({checkpoint})")
241
277
 
242
- # Handle GGUF models that have the format "checkpoint:variant"
243
- base_checkpoint = parse_checkpoint(checkpoint)[0]
278
+ # Parse checkpoint to get base and variant
279
+ base_checkpoint, variant = parse_checkpoint(checkpoint)
244
280
 
281
+ # Get the repository cache directory
282
+ snapshot_path = None
283
+ model_cache_dir = None
245
284
  try:
246
- # Get the local path using snapshot_download with local_files_only=True
285
+ # First, try to get the local path using snapshot_download with local_files_only=True
247
286
  snapshot_path = custom_snapshot_download(
248
287
  base_checkpoint, local_files_only=True
249
288
  )
250
-
251
289
  # Navigate up to the model directory (parent of snapshots directory)
252
- model_path = os.path.dirname(os.path.dirname(snapshot_path))
253
-
254
- # Delete the entire model directory (including all snapshots)
255
- if os.path.exists(model_path):
256
- shutil.rmtree(model_path)
257
- print(f"Successfully deleted model {model_name} from {model_path}")
258
- else:
259
- raise ValueError(
260
- f"Model {model_name} not found locally at {model_path}"
261
- )
290
+ model_cache_dir = os.path.dirname(os.path.dirname(snapshot_path))
262
291
 
263
292
  except Exception as e:
293
+ # If snapshot_download fails, try to construct the cache path manually
264
294
  if (
265
295
  "not found in cache" in str(e).lower()
266
- or "no such file" in str(e).lower()
296
+ or "localentrynotfounderror" in str(e).lower()
297
+ or "cannot find an appropriate cached snapshot" in str(e).lower()
267
298
  ):
268
- raise ValueError(f"Model {model_name} is not installed locally")
299
+ # Construct the Hugging Face cache path manually
300
+ cache_home = huggingface_hub.constants.HF_HUB_CACHE
301
+ # Convert repo format (e.g., "unsloth/GLM-4.5-Air-GGUF") to cache format
302
+ repo_cache_name = base_checkpoint.replace("/", "--")
303
+ model_cache_dir = os.path.join(cache_home, f"models--{repo_cache_name}")
304
+ # Try to find the snapshot path within the model cache directory
305
+ if os.path.exists(model_cache_dir):
306
+ snapshots_dir = os.path.join(model_cache_dir, "snapshots")
307
+ if os.path.exists(snapshots_dir):
308
+ snapshot_dirs = [
309
+ d
310
+ for d in os.listdir(snapshots_dir)
311
+ if os.path.isdir(os.path.join(snapshots_dir, d))
312
+ ]
313
+ if snapshot_dirs:
314
+ # Use the first (likely only) snapshot directory
315
+ snapshot_path = os.path.join(
316
+ snapshots_dir, snapshot_dirs[0]
317
+ )
269
318
  else:
270
319
  raise ValueError(f"Failed to delete model {model_name}: {str(e)}")
271
320
 
321
+ # Handle deletion based on whether this is a GGUF model with variants
322
+ if variant and snapshot_path and os.path.exists(snapshot_path):
323
+ # This is a GGUF model with a specific variant - delete only variant files
324
+ try:
325
+ from lemonade.tools.llamacpp.utils import identify_gguf_models
326
+
327
+ # Get the specific files for this variant
328
+ core_files, sharded_files = identify_gguf_models(
329
+ base_checkpoint,
330
+ variant,
331
+ self.supported_models[model_name].get("mmproj", ""),
332
+ )
333
+ all_variant_files = list(core_files.values()) + sharded_files
334
+
335
+ # Delete the specific variant files
336
+ deleted_files = []
337
+ for file_path in all_variant_files:
338
+ full_file_path = os.path.join(snapshot_path, file_path)
339
+ if os.path.exists(full_file_path):
340
+ if os.path.isfile(full_file_path):
341
+ os.remove(full_file_path)
342
+ deleted_files.append(file_path)
343
+ elif os.path.isdir(full_file_path):
344
+ shutil.rmtree(full_file_path)
345
+ deleted_files.append(file_path)
346
+
347
+ if deleted_files:
348
+ print(f"Successfully deleted variant files: {deleted_files}")
349
+ else:
350
+ print(f"No variant files found for {variant} in {snapshot_path}")
351
+
352
+ # Check if the snapshot directory is now empty (only containing .gitattributes, README, etc.)
353
+ remaining_files = [
354
+ f
355
+ for f in os.listdir(snapshot_path)
356
+ if f.endswith(".gguf")
357
+ or os.path.isdir(os.path.join(snapshot_path, f))
358
+ ]
359
+
360
+ # If no GGUF files remain, we can delete the entire repository
361
+ if not remaining_files:
362
+ print(f"No other variants remain, deleting entire repository cache")
363
+ shutil.rmtree(model_cache_dir)
364
+ print(
365
+ f"Successfully deleted entire model cache at {model_cache_dir}"
366
+ )
367
+ else:
368
+ print(
369
+ f"Other variants still exist in repository, keeping cache directory"
370
+ )
371
+
372
+ except Exception as variant_error:
373
+ print(
374
+ f"Warning: Could not perform selective variant deletion: {variant_error}"
375
+ )
376
+ print("This may indicate the files were already manually deleted")
377
+
378
+ elif model_cache_dir and os.path.exists(model_cache_dir):
379
+ # Non-GGUF model or GGUF without variant - delete entire repository as before
380
+ shutil.rmtree(model_cache_dir)
381
+ print(f"Successfully deleted model {model_name} from {model_cache_dir}")
382
+
383
+ elif model_cache_dir:
384
+ # Model directory doesn't exist - it was likely already manually deleted
385
+ print(
386
+ f"Model {model_name} directory not found at {model_cache_dir} - may have been manually deleted"
387
+ )
388
+
389
+ else:
390
+ raise ValueError(f"Unable to determine cache path for model {model_name}")
391
+
392
+ # Clean up user models registry if applicable
393
+ if model_name.startswith("user.") and os.path.exists(USER_MODELS_FILE):
394
+ with open(USER_MODELS_FILE, "r", encoding="utf-8") as file:
395
+ user_models = json.load(file)
396
+
397
+ # Remove the "user." prefix to get the actual model name in the file
398
+ base_model_name = model_name[5:] # Remove "user." prefix
399
+
400
+ if base_model_name in user_models:
401
+ del user_models[base_model_name]
402
+ with open(USER_MODELS_FILE, "w", encoding="utf-8") as file:
403
+ json.dump(user_models, file)
404
+ print(f"Removed {model_name} from user models registry")
405
+
272
406
 
273
407
  # This file was originally licensed under Apache 2.0. It has been modified.
274
408
  # Modifications Copyright (c) 2025 AMD