"""/hardware listing, /hardware/fit (ESTIMATED memory fit) and /hardware/{slug} alias.""" from __future__ import annotations from typing import Any from fastapi import APIRouter, Query, Request from aiatlas.api.common import ( ENTITY_COLS, ENTITY_FROM, PAGINATION, ApiError, Pagination, attr_num, cached, entity_summary, page, ) from aiatlas.api.routers.entities import detail_for_type from aiatlas.db import connection, fetch_all, fetch_val from aiatlas.services import hardware_fit as hf router = APIRouter(prefix="/api/v1/hardware", tags=["hardware"]) MEMORY = ("(case when jsonb_typeof(e.attributes->'memory_gb') = 'array' then (select max(x::text::double precision) from jsonb_array_elements(e.attributes->'memory_gb') x where jsonb_typeof(x) = 'number') " "when jsonb_typeof(e.attributes->'memory_gb') = 'number' then (e.attributes->>'memory_gb')::double precision else " + attr_num("memory_gb") + " end)") SORTS = {"memory": f"{MEMORY} desc nulls last", "name": "e.canonical_name asc", "release": "e.attributes->>'release_date' desc nulls last", "updated": "e.updated_at desc", "bandwidth": attr_num("memory_bandwidth_gbs") + " desc nulls last"} @router.get("") @cached(300) async def list_hardware(request: Request, p: Pagination = PAGINATION, kind: str | None = None, manufacturer: str | None = None, min_memory: float | None = Query(None, ge=0), q: str | None = Query(None, max_length=200), sort: str = "memory") -> dict[str, Any]: if sort not in SORTS: raise ApiError(400, f"sort must be one of {', '.join(SORTS)}") where = ["e.entity_type = 'hardware'", "e.merged_into is null"] params: dict[str, Any] = {} if kind: where.append("e.attributes->>'kind' ilike :kind") params["kind"] = kind if manufacturer: where.append("(e.attributes->>'manufacturer' ilike :man or eo.slug = :man or eo.canonical_name ilike :man)") params["man"] = manufacturer if min_memory is not None: where.append(f"{MEMORY} >= :min_memory") params["min_memory"] = float(min_memory) if q: where.append("e.canonical_name ilike :qlike") params["qlike"] = f"%{q}%" where_sql = " and ".join(where) async with connection() as conn: rows = await fetch_all(conn, f"select {ENTITY_COLS} from {ENTITY_FROM} where {where_sql} order by {SORTS[sort]}, e.id limit :lim offset :off", lim=p.limit, off=p.offset, **params) total = await fetch_val(conn, f"select count(*) from {ENTITY_FROM} where {where_sql}", **params) kinds = await fetch_all(conn, "select e.attributes->>'kind' as value, count(*) as count from entities e where e.entity_type = 'hardware' and e.merged_into is null and e.attributes ? 'kind' group by 1 order by 2 desc") mans = await fetch_all(conn, "select e.attributes->>'manufacturer' as value, count(*) as count from entities e where e.entity_type = 'hardware' and e.merged_into is null and e.attributes ? 'manufacturer' group by 1 order by 2 desc") out = page([entity_summary(r) for r in rows], int(total or 0), p) out["facets"] = {"kinds": [{"value": r["value"], "count": int(r["count"])} for r in kinds], "manufacturers": [{"value": r["value"], "count": int(r["count"])} for r in mans]} return out @router.get("/fit") @cached(300) async def hardware_fit(request: Request, memory_gb: float = Query(..., gt=0, le=100000), quant: str = Query("4bit"), context: int = Query(8192, ge=0, le=10_000_000), limit: int = Query(100, ge=1, le=500), openness: str | None = None) -> dict[str, Any]: if quant not in hf.BYTES_PER_PARAM: raise ApiError(400, f"quant must be one of {', '.join(hf.BYTES_PER_PARAM)}") where = "e.entity_type = 'model' and e.merged_into is null and e.attributes ? 'parameter_count'" params: dict[str, Any] = {} if openness: vals = [v.strip() for v in openness.split(",") if v.strip()] if "open" in vals: vals += ["open-weights", "open-source"] where += " and e.attributes->>'openness' = any(cast(:openness as text[]))" params["openness"] = vals async with connection() as conn: rows = await fetch_all(conn, f"select {ENTITY_COLS} from {ENTITY_FROM} where {where} order by {attr_num('parameter_count')} desc nulls last limit 2000", **params) items = [] for r in rows: pc = hf.parameter_count(r["attributes"]) if pc is None: continue items.append({"model": entity_summary(r), "parameter_count": pc, **hf.fit(pc, memory_gb, quant, context)}) items.sort(key=lambda x: (not x["fits"], -x["parameter_count"] if x["fits"] else x["parameter_count"])) return {"inputs": {"memory_gb": memory_gb, "quant": quant, "context": context}, "estimated": True, "assumptions": hf.ASSUMPTIONS, "counts": {"fits": sum(1 for i in items if i["fits"]), "evaluated": len(items)}, "items": items[:limit]} @router.get("/{slug}/fit") @cached(300) async def hardware_entity_fit(request: Request, slug: str, quant: str = Query("4bit"), context: int = Query(8192, ge=0, le=10_000_000), memory_gb: float | None = Query(None, gt=0), gpu_count: int = Query(1, ge=1, le=8), limit: int = Query(100, ge=1, le=500), openness: str | None = None) -> dict[str, Any]: """ESTIMATED fit of canonical models on ONE hardware entity (largest memory configuration unless `memory_gb` picks one).""" from aiatlas.api.common import openness_values, resolve_entity q = hf.normalize_quant(quant) async with connection() as conn: hw = await resolve_entity(conn, slug, ("hardware",)) options = hf.hardware_memory_options(hw.get("attributes")) if not options: return {"hardware": entity_summary(hw), "memory_options_gb": [], "items": [], "estimated": True, "note": "no memory_gb recorded for this hardware — nothing is estimated"} mem = memory_gb if memory_gb is not None else max(options) where = "e.entity_type = 'model' and e.merged_into is null and e.attributes ? 'parameter_count'" params: dict[str, Any] = {} if openness: where += " and e.attributes->>'openness' = any(cast(:openness as text[]))" params["openness"] = openness_values(openness) rows = await fetch_all(conn, f"select {ENTITY_COLS} from {ENTITY_FROM} where {where} order by {attr_num('parameter_count')} desc nulls last limit 3000", **params) items = [] for r in rows: f = hf.fit_detailed(r["attributes"], mem, quant=q, context=context, gpu_count=gpu_count) if f: items.append({"model": entity_summary(r), **f}) items.sort(key=lambda x: (not x["fits"], -(x.get("parameter_count") or 0) if x["fits"] else (x.get("parameter_count") or 0))) return {"hardware": entity_summary(hw), "memory_options_gb": options, "inputs": {"memory_gb": mem, "quant": q, "context": context, "gpu_count": gpu_count}, "estimated": True, "assumptions": hf.ASSUMPTIONS, "runtimes": (hw.get("attributes") or {}).get("runtimes"), "counts": {"fits": sum(1 for i in items if i["fits"]), "evaluated": len(items)}, "items": items[:limit]} @router.get("/{slug}") @cached(300) async def get_hardware(request: Request, slug: str) -> dict[str, Any]: return await detail_for_type(slug, ("hardware",))