Skip to content

Commit 520629e

Browse files
committed
filter cached benchmarks by template server-side
lookup_cached_benchmark pulled every perf row for a gpu_name/num_gpus combo and matched the template client-side, discarding the rest: 3275 rows for a common combo like 1x RTX 5090, once per GPU in the sweep. The benchmarks table is indexed on (template_hash, last_update desc) and (template_id, last_update desc), so neither index could be used. Filter on the template columns instead, newest-first and capped, which is index-backed and returns ~2 rows. A workergroup is created with either template_id or template_hash, so a row can carry either; query once per column and dedupe. search_benchmarks grows order/limit params to pass these through.
1 parent 9d16f3e commit 520629e

4 files changed

Lines changed: 80 additions & 14 deletions

File tree

tests/cli/test_benchmarks_commands.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,21 @@ def _run_cli(parse_argv, argv, *, create_resp=None, workers_seq=None,
244244
vast.client = MagicMock(api_key="k")
245245
vast.search_templates.return_value = [template]
246246
vast.search_offers.return_value = fake_offer_list
247-
vast.search_benchmarks.return_value = list(benchmark_rows or [])
247+
# Template matching happens server-side now, so the fake has to apply
248+
# select_filters; returning every row regardless of query would let a
249+
# different template's rows count as a cache hit.
250+
def _fake_search_benchmarks(query=None, order=None, limit=None):
251+
def matches(row):
252+
for col, cond in (query or {}).items():
253+
if "eq" in cond and row.get(col) != cond["eq"]:
254+
return False
255+
if "gte" in cond and (row.get(col) or 0) < cond["gte"]:
256+
return False
257+
return True
258+
hits = [r for r in (benchmark_rows or []) if matches(r)]
259+
return hits[:limit] if limit else hits
260+
261+
vast.search_benchmarks.side_effect = _fake_search_benchmarks
248262
vast.show_instance.return_value = instance_resp
249263
vast.create_endpoint.return_value = {"success": True, "result": 11}
250264
vast.delete_endpoint.return_value = {}
@@ -491,3 +505,26 @@ def test_cache_query_shape(self, parse_argv):
491505
assert abs((time.time() - cutoff)
492506
- DEFAULT_CACHE_MAX_AGE_DAYS * 86400) < 60
493507

508+
def test_cache_query_is_template_filtered_and_bounded(self, parse_argv):
509+
"""The template is filtered server-side, one query per template column.
510+
511+
Both columns are indexed with last_update, so filtering server-side and
512+
ordering newest-first keeps the query index-backed and bounded instead
513+
of pulling every perf row for the GPU and discarding most of them.
514+
"""
515+
from vastai.cli.commands.benchmarks import MAX_CACHE_ROWS
516+
_, vast = _run_cli(
517+
parse_argv,
518+
["run", "benchmarks", "--template_id", "99999",
519+
"--gpus", "RTX_3060", "-y", "--raw"],
520+
benchmark_rows=[_bench_row(20.0)],
521+
)
522+
calls = vast.search_benchmarks.call_args_list
523+
assert [c.kwargs["query"].get("template_hash") for c in calls] == [
524+
{"eq": "x"}, None]
525+
assert [c.kwargs["query"].get("template_id") for c in calls] == [
526+
None, {"eq": 99999}]
527+
for c in calls:
528+
assert c.kwargs["limit"] == MAX_CACHE_ROWS
529+
assert c.kwargs["order"] == [{"col": "last_update", "dir": "desc"}]
530+

vastai/api/offers.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,24 @@ def search_templates(client: VastClient, query: dict = None) -> list:
113113
return r.json().get("templates", [])
114114

115115

116-
def search_benchmarks(client: VastClient, query: dict = None) -> list:
116+
def search_benchmarks(client: VastClient, query: dict = None, order: list = None,
117+
limit: int = None) -> list:
117118
"""Search for benchmarks using a query dict.
118119
119120
Args:
120121
client: VastClient instance.
121122
query: Pre-parsed query dict of select_filters.
123+
order: List of {"col": ..., "dir": ...} dicts, e.g. [{"col": "last_update", "dir": "desc"}].
124+
limit: Max number of results. Omit for an unbounded result set.
122125
123126
Returns:
124127
List of benchmark dicts.
125128
"""
126129
query_args = {"select_cols": ["*"], "select_filters": query or {}}
130+
if order is not None:
131+
query_args["order_by"] = order
132+
if limit is not None:
133+
query_args["limit"] = int(limit)
127134
r = client.get("/benchmarks", query_args=query_args)
128135
r.raise_for_status()
129136
return r.json()

vastai/cli/commands/benchmarks.py

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -303,30 +303,49 @@ def pick_num_gpus(gpu_name, extra_filters):
303303

304304

305305
DEFAULT_CACHE_MAX_AGE_DAYS = 30
306+
MAX_CACHE_ROWS = 100 # newest rows per template column; caps popular template+GPU combos
306307

307308

308309
def lookup_cached_benchmark(vast, *, gpu_name, num_gpus, template_hash,
309310
template_id, max_age_days):
310311
"""Recent reported benchmarks for this exact spec (median + spread), or None.
311312
312-
Template is matched client-side: rows carry template_hash or template_id
313-
depending on how the benchmarked workergroup was created.
313+
Queried once per template column rather than filtered client-side: a
314+
workergroup is created with either template_id or template_hash, so a row
315+
can carry either one. Both columns are indexed with last_update on the
316+
benchmarks table, so each query is index-backed instead of pulling every
317+
perf row for the GPU and discarding the non-matching ones.
314318
"""
315-
query = {
319+
base = {
316320
"type": {"eq": "perf"},
317321
"gpu_name": {"eq": gpu_name},
318322
"num_gpus": {"eq": num_gpus},
319323
"last_update": {"gte": time.time() - max_age_days * 86400},
320324
}
321-
rows = vast.search_benchmarks(query=query)
322-
if not isinstance(rows, list):
323-
return None
325+
rows = []
326+
seen_ids = set()
327+
for col, value in (("template_hash", template_hash), ("template_id", template_id)):
328+
if not value:
329+
continue
330+
found = vast.search_benchmarks(
331+
query=dict(base, **{col: {"eq": value}}),
332+
order=[{"col": "last_update", "dir": "desc"}],
333+
limit=MAX_CACHE_ROWS,
334+
)
335+
for r in found if isinstance(found, list) else []:
336+
if not isinstance(r, dict):
337+
continue
338+
# A row carrying both columns comes back from both queries; dedupe on
339+
# id, but keep rows without one rather than collapsing them together.
340+
row_id = r.get("id")
341+
if row_id is not None:
342+
if row_id in seen_ids:
343+
continue
344+
seen_ids.add(row_id)
345+
rows.append(r)
324346
matched = [
325347
r for r in rows
326-
if isinstance(r, dict)
327-
and ((template_hash and r.get("template_hash") == template_hash)
328-
or (template_id and r.get("template_id") == template_id))
329-
and isinstance(r.get("value"), (int, float)) and r["value"] > 0
348+
if isinstance(r.get("value"), (int, float)) and r["value"] > 0
330349
]
331350
if not matched:
332351
return None

vastai/sdk.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -276,9 +276,12 @@ def search_templates(self, query: Optional[str] = None) -> list[dict]:
276276
"""Search for templates."""
277277
return offers.search_templates(self.client, query=query)
278278

279-
def search_benchmarks(self, query: Optional[Union[str, dict]] = None) -> list[dict]:
279+
def search_benchmarks(self, query: Optional[Union[str, dict]] = None,
280+
order: Optional[list] = None,
281+
limit: Optional[int] = None) -> list[dict]:
280282
"""Search for benchmarks."""
281-
return offers.search_benchmarks(self.client, query=query)
283+
return offers.search_benchmarks(self.client, query=query, order=order,
284+
limit=limit)
282285

283286
def search_volumes(self, query: Optional[str] = None, **kwargs) -> list[dict]:
284287
"""Search for volume offers."""

0 commit comments

Comments
 (0)