| """ |
| Describing a machine in terms the maths cares about. |
| |
| The whole job here is to turn "I have a Windows laptop with an RTX 3060 and |
| 16 GB of RAM" into two numbers the advisor can reason about: |
| |
| - fast_budget_gb : memory the model can use *on the fast path* (the GPU, or |
| on Apple Silicon, the shared memory the GPU can borrow) |
| - total_budget_gb: the absolute most a model can use if we let it spill onto |
| ordinary RAM (slower, but it runs) |
| |
| Everything is deliberately conservative. We'd rather say "this might be tight" |
| and be wrong than promise something that then fails to load. |
| """ |
|
|
| from dataclasses import dataclass |
|
|
|
|
| |
| |
| |
| |
| GPU_PRESETS: dict[str, float] = { |
| |
| "NVIDIA RTX 5090 (32 GB)": 32, |
| "NVIDIA RTX 5080 (16 GB)": 16, |
| "NVIDIA RTX 5070 Ti (16 GB)": 16, |
| "NVIDIA RTX 5070 (12 GB)": 12, |
| "NVIDIA RTX 5060 Ti (16 GB)": 16, |
| "NVIDIA RTX 5060 (8 GB)": 8, |
| |
| "NVIDIA RTX 4090 (24 GB)": 24, |
| "NVIDIA RTX 4080 (16 GB)": 16, |
| "NVIDIA RTX 4070 Ti (12 GB)": 12, |
| "NVIDIA RTX 4070 (12 GB)": 12, |
| "NVIDIA RTX 4060 Ti (16 GB)": 16, |
| "NVIDIA RTX 4060 (8 GB)": 8, |
| |
| "NVIDIA RTX 3090 (24 GB)": 24, |
| "NVIDIA RTX 3080 (10 GB)": 10, |
| "NVIDIA RTX 3070 (8 GB)": 8, |
| "NVIDIA RTX 3060 (12 GB)": 12, |
| "NVIDIA RTX 3050 (8 GB)": 8, |
| |
| "NVIDIA GTX 1660 (6 GB)": 6, |
| "NVIDIA GTX 1650 (4 GB)": 4, |
| |
| "AMD RX 7900 XTX (24 GB)": 24, |
| "AMD RX 7800 XT (16 GB)": 16, |
| "AMD RX 7600 (8 GB)": 8, |
| "AMD RX 6700 XT (12 GB)": 12, |
| |
| "Intel built-in graphics (no separate card)": 0, |
| "AMD built-in graphics (no separate card)": 0, |
| } |
|
|
| |
| |
| APPLE_CHIPS: dict[str, int] = { |
| "Apple M1 / M2 / M3 / M4 (base)": 8, |
| "Apple M-series Pro": 16, |
| "Apple M-series Max": 32, |
| "Apple M-series Ultra": 64, |
| } |
|
|
|
|
| @dataclass |
| class HardwareSpec: |
| """A machine, described just enough to reason about it.""" |
|
|
| os: str = "windows" |
| ram_gb: float = 16.0 |
| gpu_vendor: str = "none" |
| vram_gb: float = 0.0 |
| is_apple_silicon: bool = False |
| gpu_label: str = "No dedicated graphics card" |
| form_factor: str = "laptop" |
|
|
| |
|
|
| @property |
| def fast_budget_gb(self) -> float: |
| """Memory available on the *fast* path (GPU / Apple shared memory).""" |
| if self.is_apple_silicon: |
| |
| |
| return round(self.ram_gb * 0.70, 1) |
| if self.gpu_vendor in ("nvidia", "amd") and self.vram_gb > 0: |
| |
| return round(self.vram_gb * 0.85, 1) |
| |
| return 0.0 |
|
|
| @property |
| def os_reserve_gb(self) -> float: |
| """RAM we set aside for the operating system + other open programs. |
| |
| Windows idles heavy; a headless Raspberry Pi barely uses anything. |
| Being honest here matters: too small a reserve over-promises. |
| """ |
| return { |
| "sbc": 1.0, |
| "mac": 3.0, |
| "desktop": 3.0, |
| "laptop": 3.5, |
| }.get(self.form_factor, 3.0) if self.os != "linux" else { |
| "sbc": 1.0, |
| }.get(self.form_factor, 2.0) |
|
|
| @property |
| def total_budget_gb(self) -> float: |
| """The most a model can use if it spills onto ordinary RAM (slower).""" |
| if self.is_apple_silicon: |
| return self.fast_budget_gb |
| |
| |
| ram_for_model = max(0.0, self.ram_gb - self.os_reserve_gb) * 0.9 |
| return round(self.vram_gb + ram_for_model, 1) |
|
|
| @property |
| def has_fast_path(self) -> bool: |
| return self.fast_budget_gb >= 1.0 |
|
|
|
|
| def build_spec( |
| *, |
| computer_kind: str, |
| ram_gb: float, |
| gpu_choice: str, |
| apple_chip: str | None = None, |
| ) -> HardwareSpec: |
| """Turn friendly UI selections into a HardwareSpec. |
| |
| computer_kind: "Windows laptop/desktop", "Mac", "Linux PC", |
| "Raspberry Pi / mini PC" |
| gpu_choice: a key from GPU_PRESETS, or one of the "don't know" options. |
| """ |
| kind = computer_kind.lower() |
|
|
| |
| if "mac" in kind: |
| chip = apple_chip or "Apple M1 / M2 / M3 / M4 (base)" |
| return HardwareSpec( |
| os="macos", |
| ram_gb=ram_gb, |
| gpu_vendor="apple", |
| vram_gb=0.0, |
| is_apple_silicon=True, |
| gpu_label=f"{chip} (shares your {ram_gb:g} GB of memory)", |
| form_factor="mac", |
| ) |
|
|
| |
| if "raspberry" in kind or "mini" in kind or "sbc" in kind: |
| return HardwareSpec( |
| os="linux", |
| ram_gb=ram_gb, |
| gpu_vendor="none", |
| vram_gb=0.0, |
| gpu_label="No dedicated graphics card (tiny computer)", |
| form_factor="sbc", |
| ) |
|
|
| |
| os_name = "linux" if "linux" in kind else "windows" |
| form = "desktop" if "desktop" in kind else "laptop" |
|
|
| vram = GPU_PRESETS.get(gpu_choice, 0.0) |
| if "nvidia" in gpu_choice.lower(): |
| vendor = "nvidia" |
| elif "amd" in gpu_choice.lower() and "built-in" not in gpu_choice.lower(): |
| vendor = "amd" |
| elif "built-in" in gpu_choice.lower(): |
| vendor = "intel" if "intel" in gpu_choice.lower() else "amd" |
| else: |
| vendor = "none" |
|
|
| label = gpu_choice if vram > 0 else "No dedicated graphics card (built-in graphics only)" |
|
|
| return HardwareSpec( |
| os=os_name, |
| ram_gb=ram_gb, |
| gpu_vendor=vendor, |
| vram_gb=vram, |
| is_apple_silicon=False, |
| gpu_label=label, |
| form_factor=form, |
| ) |
|
|