@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.
Files changed (192) hide show
  1. package/README.md +86 -406
  2. package/dist/index.js +21285 -0
  3. package/package.json +34 -91
  4. package/.zscripts/build.sh +0 -175
  5. package/.zscripts/database-runtime-build.sh +0 -33
  6. package/.zscripts/dev.pid +0 -1
  7. package/.zscripts/dev.sh +0 -154
  8. package/.zscripts/mini-services-build.sh +0 -78
  9. package/.zscripts/mini-services-install.sh +0 -65
  10. package/.zscripts/mini-services-start.sh +0 -123
  11. package/.zscripts/python-runtime-build.sh +0 -120
  12. package/.zscripts/start.sh +0 -145
  13. package/CAPACITY_STUDY.md +0 -283
  14. package/CODE_OF_CONDUCT.md +0 -55
  15. package/CONTRIBUTING.md +0 -177
  16. package/Caddyfile +0 -23
  17. package/LICENSE +0 -204
  18. package/bun.lock +0 -1965
  19. package/components.json +0 -21
  20. package/db/custom.db +0 -0
  21. package/download/README.md +0 -1
  22. package/download/tokcalc-dark-calculator.png +0 -0
  23. package/download/tokcalc-dark-default.png +0 -0
  24. package/download/tokcalc-demo.webm +0 -0
  25. package/download/tokcalc-github-link.png +0 -0
  26. package/download/tokcalc-hydration-fixed.png +0 -0
  27. package/download/tokcalc-issue-resolved.png +0 -0
  28. package/download/tokcalc-light-mode.png +0 -0
  29. package/download/tokcalc-light-reference.png +0 -0
  30. package/download/tokcalc-long-context-qwen.png +0 -0
  31. package/download/tokcalc-long-context.png +0 -0
  32. package/download/tokcalc-og-image-preview.png +0 -0
  33. package/download/tokcalc-phase2-3.png +0 -0
  34. package/download/tokcalc-plain-english.png +0 -0
  35. package/download/tokcalc-preview.png +0 -0
  36. package/download/tokcalc-share-bvb.png +0 -0
  37. package/download/tokcalc-share-feature.png +0 -0
  38. package/download/tokcalc-tab-build-vs-buy.png +0 -0
  39. package/download/tokcalc-tab-calculator.png +0 -0
  40. package/download/tokcalc-tab-reference.png +0 -0
  41. package/eslint.config.mjs +0 -50
  42. package/examples/websocket/frontend.tsx +0 -196
  43. package/examples/websocket/server.ts +0 -138
  44. package/mini-services/.gitkeep +0 -0
  45. package/mini-services/mcp-server/README.md +0 -86
  46. package/mini-services/mcp-server/bun.lock +0 -202
  47. package/mini-services/mcp-server/index.ts +0 -504
  48. package/mini-services/mcp-server/package.json +0 -40
  49. package/next.config.ts +0 -12
  50. package/postcss.config.mjs +0 -5
  51. package/prisma/schema.prisma +0 -32
  52. package/public/google6f58ca6be85fa903.html +0 -1
  53. package/public/logo.svg +0 -29
  54. package/public/manifest.json +0 -51
  55. package/public/og-icon-256.png +0 -0
  56. package/public/og.png +0 -0
  57. package/public/robots.txt +0 -25
  58. package/public/sitemap.xml +0 -23
  59. package/public/tokcalc-demo.gif +0 -0
  60. package/scripts/og-template.html +0 -120
  61. package/scripts/render-og.mjs +0 -43
  62. package/server.json +0 -21
  63. package/src/app/api/pricing/aws/route.ts +0 -186
  64. package/src/app/api/pricing/azure/route.ts +0 -168
  65. package/src/app/api/pricing/gcp/route.ts +0 -230
  66. package/src/app/api/pricing/vast-ai/route.ts +0 -164
  67. package/src/app/api/route.ts +0 -5
  68. package/src/app/compare/h100-vs-h200/layout.tsx +0 -30
  69. package/src/app/compare/h100-vs-h200/page.tsx +0 -328
  70. package/src/app/globals.css +0 -122
  71. package/src/app/layout.tsx +0 -276
  72. package/src/app/page.tsx +0 -2670
  73. package/src/components/azure-live-pricing.tsx +0 -185
  74. package/src/components/benchmark-import.tsx +0 -340
  75. package/src/components/confidence-badge.tsx +0 -116
  76. package/src/components/live-pricing-comparison.tsx +0 -241
  77. package/src/components/theme-provider.tsx +0 -11
  78. package/src/components/theme-toggle.tsx +0 -55
  79. package/src/components/ui/accordion.tsx +0 -66
  80. package/src/components/ui/alert-dialog.tsx +0 -157
  81. package/src/components/ui/alert.tsx +0 -66
  82. package/src/components/ui/aspect-ratio.tsx +0 -11
  83. package/src/components/ui/avatar.tsx +0 -53
  84. package/src/components/ui/badge.tsx +0 -46
  85. package/src/components/ui/breadcrumb.tsx +0 -109
  86. package/src/components/ui/button.tsx +0 -59
  87. package/src/components/ui/calendar.tsx +0 -213
  88. package/src/components/ui/card.tsx +0 -92
  89. package/src/components/ui/carousel.tsx +0 -241
  90. package/src/components/ui/chart.tsx +0 -353
  91. package/src/components/ui/checkbox.tsx +0 -32
  92. package/src/components/ui/collapsible.tsx +0 -33
  93. package/src/components/ui/command.tsx +0 -184
  94. package/src/components/ui/context-menu.tsx +0 -252
  95. package/src/components/ui/dialog.tsx +0 -143
  96. package/src/components/ui/drawer.tsx +0 -135
  97. package/src/components/ui/dropdown-menu.tsx +0 -257
  98. package/src/components/ui/form.tsx +0 -167
  99. package/src/components/ui/hover-card.tsx +0 -44
  100. package/src/components/ui/input-otp.tsx +0 -77
  101. package/src/components/ui/input.tsx +0 -21
  102. package/src/components/ui/label.tsx +0 -24
  103. package/src/components/ui/menubar.tsx +0 -276
  104. package/src/components/ui/navigation-menu.tsx +0 -168
  105. package/src/components/ui/pagination.tsx +0 -127
  106. package/src/components/ui/popover.tsx +0 -48
  107. package/src/components/ui/progress.tsx +0 -31
  108. package/src/components/ui/radio-group.tsx +0 -45
  109. package/src/components/ui/resizable.tsx +0 -56
  110. package/src/components/ui/scroll-area.tsx +0 -58
  111. package/src/components/ui/select.tsx +0 -185
  112. package/src/components/ui/separator.tsx +0 -28
  113. package/src/components/ui/sheet.tsx +0 -139
  114. package/src/components/ui/sidebar.tsx +0 -726
  115. package/src/components/ui/skeleton.tsx +0 -13
  116. package/src/components/ui/slider.tsx +0 -63
  117. package/src/components/ui/sonner.tsx +0 -25
  118. package/src/components/ui/switch.tsx +0 -31
  119. package/src/components/ui/table.tsx +0 -116
  120. package/src/components/ui/tabs.tsx +0 -66
  121. package/src/components/ui/textarea.tsx +0 -18
  122. package/src/components/ui/toast.tsx +0 -129
  123. package/src/components/ui/toaster.tsx +0 -35
  124. package/src/components/ui/toggle-group.tsx +0 -73
  125. package/src/components/ui/toggle.tsx +0 -47
  126. package/src/components/ui/tooltip.tsx +0 -61
  127. package/src/components/vast-ai-live-pricing.tsx +0 -176
  128. package/src/hooks/use-mobile.ts +0 -19
  129. package/src/hooks/use-toast.ts +0 -194
  130. package/src/lib/benchmark-parser-sglang.ts +0 -150
  131. package/src/lib/benchmark-parser-tokcalc.ts +0 -247
  132. package/src/lib/benchmark-parser-trtllm.ts +0 -152
  133. package/src/lib/benchmark-parser-vllm.ts +0 -198
  134. package/src/lib/benchmark-schema.ts +0 -263
  135. package/src/lib/db.ts +0 -13
  136. package/src/lib/engine-presets.ts +0 -183
  137. package/src/lib/price-schema.ts +0 -141
  138. package/src/lib/token-calc.ts +0 -808
  139. package/src/lib/track.ts +0 -31
  140. package/src/lib/url-state.ts +0 -256
  141. package/src/lib/utils.ts +0 -6
  142. package/tailwind.config.ts +0 -64
  143. package/tests/database-runtime-build.sh +0 -75
  144. package/tests/python-runtime-build.sh +0 -64
  145. package/tests/python-runtime-container.sh +0 -31
  146. package/tool-results/bash_1789888171144_2c5381860539.txt +0 -161
  147. package/tool-results/bash_1789888175925_49c53ba3c61b.txt +0 -191
  148. package/tool-results/bash_1789888181202_49c53ba3c61b.txt +0 -191
  149. package/tool-results/bash_1789888195219_4a86a5c91411.txt +0 -200
  150. package/tool-results/bash_1789888203128_6cca13c71b47.txt +0 -199
  151. package/tool-results/bash_1789929256963_2a52aff0d0a8.txt +0 -160
  152. package/tool-results/read_1789888151021_69f58eec6a5b.txt +0 -653
  153. package/tool-results/read_1789888153837_1d3a8bfc2a94.txt +0 -653
  154. package/tool-results/read_1789888163087_ccc406d47505.txt +0 -122
  155. package/tool-results/read_1789888167347_67d1d7c9830a.txt +0 -122
  156. package/tool-results/read_1789929252529_d90e8f383a25.txt +0 -285
  157. package/tsconfig.json +0 -42
  158. package/upload/Pasted Content_1789887800864.txt +0 -652
  159. package/upload/Pasted Content_1789887909561.txt +0 -652
  160. package/upload/Pasted Content_1789887918428.txt +0 -652
  161. package/upload/Pasted Content_1789887959420.txt +0 -652
  162. package/upload/Pasted Content_1789888020485.txt +0 -652
  163. package/upload/Pasted Content_1789888058079.txt +0 -652
  164. package/upload/Pasted Content_1789888885033.txt +0 -686
  165. package/upload/Pasted Content_1789928912741.txt +0 -285
  166. package/upload/Pasted Content_1789928938402.txt +0 -285
  167. package/upload/Pasted Content_1789929160389.txt +0 -285
  168. package/upload/Pasted Content_1789929176660.txt +0 -285
  169. package/upload/issue_vision.json +0 -28
  170. package/upload/pasted_image_1789883175209.png +0 -0
  171. package/upload/pasted_image_1789899056690.png +0 -0
  172. package/upload/pasted_image_1789900371483.png +0 -0
  173. package/upload/pasted_image_1789900472823.png +0 -0
  174. package/upload/pasted_image_1789900490374.png +0 -0
  175. package/upload/pasted_image_1789900585552.png +0 -0
  176. package/upload/pasted_image_1789900606519.png +0 -0
  177. package/upload/pasted_image_1789901598705.png +0 -0
  178. package/upload/pasted_image_1789901613545.png +0 -0
  179. package/upload/pasted_image_1789978382674.png +0 -0
  180. package/upload/pasted_image_1789978392749.png +0 -0
  181. package/upload/pasted_image_1789978474879.png +0 -0
  182. package/upload/pasted_image_1789978523652.png +0 -0
  183. package/upload/pasted_image_1789984219089.png +0 -0
  184. package/upload/pasted_image_1789984491896.png +0 -0
  185. package/upload/pasted_image_1789985017950.png +0 -0
  186. package/upload/pasted_image_1789985036765.png +0 -0
  187. package/upload/pasted_image_1789985049848.png +0 -0
  188. package/upload/pasted_image_1790002427833.png +0 -0
  189. package/upload/pasted_image_1790002659944.png +0 -0
  190. package/upload/pasted_image_1790037038476.png +0 -0
  191. package/upload/screenshot_analysis.json +0 -28
  192. package/upload/vision_output.json +0 -28
@@ -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
- }