import type { Metadata } from 'next'; import Link from 'next/link'; import { CompareButton } from '@/components/compare/compare-button'; import { CompareTrayBar } from '@/components/compare/compare-tray-bar'; import { QUANTS } from '@/components/hardware/fit-form'; import { EstimateBanner, FitBreakdownList, Methodology, SourceTag } from '@/components/intelligence/bits'; import { CONTEXT_PRESETS, PLATFORMS, RunLocallyForm, type RunLocallyInputs, USE_CASES } from '@/components/intelligence/run-locally-form'; import { TerminalLayout } from '@/components/layout/terminal'; import { Chip, Estimated, OpennessBadge } from '@/components/ui/badges'; import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table'; import { EntityLink } from '@/components/ui/entity'; import { Note, PageHeader } from '@/components/ui/section'; import { EmptyState, Unavailable } from '@/components/ui/unavailable'; import { ApiError, intel, safe } from '@/lib/api'; import { fmtGb, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format'; import { routes, SITE_NAME, SITE_URL } from '@/lib/site'; import type { Fit, RunLocallyItem, RunLocallyPayload } from '@/lib/types'; export const revalidate = 600; type SP = Record; function parse(sp: SP): RunLocallyInputs { const custom = num(sp.memory_custom); const preset = num(sp.memory_gb); const memory = custom !== null && custom > 0 ? custom : preset !== null && preset > 0 ? preset : null; const gpu = num(sp.gpu_count); const ctx = num(sp.context); const batch = num(sp.batch); return { memory, gpuCount: gpu !== null && [1, 2, 4, 8].includes(gpu) ? gpu : 1, quant: QUANTS.some((q) => q.value === sp.quant) ? (sp.quant as string) : '4bit', context: ctx !== null && ctx > 0 ? Math.round(ctx) : 8192, batch: batch !== null && batch > 0 ? Math.round(batch) : 1, platform: PLATFORMS.some((p) => p.value === sp.platform) ? (sp.platform as string) : 'any', useCase: USE_CASES.some((u) => u.value === sp.use_case) ? (sp.use_case as string) : '', openness: ['open-source', 'open-weights', 'restricted-weights'].includes(sp.openness ?? '') ? (sp.openness as string) : '', hardware: sp.hardware?.trim() || null, fitsOnly: sp.fits === '1', }; } const TITLE = 'Run locally — which AI models fit your machine? (estimated)'; const DESC = 'Local AI Explorer: choose memory, GPU count, platform, quantization, context and batch; the atlas estimates which downloadable models fit — with the weight, KV-cache and overhead breakdown — and lists their compatible GGUF/MLX artifacts, using observed file sizes where a source records them. Every figure is an estimate.'; export async function generateMetadata({ searchParams }: { searchParams: Promise }): Promise { const v = parse(await searchParams); const title = v.hardware ? `What can ${v.hardware} run? (estimated)` : v.memory ? `Models estimated to fit ${fmtGb(v.memory * v.gpuCount)} at ${v.quant}, ${fmtTokens(v.context)} context` : TITLE; return { title, description: DESC, alternates: { canonical: routes.runLocally() }, openGraph: { title: `${title} | ${SITE_NAME}`, description: DESC, url: `${SITE_URL}${routes.runLocally()}`, siteName: SITE_NAME }, robots: v.memory || v.hardware ? { index: false, follow: true } : undefined }; } export default async function RunLocallyPage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const v = parse(sp); let res: RunLocallyPayload | null = null; let hardwareName: string | null = null; let memoryOptions: number[] | undefined; let error: string | null = null; let notFound = false; if (v.hardware) { try { const h = await intel.hardwareSlugFit(v.hardware, { quant: v.quant, context: v.context, memory_gb: v.memory ?? undefined, gpu_count: v.gpuCount, openness: v.openness || undefined, limit: 300 }); hardwareName = h.hardware.name; memoryOptions = h.memory_options_gb; res = { inputs: h.inputs, estimated: h.estimated, assumptions: h.assumptions, counts: h.counts, note: h.note ?? `Runtimes listed for this device: ${h.runtimes.join(', ') || 'unavailable'}.`, items: h.items.map(({ model, ...fit }) => ({ model, fit: fit as Fit, artifacts: [], artifact_count: 0 })) }; } catch (e) { if (e instanceof ApiError && e.notFound) notFound = true; else error = e instanceof ApiError ? e.detail ?? e.message : 'API unreachable'; } } else if (v.memory) { try { res = await intel.runLocally({ memory_gb: v.memory, gpu_count: v.gpuCount, quant: v.quant, context: v.context, batch: v.batch, platform: v.platform, use_case: v.useCase || undefined, openness: v.openness || undefined, limit: 300 }); } catch (e) { error = e instanceof ApiError ? e.detail ?? e.message : 'API unreachable'; } } const methodology = res ? null : await safe(intel.methodology()); const items: RunLocallyItem[] = res ? (v.fitsOnly ? res.items.filter((i) => i.fit.fits) : res.items) : []; const multiNote = v.gpuCount > 1 ? res?.items.find((i) => i.fit.multi_gpu_note)?.fit.multi_gpu_note ?? res?.note : null; const total = v.memory ? v.memory * v.gpuCount : num(res?.inputs?.total_memory_gb); const ctxLabel = CONTEXT_PRESETS.find((c) => c.value === v.context)?.label ?? fmtTokens(v.context); const filterCount = [v.memory, v.gpuCount !== 1, v.quant !== '4bit', v.context !== 8192, v.batch !== 1, v.platform !== 'any', v.useCase, v.openness, v.fitsOnly].filter(Boolean).length; const withArtifacts = items.filter((i) => i.artifacts.length > 0).length; const inspector = (

Method

    {(res?.assumptions ?? methodology?.hardware_fit?.assumptions ?? []).map((a) => (
  • {a}
  • ))}
{methodology?.hardware_fit?.bytes_per_param && !res && (

bytes / param:{' '} {Object.entries(methodology.hardware_fit.bytes_per_param) .map(([k, b]) => `${k} ${b}`) .join(' · ')}

)}

/methodology · GET /run-locally

); return ( } inspector={inspector} filtersTitle="Machine" inspectorTitle="Method" storageKey="aia-inspector-run-locally" filterCount={filterCount}> } className="pt-4 md:pt-6" />
{notFound ? ( Pick a device from the hardware listing or describe the machine in the rail. ) : !v.memory && !v.hardware ? ( Try 64 GB · 4-bit, 2 × 24 GB · 8-bit · NVIDIA, 128 GB · 4-bit · 128K · coding — or start from a device on /hardware. ) : !res ? ( ) : ( <>

{fmtGb(total)} total {v.gpuCount} × {fmtGb(v.memory ?? num(res.inputs?.memory_gb))} {QUANTS.find((q) => q.value === v.quant)?.label.split(' (')[0] ?? v.quant} {ctxLabel} context batch {v.batch} {v.platform !== 'any' && {PLATFORMS.find((p) => p.value === v.platform)?.label.split(' (')[0]}} {v.useCase && {USE_CASES.find((u) => u.value === v.useCase)?.label}} {v.fitsOnly ? x && k !== 'fits') as [string, string][]).toString()}`} className="link">Show all evaluated models : x)) as Record), fits: '1' }).toString()}`} className="link">Only fitting models}

{multiNote && v.gpuCount > 1 && ( Multi-device: {multiNote} )} {items.length === 0 ? ( {v.fitsOnly ? 'Try more memory, a lower-precision quantization, a shorter context or a smaller batch.' : 'Models without a sourced parameter count are not estimated.'} ) : ( Model Params Est. memory Headroom Fits Breakdown Artifacts {items.map((it, i) => { const openness = typeof it.model.attributes?.openness === 'string' ? it.model.attributes.openness : null; const head = num(it.fit.headroom_gb); return ( ); })} {items.length === 0 && } )} Headroom = total device memory − reserve − estimate. {withArtifacts > 0 ? `${fmtInt(withArtifacts)} of ${fmtInt(items.length)} models have quantized artifacts recorded; ` : 'No quantized artifact is recorded for these models yet; '} artifact rows use the observed file size when a source publishes it, otherwise the estimate. Sorted by the API (fitting models first). Compare shortlisted models with the + buttons. )}
); } function RowGroup({ it, openness, head }: { it: RunLocallyItem; openness: string | null; head: number | null }) { return ( <> {openness && } {it.model.organization && {it.model.organization.name}} {typeof it.model.attributes?.license === 'string' && {it.model.attributes.license as string}} {fmtParams(it.fit.parameter_count ?? it.model.attributes?.parameter_count)} {fmtGb(it.fit.estimated_memory_gb, 1)} {head === null ? '—' : `${head >= 0 ? '+' : '−'}${fmtGb(Math.abs(head), 1)}`} {it.fit.fits ? '✓ fits' : '✗ too large'} {num(it.artifact_count) ? `${fmtInt(it.artifact_count)} recorded` : none recorded} {it.artifacts.slice(0, 4).map((a, j) => ( ↳ {a.quant_format && {a.quant_format}} {num(a.file_size_gb) === null ? '—' : fmtGb(a.file_size_gb, 1)} {fmtGb(a.fit.estimated_memory_gb, 1)} {num(a.fit.headroom_gb) === null ? '—' : `${(num(a.fit.headroom_gb) as number) >= 0 ? '+' : '−'}${fmtGb(Math.abs(num(a.fit.headroom_gb) as number), 1)}`} {a.fit.fits ? '✓ fits' : '✗ too large'} {a.fit.quantization} ))} {it.artifacts.length > 4 && ( +{it.artifacts.length - 4} more artifacts on the model page. )} ); }