mirror of
https://github.com/kvcache-ai/ktransformers.git
synced 2026-04-28 20:00:06 +00:00
parent
e7d277d163
commit
d8046e1bb4
65 changed files with 12111 additions and 2502 deletions
311
kt-kernel/python/cli/config/settings.py
Normal file
311
kt-kernel/python/cli/config/settings.py
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
"""
|
||||
Configuration management for kt-cli.
|
||||
|
||||
Handles reading and writing configuration from ~/.ktransformers/config.yaml
|
||||
"""
|
||||
|
||||
import os
|
||||
from pathlib import Path
|
||||
from typing import Any, Optional
|
||||
|
||||
import yaml
|
||||
|
||||
# Default configuration directory
|
||||
DEFAULT_CONFIG_DIR = Path.home() / ".ktransformers"
|
||||
DEFAULT_CONFIG_FILE = DEFAULT_CONFIG_DIR / "config.yaml"
|
||||
DEFAULT_MODELS_DIR = DEFAULT_CONFIG_DIR / "models"
|
||||
DEFAULT_CACHE_DIR = DEFAULT_CONFIG_DIR / "cache"
|
||||
|
||||
# Default configuration values
|
||||
DEFAULT_CONFIG = {
|
||||
"general": {
|
||||
"language": "auto", # auto, en, zh
|
||||
"color": True,
|
||||
"verbose": False,
|
||||
},
|
||||
"paths": {
|
||||
"models": str(DEFAULT_MODELS_DIR),
|
||||
"cache": str(DEFAULT_CACHE_DIR),
|
||||
"weights": "", # Custom quantized weights path
|
||||
},
|
||||
"server": {
|
||||
"host": "0.0.0.0",
|
||||
"port": 30000,
|
||||
},
|
||||
"inference": {
|
||||
# Inference parameters are model-specific and should not have defaults
|
||||
# They will be auto-detected or use model-specific optimizations
|
||||
# Environment variables (general optimizations)
|
||||
"env": {
|
||||
"PYTORCH_ALLOC_CONF": "expandable_segments:True",
|
||||
"SGLANG_ENABLE_JIT_DEEPGEMM": "0",
|
||||
},
|
||||
},
|
||||
"download": {
|
||||
"mirror": "", # HuggingFace mirror URL
|
||||
"resume": True,
|
||||
"verify": True,
|
||||
},
|
||||
"advanced": {
|
||||
# Environment variables to set when running
|
||||
"env": {},
|
||||
# Extra arguments to pass to sglang
|
||||
"sglang_args": [],
|
||||
# Extra arguments to pass to llamafactory
|
||||
"llamafactory_args": [],
|
||||
},
|
||||
"dependencies": {
|
||||
# SGLang installation source configuration
|
||||
"sglang": {
|
||||
"source": "github", # "pypi" or "github"
|
||||
"repo": "https://github.com/kvcache-ai/sglang",
|
||||
"branch": "main",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class Settings:
|
||||
"""Configuration manager for kt-cli."""
|
||||
|
||||
def __init__(self, config_path: Optional[Path] = None):
|
||||
"""Initialize settings manager.
|
||||
|
||||
Args:
|
||||
config_path: Path to config file. Defaults to ~/.ktransformers/config.yaml
|
||||
"""
|
||||
self.config_path = config_path or DEFAULT_CONFIG_FILE
|
||||
self.config_dir = self.config_path.parent
|
||||
self._config: dict[str, Any] = {}
|
||||
self._load()
|
||||
|
||||
def _ensure_dirs(self) -> None:
|
||||
"""Ensure configuration directories exist."""
|
||||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Ensure all model paths exist
|
||||
model_paths = self.get_model_paths()
|
||||
for path in model_paths:
|
||||
path.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
Path(self.get("paths.cache", DEFAULT_CACHE_DIR)).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
def _load(self) -> None:
|
||||
"""Load configuration from file."""
|
||||
self._config = self._deep_copy(DEFAULT_CONFIG)
|
||||
|
||||
if self.config_path.exists():
|
||||
try:
|
||||
with open(self.config_path, "r", encoding="utf-8") as f:
|
||||
user_config = yaml.safe_load(f) or {}
|
||||
self._deep_merge(self._config, user_config)
|
||||
except (yaml.YAMLError, OSError) as e:
|
||||
# Log warning but continue with defaults
|
||||
print(f"Warning: Failed to load config: {e}")
|
||||
|
||||
self._ensure_dirs()
|
||||
|
||||
def _save(self) -> None:
|
||||
"""Save configuration to file."""
|
||||
self._ensure_dirs()
|
||||
try:
|
||||
with open(self.config_path, "w", encoding="utf-8") as f:
|
||||
yaml.dump(self._config, f, default_flow_style=False, allow_unicode=True)
|
||||
except OSError as e:
|
||||
raise RuntimeError(f"Failed to save config: {e}")
|
||||
|
||||
def _deep_copy(self, obj: Any) -> Any:
|
||||
"""Create a deep copy of a nested dict."""
|
||||
if isinstance(obj, dict):
|
||||
return {k: self._deep_copy(v) for k, v in obj.items()}
|
||||
if isinstance(obj, list):
|
||||
return [self._deep_copy(item) for item in obj]
|
||||
return obj
|
||||
|
||||
def _deep_merge(self, base: dict, override: dict) -> None:
|
||||
"""Deep merge override into base."""
|
||||
for key, value in override.items():
|
||||
if key in base and isinstance(base[key], dict) and isinstance(value, dict):
|
||||
self._deep_merge(base[key], value)
|
||||
else:
|
||||
base[key] = value
|
||||
|
||||
def get(self, key: str, default: Any = None) -> Any:
|
||||
"""Get a configuration value by dot-separated key.
|
||||
|
||||
Args:
|
||||
key: Dot-separated key path (e.g., "server.port")
|
||||
default: Default value if key not found
|
||||
|
||||
Returns:
|
||||
Configuration value or default
|
||||
"""
|
||||
parts = key.split(".")
|
||||
value = self._config
|
||||
|
||||
for part in parts:
|
||||
if isinstance(value, dict) and part in value:
|
||||
value = value[part]
|
||||
else:
|
||||
return default
|
||||
|
||||
return value
|
||||
|
||||
def set(self, key: str, value: Any) -> None:
|
||||
"""Set a configuration value by dot-separated key.
|
||||
|
||||
Args:
|
||||
key: Dot-separated key path (e.g., "server.port")
|
||||
value: Value to set
|
||||
"""
|
||||
parts = key.split(".")
|
||||
config = self._config
|
||||
|
||||
# Navigate to parent
|
||||
for part in parts[:-1]:
|
||||
if part not in config:
|
||||
config[part] = {}
|
||||
config = config[part]
|
||||
|
||||
# Set value
|
||||
config[parts[-1]] = value
|
||||
self._save()
|
||||
|
||||
def delete(self, key: str) -> bool:
|
||||
"""Delete a configuration value.
|
||||
|
||||
Args:
|
||||
key: Dot-separated key path
|
||||
|
||||
Returns:
|
||||
True if key was deleted, False if not found
|
||||
"""
|
||||
parts = key.split(".")
|
||||
config = self._config
|
||||
|
||||
# Navigate to parent
|
||||
for part in parts[:-1]:
|
||||
if part not in config:
|
||||
return False
|
||||
config = config[part]
|
||||
|
||||
# Delete key
|
||||
if parts[-1] in config:
|
||||
del config[parts[-1]]
|
||||
self._save()
|
||||
return True
|
||||
return False
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset configuration to defaults."""
|
||||
self._config = self._deep_copy(DEFAULT_CONFIG)
|
||||
self._save()
|
||||
|
||||
def get_all(self) -> dict[str, Any]:
|
||||
"""Get all configuration values."""
|
||||
return self._deep_copy(self._config)
|
||||
|
||||
def get_env_vars(self) -> dict[str, str]:
|
||||
"""Get environment variables to set."""
|
||||
env_vars = {}
|
||||
|
||||
# Get from advanced.env
|
||||
advanced_env = self.get("advanced.env", {})
|
||||
if isinstance(advanced_env, dict):
|
||||
env_vars.update({k: str(v) for k, v in advanced_env.items()})
|
||||
|
||||
return env_vars
|
||||
|
||||
@property
|
||||
def models_dir(self) -> Path:
|
||||
"""Get the primary models directory path (for backward compatibility)."""
|
||||
paths = self.get_model_paths()
|
||||
return paths[0] if paths else Path(DEFAULT_MODELS_DIR)
|
||||
|
||||
def get_model_paths(self) -> list[Path]:
|
||||
"""Get all model directory paths.
|
||||
|
||||
Returns a list of Path objects. Supports both:
|
||||
- Single path: paths.models = "/path/to/models"
|
||||
- Multiple paths: paths.models = ["/path/1", "/path/2"]
|
||||
"""
|
||||
models_config = self.get("paths.models", DEFAULT_MODELS_DIR)
|
||||
|
||||
# Handle both string and list
|
||||
if isinstance(models_config, str):
|
||||
return [Path(models_config)]
|
||||
elif isinstance(models_config, list):
|
||||
return [Path(p) for p in models_config]
|
||||
else:
|
||||
return [Path(DEFAULT_MODELS_DIR)]
|
||||
|
||||
def add_model_path(self, path: str) -> None:
|
||||
"""Add a new model path to the configuration."""
|
||||
models_config = self.get("paths.models", DEFAULT_MODELS_DIR)
|
||||
|
||||
# Convert to list if it's a string
|
||||
if isinstance(models_config, str):
|
||||
paths = [models_config]
|
||||
elif isinstance(models_config, list):
|
||||
paths = list(models_config)
|
||||
else:
|
||||
paths = []
|
||||
|
||||
# Add new path if not already present
|
||||
if path not in paths:
|
||||
paths.append(path)
|
||||
self.set("paths.models", paths)
|
||||
|
||||
def remove_model_path(self, path: str) -> bool:
|
||||
"""Remove a model path from the configuration.
|
||||
|
||||
Returns True if path was removed, False if not found.
|
||||
"""
|
||||
models_config = self.get("paths.models", DEFAULT_MODELS_DIR)
|
||||
|
||||
if isinstance(models_config, str):
|
||||
# Can't remove if it's a single string
|
||||
if models_config == path:
|
||||
# Don't remove the last path
|
||||
return False
|
||||
return False
|
||||
elif isinstance(models_config, list):
|
||||
if path in models_config:
|
||||
paths = list(models_config)
|
||||
paths.remove(path)
|
||||
# Don't allow removing all paths
|
||||
if not paths:
|
||||
return False
|
||||
self.set("paths.models", paths if len(paths) > 1 else paths[0])
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@property
|
||||
def cache_dir(self) -> Path:
|
||||
"""Get the cache directory path."""
|
||||
return Path(self.get("paths.cache", DEFAULT_CACHE_DIR))
|
||||
|
||||
@property
|
||||
def weights_dir(self) -> Optional[Path]:
|
||||
"""Get the custom weights directory path."""
|
||||
weights = self.get("paths.weights", "")
|
||||
return Path(weights) if weights else None
|
||||
|
||||
|
||||
# Global settings instance
|
||||
_settings: Optional[Settings] = None
|
||||
|
||||
|
||||
def get_settings() -> Settings:
|
||||
"""Get the global settings instance."""
|
||||
global _settings
|
||||
if _settings is None:
|
||||
_settings = Settings()
|
||||
return _settings
|
||||
|
||||
|
||||
def reset_settings() -> None:
|
||||
"""Reset the global settings instance."""
|
||||
global _settings
|
||||
_settings = None
|
||||
Loading…
Add table
Add a link
Reference in a new issue