1212API_URL = "https://backendprod.jarvislabs.net"
1313SERVER_META_PATH = "/misc/server_meta"
1414TIMEOUT = 30
15- VM_MIN_STORAGE_GB = 100
1615# JarvisLabs exposes offer regions in server_meta, but VM provisioning calls must be sent
1716# to region-specific API hosts and server_meta does not include those hosts. Keep this
1817# allowlist in sync with the known provisioning hosts and do not advertise offers for
2221 "india-noida-01" : "https://backendn.jarvislabs.net" ,
2322 "europe-01" : "https://backendeu.jarvislabs.net" ,
2423}
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+ JARVISLABS_GPU_NAME_OVERRIDES = {
28+ "A100-80GB" : ("A100" , 80.0 ),
29+ }
2530
2631
2732class JarvisLabsProvider (AbstractProvider ):
@@ -42,9 +47,7 @@ def get(
4247
4348 def fetch_offers (self , query_filter : QueryFilter | None = None ) -> list [RawCatalogItem ]:
4449 response = self ._make_request ("GET" , SERVER_META_PATH )
45- return convert_response_to_raw_catalog_items (
46- response .json (), disk_size = _disk_size (query_filter )
47- )
50+ return convert_response_to_raw_catalog_items (response .json ())
4851
4952 def _make_request (self , method : str , path : str ) -> Response :
5053 response = requests .request (
@@ -57,17 +60,15 @@ def _make_request(self, method: str, path: str) -> Response:
5760 return response
5861
5962
60- def convert_response_to_raw_catalog_items (
61- data : dict , disk_size : float = VM_MIN_STORAGE_GB
62- ) -> list [RawCatalogItem ]:
63+ def convert_response_to_raw_catalog_items (data : dict ) -> list [RawCatalogItem ]:
6364 offers = []
6465 for gpu in data .get ("server_meta" ) or []:
65- offers .extend (_make_gpu_catalog_items (gpu , disk_size = disk_size ))
66- offers .extend (_make_cpu_catalog_items (data .get ("cpu_meta" ) or {}, disk_size = disk_size ))
66+ offers .extend (_make_gpu_catalog_items (gpu ))
67+ offers .extend (_make_cpu_catalog_items (data .get ("cpu_meta" ) or {}))
6768 return offers
6869
6970
70- def _make_gpu_catalog_items (gpu : dict , disk_size : float ) -> list [RawCatalogItem ]:
71+ def _make_gpu_catalog_items (gpu : dict ) -> list [RawCatalogItem ]:
7172 region = gpu .get ("region" )
7273 if not region :
7374 return []
@@ -94,7 +95,11 @@ def _make_gpu_catalog_items(gpu: dict, disk_size: float) -> list[RawCatalogItem]
9495 logger .warning ("Skipping JarvisLabs GPU offer without price: %s" , gpu_type )
9596 return []
9697
97- gpu_name , gpu_memory = _gpu_name_and_memory (gpu_type , gpu .get ("vram" ))
98+ gpu_spec = _gpu_name_and_memory (gpu_type , gpu .get ("vram" ))
99+ if gpu_spec is None :
100+ logger .warning ("Skipping JarvisLabs GPU offer with ambiguous gpu_type: %s" , gpu_type )
101+ return []
102+ gpu_name , gpu_memory = gpu_spec
98103 if gpu_memory is None :
99104 logger .warning ("Skipping JarvisLabs GPU offer with unknown VRAM: %s" , gpu_type )
100105 return []
@@ -115,7 +120,6 @@ def _make_gpu_catalog_items(gpu: dict, disk_size: float) -> list[RawCatalogItem]
115120 available_devices = _available_devices (gpu ),
116121 max_gpus_per_instance = _max_gpus_per_instance (gpu ),
117122 spot = False ,
118- disk_size = disk_size ,
119123 )
120124
121125 spot_price = _as_float (gpu .get ("spot_price" ))
@@ -131,7 +135,6 @@ def _make_gpu_catalog_items(gpu: dict, disk_size: float) -> list[RawCatalogItem]
131135 available_devices = _spot_available_devices (gpu ),
132136 max_gpus_per_instance = _max_gpus_per_instance (gpu ),
133137 spot = True ,
134- disk_size = disk_size ,
135138 )
136139 )
137140 return items
@@ -148,7 +151,6 @@ def _make_gpu_catalog_items_for_price(
148151 available_devices : int ,
149152 max_gpus_per_instance : int ,
150153 spot : bool ,
151- disk_size : float ,
152154) -> list [RawCatalogItem ]:
153155 items = []
154156 for gpu_count in _supported_gpu_counts (
@@ -167,13 +169,13 @@ def _make_gpu_catalog_items_for_price(
167169 gpu_name = gpu_name ,
168170 gpu_memory = gpu_memory ,
169171 spot = spot ,
170- disk_size = disk_size ,
172+ disk_size = None ,
171173 )
172174 )
173175 return items
174176
175177
176- def _make_cpu_catalog_items (cpu_meta : dict , disk_size : float ) -> list [RawCatalogItem ]:
178+ def _make_cpu_catalog_items (cpu_meta : dict ) -> list [RawCatalogItem ]:
177179 offers = []
178180 # The JarvisLabs SDK resolves CPU VMs from cpu_meta.combinations and creates them via
179181 # templates/vm/cpu/create; cpu_meta.workload_type is not the GPU workload selector.
@@ -208,7 +210,7 @@ def _make_cpu_catalog_items(cpu_meta: dict, disk_size: float) -> list[RawCatalog
208210 gpu_name = None ,
209211 gpu_memory = None ,
210212 spot = False ,
211- disk_size = disk_size ,
213+ disk_size = None ,
212214 )
213215 )
214216 return offers
@@ -234,17 +236,11 @@ def _max_gpus_per_instance(gpu: dict) -> int:
234236 return _as_int (gpu .get ("num_gpus" )) or 1
235237
236238
237- def _disk_size (query_filter : QueryFilter | None ) -> float :
238- if query_filter is None or query_filter .min_disk_size is None :
239- return float (VM_MIN_STORAGE_GB )
240- return float (max (VM_MIN_STORAGE_GB , query_filter .min_disk_size ))
241-
242-
243- def _gpu_name_and_memory (gpu_type : str , vram : object ) -> tuple [str , float | None ]:
244- gpu_memory = _as_float (vram )
245- if gpu_type == "A100-80GB" :
246- return "A100" , gpu_memory or 80.0
247- return gpu_type .replace (" " , "" ), gpu_memory
239+ def _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
242+ gpu_name , default_memory = JARVISLABS_GPU_NAME_OVERRIDES .get (gpu_type , (gpu_type , None ))
243+ return gpu_name , _as_float (vram ) or default_memory
248244
249245
250246def _gpu_instance_name (gpu_name : str , gpu_count : int ) -> str :
0 commit comments