"""Configuration and hardware detection for SplitBit LLM.""" from __future__ import annotations import logging import os import platform from dataclasses import dataclass, field from enum import Enum from typing import Any logger = logging.getLogger(__name__) try: import psutil except ImportError: psutil = None # type: ignore[assignment] class HardwareTier(str, Enum): MOBILE = "mobile" MINIMAL = "minimal" LIGHT = "light" STANDARD = "standard" FULL = "full" MAXIMUM = "maximum" DATACENTER = "datacenter" SUPERCOMPUTER = "supercomputer" @dataclass class ModelConfig: """Model architecture config — auto-adjusted per hardware tier.""" n_layers: int = 3 n_heads: int = 4 d_model: int = 256 d_ff: int = 512 vocab_size: int = 4096 max_seq_len: int = 512 dropout: float = 0.0 @property def d_head(self) -> int: return self.d_model // self.n_heads @property def param_count(self) -> int: """Rough parameter count estimate.""" emb = self.vocab_size * self.d_model attn = 4 * self.d_model * self.d_model * self.n_layers ffn = 2 * self.d_model * self.d_ff * self.n_layers ln = 2 * self.d_model * self.n_layers return emb + attn + ffn + ln @dataclass class QuantConfig: """SplitBit weight quantization config.""" format: str = "q4_k_m" bpw: float = 4.0 @staticmethod def for_tier(tier: HardwareTier) -> "QuantConfig": formats = { HardwareTier.MOBILE: ("ternary", 1.585), HardwareTier.MINIMAL: ("q2_k", 2.0), HardwareTier.LIGHT: ("q3_k_s", 3.0), HardwareTier.STANDARD: ("q4_k_m", 4.0), HardwareTier.FULL: ("q5_k_m", 5.0), HardwareTier.MAXIMUM: ("q8_0", 8.0), HardwareTier.DATACENTER: ("fp8_e4m3", 8.0), HardwareTier.SUPERCOMPUTER: ("fp16", 16.0), } fmt, bpw = formats.get(tier, ("q4_k_m", 4.0)) return QuantConfig(format=fmt, bpw=bpw) @dataclass class StorageConfig: """Storage paths and limits.""" data_dir: str = os.path.expanduser("~/.splitbit-llm") skill_storage_mb: int = 200 max_skills: int = 2000 @staticmethod def for_tier(tier: HardwareTier) -> "StorageConfig": limits = { HardwareTier.MOBILE: (50, 500), HardwareTier.MINIMAL: (200, 2000), HardwareTier.LIGHT: (1000, 10000), HardwareTier.STANDARD: (5000, 50000), HardwareTier.FULL: (20000, 200000), HardwareTier.MAXIMUM: (50000, 500000), HardwareTier.DATACENTER: (200000, 2000000), HardwareTier.SUPERCOMPUTER: (1000000, 10000000), } mb, skills = limits.get(tier, (200, 2000)) return StorageConfig(skill_storage_mb=mb, max_skills=skills) def detect_hardware() -> HardwareTier: """Detect hardware tier based on available RAM, CPU, and GPU.""" ram_mb = 0 cpu_cores = os.cpu_count() or 1 if psutil: ram_mb = psutil.virtual_memory().available // (1024 * 1024) cpu_cores = psutil.cpu_count() or cpu_cores gpu_vram_mb = 0 try: import torch if torch.cuda.is_available(): gpu_vram_mb = torch.cuda.get_device_properties(0).total_memory // (1024 * 1024) except ImportError: pass is_mobile = platform.machine().startswith(("arm", "aarch")) and ram_mb < 2048 if is_mobile or ram_mb < 2048: return HardwareTier.MOBILE if ram_mb < 4096: return HardwareTier.MINIMAL if ram_mb < 8192: return HardwareTier.LIGHT if ram_mb < 16384 or gpu_vram_mb < 6144: return HardwareTier.STANDARD if ram_mb < 32768 or gpu_vram_mb < 12288: return HardwareTier.FULL if ram_mb < 65536: return HardwareTier.MAXIMUM if ram_mb < 262144: return HardwareTier.DATACENTER return HardwareTier.SUPERCOMPUTER def get_model_config(tier: HardwareTier) -> ModelConfig: """Get optimal model config for a hardware tier.""" configs = { HardwareTier.MOBILE: ModelConfig(n_layers=2, n_heads=4, d_model=128, d_ff=256, vocab_size=2048, max_seq_len=256), HardwareTier.MINIMAL: ModelConfig(n_layers=3, n_heads=4, d_model=256, d_ff=512, vocab_size=4096, max_seq_len=512), HardwareTier.LIGHT: ModelConfig(n_layers=4, n_heads=8, d_model=384, d_ff=768, vocab_size=8192, max_seq_len=1024), HardwareTier.STANDARD: ModelConfig(n_layers=6, n_heads=8, d_model=512, d_ff=1024, vocab_size=16384, max_seq_len=2048), HardwareTier.FULL: ModelConfig(n_layers=8, n_heads=16, d_model=768, d_ff=2048, vocab_size=32000, max_seq_len=4096), HardwareTier.MAXIMUM: ModelConfig(n_layers=12, n_heads=16, d_model=1024, d_ff=3072, vocab_size=64000, max_seq_len=8192), HardwareTier.DATACENTER: ModelConfig(n_layers=24, n_heads=32, d_model=2048, d_ff=6144, vocab_size=128000, max_seq_len=16384), HardwareTier.SUPERCOMPUTER: ModelConfig(n_layers=48, n_heads=64, d_model=4096, d_ff=12288, vocab_size=128000, max_seq_len=32768), } return configs.get(tier, configs[HardwareTier.MINIMAL]) @dataclass class Settings: """Global settings for SplitBit LLM.""" tier: HardwareTier = field(default_factory=detect_hardware) model: ModelConfig = field(default_factory=lambda: get_model_config(detect_hardware())) quant: QuantConfig = field(default_factory=lambda: QuantConfig.for_tier(detect_hardware())) storage: StorageConfig = field(default_factory=lambda: StorageConfig.for_tier(detect_hardware())) @staticmethod def from_env() -> "Settings": tier = detect_hardware() return Settings( tier=tier, model=get_model_config(tier), quant=QuantConfig.for_tier(tier), storage=StorageConfig.for_tier(tier), )