mirror of
https://github.com/cloudstack-llc/mlx-knife.git
synced 2026-07-15 12:55:42 -04:00
7d0d6be66d
Three major bug fixes for production readiness: - Issue #21: Fix crash on fresh installations (empty cache directory) - Issue #22: Suppress urllib3 LibreSSL warnings on macOS Python 3.9 - Issue #23: Fix double rm execution bug - models now deleted in single command Test improvements: - 140/140 tests passing (up from 137) - Added real integration tests for lock cleanup - Fixed unit tests broken by path corrections All known cache management issues resolved for stable release.
134 lines
6.5 KiB
Python
134 lines
6.5 KiB
Python
# mlx_knife/cli.py
|
|
|
|
import argparse
|
|
import sys
|
|
|
|
from . import __version__
|
|
from .cache_utils import (
|
|
check_all_models_health,
|
|
check_model_health,
|
|
list_models,
|
|
rm_model,
|
|
run_model,
|
|
show_model,
|
|
)
|
|
from .hf_download import pull_model
|
|
from .server import run_server
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="MLX Knife CLI (HuggingFace-style cache management for MLX models)"
|
|
)
|
|
parser.add_argument('--version', action='version', version=f'MLX Knife {__version__}')
|
|
subparsers = parser.add_subparsers(dest="cmd")
|
|
|
|
# list
|
|
list_p = subparsers.add_parser("list", help="List available models in cache")
|
|
list_p.add_argument("model", nargs="?", help="Specific model to list (optional)")
|
|
list_p.add_argument("--all", action="store_true", help="Show all models (not just MLX)")
|
|
list_p.add_argument("--framework", choices=["mlx", "pytorch", "tokenizer"], help="Filter by framework")
|
|
list_p.add_argument("--health", action="store_true", help="Show health status")
|
|
list_p.add_argument("--verbose", action="store_true", help="Show detailed information (requires model argument)")
|
|
|
|
# pull
|
|
pull_p = subparsers.add_parser("pull", help="Download a model from HuggingFace")
|
|
pull_p.add_argument("model_spec", help="Model[@hash] (e.g. mlx-community/Qwen2.5-0.5B-Instruct-4bit@a5339a41)")
|
|
|
|
# run
|
|
run_p = subparsers.add_parser("run", help="Run a model with prompt")
|
|
run_p.add_argument("model_spec", help="Model[@hash] (e.g. mlx-community/Qwen2.5-0.5B-Instruct-4bit@a5339a41)")
|
|
run_p.add_argument("prompt", nargs="?", default=None, help="Prompt text (if not provided, enters interactive mode)")
|
|
run_p.add_argument("--interactive", "-i", action="store_true", help="Force interactive dialog mode")
|
|
run_p.add_argument("--temperature", type=float, default=0.7, help="Sampling temperature (default: 0.7)")
|
|
run_p.add_argument("--max-tokens", type=int, default=None, help="Maximum tokens to generate (default: model context length)")
|
|
run_p.add_argument("--top-p", type=float, default=0.9, help="Top-p sampling parameter (default: 0.9)")
|
|
run_p.add_argument("--repetition-penalty", type=float, default=1.1, help="Penalty for repeated tokens (default: 1.1)")
|
|
run_p.add_argument("--no-stream", action="store_true", help="Disable streaming output")
|
|
run_p.add_argument("--no-chat-template", action="store_true", help="Disable chat template formatting (use raw prompt)")
|
|
run_p.add_argument("--verbose", "-v", action="store_true", help="Show detailed output (model loading, memory usage, token stats)")
|
|
|
|
# rm
|
|
rm_p = subparsers.add_parser("rm", help="Delete a model from cache")
|
|
rm_p.add_argument("model_spec", help="Model[@hash] (e.g. mlx-community/Qwen2.5-0.5B-Instruct-4bit@a5339a41)")
|
|
rm_p.add_argument("--force", action="store_true", help="Skip confirmation and clean up cache files automatically")
|
|
|
|
# health
|
|
health_p = subparsers.add_parser("health", help="Check model integrity")
|
|
health_p.add_argument("model_spec", nargs="?", help="Model[@hash] (optional)")
|
|
health_p.add_argument("--all", action="store_true", help="Check all models in cache")
|
|
|
|
# show
|
|
show_p = subparsers.add_parser("show", help="Show detailed information about a specific model")
|
|
show_p.add_argument("model_spec", help="Model[@hash] (e.g. mlx-community/Qwen2.5-0.5B-Instruct-4bit@a5339a41)")
|
|
show_p.add_argument("--files", action="store_true", help="List all files and sizes under the model path")
|
|
show_p.add_argument("--config", action="store_true", help="Print pretty-formatted config.json")
|
|
|
|
# server
|
|
server_p = subparsers.add_parser("server", help="Start OpenAI-compatible API server")
|
|
server_p.add_argument("--host", default="127.0.0.1", help="Server host (default: 127.0.0.1)")
|
|
server_p.add_argument("--port", type=int, default=8000, help="Server port (default: 8000)")
|
|
server_p.add_argument("--max-tokens", type=int, default=None, help="Default max tokens for completions (default: model-aware dynamic limits)")
|
|
server_p.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
|
|
server_p.add_argument("--log-level", default="info", choices=["debug", "info", "warning", "error"], help="Log level (default: info)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.cmd == "list":
|
|
if args.model:
|
|
if args.verbose and not args.all and not args.framework and not args.health:
|
|
# Show detailed info for a specific model (same as show command)
|
|
show_model(args.model)
|
|
else:
|
|
# Show just the single model row
|
|
list_models(show_all=args.all, framework_filter=args.framework, show_health=args.health, single_model=args.model, verbose=args.verbose)
|
|
else:
|
|
# Normal list behavior - verbose works with MLX models too
|
|
list_models(show_all=args.all, framework_filter=args.framework, show_health=args.health, verbose=args.verbose)
|
|
elif args.cmd == "pull":
|
|
pull_model(args.model_spec)
|
|
elif args.cmd == "run":
|
|
run_model(
|
|
args.model_spec,
|
|
prompt=args.prompt,
|
|
interactive=args.interactive,
|
|
temperature=args.temperature,
|
|
max_tokens=args.max_tokens,
|
|
top_p=args.top_p,
|
|
repetition_penalty=args.repetition_penalty,
|
|
stream=not args.no_stream,
|
|
use_chat_template=not args.no_chat_template,
|
|
verbose=args.verbose
|
|
)
|
|
elif args.cmd == "rm":
|
|
rm_model(args.model_spec, force=args.force)
|
|
elif args.cmd == "health":
|
|
if args.model_spec:
|
|
check_model_health(args.model_spec)
|
|
else:
|
|
# Default to checking all models if no specific model is provided
|
|
check_all_models_health()
|
|
elif args.cmd == "show":
|
|
show_model(args.model_spec, show_files=args.files, show_config=args.config)
|
|
elif args.cmd == "server":
|
|
# Validate server arguments
|
|
if args.max_tokens is not None and args.max_tokens <= 0:
|
|
print(f"Error: --max-tokens must be positive, got: {args.max_tokens}")
|
|
sys.exit(1)
|
|
if args.port <= 0 or args.port > 65535:
|
|
print(f"Error: --port must be between 1-65535, got: {args.port}")
|
|
sys.exit(1)
|
|
|
|
run_server(
|
|
host=args.host,
|
|
port=args.port,
|
|
max_tokens=args.max_tokens,
|
|
reload=args.reload,
|
|
log_level=args.log_level
|
|
)
|
|
else:
|
|
parser.print_help()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|