11import logging
22import os
3+ from typing import cast
34
45import requests
56from requests import Response
7+ from typing_extensions import NotRequired , TypedDict
68
7- from gpuhunt ._internal .models import AcceleratorVendor , QueryFilter , RawCatalogItem
9+ from gpuhunt ._internal .models import AcceleratorVendor , JSONObject , QueryFilter , RawCatalogItem
810from gpuhunt .providers import AbstractProvider
911
1012logger = logging .getLogger (__name__ )
1113
12- API_URL = "https://backendprod .jarvislabs.net"
14+ API_URL = "https://backendn .jarvislabs.net"
1315SERVER_META_PATH = "/misc/server_meta"
1416TIMEOUT = 30
1517# JarvisLabs exposes offer regions in server_meta, but VM provisioning calls must be sent
1820# unknown regions, otherwise dstack may select capacity it cannot create.
1921JARVISLABS_REGION_URLS = {
2022 "india-01" : "https://backendprod.jarvislabs.net" ,
23+ "india-chennai-01" : "https://backendc.jarvislabs.net" ,
2124 "india-noida-01" : "https://backendn.jarvislabs.net" ,
2225 "europe-01" : "https://backendeu.jarvislabs.net" ,
2326}
24- # dstack provisions JarvisLabs GPU VMs by passing a GPU type back to the API.
25- # Keep ambiguous API names with spaces out of the catalog; otherwise the
26- # normalized gpuhunt name cannot be converted back safely without provider_data .
27+ # Explicit mappings for human-reviewed JarvisLabs GPU tokens that differ from
28+ # gpuhunt canonical GPU names. Keep unmapped spaced names out of the catalog so
29+ # new provider tokens do not get normalized incorrectly and silently .
2730JARVISLABS_GPU_NAME_OVERRIDES = {
2831 "A100-80GB" : ("A100" , 80.0 ),
32+ "RTX-PRO6000" : ("RTXPRO6000" , 96.0 ),
33+ "RTX PRO 6000" : ("RTXPRO6000" , 96.0 ),
2934}
3035
3136
37+ class JarvisLabsCatalogItemProviderData (TypedDict ):
38+ # Original JarvisLabs API GPU type, set only when gpuhunt normalization loses
39+ # the create-time token, e.g. A100-80GB -> A100 or RTX-PRO6000 -> RTXPRO6000.
40+ # dstack uses this value for VM creation.
41+ gpu_type : NotRequired [str ]
42+
43+
3244class JarvisLabsProvider (AbstractProvider ):
3345 NAME = "jarvislabs"
3446
@@ -97,7 +109,7 @@ def _make_gpu_catalog_items(gpu: dict) -> list[RawCatalogItem]:
97109
98110 gpu_spec = _gpu_name_and_memory (gpu_type , gpu .get ("vram" ))
99111 if gpu_spec is None :
100- logger .warning ("Skipping JarvisLabs GPU offer with ambiguous gpu_type: %s" , gpu_type )
112+ logger .warning ("Skipping JarvisLabs GPU offer with unmapped gpu_type: %s" , gpu_type )
101113 return []
102114 gpu_name , gpu_memory = gpu_spec
103115 if gpu_memory is None :
@@ -119,24 +131,12 @@ def _make_gpu_catalog_items(gpu: dict) -> list[RawCatalogItem]:
119131 ram_per_gpu = ram_per_gpu ,
120132 available_devices = _available_devices (gpu ),
121133 max_gpus_per_instance = _max_gpus_per_instance (gpu ),
134+ provider_data = _gpu_provider_data (gpu_type , gpu_name ),
122135 spot = False ,
123136 )
124137
125- spot_price = _as_float (gpu .get ("spot_price" ))
126- if spot_price is not None :
127- items .extend (
128- _make_gpu_catalog_items_for_price (
129- region = region ,
130- gpu_name = gpu_name ,
131- gpu_memory = gpu_memory ,
132- price = spot_price ,
133- cpu_per_gpu = cpu_per_gpu ,
134- ram_per_gpu = ram_per_gpu ,
135- available_devices = _spot_available_devices (gpu ),
136- max_gpus_per_instance = _max_gpus_per_instance (gpu ),
137- spot = True ,
138- )
139- )
138+ # JarvisLabs supports spot for containers/templates, not VMs. This provider
139+ # only publishes VM-capable offers because dstack provisions JarvisLabs VMs.
140140 return items
141141
142142
@@ -150,6 +150,7 @@ def _make_gpu_catalog_items_for_price(
150150 ram_per_gpu : float ,
151151 available_devices : int ,
152152 max_gpus_per_instance : int ,
153+ provider_data : JSONObject ,
153154 spot : bool ,
154155) -> list [RawCatalogItem ]:
155156 items = []
@@ -170,6 +171,7 @@ def _make_gpu_catalog_items_for_price(
170171 gpu_memory = gpu_memory ,
171172 spot = spot ,
172173 disk_size = None ,
174+ provider_data = provider_data ,
173175 )
174176 )
175177 return items
@@ -216,6 +218,12 @@ def _make_cpu_catalog_items(cpu_meta: dict) -> list[RawCatalogItem]:
216218 return offers
217219
218220
221+ def _gpu_provider_data (gpu_type : str , gpu_name : str ) -> JSONObject :
222+ if gpu_type == gpu_name :
223+ return {}
224+ return cast (JSONObject , JarvisLabsCatalogItemProviderData (gpu_type = gpu_type ))
225+
226+
219227def _supported_gpu_counts (* , available_devices : int , max_gpus_per_instance : int ) -> list [int ]:
220228 if available_devices <= 0 or max_gpus_per_instance <= 0 :
221229 return []
@@ -228,18 +236,14 @@ def _available_devices(gpu: dict) -> int:
228236 )
229237
230238
231- def _spot_available_devices (gpu : dict ) -> int :
232- return _as_int (gpu .get ("spot_num_free_devices" )) or 0
233-
234-
235239def _max_gpus_per_instance (gpu : dict ) -> int :
236240 return _as_int (gpu .get ("num_gpus" )) or 1
237241
238242
239243def _gpu_name_and_memory (gpu_type : str , vram : object ) -> tuple [str , float | None ] | None :
240- if any (c .isspace () for c in gpu_type ):
241- return None
242244 gpu_name , default_memory = JARVISLABS_GPU_NAME_OVERRIDES .get (gpu_type , (gpu_type , None ))
245+ if gpu_name == gpu_type and any (c .isspace () for c in gpu_type ):
246+ return None
243247 return gpu_name , _as_float (vram ) or default_memory
244248
245249
0 commit comments