SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
9.0 KB · 167 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { CompareButton } from '@/components/compare/compare-button';4import { CompareTrayBar } from '@/components/compare/compare-tray-bar';5import { CONTEXTS, FitForm, QUANTS } from '@/components/hardware/fit-form';6import { Estimated, OpennessBadge } from '@/components/ui/badges';7import { DataTable, EmptyRow, Td, Th } from '@/components/ui/data-table';8import { EntityLink } from '@/components/ui/entity';9import { Container, Note, PageHeader } from '@/components/ui/section';10import { EmptyState, Unavailable } from '@/components/ui/unavailable';11import { api, ApiError } from '@/lib/api';12import { fmtGb, fmtInt, fmtParams, fmtTokens, num } from '@/lib/format';13import { routes } from '@/lib/site';14import type { HardwareFit } from '@/lib/types';1516export const revalidate = 600;1718type SP = Record<string, string | undefined>;1920function parseInputs(sp: SP): { memory: number | null; quant: string; context: number; fitsOnly: boolean } {21  const custom = num(sp.memory_custom);22  const preset = num(sp.memory_gb);23  const memory = custom !== null && custom > 0 ? custom : preset !== null && preset > 0 ? preset : null;24  const quant = QUANTS.some((q) => q.value === sp.quant) ? (sp.quant as string) : '4bit';25  const ctx = num(sp.context);26  const context = ctx !== null && ctx > 0 ? Math.round(ctx) : 8192;27  return { memory, quant, context, fitsOnly: sp.fits === '1' };28}2930export async function generateMetadata({ searchParams }: { searchParams: Promise<SP> }): Promise<Metadata> {31  const { memory, quant, context } = parseInputs(await searchParams);32  const base = 'Hardware fit — which models run on my machine? (estimated)';33  const title = memory ? `Models estimated to fit ${fmtGb(memory)} at ${quant}, ${fmtTokens(context)} context` : base;34  return {35    title,36    description: 'Estimate which AI models fit a given amount of device memory at 4-bit, 8-bit or fp16 with a chosen context window. Estimates only — bytes per parameter plus a KV-cache allowance, never a measurement.',37    alternates: { canonical: memory ? routes.hardwareFit({ memory_gb: memory, quant, context }) : '/hardware/fit' },38  };39}4041export default async function HardwareFitPage({ searchParams }: { searchParams: Promise<SP> }) {42  const sp = await searchParams;43  const { memory, quant, context, fitsOnly } = parseInputs(sp);44  let res: HardwareFit | null = null;45  let error: string | null = null;46  if (memory) {47    try {48      res = await api.hardwareFit({ memory_gb: memory, quant, context, limit: 200 });49    } catch (e) {50      error = e instanceof ApiError ? e.detail ?? e.message : 'API unreachable';51    }52  }53  const items = res ? (fitsOnly ? res.items.filter((i) => i.fits) : res.items) : [];54  const fits = num(res?.counts?.fits) ?? res?.items.filter((i) => i.fits).length ?? null;55  const evaluated = num(res?.counts?.evaluated) ?? res?.items.length ?? null;56  const self = (patch: { fits?: boolean }) => routes.hardwareFit({ memory_gb: memory ?? undefined, quant, context }) + (patch.fits ? '&fits=1' : '');57  const ctxLabel = CONTEXTS.find((c) => Number(c.value) === context)?.label ?? `${fmtTokens(context)} tokens`;5859  return (60    <Container wide>61      <PageHeader62        eyebrow={63          <>64            <Link href={routes.hardware()} className="hover:text-ink">Hardware</Link> <span aria-hidden>/</span> Fit tool65          </>66        }67        title="What fits my machine?"68        lede="Pick a device memory size, a quantization and a context window: the atlas estimates each model's memory need from its parameter count. Every figure here is an estimate, never a measurement."69        aside={<Estimated />}70      >71        <FitForm memory={memory ? String(memory) : undefined} quant={quant} context={String(context)} className="mt-6" />72      </PageHeader>7374      <div className="pb-16">75        {!memory ? (76          <EmptyState title="Choose a memory size to start">77            Try <Link href={routes.hardwareFit({ memory_gb: 32, quant: '4bit', context: 8192 })} className="link">32 GB · 4-bit · 8k</Link>, <Link href={routes.hardwareFit({ memory_gb: 80, quant: '8bit', context: 32768 })} className="link">80 GB · 8-bit · 32k</Link> or <Link href={routes.hardwareFit({ memory_gb: 192, quant: 'fp16', context: 131072 })} className="link">192 GB · fp16 · 128k</Link>.78          </EmptyState>79        ) : !res ? (80          <Unavailable what="Hardware fit" reason={error ?? undefined} />81        ) : (82          <>83            <div className="mb-5 border-y border-rule py-3">84              <p className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm">85                <Estimated />86                <span className="font-semibold text-ink">All figures are estimates, not measurements.</span>87                <span className="tnum text-ink-2">88                  {fmtGb(memory)} · {QUANTS.find((q) => q.value === quant)?.label.split(' (')[0] ?? quant} · {ctxLabel}89                </span>90                {fits !== null && evaluated !== null && (91                  <span className="tnum text-ink-3">92                    <span className="font-medium text-positive">{fmtInt(fits)}</span> of {fmtInt(evaluated)} models with a known parameter count fit93                  </span>94                )}95              </p>96              <ul className="mt-2 list-disc space-y-0.5 pl-5 text-xs leading-relaxed text-ink-3">97                {res.assumptions.map((a) => (98                  <li key={a}>{a}</li>99                ))}100                {res.assumptions.length === 0 && <li>Assumptions unavailable from the API.</li>}101              </ul>102              <p className="mt-2 flex flex-wrap items-center gap-3 text-xs">103                {fitsOnly ? (104                  <Link href={self({})} className="link">Show all evaluated models</Link>105                ) : (106                  <Link href={self({ fits: true })} className="link">Show only fitting models</Link>107                )}108                <Link href="/methodology#estimates" className="text-ink-3 hover:text-ink">Method →</Link>109              </p>110            </div>111112            {items.length === 0 ? (113              <EmptyState title={fitsOnly ? 'No model is estimated to fit' : 'No models to evaluate'}>{fitsOnly ? 'Try a larger memory size or a lower-precision quantization.' : 'Models without a sourced parameter count are not estimated.'}</EmptyState>114            ) : (115              <DataTable caption="Estimated hardware fit">116                <thead>117                  <tr>118                    <Th>Model</Th>119                    <Th num>Params</Th>120                    <Th>Quant</Th>121                    <Th num>Est. memory</Th>122                    <Th num>Headroom</Th>123                    <Th>Fits</Th>124                    <Th className="w-24"><span className="sr-only">Compare</span></Th>125                  </tr>126                </thead>127                <tbody>128                  {items.length === 0 && <EmptyRow cols={7} />}129                  {items.map((r) => {130                    const openness = typeof r.model.attributes?.openness === 'string' ? r.model.attributes.openness : null;131                    return (132                      <tr key={`${r.model.id}-${r.quantization}`}>133                        <Td primary>134                          <span className="flex flex-wrap items-center gap-x-2 gap-y-0.5">135                            <EntityLink e={r.model} />136                            {openness && <OpennessBadge openness={openness} />}137                          </span>138                          {r.model.organization && <span className="block text-xs text-ink-3">{r.model.organization.name}</span>}139                          {r.note && <span className="block text-xs text-ink-3">{r.note}</span>}140                        </Td>141                        <Td num label="Params" className="tnum">{fmtParams(r.parameter_count)}</Td>142                        <Td label="Quant" className="mono text-xs text-ink-2">{r.quantization}</Td>143                        <Td num label="Est. memory" className="tnum">{fmtGb(r.estimated_memory_gb, 1)}</Td>144                        <Td num label="Headroom" className={r.fits ? 'tnum text-positive' : 'tnum text-danger'}>145                          {num(r.headroom_gb) === null ? '—' : `${r.headroom_gb >= 0 ? '+' : '−'}${fmtGb(Math.abs(r.headroom_gb), 1)}`}146                        </Td>147                        <Td label="Fits" className={r.fits ? 'font-medium text-positive' : 'text-ink-3'}>{r.fits ? '✓ fits' : '✗ too large'}</Td>148                        <Td className="text-right">149                          <CompareButton e={r.model} size="sm" />150                        </Td>151                      </tr>152                    );153                  })}154                </tbody>155              </DataTable>156            )}157            <Note className="mt-3">158              Headroom = device memory − 2 GB reserve − estimated need. Sorted by the API (fitting models first). Compare the models you shortlist with the + buttons. Devices with these memory sizes: <Link href={routes.hardware()} className="link">hardware listing</Link>.159            </Note>160          </>161        )}162      </div>163      <CompareTrayBar />164    </Container>165  );166}167