tostido Claude Opus 4.8 commited on
Commit
cff2075
·
1 Parent(s): b99ec83

Align GPU compute across surfaces: shared flavor+cost catalog + OS-search route

Browse files

key_farm/flavors.py is now the single source of truth for compute flavors
(name/kind/hourly_usd/vram). The scaler budget math costs each GPU flavor
from the catalog, and the OS-search route_query() answers gpu/cost/hardware/
flavor queries (previously 'Route not matched').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

key_farm/flavors.py ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Canonical compute-flavor catalog — ONE source of truth across all surfaces.
2
+
3
+ Consumed by:
4
+ - ``key_farm.scaler.ScalePolicy`` -> per-flavor hourly cost for the budget math
5
+ - ``meshscale_space.os_routes.route_query`` -> the OS-search "gpu"/"cost"/"hardware" route
6
+ - KEY controller/config -> GPU lane selection (``gpu_hardware`` / ``MESHSCALE_GPU_HARDWARE``)
7
+
8
+ Keep hardware facts HERE and nowhere else so every surface stays aligned.
9
+ Rates are approximate HF Spaces/Jobs list price (USD/hour, 2026); vram/ram are
10
+ approximate host specs for guidance, not billing. cpu-upgrade 0.03 matches the
11
+ existing ``cpu_space_hourly_usd`` default.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class Flavor:
20
+ name: str
21
+ kind: str # "cpu" or "gpu"
22
+ hourly_usd: float
23
+ vram_gb: int # 0 for CPU flavors
24
+ ram_gb: int
25
+ note: str = ""
26
+
27
+
28
+ FLAVORS: dict[str, Flavor] = {
29
+ f.name: f
30
+ for f in [
31
+ Flavor("cpu-basic", "cpu", 0.00, 0, 16, "2 vCPU - free tier"),
32
+ Flavor("cpu-upgrade", "cpu", 0.03, 0, 32, "8 vCPU - default farm worker"),
33
+ Flavor("t4-small", "gpu", 0.40, 16, 15, "NVIDIA T4 - cheapest GPU"),
34
+ Flavor("t4-medium", "gpu", 0.60, 16, 30, "NVIDIA T4 - more host RAM"),
35
+ Flavor("l4x1", "gpu", 0.80, 24, 48, "NVIDIA L4"),
36
+ Flavor("a10g-small", "gpu", 1.00, 24, 30, "NVIDIA A10G"),
37
+ Flavor("a10g-large", "gpu", 1.50, 24, 92, "NVIDIA A10G - large host"),
38
+ Flavor("a100-large", "gpu", 2.50, 80, 142, "NVIDIA A100 80GB - heavy brains fit"),
39
+ Flavor("rtx-pro-6000", "gpu", 2.75, 96, 188, "RTX PRO 6000 - 96GB VRAM"),
40
+ Flavor("h100", "gpu", 4.50, 80, 250, "NVIDIA H100"),
41
+ Flavor("h200", "gpu", 5.00, 141, 276, "NVIDIA H200 - 141GB VRAM"),
42
+ ]
43
+ }
44
+
45
+
46
+ def get_flavor(name: str | None) -> Flavor | None:
47
+ if not name:
48
+ return None
49
+ return FLAVORS.get(str(name).strip().lower())
50
+
51
+
52
+ def list_flavors(kind: str | None = None) -> list[Flavor]:
53
+ """Flavors sorted cheapest-first, optionally filtered to 'cpu' or 'gpu'."""
54
+ fs = [f for f in FLAVORS.values() if kind is None or f.kind == kind]
55
+ return sorted(fs, key=lambda f: f.hourly_usd)
56
+
57
+
58
+ def hourly_cost(name: str | None, default: float = 0.0) -> float:
59
+ """Catalog hourly rate for a flavor; ``default`` if the flavor is unknown."""
60
+ f = get_flavor(name)
61
+ return f.hourly_usd if f else default
62
+
63
+
64
+ def cheapest_gpu() -> Flavor:
65
+ return list_flavors("gpu")[0]
66
+
67
+
68
+ def workers_within_budget(name: str | None, budget_hourly_usd: float, default_cost: float = 0.0) -> int:
69
+ """How many concurrent workers of ``name`` fit under an hourly budget."""
70
+ cost = hourly_cost(name, default_cost)
71
+ if cost <= 0:
72
+ return 0
73
+ return int(budget_hourly_usd // cost)
key_farm/scaler.py CHANGED
@@ -53,7 +53,14 @@ class ScalePolicy:
53
 
54
  def _effective_hourly_cost(self) -> float:
55
  if self.gpu_hardware:
56
- return self.gpu_space_hourly_usd
 
 
 
 
 
 
 
57
  return self.cpu_space_hourly_usd
58
 
59
  def effective_max_workers(self) -> int:
 
53
 
54
  def _effective_hourly_cost(self) -> float:
55
  if self.gpu_hardware:
56
+ # Prefer the canonical catalog rate for the chosen flavor so the
57
+ # budget math stays aligned with every other surface; fall back to
58
+ # the policy's explicit gpu_space_hourly_usd for unknown flavors.
59
+ try:
60
+ from .flavors import hourly_cost
61
+ return hourly_cost(self.gpu_hardware, self.gpu_space_hourly_usd)
62
+ except Exception:
63
+ return self.gpu_space_hourly_usd
64
  return self.cpu_space_hourly_usd
65
 
66
  def effective_max_workers(self) -> int:
meshscale_space/os_routes.py CHANGED
@@ -144,6 +144,42 @@ def route_query(query: str, status: dict[str, Any], links: dict[str, str] | None
144
  ],
145
  }
146
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  return {
148
  "title": "Route not matched",
149
  "message": "The OS route did not find a direct match. Start with status or ask for a guide.",
 
144
  ],
145
  }
146
 
147
+ if any(token in q for token in (
148
+ "gpu", "cpu-upgrade", "hardware", "flavor", "accelerator", "cost",
149
+ "price", "budget", "rent", "rental", "cuda", "compute", "a100", "t4",
150
+ "h200", "h100", "l4", "a10g", "rtx",
151
+ )):
152
+ try:
153
+ from key_farm.flavors import list_flavors, cheapest_gpu
154
+ cpus = list_flavors("cpu")
155
+ gpus = list_flavors("gpu")
156
+ cheap = cheapest_gpu()
157
+ cpu_str = ", ".join(f"{f.name} ${f.hourly_usd:.2f}/hr" for f in cpus)
158
+ gpu_str = ", ".join(f"{f.name} ${f.hourly_usd:.2f}/hr" for f in gpus)
159
+ message = (
160
+ f"CPU flavors: {cpu_str}. GPU flavors: {gpu_str}. "
161
+ f"Cheapest GPU is {cheap.name} (${cheap.hourly_usd:.2f}/hr, {cheap.vram_gb}GB VRAM). "
162
+ "Route workers onto a GPU by setting gpu_hardware in the KEY run config "
163
+ "or MESHSCALE_GPU_HARDWARE on this Space; the budget then caps the worker count."
164
+ )
165
+ except Exception:
166
+ message = (
167
+ "Compute flavors: cpu-upgrade $0.03/hr (default), GPUs from t4-small "
168
+ "$0.40/hr up to h200 $5.00/hr. Set gpu_hardware / MESHSCALE_GPU_HARDWARE "
169
+ "to route workers onto a GPU; budget caps the count."
170
+ )
171
+ return {
172
+ "title": "Compute / GPU routes",
173
+ "message": message,
174
+ "severity": "info",
175
+ "actions": [
176
+ _action("command", "Plan capacity", "plan 420", "Estimate worker count under the budget for the chosen flavor."),
177
+ _action("command", "Dry-run scale", "scale dry", "Preview worker starts and the flavor the scaler will launch on."),
178
+ _action("link", "Worker template", links["worker_template"], "The image GPU/CPU workers run."),
179
+ _action("link", "Open KEY Space", links["key_space"], "Set gpu_hardware in the run config."),
180
+ ],
181
+ }
182
+
183
  return {
184
  "title": "Route not matched",
185
  "message": "The OS route did not find a direct match. Start with status or ask for a guide.",