File size: 5,879 Bytes
0e3d4b8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 | """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),
)
|