2026-07-23 05:47:43 +00:00
|
|
|
from __future__ import annotations
|
|
|
|
|
|
2026-07-26 09:05:38 +00:00
|
|
|
from datetime import UTC, datetime
|
2026-07-23 05:47:43 +00:00
|
|
|
import json
|
|
|
|
|
import math
|
|
|
|
|
import os
|
2026-07-26 09:05:38 +00:00
|
|
|
import time
|
|
|
|
|
import urllib.error
|
2026-07-23 05:47:43 +00:00
|
|
|
import urllib.parse
|
|
|
|
|
import urllib.request
|
2026-07-26 09:05:38 +00:00
|
|
|
from dataclasses import asdict, dataclass, field
|
|
|
|
|
from typing import Any, Iterable, Mapping
|
2026-07-23 05:47:43 +00:00
|
|
|
|
|
|
|
|
from airfrans_frontier.remote.config import RemoteRunConfig, SelectionConfig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class VastOffer:
|
|
|
|
|
id: int
|
|
|
|
|
gpu_name: str
|
|
|
|
|
dph_total: float
|
|
|
|
|
gpu_ram: float | None
|
2026-07-26 09:05:38 +00:00
|
|
|
disk_space: float | None
|
2026-07-23 05:47:43 +00:00
|
|
|
geolocation: str | None
|
|
|
|
|
inet_down_cost_per_tb: float
|
|
|
|
|
inet_up_cost_per_tb: float
|
|
|
|
|
host_id: int | None
|
|
|
|
|
verification: str | None
|
|
|
|
|
reliability2: float | None
|
|
|
|
|
cuda_max_good: float | None
|
|
|
|
|
direct_port_count: int | None
|
|
|
|
|
inet_down: float | None
|
|
|
|
|
inet_up: float | None
|
|
|
|
|
verified: bool | None
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_mapping(cls, data: Mapping[str, Any]) -> VastOffer:
|
|
|
|
|
return cls(
|
|
|
|
|
id=_int(data, "id"),
|
|
|
|
|
gpu_name=_string(data, "gpu_name"),
|
|
|
|
|
dph_total=_float(data, "dph_total"),
|
|
|
|
|
gpu_ram=_optional_float(data, "gpu_ram"),
|
2026-07-26 09:05:38 +00:00
|
|
|
disk_space=_optional_float(data, "disk_space"),
|
2026-07-23 05:47:43 +00:00
|
|
|
geolocation=_optional_string(data, "geolocation"),
|
|
|
|
|
inet_down_cost_per_tb=_optional_float(data, "internet_down_cost_per_tb") or 0.0,
|
|
|
|
|
inet_up_cost_per_tb=_optional_float(data, "internet_up_cost_per_tb") or 0.0,
|
|
|
|
|
host_id=_optional_int(data, "host_id"),
|
|
|
|
|
verification=_optional_string(data, "verification"),
|
|
|
|
|
reliability2=_optional_float(data, "reliability2"),
|
|
|
|
|
cuda_max_good=_optional_float(data, "cuda_max_good"),
|
|
|
|
|
direct_port_count=_optional_int(data, "direct_port_count"),
|
|
|
|
|
inet_down=_optional_float(data, "inet_down"),
|
|
|
|
|
inet_up=_optional_float(data, "inet_up"),
|
|
|
|
|
verified=_optional_bool(data, "verified"),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
|
|
|
class SelectionResult:
|
|
|
|
|
selected_offer: VastOffer
|
|
|
|
|
candidate_count: int
|
|
|
|
|
survivor_count: int
|
|
|
|
|
effective_price: float
|
|
|
|
|
query: dict[str, Any]
|
|
|
|
|
policy: dict[str, Any]
|
2026-07-26 09:05:38 +00:00
|
|
|
created_at: float = field(default_factory=time.time)
|
2026-07-23 05:47:43 +00:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def selected_offer_id(self) -> int:
|
|
|
|
|
return self.selected_offer.id
|
|
|
|
|
|
|
|
|
|
def to_manifest(self) -> dict[str, Any]:
|
|
|
|
|
offer = asdict(self.selected_offer)
|
|
|
|
|
offer["effective_price"] = self.effective_price
|
2026-07-26 09:05:38 +00:00
|
|
|
now = time.time()
|
2026-07-23 05:47:43 +00:00
|
|
|
return {
|
|
|
|
|
"selected_offer_id": self.selected_offer_id,
|
|
|
|
|
"selected_offer": offer,
|
|
|
|
|
"candidate_count": self.candidate_count,
|
|
|
|
|
"survivor_count": self.survivor_count,
|
|
|
|
|
"query": self.query,
|
|
|
|
|
"policy": self.policy,
|
2026-07-26 09:05:38 +00:00
|
|
|
"created_at": self.created_at,
|
|
|
|
|
"created_at_iso": datetime.fromtimestamp(self.created_at, UTC).isoformat(),
|
|
|
|
|
"age_seconds": max(0.0, now - self.created_at),
|
2026-07-23 05:47:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def select_offer(config: RemoteRunConfig, *, api_key: str | None = None) -> SelectionResult:
|
|
|
|
|
if config.provider.kind != "vastai":
|
|
|
|
|
raise ValueError(f"Unsupported provider: {config.provider.kind}")
|
|
|
|
|
resolved_key = api_key or os.environ.get("VAST_API_KEY")
|
|
|
|
|
if not resolved_key:
|
|
|
|
|
raise RuntimeError("VAST_API_KEY is required for Vast.ai offer selection")
|
|
|
|
|
|
|
|
|
|
query = build_query(config)
|
|
|
|
|
offers = search_offers(
|
|
|
|
|
base_url=config.selection.base_url,
|
|
|
|
|
api_key=resolved_key,
|
|
|
|
|
query=query,
|
|
|
|
|
)
|
|
|
|
|
return choose_offer(offers, config, query=query)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def build_query(config: RemoteRunConfig) -> dict[str, Any]:
|
|
|
|
|
selection = config.selection
|
|
|
|
|
provider = config.provider
|
|
|
|
|
query: dict[str, Any] = {
|
|
|
|
|
"rentable": {"eq": True},
|
|
|
|
|
"rented": {"eq": False},
|
|
|
|
|
"reliability2": {"gte": selection.min_reliability},
|
|
|
|
|
"cuda_max_good": {"gte": 12.6},
|
|
|
|
|
"direct_port_count": {"gte": 1},
|
|
|
|
|
"num_gpus": {"eq": provider.gpu.count},
|
|
|
|
|
"inet_down": {"gte": selection.min_down_mbps},
|
|
|
|
|
"limit": 5000,
|
|
|
|
|
}
|
|
|
|
|
if selection.min_up_mbps is not None:
|
|
|
|
|
query["inet_up"] = {"gte": selection.min_up_mbps}
|
|
|
|
|
if selection.require_verified:
|
|
|
|
|
query["verified"] = {"eq": True}
|
|
|
|
|
if provider.gpu.min_vram_gb is not None:
|
|
|
|
|
query["gpu_ram"] = {"gte": provider.gpu.min_vram_gb * 1024}
|
2026-07-26 09:05:38 +00:00
|
|
|
query["disk_space"] = {"gte": provider.disk_gb}
|
2026-07-23 05:47:43 +00:00
|
|
|
if provider.gpu.name:
|
|
|
|
|
query["gpu_name"] = {"eq": provider.gpu.name}
|
|
|
|
|
return query
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def search_offers(*, base_url: str, api_key: str, query: Mapping[str, Any]) -> list[VastOffer]:
|
|
|
|
|
encoded = urllib.parse.quote(json.dumps(query, separators=(",", ":")))
|
|
|
|
|
url = f"{base_url.rstrip('/')}/api/v0/bundles/?q={encoded}"
|
|
|
|
|
request = urllib.request.Request(url, headers={"Authorization": f"Bearer {api_key}"})
|
|
|
|
|
try:
|
|
|
|
|
with urllib.request.urlopen(request, timeout=45) as response:
|
|
|
|
|
payload = json.loads(response.read().decode("utf-8"))
|
|
|
|
|
except urllib.error.HTTPError as exc:
|
|
|
|
|
body = exc.read().decode("utf-8", errors="replace")
|
|
|
|
|
raise RuntimeError(f"Vast offer search HTTP {exc.code}: {body}") from exc
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
raise RuntimeError(f"Vast offer search failed: {exc}") from exc
|
|
|
|
|
|
|
|
|
|
raw_offers = payload.get("offers")
|
|
|
|
|
if not isinstance(raw_offers, list):
|
|
|
|
|
raise RuntimeError("Vast offer search response missing offers list")
|
|
|
|
|
return [VastOffer.from_mapping(item) for item in raw_offers if isinstance(item, Mapping)]
|
|
|
|
|
|
2026-07-26 09:05:38 +00:00
|
|
|
def list_instances(*, base_url: str, api_key: str) -> list[dict[str, Any]]:
|
|
|
|
|
payload = _vast_api_json_request(
|
|
|
|
|
base_url=base_url,
|
|
|
|
|
api_key=api_key,
|
|
|
|
|
path="/api/v0/instances/",
|
|
|
|
|
method="GET",
|
|
|
|
|
)
|
|
|
|
|
return _instances_from_payload(payload)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def destroy_instance(*, base_url: str, api_key: str, instance_id: int) -> Any:
|
|
|
|
|
return _vast_api_json_request(
|
|
|
|
|
base_url=base_url,
|
|
|
|
|
api_key=api_key,
|
|
|
|
|
path=f"/api/v0/instances/{int(instance_id)}/",
|
|
|
|
|
method="DELETE",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def summarize_instances(instances: list[Mapping[str, Any]]) -> list[dict[str, Any]]:
|
|
|
|
|
fields = (
|
|
|
|
|
"id",
|
|
|
|
|
"instance_id",
|
|
|
|
|
"machine_id",
|
|
|
|
|
"host_id",
|
|
|
|
|
"label",
|
|
|
|
|
"status",
|
|
|
|
|
"actual_status",
|
|
|
|
|
"gpu_name",
|
|
|
|
|
"num_gpus",
|
|
|
|
|
"dph_total",
|
|
|
|
|
"ssh_host",
|
|
|
|
|
"ssh_port",
|
|
|
|
|
"start_date",
|
|
|
|
|
)
|
|
|
|
|
summaries: list[dict[str, Any]] = []
|
|
|
|
|
for instance in instances:
|
|
|
|
|
summary = {field: instance[field] for field in fields if field in instance}
|
|
|
|
|
summaries.append(summary)
|
|
|
|
|
return summaries
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _vast_api_json_request(*, base_url: str, api_key: str, path: str, method: str) -> Any:
|
|
|
|
|
url = f"{base_url.rstrip('/')}/{path.lstrip('/')}"
|
|
|
|
|
request = urllib.request.Request(url, headers={"Authorization": f"Bearer {api_key}"}, method=method)
|
|
|
|
|
try:
|
|
|
|
|
with urllib.request.urlopen(request, timeout=45) as response:
|
|
|
|
|
return json.loads(response.read().decode("utf-8"))
|
|
|
|
|
except urllib.error.HTTPError as exc:
|
|
|
|
|
body = exc.read().decode("utf-8", errors="replace")
|
|
|
|
|
raise RuntimeError(f"Vast API {method} {path} HTTP {exc.code}: {body}") from exc
|
|
|
|
|
except OSError as exc:
|
|
|
|
|
raise RuntimeError(f"Vast API {method} {path} failed: {exc}") from exc
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _instances_from_payload(payload: Any) -> list[dict[str, Any]]:
|
|
|
|
|
if isinstance(payload, list):
|
|
|
|
|
raw_instances = payload
|
|
|
|
|
elif isinstance(payload, Mapping):
|
|
|
|
|
raw_instances = None
|
|
|
|
|
for key in ("instances", "results", "items"):
|
|
|
|
|
value = payload.get(key)
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
raw_instances = value
|
|
|
|
|
break
|
|
|
|
|
if raw_instances is None:
|
|
|
|
|
raise RuntimeError("Vast instances response missing instances list")
|
|
|
|
|
else:
|
|
|
|
|
raise RuntimeError("Vast instances response is not JSON object or list")
|
|
|
|
|
return [dict(item) for item in raw_instances if isinstance(item, Mapping)]
|
|
|
|
|
|
2026-07-23 05:47:43 +00:00
|
|
|
|
2026-07-26 09:05:38 +00:00
|
|
|
def choose_offer(
|
|
|
|
|
offers: list[VastOffer],
|
|
|
|
|
config: RemoteRunConfig,
|
|
|
|
|
*,
|
|
|
|
|
query: Mapping[str, Any],
|
|
|
|
|
reserved_host_ids: Iterable[int] = (),
|
|
|
|
|
allow_reserved_hosts: bool = False,
|
|
|
|
|
) -> SelectionResult:
|
2026-07-23 05:47:43 +00:00
|
|
|
survivors = reachable_offers(offers, config.selection)
|
|
|
|
|
ranked = rank_survivors(survivors, config.selection)
|
|
|
|
|
if config.provider.max_price_per_hour is not None:
|
|
|
|
|
ranked = [offer for offer in ranked if effective_price(offer, config.selection) <= config.provider.max_price_per_hour]
|
2026-07-26 09:05:38 +00:00
|
|
|
reserved_hosts = set(reserved_host_ids)
|
|
|
|
|
if reserved_hosts and not allow_reserved_hosts:
|
|
|
|
|
ranked = [offer for offer in ranked if offer.host_id is None or offer.host_id not in reserved_hosts]
|
2026-07-23 05:47:43 +00:00
|
|
|
if not ranked:
|
2026-07-26 09:05:38 +00:00
|
|
|
raise RuntimeError("No Vast offers survived quality, price, and host anti-collision filters")
|
2026-07-23 05:47:43 +00:00
|
|
|
selected = ranked[0]
|
2026-07-26 09:05:38 +00:00
|
|
|
policy = selection_policy_manifest(config)
|
|
|
|
|
policy["reserved_host_ids"] = sorted(reserved_hosts)
|
|
|
|
|
policy["allow_reserved_hosts"] = bool(allow_reserved_hosts)
|
2026-07-23 05:47:43 +00:00
|
|
|
return SelectionResult(
|
|
|
|
|
selected_offer=selected,
|
|
|
|
|
candidate_count=len(offers),
|
|
|
|
|
survivor_count=len(ranked),
|
|
|
|
|
effective_price=effective_price(selected, config.selection),
|
|
|
|
|
query=dict(query),
|
2026-07-26 09:05:38 +00:00
|
|
|
policy=policy,
|
2026-07-23 05:47:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reachable_offers(offers: list[VastOffer], selection: SelectionConfig) -> list[VastOffer]:
|
|
|
|
|
blacklist = set(selection.blacklist_hosts)
|
|
|
|
|
blocked = tuple(item.upper() for item in selection.blocked_geos)
|
|
|
|
|
result: list[VastOffer] = []
|
|
|
|
|
for offer in offers:
|
|
|
|
|
geo = (offer.geolocation or "").upper()
|
|
|
|
|
if blocked and any(token and token in geo for token in blocked):
|
|
|
|
|
continue
|
|
|
|
|
if offer.host_id is not None and offer.host_id in blacklist:
|
|
|
|
|
continue
|
|
|
|
|
if offer.verification == "deverified":
|
|
|
|
|
continue
|
|
|
|
|
result.append(offer)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def rank_survivors(offers: list[VastOffer], selection: SelectionConfig) -> list[VastOffer]:
|
|
|
|
|
by_model: dict[str, list[VastOffer]] = {}
|
|
|
|
|
for offer in offers:
|
|
|
|
|
by_model.setdefault(offer.gpu_name, []).append(offer)
|
|
|
|
|
|
|
|
|
|
survivors: list[VastOffer] = []
|
|
|
|
|
for group in by_model.values():
|
|
|
|
|
group.sort(key=lambda offer: effective_price(offer, selection))
|
|
|
|
|
drop = math.floor(selection.drop_cheap_frac * len(group))
|
|
|
|
|
survivors.extend(group[drop:])
|
|
|
|
|
survivors.sort(key=lambda offer: effective_price(offer, selection))
|
|
|
|
|
return survivors
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def effective_price(offer: VastOffer, selection: SelectionConfig) -> float:
|
|
|
|
|
image_pull = 0.0
|
|
|
|
|
if selection.image_size_gb is not None:
|
|
|
|
|
image_pull = selection.image_size_gb * offer.inet_down_cost_per_tb / 1000.0
|
|
|
|
|
return offer.dph_total + image_pull
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def selection_policy_manifest(config: RemoteRunConfig) -> dict[str, Any]:
|
|
|
|
|
return {
|
|
|
|
|
"gpu_name": config.provider.gpu.name,
|
|
|
|
|
"gpu_count": config.provider.gpu.count,
|
|
|
|
|
"min_vram_gb": config.provider.gpu.min_vram_gb,
|
|
|
|
|
"max_price_per_hour": config.provider.max_price_per_hour,
|
|
|
|
|
"min_reliability": config.selection.min_reliability,
|
|
|
|
|
"min_down_mbps": config.selection.min_down_mbps,
|
|
|
|
|
"min_up_mbps": config.selection.min_up_mbps,
|
|
|
|
|
"require_verified": config.selection.require_verified,
|
|
|
|
|
"blocked_geos": list(config.selection.blocked_geos),
|
|
|
|
|
"blacklist_hosts": list(config.selection.blacklist_hosts),
|
|
|
|
|
"drop_cheap_frac": config.selection.drop_cheap_frac,
|
|
|
|
|
"image_size_gb": config.selection.image_size_gb,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _string(data: Mapping[str, Any], key: str) -> str:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
if not isinstance(value, str):
|
|
|
|
|
raise ValueError(f"Vast offer missing string field: {key}")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _optional_string(data: Mapping[str, Any], key: str) -> str | None:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
return value if isinstance(value, str) else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _int(data: Mapping[str, Any], key: str) -> int:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
if isinstance(value, bool) or not isinstance(value, int):
|
|
|
|
|
raise ValueError(f"Vast offer missing integer field: {key}")
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _optional_int(data: Mapping[str, Any], key: str) -> int | None:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
return None
|
|
|
|
|
if isinstance(value, int):
|
|
|
|
|
return value
|
|
|
|
|
if isinstance(value, float) and value.is_integer():
|
|
|
|
|
return int(value)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _float(data: Mapping[str, Any], key: str) -> float:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
|
|
|
raise ValueError(f"Vast offer missing numeric field: {key}")
|
|
|
|
|
return float(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _optional_float(data: Mapping[str, Any], key: str) -> float | None:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
return None
|
|
|
|
|
if isinstance(value, (int, float)):
|
|
|
|
|
return float(value)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _optional_bool(data: Mapping[str, Any], key: str) -> bool | None:
|
|
|
|
|
value = data.get(key)
|
|
|
|
|
return value if isinstance(value, bool) else None
|