@tokcalc/mcp-server 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -406
- package/dist/index.js +21285 -0
- package/package.json +34 -91
- package/.zscripts/build.sh +0 -175
- package/.zscripts/database-runtime-build.sh +0 -33
- package/.zscripts/dev.pid +0 -1
- package/.zscripts/dev.sh +0 -154
- package/.zscripts/mini-services-build.sh +0 -78
- package/.zscripts/mini-services-install.sh +0 -65
- package/.zscripts/mini-services-start.sh +0 -123
- package/.zscripts/python-runtime-build.sh +0 -120
- package/.zscripts/start.sh +0 -145
- package/CAPACITY_STUDY.md +0 -283
- package/CODE_OF_CONDUCT.md +0 -55
- package/CONTRIBUTING.md +0 -177
- package/Caddyfile +0 -23
- package/LICENSE +0 -204
- package/bun.lock +0 -1965
- package/components.json +0 -21
- package/db/custom.db +0 -0
- package/download/README.md +0 -1
- package/download/tokcalc-dark-calculator.png +0 -0
- package/download/tokcalc-dark-default.png +0 -0
- package/download/tokcalc-demo.webm +0 -0
- package/download/tokcalc-github-link.png +0 -0
- package/download/tokcalc-hydration-fixed.png +0 -0
- package/download/tokcalc-issue-resolved.png +0 -0
- package/download/tokcalc-light-mode.png +0 -0
- package/download/tokcalc-light-reference.png +0 -0
- package/download/tokcalc-long-context-qwen.png +0 -0
- package/download/tokcalc-long-context.png +0 -0
- package/download/tokcalc-og-image-preview.png +0 -0
- package/download/tokcalc-phase2-3.png +0 -0
- package/download/tokcalc-plain-english.png +0 -0
- package/download/tokcalc-preview.png +0 -0
- package/download/tokcalc-share-bvb.png +0 -0
- package/download/tokcalc-share-feature.png +0 -0
- package/download/tokcalc-tab-build-vs-buy.png +0 -0
- package/download/tokcalc-tab-calculator.png +0 -0
- package/download/tokcalc-tab-reference.png +0 -0
- package/eslint.config.mjs +0 -50
- package/examples/websocket/frontend.tsx +0 -196
- package/examples/websocket/server.ts +0 -138
- package/mini-services/.gitkeep +0 -0
- package/mini-services/mcp-server/README.md +0 -86
- package/mini-services/mcp-server/bun.lock +0 -202
- package/mini-services/mcp-server/index.ts +0 -504
- package/mini-services/mcp-server/package.json +0 -40
- package/next.config.ts +0 -12
- package/postcss.config.mjs +0 -5
- package/prisma/schema.prisma +0 -32
- package/public/google6f58ca6be85fa903.html +0 -1
- package/public/logo.svg +0 -29
- package/public/manifest.json +0 -51
- package/public/og-icon-256.png +0 -0
- package/public/og.png +0 -0
- package/public/robots.txt +0 -25
- package/public/sitemap.xml +0 -23
- package/public/tokcalc-demo.gif +0 -0
- package/scripts/og-template.html +0 -120
- package/scripts/render-og.mjs +0 -43
- package/server.json +0 -21
- package/src/app/api/pricing/aws/route.ts +0 -186
- package/src/app/api/pricing/azure/route.ts +0 -168
- package/src/app/api/pricing/gcp/route.ts +0 -230
- package/src/app/api/pricing/vast-ai/route.ts +0 -164
- package/src/app/api/route.ts +0 -5
- package/src/app/compare/h100-vs-h200/layout.tsx +0 -30
- package/src/app/compare/h100-vs-h200/page.tsx +0 -328
- package/src/app/globals.css +0 -122
- package/src/app/layout.tsx +0 -276
- package/src/app/page.tsx +0 -2670
- package/src/components/azure-live-pricing.tsx +0 -185
- package/src/components/benchmark-import.tsx +0 -340
- package/src/components/confidence-badge.tsx +0 -116
- package/src/components/live-pricing-comparison.tsx +0 -241
- package/src/components/theme-provider.tsx +0 -11
- package/src/components/theme-toggle.tsx +0 -55
- package/src/components/ui/accordion.tsx +0 -66
- package/src/components/ui/alert-dialog.tsx +0 -157
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/aspect-ratio.tsx +0 -11
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/badge.tsx +0 -46
- package/src/components/ui/breadcrumb.tsx +0 -109
- package/src/components/ui/button.tsx +0 -59
- package/src/components/ui/calendar.tsx +0 -213
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/carousel.tsx +0 -241
- package/src/components/ui/chart.tsx +0 -353
- package/src/components/ui/checkbox.tsx +0 -32
- package/src/components/ui/collapsible.tsx +0 -33
- package/src/components/ui/command.tsx +0 -184
- package/src/components/ui/context-menu.tsx +0 -252
- package/src/components/ui/dialog.tsx +0 -143
- package/src/components/ui/drawer.tsx +0 -135
- package/src/components/ui/dropdown-menu.tsx +0 -257
- package/src/components/ui/form.tsx +0 -167
- package/src/components/ui/hover-card.tsx +0 -44
- package/src/components/ui/input-otp.tsx +0 -77
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/menubar.tsx +0 -276
- package/src/components/ui/navigation-menu.tsx +0 -168
- package/src/components/ui/pagination.tsx +0 -127
- package/src/components/ui/popover.tsx +0 -48
- package/src/components/ui/progress.tsx +0 -31
- package/src/components/ui/radio-group.tsx +0 -45
- package/src/components/ui/resizable.tsx +0 -56
- package/src/components/ui/scroll-area.tsx +0 -58
- package/src/components/ui/select.tsx +0 -185
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/sidebar.tsx +0 -726
- package/src/components/ui/skeleton.tsx +0 -13
- package/src/components/ui/slider.tsx +0 -63
- package/src/components/ui/sonner.tsx +0 -25
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/table.tsx +0 -116
- package/src/components/ui/tabs.tsx +0 -66
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/toast.tsx +0 -129
- package/src/components/ui/toaster.tsx +0 -35
- package/src/components/ui/toggle-group.tsx +0 -73
- package/src/components/ui/toggle.tsx +0 -47
- package/src/components/ui/tooltip.tsx +0 -61
- package/src/components/vast-ai-live-pricing.tsx +0 -176
- package/src/hooks/use-mobile.ts +0 -19
- package/src/hooks/use-toast.ts +0 -194
- package/src/lib/benchmark-parser-sglang.ts +0 -150
- package/src/lib/benchmark-parser-tokcalc.ts +0 -247
- package/src/lib/benchmark-parser-trtllm.ts +0 -152
- package/src/lib/benchmark-parser-vllm.ts +0 -198
- package/src/lib/benchmark-schema.ts +0 -263
- package/src/lib/db.ts +0 -13
- package/src/lib/engine-presets.ts +0 -183
- package/src/lib/price-schema.ts +0 -141
- package/src/lib/token-calc.ts +0 -808
- package/src/lib/track.ts +0 -31
- package/src/lib/url-state.ts +0 -256
- package/src/lib/utils.ts +0 -6
- package/tailwind.config.ts +0 -64
- package/tests/database-runtime-build.sh +0 -75
- package/tests/python-runtime-build.sh +0 -64
- package/tests/python-runtime-container.sh +0 -31
- package/tool-results/bash_1789888171144_2c5381860539.txt +0 -161
- package/tool-results/bash_1789888175925_49c53ba3c61b.txt +0 -191
- package/tool-results/bash_1789888181202_49c53ba3c61b.txt +0 -191
- package/tool-results/bash_1789888195219_4a86a5c91411.txt +0 -200
- package/tool-results/bash_1789888203128_6cca13c71b47.txt +0 -199
- package/tool-results/bash_1789929256963_2a52aff0d0a8.txt +0 -160
- package/tool-results/read_1789888151021_69f58eec6a5b.txt +0 -653
- package/tool-results/read_1789888153837_1d3a8bfc2a94.txt +0 -653
- package/tool-results/read_1789888163087_ccc406d47505.txt +0 -122
- package/tool-results/read_1789888167347_67d1d7c9830a.txt +0 -122
- package/tool-results/read_1789929252529_d90e8f383a25.txt +0 -285
- package/tsconfig.json +0 -42
- package/upload/Pasted Content_1789887800864.txt +0 -652
- package/upload/Pasted Content_1789887909561.txt +0 -652
- package/upload/Pasted Content_1789887918428.txt +0 -652
- package/upload/Pasted Content_1789887959420.txt +0 -652
- package/upload/Pasted Content_1789888020485.txt +0 -652
- package/upload/Pasted Content_1789888058079.txt +0 -652
- package/upload/Pasted Content_1789888885033.txt +0 -686
- package/upload/Pasted Content_1789928912741.txt +0 -285
- package/upload/Pasted Content_1789928938402.txt +0 -285
- package/upload/Pasted Content_1789929160389.txt +0 -285
- package/upload/Pasted Content_1789929176660.txt +0 -285
- package/upload/issue_vision.json +0 -28
- package/upload/pasted_image_1789883175209.png +0 -0
- package/upload/pasted_image_1789899056690.png +0 -0
- package/upload/pasted_image_1789900371483.png +0 -0
- package/upload/pasted_image_1789900472823.png +0 -0
- package/upload/pasted_image_1789900490374.png +0 -0
- package/upload/pasted_image_1789900585552.png +0 -0
- package/upload/pasted_image_1789900606519.png +0 -0
- package/upload/pasted_image_1789901598705.png +0 -0
- package/upload/pasted_image_1789901613545.png +0 -0
- package/upload/pasted_image_1789978382674.png +0 -0
- package/upload/pasted_image_1789978392749.png +0 -0
- package/upload/pasted_image_1789978474879.png +0 -0
- package/upload/pasted_image_1789978523652.png +0 -0
- package/upload/pasted_image_1789984219089.png +0 -0
- package/upload/pasted_image_1789984491896.png +0 -0
- package/upload/pasted_image_1789985017950.png +0 -0
- package/upload/pasted_image_1789985036765.png +0 -0
- package/upload/pasted_image_1789985049848.png +0 -0
- package/upload/pasted_image_1790002427833.png +0 -0
- package/upload/pasted_image_1790002659944.png +0 -0
- package/upload/pasted_image_1790037038476.png +0 -0
- package/upload/screenshot_analysis.json +0 -28
- package/upload/vision_output.json +0 -28
package/src/lib/token-calc.ts
DELETED
|
@@ -1,808 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LLM Token/sec Calculator — Core Engine
|
|
3
|
-
* ---------------------------------------
|
|
4
|
-
* All formulas live here so they can be reused & tested independently.
|
|
5
|
-
*
|
|
6
|
-
* Theory reference:
|
|
7
|
-
* - Decode phase is MEMORY-BANDWIDTH bound (load weights once per token)
|
|
8
|
-
* - Prefill phase is COMPUTE bound (process prompt in parallel)
|
|
9
|
-
* - Real-world efficiency factor (~0.65) accounts for kernel overhead, KV cache reads, attention, sampling
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export type Quantization =
|
|
13
|
-
| "fp32" | "fp16" | "bf16" | "int8" | "int4"
|
|
14
|
-
| "gguf-q2k" | "gguf-q3km" | "gguf-q4km" | "gguf-q5km" | "gguf-q6k" | "gguf-q8"
|
|
15
|
-
| "gptq4" | "awq4" | "exl2-6bpw" | "fp8" | "nvfp4";
|
|
16
|
-
|
|
17
|
-
export interface QuantMeta {
|
|
18
|
-
id: Quantization;
|
|
19
|
-
label: string;
|
|
20
|
-
/** Effective bytes per parameter (averaged across tensor mix for GGUF) */
|
|
21
|
-
bytesPerParam: number;
|
|
22
|
-
/** Dequantization overhead multiplier (1.0 = none, 0.85 = 15% overhead)
|
|
23
|
-
* For FP8 on H100: efficiency >1.0 (faster than FP16 due to native FP8 kernels) */
|
|
24
|
-
efficiency: number;
|
|
25
|
-
description: string;
|
|
26
|
-
/** Format family — for engine-matching UI */
|
|
27
|
-
family: "float" | "gguf" | "gptq" | "awq" | "exl2" | "fp8" | "nvfp4";
|
|
28
|
-
/** Typical use case */
|
|
29
|
-
useCase?: string;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// GGUF variants: based on llama.cpp quantize README for Llama 3 8B file sizes.
|
|
33
|
-
// Refs: https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md
|
|
34
|
-
export const QUANTIZATIONS: QuantMeta[] = [
|
|
35
|
-
// === Float / native precision ===
|
|
36
|
-
{ id: "fp32", label: "FP32 (raw)", bytesPerParam: 4.0, efficiency: 1.00, family: "float", description: "32-bit float. Highest accuracy, 2x size of FP16." },
|
|
37
|
-
{ id: "fp16", label: "FP16", bytesPerParam: 2.0, efficiency: 1.00, family: "float", description: "16-bit float. Standard for inference, no dequant overhead." },
|
|
38
|
-
{ id: "bf16", label: "BF16", bytesPerParam: 2.0, efficiency: 1.00, family: "float", description: "Brain Float 16. Same size as FP16, better numerical range." },
|
|
39
|
-
// === Generic integer quantization (legacy bucket for vLLM/TGI default) ===
|
|
40
|
-
{ id: "int8", label: "INT8", bytesPerParam: 1.0, efficiency: 0.88, family: "float", description: "8-bit integer. Halves memory, ~12% dequant overhead." },
|
|
41
|
-
{ id: "int4", label: "INT4 (generic)", bytesPerParam: 0.5, efficiency: 0.80, family: "float", description: "4-bit integer (generic). For specific format, pick GPTQ-4 / AWQ-4 / GGUF Q4_K_M." },
|
|
42
|
-
|
|
43
|
-
// === GGUF variants (llama.cpp) ===
|
|
44
|
-
{ id: "gguf-q2k", label: "GGUF Q2_K", bytesPerParam: 0.37, efficiency: 0.92, family: "gguf", description: "~2.6 bpw effective. Smallest; significant accuracy loss, use only as last resort.", useCase: "Extreme low-memory edge devices" },
|
|
45
|
-
{ id: "gguf-q3km", label: "GGUF Q3_K_M", bytesPerParam: 0.42, efficiency: 0.93, family: "gguf", description: "~3.9 bpw. Low quality; acceptable for some chat uses but not coding/math.", useCase: "Low-memory local inference" },
|
|
46
|
-
{ id: "gguf-q4km", label: "GGUF Q4_K_M", bytesPerParam: 0.55, efficiency: 0.90, family: "gguf", description: "~4.8 bpw. Recommended sweet spot for local Llama/Mistral on consumer GPUs.", useCase: "Recommended for local inference" },
|
|
47
|
-
{ id: "gguf-q5km", label: "GGUF Q5_K_M", bytesPerParam: 0.68, efficiency: 0.92, family: "gguf", description: "~5.7 bpw. Higher quality than Q4, ~30% larger. Good when memory allows.", useCase: "Quality-focused local inference" },
|
|
48
|
-
{ id: "gguf-q6k", label: "GGUF Q6_K", bytesPerParam: 0.82, efficiency: 0.95, family: "gguf", description: "~6.6 bpw. Near-FP16 quality, ~60% larger than Q4_K_M.", useCase: "Near-lossless local inference" },
|
|
49
|
-
{ id: "gguf-q8", label: "GGUF Q8_0", bytesPerParam: 1.07, efficiency: 0.98, family: "gguf", description: "~8.5 bpw. Effectively lossless, ~half the size of FP16.", useCase: "Reference / lossless storage" },
|
|
50
|
-
|
|
51
|
-
// === GPTQ (post-training weight quantization) ===
|
|
52
|
-
{ id: "gptq4", label: "GPTQ 4-bit", bytesPerParam: 0.55, efficiency: 0.85, family: "gptq", description: "Post-training 4-bit weight quant. Supported by vLLM, TGI, TensorRT-LLM (varies by version).", useCase: "vLLM/TGI serverless" },
|
|
53
|
-
// === AWQ (activation-aware weight quantization) ===
|
|
54
|
-
{ id: "awq4", label: "AWQ 4-bit", bytesPerParam: 0.55, efficiency: 0.88, family: "awq", description: "Activation-aware W4A16. Preserves salient weights; better accuracy than GPTQ in some setups.", useCase: "vLLM/TensorRT-LLM serving" },
|
|
55
|
-
// === EXL2 (ExLlamaV2, mixed-bit) ===
|
|
56
|
-
{ id: "exl2-6bpw", label: "EXL2 6.0 bpw", bytesPerParam: 0.75, efficiency: 0.92, family: "exl2", description: "Mixed-bit quantization for ExLlamaV2. Common 6.0 bpw preset balances speed/quality on consumer NVIDIA.", useCase: "Consumer NVIDIA + ExLlamaV2" },
|
|
57
|
-
// === FP8 (H100/H200 native) ===
|
|
58
|
-
{ id: "fp8", label: "FP8 (E4M3)", bytesPerParam: 1.0, efficiency: 1.5, family: "fp8", description: "8-bit floating-point. Native on H100/H200 — FASTER than FP16 (efficiency >1). Same VRAM as INT8 but better quality.", useCase: "H100/H200 with TRT-LLM/vLLM" },
|
|
59
|
-
// === NVFP4 (Blackwell only) ===
|
|
60
|
-
{ id: "nvfp4", label: "NVFP4 (Blackwell)", bytesPerParam: 0.5, efficiency: 1.2, family: "nvfp4", description: "4-bit floating-point with block scaling. Native on Blackwell (B200/B300). Roughly 2x faster than FP8 on B200 in cited tests.", useCase: "Blackwell B200/B300 only" },
|
|
61
|
-
];
|
|
62
|
-
|
|
63
|
-
export const QUANT_MAP: Record<Quantization, QuantMeta> =
|
|
64
|
-
Object.fromEntries(QUANTIZATIONS.map((q) => [q.id, q])) as Record<Quantization, QuantMeta>;
|
|
65
|
-
|
|
66
|
-
export type GpuCategory = "datacenter" | "workstation" | "consumer" | "mac" | "tpu" | "lpu" | "wse" | "rdu" | "legacy";
|
|
67
|
-
|
|
68
|
-
export interface GpuSpec {
|
|
69
|
-
id: string;
|
|
70
|
-
name: string;
|
|
71
|
-
vendor: string;
|
|
72
|
-
category: GpuCategory;
|
|
73
|
-
/** HBM bandwidth in GB/s */
|
|
74
|
-
memBandwidthGbps: number;
|
|
75
|
-
/** FP16/BF16 compute in TFLOPS (dense, not sparse). null = vendor does not publish dense FP16 (e.g. B200 exposes FP4/FP8 only) */
|
|
76
|
-
flopsTflops: number | null;
|
|
77
|
-
/** VRAM in GB */
|
|
78
|
-
vramGb: number;
|
|
79
|
-
/** NVLink bandwidth per GPU in GB/s (0 if no NVLink) */
|
|
80
|
-
nvlinkGbps: number;
|
|
81
|
-
/** Typical on-demand $/hr in USD (cloud, rough 2025-2026 estimate). null = quote required */
|
|
82
|
-
usdPerHour: number | null;
|
|
83
|
-
/** Release year */
|
|
84
|
-
year: number;
|
|
85
|
-
/** Special note (e.g. 'FP16 dense not publicly reported', 'rack-scale system', etc.) */
|
|
86
|
-
note?: string;
|
|
87
|
-
/** HuggingFace URL or vendor source for verification */
|
|
88
|
-
sourceUrl?: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export const GPUS: GpuSpec[] = [
|
|
92
|
-
// === NVIDIA Datacenter ===
|
|
93
|
-
// Refs: https://www.nvidia.com/en-us/data-center/h100 · https://www.nvidia.com/en-us/data-center/h200 · https://www.nvidia.com/en-us/data-center/b200
|
|
94
|
-
{ id: "h100-sxm", name: "H100 SXM5 80GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 3350, flopsTflops: 990, vramGb: 80, nvlinkGbps: 900, usdPerHour: 2.50, year: 2022, sourceUrl: "https://www.nvidia.com/en-us/data-center/h100" },
|
|
95
|
-
{ id: "h100-pcie", name: "H100 PCIe 80GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 2000, flopsTflops: 756, vramGb: 80, nvlinkGbps: 0, usdPerHour: 2.00, year: 2023, sourceUrl: "https://www.nvidia.com/en-us/data-center/h100" },
|
|
96
|
-
{ id: "h200-sxm", name: "H200 SXM5 141GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 4800, flopsTflops: 990, vramGb: 141, nvlinkGbps: 900, usdPerHour: 4.00, year: 2024, sourceUrl: "https://www.nvidia.com/en-us/data-center/h200" },
|
|
97
|
-
// B200: NVIDIA publishes FP4/FP8 but not dense FP16 — set to null per research guidance.
|
|
98
|
-
{ id: "b200-sxm", name: "B200 SXM 192GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 8000, flopsTflops: null, vramGb: 192, nvlinkGbps: 1800,usdPerHour: 6.00, year: 2025, note: "FP4/FP8 specs only; FP16 dense not publicly reported", sourceUrl: "https://www.nvidia.com/en-us/data-center/blackwell-architecture" },
|
|
99
|
-
// B300 (2025) — announced/limited availability. Specs subject to change.
|
|
100
|
-
{ id: "b300-sxm", name: "B300 SXM 288GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 8000, flopsTflops: null, vramGb: 288, nvlinkGbps: 1800,usdPerHour: 8.00, year: 2025, note: "Announced; FP16 dense not published", sourceUrl: "https://www.nvidia.com/en-us/data-center/dgx-b300" },
|
|
101
|
-
{ id: "a100-80", name: "A100 80GB SXM4", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 2000, flopsTflops: 312, vramGb: 80, nvlinkGbps: 600, usdPerHour: 1.20, year: 2020, sourceUrl: "https://www.nvidia.com/en-us/data-center/a100" },
|
|
102
|
-
{ id: "a100-40", name: "A100 40GB SXM4", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 1550, flopsTflops: 312, vramGb: 40, nvlinkGbps: 600, usdPerHour: 0.90, year: 2020, sourceUrl: "https://www.nvidia.com/en-us/data-center/a100" },
|
|
103
|
-
{ id: "l40s", name: "L40S 48GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 864, flopsTflops: 91.6, vramGb: 48, nvlinkGbps: 0, usdPerHour: 0.70, year: 2023, sourceUrl: "https://www.nvidia.com/en-us/data-center/l40s" },
|
|
104
|
-
{ id: "a10g", name: "A10G 24GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 600, flopsTflops: 31, vramGb: 24, nvlinkGbps: 0, usdPerHour: 0.36, year: 2021 },
|
|
105
|
-
{ id: "t4", name: "T4 16GB", vendor: "NVIDIA", category: "datacenter", memBandwidthGbps: 320, flopsTflops: 8.1, vramGb: 16, nvlinkGbps: 0, usdPerHour: 0.20, year: 2018 },
|
|
106
|
-
// V100 — legacy but still in production for some workloads
|
|
107
|
-
{ id: "v100", name: "V100 SXM2 32GB", vendor: "NVIDIA", category: "legacy", memBandwidthGbps: 900, flopsTflops: 125, vramGb: 32, nvlinkGbps: 300, usdPerHour: 0.50, year: 2017, note: "Legacy architecture (Volta)" },
|
|
108
|
-
|
|
109
|
-
// === NVIDIA Workstation (Blackwell + Ada) ===
|
|
110
|
-
{ id: "l4", name: "L4 24GB", vendor: "NVIDIA", category: "workstation", memBandwidthGbps: 300, flopsTflops: 30.3, vramGb: 24, nvlinkGbps: 0, usdPerHour: 0.40, year: 2023 },
|
|
111
|
-
{ id: "rtx-6000a",name: "RTX 6000 Ada 48GB", vendor: "NVIDIA", category: "workstation", memBandwidthGbps: 960, flopsTflops: 91.6, vramGb: 48, nvlinkGbps: 0, usdPerHour: 0.80, year: 2022 },
|
|
112
|
-
// RTX PRO 6000 Blackwell — 96GB VRAM, 2025 release
|
|
113
|
-
{ id: "rtx-pro-6000-bw", name: "RTX PRO 6000 Blackwell 96GB", vendor: "NVIDIA", category: "workstation", memBandwidthGbps: 1792, flopsTflops: 125, vramGb: 96, nvlinkGbps: 0, usdPerHour: 1.50, year: 2025, sourceUrl: "https://www.nvidia.com/en-us/products/workstations/professional-desktop-gpus/rtx-pro-6000-family" },
|
|
114
|
-
{ id: "a4000", name: "RTX A4000 16GB", vendor: "NVIDIA", category: "workstation", memBandwidthGbps: 448, flopsTflops: 19.5, vramGb: 16, nvlinkGbps: 0, usdPerHour: 0.30, year: 2021 },
|
|
115
|
-
|
|
116
|
-
// === NVIDIA Consumer (Blackwell + Ada + Ampere) ===
|
|
117
|
-
{ id: "rtx-5090", name: "RTX 5090 32GB", vendor: "NVIDIA", category: "consumer", memBandwidthGbps: 1792, flopsTflops: 105, vramGb: 32, nvlinkGbps: 0, usdPerHour: 0.50, year: 2025, sourceUrl: "https://www.nvidia.com/en-us/geforce/graphics-cards/50-series" },
|
|
118
|
-
{ id: "rtx-4090", name: "RTX 4090 24GB", vendor: "NVIDIA", category: "consumer", memBandwidthGbps: 1008, flopsTflops: 82.6, vramGb: 24, nvlinkGbps: 0, usdPerHour: 0.40, year: 2022 },
|
|
119
|
-
{ id: "rtx-3090", name: "RTX 3090 24GB", vendor: "NVIDIA", category: "consumer", memBandwidthGbps: 936, flopsTflops: 35.6, vramGb: 24, nvlinkGbps: 0, usdPerHour: 0.25, year: 2020 },
|
|
120
|
-
{ id: "rtx-4080", name: "RTX 4080 16GB", vendor: "NVIDIA", category: "consumer", memBandwidthGbps: 717, flopsTflops: 48.7, vramGb: 16, nvlinkGbps: 0, usdPerHour: 0.30, year: 2022 },
|
|
121
|
-
{ id: "rtx-3080", name: "RTX 3080 10GB", vendor: "NVIDIA", category: "consumer", memBandwidthGbps: 760, flopsTflops: 34.1, vramGb: 10, nvlinkGbps: 0, usdPerHour: 0.20, year: 2020 },
|
|
122
|
-
|
|
123
|
-
// === AMD Instinct (ROCm) ===
|
|
124
|
-
// Refs: https://www.amd.com/en/products/accelerators/instinct/mi300/mi300x.html
|
|
125
|
-
{ id: "mi300x", name: "AMD MI300X 192GB", vendor: "AMD", category: "datacenter", memBandwidthGbps: 5300, flopsTflops: 1307, vramGb: 192, nvlinkGbps: 0, usdPerHour: 2.50, year: 2023, note: "ROCm; performance depends on ROCm version", sourceUrl: "https://www.amd.com/en/products/accelerators/instinct/mi300/mi300x.html" },
|
|
126
|
-
{ id: "mi325x", name: "AMD MI325X 288GB", vendor: "AMD", category: "datacenter", memBandwidthGbps: 6000, flopsTflops: 1307, vramGb: 288, nvlinkGbps: 0, usdPerHour: 3.50, year: 2024, sourceUrl: "https://www.amd.com/en/products/accelerators/instinct.html" },
|
|
127
|
-
|
|
128
|
-
// === Intel Gaudi ===
|
|
129
|
-
// Refs: https://www.intel.com/content/www/us/en/products/details/processors/ai-accelerators/gaudi.html
|
|
130
|
-
{ id: "gaudi3", name: "Intel Gaudi 3 128GB", vendor: "Intel", category: "datacenter", memBandwidthGbps: 3300, flopsTflops: 1835, vramGb: 128, nvlinkGbps: 0, usdPerHour: 2.00, year: 2024, note: "Ethernet scale-out; not drop-in for CUDA", sourceUrl: "https://www.intel.com/content/www/us/en/products/details/processors/ai-accelerators/gaudi.html" },
|
|
131
|
-
|
|
132
|
-
// === Google TPU (separate schema, but in same DB for convenience) ===
|
|
133
|
-
// Refs: https://cloud.google.com/tpu/docs
|
|
134
|
-
{ id: "tpu-v5p", name: "TPU v5p (per-chip, 95GB)", vendor: "Google", category: "tpu", memBandwidthGbps: 819, flopsTflops: 459, vramGb: 95, nvlinkGbps: 0, usdPerHour: 4.20, year: 2023, note: "Pod-scale; per-chip specs only", sourceUrl: "https://cloud.google.com/tpu/docs" },
|
|
135
|
-
{ id: "tpu-trillium", name: "TPU v6e Trillium (32GB)", vendor: "Google", category: "tpu", memBandwidthGbps: 819, flopsTflops: 918, vramGb: 32, nvlinkGbps: 0, usdPerHour: 0.80, year: 2024, sourceUrl: "https://cloud.google.com/tpu/docs" },
|
|
136
|
-
|
|
137
|
-
// === Groq LPU (special — designed for LLM inference, no FLOPS-comparable metric) ===
|
|
138
|
-
// Refs: https://groq.com — listed as 'lpu' category; user can use 'continuous batching' multiplier on top
|
|
139
|
-
{ id: "groq-lpu", name: "GroqLPU (per-chip, 230MB SRAM)", vendor: "Groq", category: "lpu", memBandwidthGbps: 9000, flopsTflops: 750, vramGb: 0.23, nvlinkGbps: 0, usdPerHour: null, year: 2024, note: "LPU architecture; SRAM-based, very different from HBM GPUs. Use cited model-specific benchmarks.", sourceUrl: "https://groq.com" },
|
|
140
|
-
|
|
141
|
-
// === Cerebras CS-3 (wafer-scale) ===
|
|
142
|
-
// Refs: https://www.cerebras.ai
|
|
143
|
-
{ id: "cerebras-cs3", name: "Cerebras CS-3 (WSE-3)", vendor: "Cerebras", category: "wse", memBandwidthGbps: 20000, flopsTflops: 1250, vramGb: 44, nvlinkGbps: 0, usdPerHour: null, year: 2024, note: "Wafer-scale; not comparable to per-GPU specs. Use cited benchmarks.", sourceUrl: "https://www.cerebras.ai" },
|
|
144
|
-
|
|
145
|
-
// === Apple Silicon (unified memory) ===
|
|
146
|
-
// Refs: https://www.apple.com/newsroom/2023/06/apple-unveils-m2-ultra/
|
|
147
|
-
{ id: "m2u-800", name: "Mac M2 Ultra (800GB)", vendor: "Apple", category: "mac", memBandwidthGbps: 800, flopsTflops: 27.0, vramGb: 192, nvlinkGbps: 0, usdPerHour: 0.0, year: 2023, note: "Unified memory; purchased hardware, not rentable" },
|
|
148
|
-
{ id: "m2u-192", name: "Mac M2 Ultra (192GB)", vendor: "Apple", category: "mac", memBandwidthGbps: 800, flopsTflops: 27.0, vramGb: 192, nvlinkGbps: 0, usdPerHour: 0.0, year: 2023, note: "Unified memory" },
|
|
149
|
-
{ id: "m3max-64", name: "Mac M3 Max (64GB)", vendor: "Apple", category: "mac", memBandwidthGbps: 400, flopsTflops: 14.0, vramGb: 64, nvlinkGbps: 0, usdPerHour: 0.0, year: 2023, note: "Unified memory" },
|
|
150
|
-
{ id: "m4max-128",name: "Mac M4 Max (128GB)", vendor: "Apple", category: "mac", memBandwidthGbps: 546, flopsTflops: 17.0, vramGb: 128, nvlinkGbps: 0, usdPerHour: 0.0, year: 2024, note: "Unified memory" },
|
|
151
|
-
];
|
|
152
|
-
|
|
153
|
-
export const GPU_MAP: Record<string, GpuSpec> = Object.fromEntries(GPUS.map((g) => [g.id, g]));
|
|
154
|
-
|
|
155
|
-
export type ModelCategory = "text" | "vlm" | "embedding" | "code" | "reasoning";
|
|
156
|
-
|
|
157
|
-
export interface ModelSpec {
|
|
158
|
-
id: string;
|
|
159
|
-
name: string;
|
|
160
|
-
family: string;
|
|
161
|
-
/** Workload category — text, vision-language, embedding, code, or reasoning */
|
|
162
|
-
category: ModelCategory;
|
|
163
|
-
/** total params in billions */
|
|
164
|
-
paramsB: number;
|
|
165
|
-
/** active params in billions (for MoE; = paramsB for dense) */
|
|
166
|
-
activeParamsB: number;
|
|
167
|
-
/** number of transformer layers */
|
|
168
|
-
layers: number;
|
|
169
|
-
/** hidden dimension */
|
|
170
|
-
hiddenDim: number;
|
|
171
|
-
/** query heads */
|
|
172
|
-
qHeads: number;
|
|
173
|
-
/** kv heads (for GQA) */
|
|
174
|
-
kvHeads: number;
|
|
175
|
-
/** head dimension */
|
|
176
|
-
headDim: number;
|
|
177
|
-
/** vocab size */
|
|
178
|
-
vocabSize: number;
|
|
179
|
-
/** default max context */
|
|
180
|
-
maxContext: number;
|
|
181
|
-
/** is MoE */
|
|
182
|
-
isMoE: boolean;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export const MODELS: ModelSpec[] = [
|
|
186
|
-
// === Llama family ===
|
|
187
|
-
{ id: "llama3-8b", name: "Llama 3 8B", family: "Llama", category: "text", paramsB: 8.03, activeParamsB: 8.03, layers: 32, hiddenDim: 4096, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 128256, maxContext: 8192, isMoE: false },
|
|
188
|
-
{ id: "llama3-70b", name: "Llama 3 70B", family: "Llama", category: "text", paramsB: 70.6, activeParamsB: 70.6, layers: 80, hiddenDim: 8192, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 128256, maxContext: 8192, isMoE: false },
|
|
189
|
-
{ id: "llama3-405b", name: "Llama 3.1 405B", family: "Llama", category: "text", paramsB: 405, activeParamsB: 405, layers: 126,hiddenDim: 16384, qHeads: 128,kvHeads: 8, headDim: 128, vocabSize: 128256, maxContext: 131072, isMoE: false },
|
|
190
|
-
{ id: "llama3-3-70b",name: "Llama 3.3 70B", family: "Llama", category: "text", paramsB: 70.6, activeParamsB: 70.6, layers: 80, hiddenDim: 8192, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 128256, maxContext: 131072, isMoE: false },
|
|
191
|
-
{ id: "llama2-7b", name: "Llama 2 7B", family: "Llama", category: "text", paramsB: 6.74, activeParamsB: 6.74, layers: 32, hiddenDim: 4096, qHeads: 32, kvHeads: 32, headDim: 128, vocabSize: 32000, maxContext: 4096, isMoE: false },
|
|
192
|
-
// Llama 4 — open-weight multimodal MoE (2025-04). Exact architecture fields not fully published
|
|
193
|
-
// for all variants; we use Meta's release figures for total/active params per official blog.
|
|
194
|
-
// Refs: https://huggingface.co/meta-llama/Llama-4-Scout-17B-16E-Instruct
|
|
195
|
-
{ id: "llama4-scout", name: "Llama 4 Scout 17B-16E (MoE, VLM)", family: "Llama", category: "vlm", paramsB: 109, activeParamsB: 17, layers: 48, hiddenDim: 5120, qHeads: 40, kvHeads: 8, headDim: 128, vocabSize: 202000, maxContext: 10000000, isMoE: true },
|
|
196
|
-
{ id: "llama4-maverick", name: "Llama 4 Maverick 17B-128E (MoE, VLM)", family: "Llama", category: "vlm", paramsB: 400, activeParamsB: 17, layers: 48, hiddenDim: 5120, qHeads: 40, kvHeads: 8, headDim: 128, vocabSize: 202000, maxContext: 1000000, isMoE: true },
|
|
197
|
-
|
|
198
|
-
// === Mistral family ===
|
|
199
|
-
{ id: "mistral-7b", name: "Mistral 7B v0.3", family: "Mistral", category: "text", paramsB: 7.24, activeParamsB: 7.24, layers: 32, hiddenDim: 4096, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 32768, maxContext: 32768, isMoE: false },
|
|
200
|
-
{ id: "mixtral-8x7b",name: "Mixtral 8x7B (MoE)", family: "Mistral", category: "text", paramsB: 46.7, activeParamsB: 12.9, layers: 32, hiddenDim: 4096, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 32000, maxContext: 32768, isMoE: true },
|
|
201
|
-
{ id: "mixtral-8x22b",name:"Mixtral 8x22B (MoE)", family: "Mistral", category: "text", paramsB: 141, activeParamsB: 39, layers: 56, hiddenDim: 6144, qHeads: 48, kvHeads: 8, headDim: 128, vocabSize: 32000, maxContext: 65536, isMoE: true },
|
|
202
|
-
// Mistral Large 3 (2025-12) — open-weight MoE, 256K context. Refs: https://docs.mistral.ai/models/mistral-large-3-25-12
|
|
203
|
-
{ id: "mistral-large-3", name: "Mistral Large 3 (256K)", family: "Mistral", category: "text", paramsB: 235, activeParamsB: 71, layers: 78, hiddenDim: 7168, qHeads: 56, kvHeads: 8, headDim: 128, vocabSize: 131072, maxContext: 262144, isMoE: true },
|
|
204
|
-
{ id: "pixtral-12b", name: "Pixtral 12B (VLM)", family: "Mistral", category: "vlm", paramsB: 12, activeParamsB: 12, layers: 40, hiddenDim: 5120, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 131072, maxContext: 131072, isMoE: false },
|
|
205
|
-
{ id: "codestral-25", name: "Codestral 25.08", family: "Mistral", category: "code", paramsB: 22, activeParamsB: 22, layers: 56, hiddenDim: 6144, qHeads: 48, kvHeads: 8, headDim: 128, vocabSize: 32768, maxContext: 32768, isMoE: false },
|
|
206
|
-
|
|
207
|
-
// === Qwen family ===
|
|
208
|
-
{ id: "qwen2-7b", name: "Qwen2 7B", family: "Qwen", category: "text", paramsB: 7.62, activeParamsB: 7.62, layers: 28, hiddenDim: 3584, qHeads: 28, kvHeads: 4, headDim: 128, vocabSize: 152064, maxContext: 32768, isMoE: false },
|
|
209
|
-
{ id: "qwen2-72b", name: "Qwen2 72B", family: "Qwen", category: "text", paramsB: 72.7, activeParamsB: 72.7, layers: 80, hiddenDim: 8192, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 152064, maxContext: 32768, isMoE: false },
|
|
210
|
-
{ id: "qwen2-5-14b",name: "Qwen 2.5 14B", family: "Qwen", category: "text", paramsB: 14.0, activeParamsB: 14.0, layers: 48, hiddenDim: 5120, qHeads: 40, kvHeads: 8, headDim: 128, vocabSize: 152064, maxContext: 131072, isMoE: false },
|
|
211
|
-
{ id: "qwen2-5-72b",name: "Qwen 2.5 72B", family: "Qwen", category: "text", paramsB: 72.7, activeParamsB: 72.7, layers: 80, hiddenDim: 8192, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 152064, maxContext: 131072, isMoE: false },
|
|
212
|
-
// Qwen 3 family (2025-04). Refs: https://huggingface.co/Qwen
|
|
213
|
-
{ id: "qwen3-4b", name: "Qwen 3 4B", family: "Qwen", category: "text", paramsB: 4.0, activeParamsB: 4.0, layers: 36, hiddenDim: 2560, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 151936, maxContext: 40960, isMoE: false },
|
|
214
|
-
{ id: "qwen3-8b", name: "Qwen 3 8B", family: "Qwen", category: "text", paramsB: 8.0, activeParamsB: 8.0, layers: 36, hiddenDim: 4096, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 151936, maxContext: 40960, isMoE: false },
|
|
215
|
-
{ id: "qwen3-14b", name: "Qwen 3 14B", family: "Qwen", category: "text", paramsB: 14.0, activeParamsB: 14.0, layers: 40, hiddenDim: 5120, qHeads: 40, kvHeads: 8, headDim: 128, vocabSize: 151936, maxContext: 40960, isMoE: false },
|
|
216
|
-
{ id: "qwen3-32b", name: "Qwen 3 32B", family: "Qwen", category: "text", paramsB: 32.0, activeParamsB: 32.0, layers: 64, hiddenDim: 5120, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 151936, maxContext: 40960, isMoE: false },
|
|
217
|
-
{ id: "qwen3-30b-a3b", name: "Qwen 3 30B-A3B (MoE)", family: "Qwen", category: "text", paramsB: 30.0, activeParamsB: 3.0, layers: 48, hiddenDim: 2048, qHeads: 32, kvHeads: 4, headDim: 128, vocabSize: 151936, maxContext: 40960, isMoE: true },
|
|
218
|
-
{ id: "qwen3-235b-a22b", name: "Qwen 3 235B-A22B (MoE)", family: "Qwen", category: "text", paramsB: 235, activeParamsB: 22, layers: 94, hiddenDim: 4096, qHeads: 64, kvHeads: 4, headDim: 128, vocabSize: 151936, maxContext: 40960, isMoE: true },
|
|
219
|
-
{ id: "qwen2-5-vl-7b", name: "Qwen 2.5-VL 7B (VLM)", family: "Qwen", category: "vlm", paramsB: 7.0, activeParamsB: 7.0, layers: 28, hiddenDim: 3584, qHeads: 28, kvHeads: 4, headDim: 128, vocabSize: 152064, maxContext: 128000, isMoE: false },
|
|
220
|
-
{ id: "qwen2-5-vl-72b", name: "Qwen 2.5-VL 72B (VLM)", family: "Qwen", category: "vlm", paramsB: 72.0, activeParamsB: 72.0, layers: 80, hiddenDim: 8192, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 152064, maxContext: 128000, isMoE: false },
|
|
221
|
-
|
|
222
|
-
// === DeepSeek family ===
|
|
223
|
-
{ id: "deepseek-v3",name: "DeepSeek V3 671B (MoE)",family:"DeepSeek",category:"text", paramsB: 671, activeParamsB: 37, layers: 61, hiddenDim: 7168, qHeads: 128,kvHeads: 128,headDim: 128, vocabSize: 102400, maxContext: 65536, isMoE: true },
|
|
224
|
-
{ id: "deepseek-r1",name: "DeepSeek R1 671B (MoE)",family:"DeepSeek",category:"reasoning", paramsB: 671, activeParamsB: 37, layers: 61, hiddenDim: 7168, qHeads: 128,kvHeads: 128,headDim: 128, vocabSize: 102400, maxContext: 65536, isMoE: true },
|
|
225
|
-
{ id: "deepseek-coder-v2", name: "DeepSeek Coder V2 236B (MoE)", family:"DeepSeek", category:"code", paramsB: 236, activeParamsB: 21, layers: 47, hiddenDim: 6144, qHeads: 64, kvHeads: 8, headDim: 128, vocabSize: 102400, maxContext: 131072, isMoE: true },
|
|
226
|
-
|
|
227
|
-
// === Google Gemma ===
|
|
228
|
-
{ id: "gemma2-9b", name: "Gemma 2 9B", family: "Gemma", category: "text", paramsB: 9.0, activeParamsB: 9.0, layers: 42, hiddenDim: 3584, qHeads: 16, kvHeads: 8, headDim: 256, vocabSize: 256000, maxContext: 8192, isMoE: false },
|
|
229
|
-
{ id: "gemma2-27b", name: "Gemma 2 27B", family: "Gemma", category: "text", paramsB: 27.0, activeParamsB: 27.0, layers: 46, hiddenDim: 4608, qHeads: 32, kvHeads: 16, headDim: 128, vocabSize: 256000, maxContext: 8192, isMoE: false },
|
|
230
|
-
|
|
231
|
-
// === Microsoft Phi ===
|
|
232
|
-
{ id: "phi3-7b", name: "Phi-3 Mini 3.8B", family: "Phi", category: "text", paramsB: 3.8, activeParamsB: 3.8, layers: 32, hiddenDim: 3072, qHeads: 32, kvHeads: 32, headDim: 96, vocabSize: 32000, maxContext: 4096, isMoE: false },
|
|
233
|
-
{ id: "phi4-14b", name: "Phi-4 14B", family: "Phi", category: "text", paramsB: 14.0, activeParamsB: 14.0, layers: 40, hiddenDim: 5120, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 100352, maxContext: 16384, isMoE: false },
|
|
234
|
-
|
|
235
|
-
// === Small / open research ===
|
|
236
|
-
{ id: "smollm2-1.7b",name: "SmolLM2 1.7B Instruct", family: "HuggingFaceTB", category: "text", paramsB: 1.7, activeParamsB: 1.7, layers: 24, hiddenDim: 2048, qHeads: 32, kvHeads: 32, headDim: 64, vocabSize: 128256, maxContext: 8192, isMoE: false },
|
|
237
|
-
{ id: "falcon3-10b",name: "Falcon 3 10B Instruct", family: "TII", category: "text", paramsB: 10.0, activeParamsB: 10.0, layers: 40, hiddenDim: 4096, qHeads: 32, kvHeads: 8, headDim: 128, vocabSize: 49152, maxContext: 32768, isMoE: false },
|
|
238
|
-
{ id: "olmo2-13b", name: "OLMo 2 13B", family: "AllenAI", category: "text", paramsB: 13.0, activeParamsB: 13.0, layers: 40, hiddenDim: 5120, qHeads: 40, kvHeads: 8, headDim: 128, vocabSize: 50281, maxContext: 4096, isMoE: false },
|
|
239
|
-
{ id: "gpt-neox-20b",name:"GPT-NeoX 20B", family:"EleutherAI",category: "text", paramsB: 20.0, activeParamsB: 20.0, layers: 44, hiddenDim: 6144, qHeads: 64, kvHeads: 64, headDim: 96, vocabSize: 50432, maxContext: 2048, isMoE: false },
|
|
240
|
-
|
|
241
|
-
// === Embedding models (separate workload — encoder, not autoregressive) ===
|
|
242
|
-
// Refs: https://huggingface.co/BAAI/bge-m3 · https://huggingface.co/intfloat/multilingual-e5-large
|
|
243
|
-
{ id: "bge-m3", name: "BGE-M3 (embed, 1024-dim)", family: "BAAI", category: "embedding", paramsB: 0.568, activeParamsB: 0.568, layers: 24, hiddenDim: 1024, qHeads: 16, kvHeads: 16, headDim: 64, vocabSize: 250002, maxContext: 8192, isMoE: false },
|
|
244
|
-
{ id: "e5-mlarge", name: "Multilingual E5 Large (1024-dim)", family: "intfloat", category: "embedding", paramsB: 0.56, activeParamsB: 0.56, layers: 24, hiddenDim: 1024, qHeads: 16, kvHeads: 16, headDim: 64, vocabSize: 250047, maxContext: 514, isMoE: false },
|
|
245
|
-
{ id: "gte-large", name: "GTE Large (1024-dim)", family: "thenlper", category: "embedding", paramsB: 0.44, activeParamsB: 0.44, layers: 24, hiddenDim: 1024, qHeads: 16, kvHeads: 16, headDim: 64, vocabSize: 30522, maxContext: 512, isMoE: false },
|
|
246
|
-
];
|
|
247
|
-
|
|
248
|
-
export const MODEL_MAP: Record<string, ModelSpec> = Object.fromEntries(MODELS.map((m) => [m.id, m]));
|
|
249
|
-
|
|
250
|
-
/** ---------- EFFICIENCY CONSTANTS ---------- */
|
|
251
|
-
export const ETA_MEM = 0.65; // Memory bandwidth utilization (real-world)
|
|
252
|
-
export const ETA_COMPUTE = 0.50; // Compute utilization
|
|
253
|
-
export const ETA_NVLINK = 0.85; // NVLink tensor-parallel communication efficiency
|
|
254
|
-
export const KERNEL_OVERHEAD_MS = 30; // per-request overhead
|
|
255
|
-
|
|
256
|
-
/**
|
|
257
|
-
* Confidence levels surfaced in the UI to help users trust (or question)
|
|
258
|
-
* specific numbers. The research brief called this "honest uncertainty" —
|
|
259
|
-
* a key credibility signal for technical audiences (HN/Reddit/ML engineers).
|
|
260
|
-
*
|
|
261
|
-
* - "measured" → sourced directly from official spec sheets, model cards,
|
|
262
|
-
* API pricing pages, or HuggingFace config.json
|
|
263
|
-
* - "modeled" → derived from physics-based formulas with cited sources
|
|
264
|
-
* (e.g., decode = HBM_BW × η_mem / model_size)
|
|
265
|
-
* - "inferred" → derived from heuristics with known error bars
|
|
266
|
-
* (e.g., continuous batching multiplier 1.5× default,
|
|
267
|
-
* long-context attention O(N²) correction, B200 null FP16 fallback)
|
|
268
|
-
* - "user-supplied" → user-provided value (e.g., GPU $/hr override)
|
|
269
|
-
*/
|
|
270
|
-
export type Confidence = "measured" | "modeled" | "inferred" | "user-supplied";
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Map of metric name → confidence level.
|
|
274
|
-
* Surfaced in the UI as a colored dot + tooltip next to each metric.
|
|
275
|
-
*/
|
|
276
|
-
export type ConfidenceMap = Record<string, Confidence>;
|
|
277
|
-
|
|
278
|
-
/** Continuous-batching multiplier guidance:
|
|
279
|
-
* - Research warns NO universal multiplier exists
|
|
280
|
-
* - vLLM reported 14–24x vs HF Transformers (extreme), 2.2–2.5x vs TGI
|
|
281
|
-
* - SOSP paper: 2–4x throughput vs FasterTransformer/Orca at same latency
|
|
282
|
-
* - Conservative default: 1.5x (clearly cited as "benchmark-derived range, not universal")
|
|
283
|
-
* Refs: https://arxiv.org/abs/2309.06180 (vLLM/PagedAttention paper)
|
|
284
|
-
*/
|
|
285
|
-
export const DEFAULT_BATCHING_MULTIPLIER = 1.5;
|
|
286
|
-
|
|
287
|
-
/** Anthropic prompt caching multipliers (verified 2025):
|
|
288
|
-
* - 5-minute cache write: 1.25x base input
|
|
289
|
-
* - 1-hour cache write: 2.0x base input
|
|
290
|
-
* - cache read: 0.1x base input (90% savings)
|
|
291
|
-
* - OpenAI: 50% off cached input (no separate write fee)
|
|
292
|
-
* Refs: https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
|
|
293
|
-
*/
|
|
294
|
-
export const ANTHROPIC_CACHE_WRITE_5M = 1.25;
|
|
295
|
-
export const ANTHROPIC_CACHE_WRITE_1H = 2.0;
|
|
296
|
-
export const ANTHROPIC_CACHE_READ = 0.1;
|
|
297
|
-
export const OPENAI_CACHE_DISCOUNT = 0.5;
|
|
298
|
-
|
|
299
|
-
export type CacheTTL = "none" | "5m" | "1h";
|
|
300
|
-
|
|
301
|
-
export interface CalcInput {
|
|
302
|
-
modelId: string;
|
|
303
|
-
gpuId: string;
|
|
304
|
-
quantization: Quantization;
|
|
305
|
-
numGpus: number; // tensor parallel degree
|
|
306
|
-
batchSize: number; // concurrent requests
|
|
307
|
-
promptTokens: number; // input length (excluding cache prefix)
|
|
308
|
-
outputTokens: number; // requested output (visible answer)
|
|
309
|
-
gpuHourlyCost?: number; // optional override
|
|
310
|
-
useSpeculative?: boolean; // speculative decoding?
|
|
311
|
-
speculativeBoost?: number; // multiplier e.g. 2.0
|
|
312
|
-
// === Phase 2 additions ===
|
|
313
|
-
useContinuousBatching?: boolean; // continuous batching toggle
|
|
314
|
-
continuousBatchingMultiplier?: number; // user-tunable 1.0–4.0x
|
|
315
|
-
reasoningTokens?: number; // hidden reasoning budget (o1/R1/thinking)
|
|
316
|
-
// Prompt caching (self-hosted or API)
|
|
317
|
-
cachePrefixTokens?: number; // reusable prefix length (e.g. system prompt + RAG context)
|
|
318
|
-
cacheHitRate?: number; // 0–1 fraction of requests that hit the cache
|
|
319
|
-
cacheTTL?: CacheTTL; // "none" | "5m" | "1h"
|
|
320
|
-
cacheProvider?: "self-hosted" | "anthropic" | "openai";
|
|
321
|
-
// === Engine-aware presets ===
|
|
322
|
-
engineId?: string; // "vllm" | "sglang" | "trtllm" | "llamacpp" | "generic"
|
|
323
|
-
// When set, UI passes engine-specific effMem/effCompute
|
|
324
|
-
effMem?: number; // Engine-specific memory bandwidth utilization (overrides ETA_MEM)
|
|
325
|
-
effCompute?: number; // Engine-specific compute utilization (overrides ETA_COMPUTE)
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export interface CalcResult {
|
|
329
|
-
// ---- Raw measurements ----
|
|
330
|
-
modelSizeGb: number;
|
|
331
|
-
kvCachePerTokenKb: number;
|
|
332
|
-
kvCacheTotalGb: number;
|
|
333
|
-
totalVramNeededGb: number;
|
|
334
|
-
vramFits: boolean;
|
|
335
|
-
|
|
336
|
-
// ---- Throughput ----
|
|
337
|
-
decodeTokensPerSec: number; // per-stream (batch=1)
|
|
338
|
-
aggregateTokensPerSec: number; // batched (with continuous batching multiplier if enabled)
|
|
339
|
-
prefillTokensPerSec: number; // prefill throughput
|
|
340
|
-
batchCrossover: number; // batch size where compute becomes bound
|
|
341
|
-
|
|
342
|
-
// ---- Latency (new: split into TTFT + ITL) ----
|
|
343
|
-
prefillTimeMs: number; // = TTFT (time-to-first-token)
|
|
344
|
-
decodeTimePerTokenMs: number; // = ITL (inter-token latency)
|
|
345
|
-
totalDecodeTimeMs: number;
|
|
346
|
-
totalLatencyMs: number;
|
|
347
|
-
endToEndTokensPerSec: number; // output / total time
|
|
348
|
-
ttftMs: number; // alias for prefillTimeMs — for clarity
|
|
349
|
-
itlMs: number; // alias for decodeTimePerTokenMs — for clarity
|
|
350
|
-
|
|
351
|
-
// ---- Reasoning ----
|
|
352
|
-
billedOutputTokens: number; // visible + hidden reasoning tokens
|
|
353
|
-
|
|
354
|
-
// ---- Prompt caching ----
|
|
355
|
-
cacheHitRate: number;
|
|
356
|
-
cachePrefixTokens: number;
|
|
357
|
-
cacheTTL: CacheTTL;
|
|
358
|
-
cacheProvider: string;
|
|
359
|
-
prefillTokensAvoided: number; // how many prefill tokens saved per cache hit
|
|
360
|
-
cacheWriteCostMultiplier: number; // 1.25 (5m) or 2.0 (1h) for Anthropic; 1.0 for OpenAI
|
|
361
|
-
cacheReadCostMultiplier: number; // 0.1 (Anthropic) or 0.5 (OpenAI) or 0 (self-hosted)
|
|
362
|
-
effectivePrefillTokens: number; // after cache hit adjustment
|
|
363
|
-
prefillTimeWithCacheMs: number; // actual prefill time accounting for cache
|
|
364
|
-
cacheSavingsPct: number; // % reduction in prefill time from caching
|
|
365
|
-
|
|
366
|
-
// ---- Cost ----
|
|
367
|
-
costPerHour: number;
|
|
368
|
-
costPerMillionOutputTokens: number;
|
|
369
|
-
costPerRequest: number;
|
|
370
|
-
costPerRequestWithCache: number; // including cache write/read cost
|
|
371
|
-
|
|
372
|
-
// ---- Components for display ----
|
|
373
|
-
effectiveBandwidthGbps: number;
|
|
374
|
-
effectiveFlopsTflops: number;
|
|
375
|
-
quantEfficiency: number;
|
|
376
|
-
speculativeBoost: number;
|
|
377
|
-
multiGpuEfficiency: number;
|
|
378
|
-
continuousBatchingMultiplier: number; // actual multiplier applied
|
|
379
|
-
longContextWarning?: string; // set when prompt > 32K
|
|
380
|
-
// Per-metric confidence map — surfaced as colored dots in the UI
|
|
381
|
-
// (research: "honest uncertainty" = credibility signal for HN/Reddit/ML engineers)
|
|
382
|
-
confidence: ConfidenceMap;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Main calculation function.
|
|
387
|
-
*
|
|
388
|
-
* Math basis:
|
|
389
|
-
* decode_tokens_per_sec ≈ (HBM_BW × η_mem × quant_eff) / model_size (batch=1, mem-bound)
|
|
390
|
-
* prefill_tokens_per_sec ≈ (FLOPS × η_compute) / (2 × active_params)
|
|
391
|
-
* batch_crossover ≈ (model_size × FLOPS × η_compute) / (2 × HBM_BW × η_mem × quant_eff)
|
|
392
|
-
* = bytes_per_param × FLOPS × η_compute / (2 × HBM_BW × η_mem × quant_eff)
|
|
393
|
-
* total_latency = prefill_time + output × decode_time + overhead
|
|
394
|
-
* cost_per_M_tokens = (gpu_hourly_cost / 3600) / (decode_tokens_per_sec × batch) × 1e6
|
|
395
|
-
*/
|
|
396
|
-
export function calculate(input: CalcInput): CalcResult {
|
|
397
|
-
const model = MODEL_MAP[input.modelId];
|
|
398
|
-
const gpu = GPU_MAP[input.gpuId];
|
|
399
|
-
const quant = QUANT_MAP[input.quantization];
|
|
400
|
-
|
|
401
|
-
if (!model) throw new Error(`Unknown model: ${input.modelId}`);
|
|
402
|
-
if (!gpu) throw new Error(`Unknown GPU: ${input.gpuId}`);
|
|
403
|
-
|
|
404
|
-
// ---- Model size ----
|
|
405
|
-
// For MoE, only active params are loaded per token; full params still occupy VRAM.
|
|
406
|
-
const modelSizeGb = (model.activeParamsB * quant.bytesPerParam);
|
|
407
|
-
|
|
408
|
-
// ---- Effective bandwidth / compute ----
|
|
409
|
-
const quantEff = quant.efficiency;
|
|
410
|
-
const tp = Math.max(1, input.numGpus);
|
|
411
|
-
const multiGpuEfficiency = tp > 1 ? ETA_NVLINK : 1.0;
|
|
412
|
-
const speculativeBoost = input.useSpeculative ? (input.speculativeBoost ?? 2.0) : 1.0;
|
|
413
|
-
const continuousBatchingMultiplier = input.useContinuousBatching
|
|
414
|
-
? (input.continuousBatchingMultiplier ?? DEFAULT_BATCHING_MULTIPLIER)
|
|
415
|
-
: 1.0;
|
|
416
|
-
|
|
417
|
-
// Engine-aware efficiency factors: if effMem/effCompute are provided in input
|
|
418
|
-
// (set by the UI based on selected engine), use those instead of global defaults.
|
|
419
|
-
// This makes tokcalc engine-aware — vLLM, SGLang, TRT-LLM, llama.cpp all have
|
|
420
|
-
// different real-world utilization due to kernel optimizations and scheduler design.
|
|
421
|
-
const effMem = input.effMem ?? ETA_MEM;
|
|
422
|
-
const effCompute = input.effCompute ?? ETA_COMPUTE;
|
|
423
|
-
|
|
424
|
-
// Aggregate bandwidth across TP GPUs (uses engine-specific effMem if provided)
|
|
425
|
-
const effectiveBandwidthGbps = gpu.memBandwidthGbps * tp * effMem * multiGpuEfficiency;
|
|
426
|
-
// Some accelerators (B200/B300) don't publish dense FP16 — fall back to ~2.5x FP8 as conservative estimate.
|
|
427
|
-
const gpuFlops = gpu.flopsTflops ?? 1500; // conservative fallback if null
|
|
428
|
-
const effectiveFlopsTflops = gpuFlops * tp * effCompute;
|
|
429
|
-
|
|
430
|
-
// ---- KV cache ----
|
|
431
|
-
// KV cache per token = 2 (K&V) × layers × kvHeads × headDim × 2 bytes (FP16)
|
|
432
|
-
const kvBytesPerToken = 2 * model.layers * model.kvHeads * model.headDim * 2;
|
|
433
|
-
const kvCachePerTokenKb = kvBytesPerToken / 1024;
|
|
434
|
-
// KV cache size depends on total context (prefix + suffix) × batch
|
|
435
|
-
const totalContextTokens = input.promptTokens + (input.cachePrefixTokens ?? 0);
|
|
436
|
-
const kvCacheTotalGb =
|
|
437
|
-
(kvBytesPerToken * totalContextTokens * input.batchSize) / 1e9;
|
|
438
|
-
|
|
439
|
-
// ---- Total VRAM ----
|
|
440
|
-
const fullWeightsGb = model.paramsB * quant.bytesPerParam;
|
|
441
|
-
const totalVramNeededGb = fullWeightsGb + kvCacheTotalGb;
|
|
442
|
-
const vramFits = totalVramNeededGb <= gpu.vramGb * tp;
|
|
443
|
-
|
|
444
|
-
// ---- Throughput ----
|
|
445
|
-
const decodeTokensPerSecRaw = effectiveBandwidthGbps / modelSizeGb * quantEff * speculativeBoost;
|
|
446
|
-
const decodeTokensPerSec = vramFits ? decodeTokensPerSecRaw : 0;
|
|
447
|
-
|
|
448
|
-
// Prefill / compute-bound ceiling
|
|
449
|
-
const prefillTokensPerSec = (effectiveFlopsTflops * 1e12) / (2 * model.activeParamsB * 1e9);
|
|
450
|
-
const computeCeiling = prefillTokensPerSec; // same ceiling applies
|
|
451
|
-
|
|
452
|
-
// Batch crossover (unit-corrected with ×1000 for TFLOPS/GB-s ratio)
|
|
453
|
-
const batchCrossover =
|
|
454
|
-
(quant.bytesPerParam * gpuFlops * 1000 * ETA_COMPUTE) /
|
|
455
|
-
(2 * gpu.memBandwidthGbps * ETA_MEM * quantEff * (tp > 1 ? multiGpuEfficiency : 1));
|
|
456
|
-
|
|
457
|
-
// Aggregate tokens/sec — with continuous batching multiplier applied
|
|
458
|
-
const memoryBoundAggregate = decodeTokensPerSec * input.batchSize * continuousBatchingMultiplier;
|
|
459
|
-
const aggregateTokensPerSec = vramFits
|
|
460
|
-
? Math.min(memoryBoundAggregate, computeCeiling)
|
|
461
|
-
: 0;
|
|
462
|
-
|
|
463
|
-
// ---- Prompt caching ----
|
|
464
|
-
const cachePrefixTokens = input.cachePrefixTokens ?? 0;
|
|
465
|
-
const cacheHitRate = input.cacheHitRate ?? 0;
|
|
466
|
-
const cacheTTL = input.cacheTTL ?? "none";
|
|
467
|
-
const cacheProvider = input.cacheProvider ?? "self-hosted";
|
|
468
|
-
|
|
469
|
-
// Compute cache write/read multipliers based on provider
|
|
470
|
-
let cacheWriteCostMultiplier = 1.0;
|
|
471
|
-
let cacheReadCostMultiplier = 0.0; // self-hosted: cache reads are "free" (just memory access)
|
|
472
|
-
if (cacheProvider === "anthropic") {
|
|
473
|
-
cacheWriteCostMultiplier = cacheTTL === "1h" ? ANTHROPIC_CACHE_WRITE_1H : ANTHROPIC_CACHE_WRITE_5M;
|
|
474
|
-
cacheReadCostMultiplier = ANTHROPIC_CACHE_READ;
|
|
475
|
-
} else if (cacheProvider === "openai") {
|
|
476
|
-
cacheWriteCostMultiplier = 1.0; // OpenAI: no separate write fee
|
|
477
|
-
cacheReadCostMultiplier = OPENAI_CACHE_DISCOUNT;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
// Effective prefill tokens after cache hit (fraction of prefix avoided per request, on average)
|
|
481
|
-
// For self-hosted: full prefix is avoided on hit. For API: cost is reduced, not avoided.
|
|
482
|
-
const prefillTokensAvoided = Math.round(cachePrefixTokens * cacheHitRate);
|
|
483
|
-
const effectivePrefillTokens = Math.max(0, totalContextTokens - prefillTokensAvoided);
|
|
484
|
-
const prefillTimeWithCacheMs = effectivePrefillTokens > 0
|
|
485
|
-
? (effectivePrefillTokens / prefillTokensPerSec) * 1000
|
|
486
|
-
: 0;
|
|
487
|
-
const cacheSavingsPct = totalContextTokens > 0
|
|
488
|
-
? (prefillTokensAvoided / totalContextTokens) * 100
|
|
489
|
-
: 0;
|
|
490
|
-
|
|
491
|
-
// ---- Latency ----
|
|
492
|
-
// Use cache-adjusted prefill time if caching is enabled
|
|
493
|
-
const prefillTimeMs = input.promptTokens > 0
|
|
494
|
-
? (input.useContinuousBatching || cachePrefixTokens > 0
|
|
495
|
-
? prefillTimeWithCacheMs
|
|
496
|
-
: (input.promptTokens / prefillTokensPerSec) * 1000)
|
|
497
|
-
: 0;
|
|
498
|
-
const decodeTimePerTokenMs = decodeTokensPerSec > 0 ? (1000 / decodeTokensPerSec) : Infinity;
|
|
499
|
-
|
|
500
|
-
// ---- Reasoning tokens (billed as output but invisible to user) ----
|
|
501
|
-
const reasoningTokens = input.reasoningTokens ?? 0;
|
|
502
|
-
const billedOutputTokens = input.outputTokens + reasoningTokens;
|
|
503
|
-
const totalDecodeTimeMs = billedOutputTokens * decodeTimePerTokenMs;
|
|
504
|
-
const totalLatencyMs = prefillTimeMs + totalDecodeTimeMs + KERNEL_OVERHEAD_MS;
|
|
505
|
-
const endToEndTokensPerSec =
|
|
506
|
-
totalLatencyMs > 0 ? (billedOutputTokens / totalLatencyMs) * 1000 : 0;
|
|
507
|
-
|
|
508
|
-
// ---- Cost ----
|
|
509
|
-
const costPerHour = input.gpuHourlyCost !== undefined ? input.gpuHourlyCost : (gpu.usdPerHour ?? 0);
|
|
510
|
-
const costPerSecond = costPerHour / 3600;
|
|
511
|
-
const costPerRequest =
|
|
512
|
-
(totalLatencyMs / 1000) * costPerSecond * tp;
|
|
513
|
-
const costPerMillionOutputTokens =
|
|
514
|
-
aggregateTokensPerSec > 0 ? (costPerSecond * tp * 1e6) / aggregateTokensPerSec : Infinity;
|
|
515
|
-
|
|
516
|
-
// Cost with cache — for self-hosted, same as costPerRequest (latency-driven)
|
|
517
|
-
// For API providers, cache economics would be computed separately in the Build-vs-Buy tab
|
|
518
|
-
const costPerRequestWithCache = costPerRequest;
|
|
519
|
-
|
|
520
|
-
// ---- Long-context warning ----
|
|
521
|
-
let longContextWarning: string | undefined;
|
|
522
|
-
if (totalContextTokens > 32768) {
|
|
523
|
-
longContextWarning = `Context ${totalContextTokens.toLocaleString()} tokens > 32K. ` +
|
|
524
|
-
`Attention cost grows superlinearly with context for dense models; ` +
|
|
525
|
-
`real-world throughput may be 20–40% lower than this estimate.`;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
return {
|
|
529
|
-
modelSizeGb,
|
|
530
|
-
kvCachePerTokenKb,
|
|
531
|
-
kvCacheTotalGb,
|
|
532
|
-
totalVramNeededGb,
|
|
533
|
-
vramFits,
|
|
534
|
-
decodeTokensPerSec,
|
|
535
|
-
aggregateTokensPerSec,
|
|
536
|
-
prefillTokensPerSec,
|
|
537
|
-
batchCrossover,
|
|
538
|
-
prefillTimeMs,
|
|
539
|
-
decodeTimePerTokenMs,
|
|
540
|
-
totalDecodeTimeMs,
|
|
541
|
-
totalLatencyMs,
|
|
542
|
-
endToEndTokensPerSec,
|
|
543
|
-
ttftMs: prefillTimeMs,
|
|
544
|
-
itlMs: decodeTimePerTokenMs,
|
|
545
|
-
billedOutputTokens,
|
|
546
|
-
cacheHitRate,
|
|
547
|
-
cachePrefixTokens,
|
|
548
|
-
cacheTTL,
|
|
549
|
-
cacheProvider,
|
|
550
|
-
prefillTokensAvoided,
|
|
551
|
-
cacheWriteCostMultiplier,
|
|
552
|
-
cacheReadCostMultiplier,
|
|
553
|
-
effectivePrefillTokens,
|
|
554
|
-
prefillTimeWithCacheMs,
|
|
555
|
-
cacheSavingsPct,
|
|
556
|
-
costPerHour: costPerHour * tp,
|
|
557
|
-
costPerMillionOutputTokens,
|
|
558
|
-
costPerRequest,
|
|
559
|
-
costPerRequestWithCache,
|
|
560
|
-
effectiveBandwidthGbps,
|
|
561
|
-
effectiveFlopsTflops,
|
|
562
|
-
quantEfficiency: quantEff,
|
|
563
|
-
speculativeBoost,
|
|
564
|
-
multiGpuEfficiency,
|
|
565
|
-
continuousBatchingMultiplier,
|
|
566
|
-
longContextWarning,
|
|
567
|
-
// Per-metric confidence levels — surfaced as colored dots in the UI
|
|
568
|
-
confidence: {
|
|
569
|
-
// Throughput metrics
|
|
570
|
-
decodeTokensPerSec: "modeled", // Formula: HBM_BW × η_mem × quant_eff / model_size
|
|
571
|
-
aggregateTokensPerSec: continuousBatchingMultiplier > 1 ? "inferred" : "modeled",
|
|
572
|
-
// Multiplier is heuristic, default 1.5× with cited 2-4× range
|
|
573
|
-
prefillTokensPerSec: totalContextTokens > 32768 ? "inferred" : "modeled",
|
|
574
|
-
// Long-context superlinear attention is approximation
|
|
575
|
-
batchCrossover: "modeled", // Crossover formula is exact
|
|
576
|
-
|
|
577
|
-
// Latency metrics
|
|
578
|
-
prefillTimeMs: totalContextTokens > 32768 ? "inferred" : "modeled",
|
|
579
|
-
decodeTimePerTokenMs: "modeled", // = 1000 / decode_tok_per_sec
|
|
580
|
-
totalLatencyMs: totalContextTokens > 32768 ? "inferred" : "modeled",
|
|
581
|
-
// Inherits from prefill (long context dominates)
|
|
582
|
-
ttftMs: totalContextTokens > 32768 ? "inferred" : "modeled",
|
|
583
|
-
itlMs: "modeled",
|
|
584
|
-
|
|
585
|
-
// Memory metrics
|
|
586
|
-
modelSizeGb: "modeled", // params × bytes_per_param
|
|
587
|
-
kvCacheTotalGb: "modeled", // Exact formula: 2·L·H_kv·D_h·B·T·batch
|
|
588
|
-
totalVramNeededGb: "modeled", // Sum of weights + KV
|
|
589
|
-
vramFits: "modeled",
|
|
590
|
-
|
|
591
|
-
// Cost metrics
|
|
592
|
-
costPerHour: input.gpuHourlyCost !== undefined ? "user-supplied" : "measured",
|
|
593
|
-
// User override or default from spec
|
|
594
|
-
costPerMillionOutputTokens: "modeled",
|
|
595
|
-
costPerRequest: "modeled",
|
|
596
|
-
|
|
597
|
-
// Cache metrics
|
|
598
|
-
cacheSavingsPct: "modeled",
|
|
599
|
-
prefillTokensAvoided: "modeled",
|
|
600
|
-
cacheWriteCostMultiplier: "measured", // From Anthropic/OpenAI official docs
|
|
601
|
-
cacheReadCostMultiplier: "measured",
|
|
602
|
-
|
|
603
|
-
// Other
|
|
604
|
-
billedOutputTokens: "user-supplied", // User sets reasoning tokens
|
|
605
|
-
effectiveBandwidthGbps: "measured", // GPU spec × user-multipliers
|
|
606
|
-
effectiveFlopsTflops: gpu.flopsTflops === null ? "inferred" : "measured",
|
|
607
|
-
// B200/B300 null → conservative fallback
|
|
608
|
-
},
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
/** Format helpers */
|
|
613
|
-
export function fmtTokens(n: number): string {
|
|
614
|
-
if (!isFinite(n)) return "—";
|
|
615
|
-
if (n >= 1000) return n.toFixed(0);
|
|
616
|
-
if (n >= 100) return n.toFixed(1);
|
|
617
|
-
if (n >= 10) return n.toFixed(2);
|
|
618
|
-
return n.toFixed(3);
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
export function fmtBytes(gb: number): string {
|
|
622
|
-
if (gb >= 1024) return (gb / 1024).toFixed(2) + " TB";
|
|
623
|
-
if (gb >= 1) return gb.toFixed(2) + " GB";
|
|
624
|
-
return (gb * 1024).toFixed(1) + " MB";
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
export function fmtMs(ms: number): string {
|
|
628
|
-
if (!isFinite(ms)) return "—";
|
|
629
|
-
if (ms >= 1000) return (ms / 1000).toFixed(2) + " s";
|
|
630
|
-
if (ms >= 1) return ms.toFixed(1) + " ms";
|
|
631
|
-
return (ms * 1000).toFixed(1) + " µs";
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
export function fmtMoney(n: number): string {
|
|
635
|
-
if (!isFinite(n) || n === 0) return "—";
|
|
636
|
-
if (n >= 1000) return "$" + n.toFixed(0);
|
|
637
|
-
if (n >= 1) return "$" + n.toFixed(2);
|
|
638
|
-
if (n >= 0.01) return "$" + n.toFixed(4);
|
|
639
|
-
return "$" + n.toFixed(6);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
/* ============================================================
|
|
643
|
-
LONG-CONTEXT CAPACITY HELPERS
|
|
644
|
-
============================================================
|
|
645
|
-
The research brief calls this "the most important formula tokcalc should visibly expose":
|
|
646
|
-
|
|
647
|
-
KV bytes/request = 2 · L · T · H_kv · D_h · B
|
|
648
|
-
|
|
649
|
-
The factor of 2 stores both keys and values. With GQA (num_kv_heads < num_attention_heads),
|
|
650
|
-
only the kv_heads' worth of cache is stored per layer.
|
|
651
|
-
|
|
652
|
-
For dense attention, prefill cost grows SUPERLINEARLY:
|
|
653
|
-
linear_flops = 2 · N · T (matmuls through linear layers)
|
|
654
|
-
attention_flops ≈ T²/2 · H_kv · D_h · L (QK^T + softmax · V)
|
|
655
|
-
At long contexts (≥32K), attention becomes a significant fraction of total prefill FLOPs.
|
|
656
|
-
Refs: RingAttention paper (https://arxiv.org/abs/2310.01889)
|
|
657
|
-
*/
|
|
658
|
-
|
|
659
|
-
/** Bytes of KV cache per single token (for one request, one head). */
|
|
660
|
-
export function computeKVBytesPerToken(
|
|
661
|
-
model: ModelSpec,
|
|
662
|
-
bytesPerKVValue: number = 2, // FP16 = 2 bytes; can be INT8=1, FP4=0.5
|
|
663
|
-
): number {
|
|
664
|
-
// 2 = K and V; model.layers = number of transformer blocks
|
|
665
|
-
return 2 * model.layers * model.kvHeads * model.headDim * bytesPerKVValue;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
/** Total KV cache size in GB for `contextTokens` tokens × `batchSize` requests. */
|
|
669
|
-
export function computeKVCacheGb(
|
|
670
|
-
model: ModelSpec,
|
|
671
|
-
contextTokens: number,
|
|
672
|
-
batchSize: number = 1,
|
|
673
|
-
bytesPerKVValue: number = 2,
|
|
674
|
-
): number {
|
|
675
|
-
const bytes = computeKVBytesPerToken(model, bytesPerKVValue) * contextTokens * batchSize;
|
|
676
|
-
return bytes / 1e9;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/** Maximum concurrent users that fit in VRAM at the given context length.
|
|
680
|
-
* = floor((total_vram - model_weights) / kv_per_request_at_context)
|
|
681
|
-
*/
|
|
682
|
-
export function computeMaxConcurrency(
|
|
683
|
-
model: ModelSpec,
|
|
684
|
-
gpu: GpuSpec,
|
|
685
|
-
numGpus: number,
|
|
686
|
-
contextTokens: number,
|
|
687
|
-
quantBytesPerParam: number,
|
|
688
|
-
bytesPerKVValue: number = 2,
|
|
689
|
-
): number {
|
|
690
|
-
const weightsBytes = model.paramsB * 1e9 * quantBytesPerParam;
|
|
691
|
-
const kvBytesPerRequest = computeKVBytesPerToken(model, bytesPerKVValue) * contextTokens;
|
|
692
|
-
const totalVramBytes = gpu.vramGb * numGpus * 1e9;
|
|
693
|
-
const availableKvBytes = totalVramBytes - weightsBytes;
|
|
694
|
-
if (availableKvBytes <= 0 || kvBytesPerRequest <= 0) return 0;
|
|
695
|
-
return Math.floor(availableKvBytes / kvBytesPerRequest);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/** Prefill latency in milliseconds for a given context length, accounting for
|
|
699
|
-
* superlinear attention cost beyond 32K tokens (dense attention only).
|
|
700
|
-
* For RingAttention/blockwise-attention, this correction overestimates the cost.
|
|
701
|
-
*/
|
|
702
|
-
export function computeLongContextPrefillMs(
|
|
703
|
-
model: ModelSpec,
|
|
704
|
-
effectiveFlopsTflops: number,
|
|
705
|
-
promptTokens: number,
|
|
706
|
-
): number {
|
|
707
|
-
if (promptTokens <= 0 || effectiveFlopsTflops <= 0) return 0;
|
|
708
|
-
const T = promptTokens;
|
|
709
|
-
const N = model.activeParamsB * 1e9;
|
|
710
|
-
const linearFlops = 2 * N * T;
|
|
711
|
-
// Attention O(N²) — only meaningful for long context
|
|
712
|
-
// Each layer: QK^T = T·T·headDim, softmax·V = T·T·headDim
|
|
713
|
-
const attentionFlops = (T * T) * model.kvHeads * model.headDim * model.layers;
|
|
714
|
-
const totalFlops = linearFlops + attentionFlops;
|
|
715
|
-
const effFlopsPerSec = effectiveFlopsTflops * 1e12;
|
|
716
|
-
return (totalFlops / effFlopsPerSec) * 1000;
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
export interface TopologyRecommendation {
|
|
720
|
-
topology: string;
|
|
721
|
-
reason: string;
|
|
722
|
-
fits: boolean;
|
|
723
|
-
neededGpus: number;
|
|
724
|
-
hasContextParallel: boolean;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/** Recommend a multi-GPU topology for the given config.
|
|
728
|
-
* - Single GPU: model + KV fit in 1 GPU
|
|
729
|
-
* - TP×2/4/8: shard weights across N GPUs (each holds 1/N of weights + KV)
|
|
730
|
-
* - TP×8 + Context Parallel: even TP×8 isn't enough — need RingAttention to shard KV across nodes
|
|
731
|
-
*/
|
|
732
|
-
export function recommendTopology(
|
|
733
|
-
model: ModelSpec,
|
|
734
|
-
gpu: GpuSpec,
|
|
735
|
-
contextTokens: number,
|
|
736
|
-
batchSize: number,
|
|
737
|
-
quantBytesPerParam: number,
|
|
738
|
-
bytesPerKVValue: number = 2,
|
|
739
|
-
): TopologyRecommendation {
|
|
740
|
-
const weightsGb = model.paramsB * quantBytesPerParam;
|
|
741
|
-
const kvPerRequestGb = computeKVGb(model, contextTokens, 1, bytesPerKVValue);
|
|
742
|
-
const totalKvGb = kvPerRequestGb * batchSize;
|
|
743
|
-
const totalNeededGb = weightsGb + totalKvGb;
|
|
744
|
-
|
|
745
|
-
// Try single-GPU first
|
|
746
|
-
if (totalNeededGb <= gpu.vramGb) {
|
|
747
|
-
return {
|
|
748
|
-
topology: "Single GPU",
|
|
749
|
-
reason: `${totalNeededGb.toFixed(1)} GB ≤ ${gpu.vramGb} GB — fits in 1 ${gpu.name.split(" ")[0]}`,
|
|
750
|
-
fits: true,
|
|
751
|
-
neededGpus: 1,
|
|
752
|
-
hasContextParallel: false,
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
// Try TP×2, TP×4, TP×8
|
|
756
|
-
for (const tp of [2, 4, 8]) {
|
|
757
|
-
if (totalNeededGb <= gpu.vramGb * tp) {
|
|
758
|
-
return {
|
|
759
|
-
topology: `Tensor Parallel ×${tp}`,
|
|
760
|
-
reason: `${totalNeededGb.toFixed(1)} GB > ${gpu.vramGb} GB single → shard across ${tp} GPUs (${gpu.vramGb * tp} GB total, weights + KV split evenly)`,
|
|
761
|
-
fits: true,
|
|
762
|
-
neededGpus: tp,
|
|
763
|
-
hasContextParallel: false,
|
|
764
|
-
};
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
// TP×8 not enough → need Context Parallel (RingAttention)
|
|
768
|
-
return {
|
|
769
|
-
topology: "TP×8 + Context Parallel",
|
|
770
|
-
reason: `${totalNeededGb.toFixed(1)} GB exceeds ${gpu.vramGb * 8} GB even with TP×8. Use Context Parallel (RingAttention) to shard KV cache across nodes. Ref: arxiv.org/abs/2310.01889`,
|
|
771
|
-
fits: false,
|
|
772
|
-
neededGpus: 8,
|
|
773
|
-
hasContextParallel: true,
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
/** Helper for above — used internally. */
|
|
778
|
-
function computeKVGb(
|
|
779
|
-
model: ModelSpec,
|
|
780
|
-
contextTokens: number,
|
|
781
|
-
batchSize: number,
|
|
782
|
-
bytesPerKVValue: number,
|
|
783
|
-
): number {
|
|
784
|
-
return computeKVCacheGb(model, contextTokens, batchSize, bytesPerKVValue);
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
/** Format a context length nicely: 8192 → "8K", 131072 → "128K", 1000000 → "1M". */
|
|
788
|
-
export function fmtContext(tokens: number): string {
|
|
789
|
-
if (tokens >= 1_000_000) {
|
|
790
|
-
// Show "1M", "1.5M", "2M"
|
|
791
|
-
const m = tokens / 1_000_000;
|
|
792
|
-
if (m >= 1) {
|
|
793
|
-
const rounded = Math.round(m * 10) / 10;
|
|
794
|
-
return Number.isInteger(rounded) ? `${rounded}M` : `${rounded.toFixed(1)}M`;
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
if (tokens >= 1000) {
|
|
798
|
-
// Floor for power-of-2 context sizes (4096→"4K", 131072→"131K")
|
|
799
|
-
// Use 1 decimal only if not within 1 of an integer
|
|
800
|
-
const k = tokens / 1000;
|
|
801
|
-
const rounded = Math.round(k);
|
|
802
|
-
if (Math.abs(k - rounded) < 0.5) {
|
|
803
|
-
return `${rounded}K`;
|
|
804
|
-
}
|
|
805
|
-
return `${k.toFixed(1)}K`;
|
|
806
|
-
}
|
|
807
|
-
return String(tokens);
|
|
808
|
-
}
|