| |
| |
| |
| |
| |
| |
|
|
| export const MODELS = [ |
| |
| |
| |
|
|
| |
| { name: 'GLM-4.7-Flash', group: 'GLM', tags: ['moe', 'mla'], |
| totalParams: 30, activeParams: 3, layerCount: 47, kvHeads: 20, kvHeadDim: 256, attnHeads: 20, hiddenSize: 2048, |
| numExperts: 64, expertsPerToken: 4, mlaKvLoraRank: 512, mlaRopeDim: 64, maxContext: 202752, benchmarks: null, |
| desc: 'MoE · MLA · ~30B / ~3B active · 64 experts(top-4) · 압축 KV(576/tok/layer) · 최대 202K' }, |
| { name: 'GLM-5.2', group: 'GLM', tags: ['moe', 'mla'], |
| totalParams: 753, activeParams: 40, layerCount: 78, kvHeads: 64, kvHeadDim: 256, attnHeads: 64, hiddenSize: 6144, |
| numExperts: 256, expertsPerToken: 8, mlaKvLoraRank: 512, mlaRopeDim: 64, maxContext: 1048576, benchmarks: null, |
| desc: 'MoE · MLA · 753B / ~40B active · 256 experts(top-8) · 압축 KV · 최대 1M (4bit도 512GB급만 fit)' }, |
|
|
| |
| { name: 'gpt-oss-20b', group: 'gpt-oss', tags: ['moe'], |
| totalParams: 21, activeParams: 3.6, layerCount: 24, kvHeads: 8, kvHeadDim: 64, attnHeads: 64, hiddenSize: 2880, |
| numExperts: 32, expertsPerToken: 4, maxContext: 131072, slidingWindow: 128, globalAttnLayers: 12, benchmarks: null, |
| desc: 'MoE · 21B / 3.6B active · 32 experts(top-4) · 슬라이딩128(full 12/24) · 최대 128K' }, |
| { name: 'gpt-oss-120b', group: 'gpt-oss', tags: ['moe'], |
| totalParams: 117, activeParams: 5.1, layerCount: 36, kvHeads: 8, kvHeadDim: 64, attnHeads: 64, hiddenSize: 2880, |
| numExperts: 128, expertsPerToken: 4, maxContext: 131072, slidingWindow: 128, globalAttnLayers: 18, benchmarks: null, |
| desc: 'MoE · 117B / 5.1B active · 128 experts(top-4) · 슬라이딩128(full 18/36) · 최대 128K' }, |
|
|
| |
| { |
| name: 'Qwen 3.6 27B', |
| group: 'Qwen 3.6', |
| tags: ['dense'], |
| totalParams: 27.2, |
| activeParams: null, |
| layerCount: 64, |
| fullAttnLayers: 16, |
| kvHeads: 4, |
| kvHeadDim: 256, |
| attnHeads: 24, |
| hiddenSize: 5120, |
| maxContext: 262144, |
| benchmarks: { GPQA: 0.878, 'MMLU-Pro': 0.817, 'SWE-Bench': 0.772 }, |
| desc: 'Dense · 64레이어(풀어텐션 16 + linear 48, 1:3) · KV는 16레이어만 · 최대 256K', |
| }, |
| |
| { |
| name: 'Qwen 3.6 35B-A3B', |
| group: 'Qwen 3.6', |
| tags: ['moe'], |
| totalParams: 35.0, |
| activeParams: 3.0, |
| layerCount: 40, |
| fullAttnLayers: 10, |
| kvHeads: 2, |
| kvHeadDim: 256, |
| attnHeads: 16, |
| hiddenSize: 2048, |
| numExperts: 256, |
| expertsPerToken: 8, |
| maxContext: 262144, |
| benchmarks: { GPQA: 0.86, 'MMLU-Pro': 0.852, 'SWE-Bench': null }, |
| desc: 'MoE · ~35B total / ~3B active · 256 experts · 풀어텐션 10/40(linear 30) · 최대 256K', |
| }, |
| |
| { name: 'Qwen-AgentWorld-35B-A3B', group: 'Qwen3.5', tags: ['moe'], |
| totalParams: 34.7, activeParams: 3.0, layerCount: 40, fullAttnLayers: 10, kvHeads: 2, kvHeadDim: 256, attnHeads: 16, hiddenSize: 2048, |
| numExperts: 256, expertsPerToken: 8, maxContext: 262144, benchmarks: null, |
| desc: 'MoE · ~34.7B / ~3B active · 하이브리드(full 10 + linear 30) · KV는 10레이어만 · 최대 256K' }, |
|
|
| |
| { |
| name: 'Gemma 4 e2b', |
| group: 'Gemma 4', |
| tags: ['dense', 'ple'], |
| totalParams: 5.1, |
| activeParams: 2.3, |
| |
| |
| |
| pleParams: 2.349, |
| layerCount: 35, |
| kvHeads: 1, |
| kvHeadDim: 256, |
| attnHeads: 8, |
| hiddenSize: 1536, |
| maxContext: 131072, |
| slidingWindow: 512, |
| slidingPattern: '4:1', |
| globalAttnLayers: 7, |
| benchmarks: { GPQA: 0.3, 'MMLU-Pro': 0.68, 'SWE-Bench': null }, |
| desc: 'Dense+PLE · 5.1B raw / 2.3B 유효 · 슬라이딩윈도우 512(4:1) · 최대 128K · 벤치 근사치', |
| }, |
| |
| { |
| name: 'Gemma 4 e4b', |
| group: 'Gemma 4', |
| tags: ['dense', 'ple'], |
| totalParams: 8, |
| activeParams: 4.5, |
| |
| |
| pleParams: 2.819, |
| layerCount: 42, |
| kvHeads: 2, |
| kvHeadDim: 256, |
| attnHeads: 8, |
| hiddenSize: 2560, |
| maxContext: 131072, |
| slidingWindow: 512, |
| slidingPattern: '5:1', |
| globalAttnLayers: 7, |
| benchmarks: { GPQA: 0.586, 'MMLU-Pro': 0.694, 'SWE-Bench': null }, |
| desc: 'Dense+PLE · 8B raw / 4.5B 유효 · 슬라이딩윈도우 512(5:1) · 최대 128K', |
| }, |
| |
| { |
| name: 'Gemma 4 12b', |
| group: 'Gemma 4', |
| tags: ['dense'], |
| totalParams: 11.95, |
| activeParams: 11.95, |
| layerCount: 48, |
| kvHeads: 8, |
| kvHeadDim: 256, |
| globalKvHeads: 1, |
| globalHeadDim: 512, |
| attnHeads: 16, |
| hiddenSize: 3840, |
| maxContext: 262144, |
| slidingWindow: 1024, |
| slidingPattern: '5:1', |
| benchmarks: { GPQA: 0.788, 'MMLU-Pro': 0.772, 'SWE-Bench': null }, |
| desc: 'Dense · 11.95B · 48레이어 · 슬라이딩윈도우 1024(5:1, 글로벌 8레이어 head_dim 512×1KV) · Unified 멀티모달 · 최대 256K', |
| }, |
| |
| { |
| name: 'Gemma 4 26b A4B', |
| group: 'Gemma 4', |
| tags: ['moe'], |
| totalParams: 25.5, |
| activeParams: 4.0, |
| layerCount: 30, |
| kvHeads: 8, |
| kvHeadDim: 256, |
| globalKvHeads: 2, |
| globalHeadDim: 512, |
| attnHeads: 16, |
| hiddenSize: 2816, |
| numExperts: 128, |
| expertsPerToken: 8, |
| maxContext: 262144, |
| slidingWindow: 1024, |
| slidingPattern: '5:1', |
| benchmarks: { GPQA: 0.823, 'MMLU-Pro': 0.826, 'SWE-Bench': null }, |
| desc: 'MoE · ~26B total / ~4B active · 128 experts · 슬라이딩윈도우 1024(5:1, 글로벌 head_dim 512) · 최대 256K', |
| }, |
| |
| { |
| name: 'Gemma 4 31b', |
| group: 'Gemma 4', |
| tags: ['dense'], |
| totalParams: 30.7, |
| activeParams: 30.7, |
| layerCount: 60, |
| kvHeads: 16, |
| kvHeadDim: 256, |
| globalKvHeads: 4, |
| globalHeadDim: 512, |
| attnHeads: 32, |
| hiddenSize: 5376, |
| maxContext: 262144, |
| slidingWindow: 1024, |
| slidingPattern: '5:1', |
| benchmarks: { GPQA: 0.843, 'MMLU-Pro': 0.852, 'SWE-Bench': null }, |
| desc: 'Dense · 30.7B · 60레이어 · 슬라이딩윈도우 1024(5:1, 글로벌 10레이어 head_dim 512) · 최대 256K', |
| }, |
| |
| { name: 'Llama-3.2-3B-Instruct', group: 'Llama', tags: ['dense'], |
| totalParams: 3.2, activeParams: 3.2, layerCount: 28, kvHeads: 8, kvHeadDim: 128, attnHeads: 24, hiddenSize: 3072, |
| maxContext: 131072, benchmarks: null, |
| desc: 'Dense · 3.2B · 28레이어 · GQA(24/8) · 최대 128K' }, |
| { name: 'Llama-3.1-8B-Instruct', group: 'Llama', tags: ['dense'], |
| totalParams: 8.0, activeParams: 8.0, layerCount: 32, kvHeads: 8, kvHeadDim: 128, attnHeads: 32, hiddenSize: 4096, |
| maxContext: 131072, benchmarks: null, |
| desc: 'Dense · 8.0B · 32레이어 · GQA(32/8) · 최대 128K' }, |
|
|
| |
| |
| |
| { name: 'Qwen3-0.6B', group: 'Draft', tags: ['dense', 'draft'], |
| totalParams: 0.596, activeParams: 0.596, layerCount: 28, kvHeads: 8, kvHeadDim: 128, attnHeads: 16, hiddenSize: 1024, |
| maxContext: 40960, benchmarks: null, |
| desc: 'Dense · 0.6B · vLLM 표준 draft — Qwen 계열 타깃 페어 (vocab 151936 호환)' }, |
| { name: 'Qwen3-1.7B', group: 'Draft', tags: ['dense', 'draft'], |
| totalParams: 1.721, activeParams: 1.721, layerCount: 28, kvHeads: 8, kvHeadDim: 128, attnHeads: 16, hiddenSize: 2048, |
| maxContext: 40960, benchmarks: null, |
| desc: 'Dense · 1.7B · 30B급 타깃엔 0.6B보다 스루풋 우위(vLLM 벤치) — Qwen 계열 draft' }, |
| { name: 'Llama-3.2-1B-Instruct', group: 'Draft', tags: ['dense', 'draft'], |
| totalParams: 1.236, activeParams: 1.236, layerCount: 16, kvHeads: 8, kvHeadDim: 64, attnHeads: 32, hiddenSize: 2048, |
| maxContext: 131072, benchmarks: null, |
| desc: 'Dense · 1.2B · Llama 3.x 타깃 표준 draft (vocab 128256 호환) · 최대 128K' }, |
| { name: 'Gemma-3-1B-it', group: 'Draft', tags: ['dense', 'draft'], |
| totalParams: 1.0, activeParams: 1.0, layerCount: 26, kvHeads: 1, kvHeadDim: 256, attnHeads: 4, hiddenSize: 1152, |
| maxContext: 32768, slidingWindow: 512, globalAttnLayers: 4, |
| benchmarks: null, |
| desc: 'Dense · 1.0B · MQA(4/1) · 슬라이딩 512(글로벌 4레이어) — Gemma 계열 draft · 최대 32K' }, |
|
|
| |
| { |
| name: 'Claude Opus 4.7', |
| group: 'Claude (Cloud)', |
| tags: ['cloud', 'dense'], |
| totalParams: null, |
| activeParams: null, |
| layerCount: null, |
| kvHeads: null, |
| kvHeadDim: null, |
| attnHeads: null, |
| hiddenSize: null, |
| maxContext: 1000000, |
| isCloud: true, |
| benchmarks: { GPQA: 0.942, 'MMLU-Pro': 0.899, 'SWE-Bench': 0.876 }, |
| contextLimit: '1M', |
| desc: 'Cloud 모델 — 벤치마크 기준점 (로컬 설치 불가, 비교용)', |
| }, |
|
|
| ]; |
|
|
| |
| export const LOCAL_MODELS = MODELS.filter((m) => !m.isCloud); |
|
|
| |
| |
| |
| |
| export const MACBOOK_RAM_GROUPS = { |
| 'M5': [16, 24, 32], |
| 'M5 Pro': [24, 48, 64], |
| 'M5 Max': [36, 48, 64, 128], |
| 'M4': [16, 24, 32], |
| 'M4 Pro': [24, 48, 64], |
| 'M4 Max': [36, 48, 64, 128], |
| 'M3': [8, 16, 24], |
| 'M3 Pro': [18, 36], |
| 'M3 Max': [36, 48, 64, 96, 128], |
| 'M3 Ultra': [96, 256, 512], |
| 'M2': [8, 16, 24], |
| 'M2 Pro': [16, 32], |
| 'M2 Max': [32, 64, 96], |
| 'M2 Ultra': [64, 128, 192], |
| 'M1': [8, 16], |
| 'M1 Pro': [16, 32], |
| 'M1 Max': [32, 64], |
| 'M1 Ultra': [64, 128], |
| }; |
|
|
| |
| export const QUANT_OPTIONS = [ |
| { bits: 4, label: '4bit', sub: 'NVFP4 / Q4 — 가장 작음' }, |
| { bits: 8, label: '8bit', sub: 'MXFP8 / Q8 — 권장 균형' }, |
| { bits: 16, label: '16bit', sub: 'BF16 — 원본 정밀도' }, |
| ]; |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| const NV_BLACKWELL = 'https://images.nvidia.com/aem-dam/Solutions/geforce/blackwell/nvidia-rtx-blackwell-gpu-architecture.pdf'; |
| const WIKI40 = 'https://en.wikipedia.org/wiki/GeForce_RTX_40_series'; |
| const WIKI30 = 'https://en.wikipedia.org/wiki/GeForce_30_series'; |
| |
| const GPU_VERIFIED_AT = '2026-06-04'; |
| const GPU_TRACKS = ['claude-hw-crawler', 'codex-independent']; |
| const _GPUS = [ |
| { name: 'RTX 5090', series: '50', vramGB: 32, bandwidthGBs: 1792, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5090/', NV_BLACKWELL], bandwidthGBs: ['https://www.notebookcheck.net/NVIDIA-GeForce-RTX-5090-Benchmarks-and-Specs.935680.0.html', NV_BLACKWELL] } }, |
| { name: 'RTX 5080', series: '50', vramGB: 16, bandwidthGBs: 960, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5080/', 'https://www.techspot.com/specs/gpu/303555-nvidia-geforce-rtx-5080.html'], bandwidthGBs: ['https://www.tomshardware.com/pc-components/gpus/nvidia-rtx-5080-allegedly-adopts-faster-30-gbps-gddr7-modules-delivering-960-gb-s-of-bandwidth-the-remaining-blackwell-lineup-is-expected-to-stick-with-slower-28-gbps-memory', 'https://www.techspot.com/specs/gpu/303555-nvidia-geforce-rtx-5080.html'] } }, |
| { name: 'RTX 5070 Ti', series: '50', vramGB: 16, bandwidthGBs: 896, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5070-family/', 'https://www.notebookcheck.net/NVIDIA-GeForce-RTX-5070-Ti-Benchmarks-and-Specs.935685.0.html'], bandwidthGBs: ['https://www.notebookcheck.net/NVIDIA-GeForce-RTX-5070-Ti-Benchmarks-and-Specs.935685.0.html', 'https://www.guru3d.com/story/nvidia-rtx-5070-ti-specs-include-256bit-memory-bus-and-350w-tbp/'] } }, |
| { name: 'RTX 5070', series: '50', vramGB: 12, bandwidthGBs: 672, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5070-family/', 'https://www.pcgamesn.com/nvidia/geforce-rtx-5070-review'], bandwidthGBs: ['https://www.notebookcheck.net/NVIDIA-GeForce-RTX-5070-Benchmarks-and-Specs.935682.0.html', 'https://www.pcgamesn.com/nvidia/geforce-rtx-5070-review'] } }, |
| { name: 'RTX 4090', series: '40', vramGB: 24, bandwidthGBs: 1008, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/rtx-4090/', 'https://www.techspot.com/products/graphics-cards/nvidia-geforce-rtx-4090.252744/'], bandwidthGBs: [WIKI40, 'https://www.techspot.com/products/graphics-cards/nvidia-geforce-rtx-4090.252744/'] } }, |
| { name: 'RTX 4080 SUPER', series: '40', vramGB: 16, bandwidthGBs: 736, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/rtx-4080-family/', 'https://www.techpowerup.com/gpu-specs/geforce-rtx-4080-super.c4182'], bandwidthGBs: ['https://www.notebookcheck.net/NVIDIA-GeForce-RTX-4080-Super-Benchmarks-and-Specs.799497.0.html', 'https://www.techpowerup.com/gpu-specs/geforce-rtx-4080-super.c4182'] } }, |
| { name: 'RTX 4070 Ti SUPER', series: '40', vramGB: 16, bandwidthGBs: 672, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/rtx-4070-family/', 'https://www.techspot.com/specs/gpu/290250-nvidia-geforce-rtx-4070-ti-super.html'], bandwidthGBs: [WIKI40, 'https://www.tomshardware.com/pc-components/gpus/nvidia-geforce-rtx-4070-ti-super-review'] } }, |
| { name: 'RTX 4070', series: '40', vramGB: 12, bandwidthGBs: 504, status: 'VERIFIED', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/rtx-4070-family/', 'https://www.techspot.com/specs/gpu/254404-nvidia-geforce-rtx-4070.html'], bandwidthGBs: [WIKI40, 'https://www.techspot.com/specs/gpu/254404-nvidia-geforce-rtx-4070.html'] } }, |
| { name: 'RTX 4060 Ti 16GB', series: '40', vramGB: 16, bandwidthGBs: 288, status: 'VERIFIED', sources: { vramGB: [WIKI40, 'https://www.tomshardware.com/reviews/nvidia-geforce-rtx-4060-ti-16gb-review'], bandwidthGBs: [WIKI40, 'https://www.tomshardware.com/reviews/nvidia-geforce-rtx-4060-ti-16gb-review'] } }, |
| { name: 'RTX 3090', series: '30', vramGB: 24, bandwidthGBs: 936, status: 'VERIFIED', sources: { vramGB: [WIKI30, 'https://www.techspot.com/specs/gpu/224809-nvidia-geforce-rtx-3090.html'], bandwidthGBs: [WIKI30, 'https://www.techspot.com/specs/gpu/224809-nvidia-geforce-rtx-3090.html'] } }, |
| { name: 'RTX 3090 Ti', series: '30', vramGB: 24, bandwidthGBs: 1008, status: 'VERIFIED', sources: { vramGB: [WIKI30, 'https://www.guru3d.com/review/asus-geforce-rtx-3090-ti-tuf-gaming-review/page-4/'], bandwidthGBs: [WIKI30, 'https://www.guru3d.com/review/msi-geforce-rtx-3090-ti-suprim-x-review/page-4/'] } }, |
| { name: 'RTX 3080 10GB', series: '30', vramGB: 10, bandwidthGBs: 760, status: 'VERIFIED', sources: { vramGB: [WIKI30, 'https://www.techspot.com/specs/gpu/223293-nvidia-geforce-rtx-3080.html'], bandwidthGBs: [WIKI30, 'https://www.techspot.com/specs/gpu/223293-nvidia-geforce-rtx-3080.html'] } }, |
| { name: 'RTX 3080 12GB', series: '30', vramGB: 12, bandwidthGBs: 912, status: 'VERIFIED', sources: { vramGB: [WIKI30, 'https://www.notebookcheck.net/NVIDIA-GeForce-RTX-3080-12-GB-GPU-Benchmarks-and-Specs.635433.0.html'], bandwidthGBs: ['https://www.techspot.com/specs/gpu/247309-nvidia-geforce-rtx-3080-12gb.html', 'https://www.notebookcheck.net/NVIDIA-GeForce-RTX-3080-12-GB-GPU-Benchmarks-and-Specs.635433.0.html'] } }, |
| { name: 'RTX 3060 12GB', series: '30', vramGB: 12, bandwidthGBs: 360, status: 'VERIFIED', sources: { vramGB: [WIKI30, 'https://www.asus.com/motherboards-components/graphics-cards/dual/dual-rtx3060-12g/techspec/'], bandwidthGBs: [WIKI30, 'https://www.techpowerup.com/gpu-specs/geforce-rtx-3060-12-gb.c3682'] } }, |
|
|
| |
| |
| { name: 'RTX 5060 Ti 16GB', series: '50', vramGB: 16, bandwidthGBs: 448, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/50-series/rtx-5060-family/', 'https://www.asus.com/us/motherboards-components/graphics-cards/prime/prime-rtx5060ti-16g/techspec/'], bandwidthGBs: ['https://www.tomshardware.com/pc-components/gpus/nvidia-geforce-rtx-5060-ti-16gb-review', 'https://www.techpowerup.com/gpu-specs/geforce-rtx-5060-ti.c4246'] } }, |
| { name: 'RTX 4070 Ti', series: '40', vramGB: 12, bandwidthGBs: 504, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.techspot.com/specs/gpu/258458-nvidia-geforce-rtx-4070-ti.html', 'https://www.thefpsreview.com/gpu-family/nvidia-geforce-rtx-4070-ti-gpu-family-specifications/'], bandwidthGBs: ['https://www.pcworld.com/article/1444726/nvidia-geforce-rtx-4070-ti-review.html', 'https://www.thefpsreview.com/gpu-family/nvidia-geforce-rtx-4070-ti-gpu-family-specifications/'] } }, |
| { name: 'RTX 4080', series: '40', vramGB: 16, bandwidthGBs: 717, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.custompc.com/nvidia-geforce-rtx-4080-review', 'https://www.notebookcheck.net/NVIDIA-GeForce-RTX-4080-GPU-Benchmarks-and-Specs.674575.0.html'], bandwidthGBs: [WIKI40, 'https://www.custompc.com/nvidia-geforce-rtx-4080-review'] } }, |
| { name: 'RTX 4060 Ti 8GB', series: '40', vramGB: 8, bandwidthGBs: 288, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.tomshardware.com/reviews/nvidia-geforce-rtx-4060-ti-review', 'https://us.msi.com/Graphics-Card/GeForce-RTX-4060-Ti-GAMING-X-8G/Specification'], bandwidthGBs: ['https://www.tomshardware.com/reviews/nvidia-geforce-rtx-4060-ti-review', 'https://www.techspot.com/specs/gpu/280961-nvidia-geforce-rtx-4060-ti-16gb.html'] } }, |
| { name: 'RTX 3060 8GB', series: '30', vramGB: 8, bandwidthGBs: 240, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.tomshardware.com/news/nvidia-geforce-rtx-3060-8gb-with-128-bit-memory-bus', 'https://videocardz.com/newz/nvidia-geforce-rtx-3060-with-8gb-memory-released-features-128-bit-memory-bus'], bandwidthGBs: ['https://www.tomshardware.com/news/nvidia-geforce-rtx-3060-8gb-with-128-bit-memory-bus', 'https://www.guru3d.com/story/geforce-rtx-3060-with-8gb-128-bit-memory-bus-memory-released'] } }, |
| { name: 'RTX 2080 Ti', series: '20', vramGB: 11, bandwidthGBs: 616, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/rtx-2080-ti/', 'https://www.techpowerup.com/gpu-specs/geforce-rtx-2080-ti.c3305'], bandwidthGBs: ['https://www.techpowerup.com/gpu-specs/geforce-rtx-2080-ti.c3305', 'https://videocardz.net/nvidia-geforce-rtx-2080ti'] } }, |
| |
| { name: 'RTX 6000 Ada', series: 'workstation', vramGB: 48, bandwidthGBs: 960, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/products/workstations/rtx-6000/', 'https://www.leadtek.com/eng/products/workstation_graphics(2)/NVIDIA_RTX_6000_Ada_Generation(40949)/detail'], bandwidthGBs: ['https://www.nvidia.com/content/dam/en-zz/Solutions/design-visualization/rtx-6000/proviz-print-rtx6000-datasheet-web-2504660.pdf', 'https://www.techpowerup.com/gpu-specs/rtx-6000-ada-generation.c3933'] } }, |
| { name: 'RTX PRO 6000 Blackwell', series: 'workstation', vramGB: 96, bandwidthGBs: 1792, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/products/workstations/professional-desktop-gpus/rtx-pro-6000/', 'https://www.storagereview.com/review/nvidia-rtx-pro-6000-workstation-gpu-review-blackwell-architecture-and-96-gb-for-pro-workflows'], bandwidthGBs: ['https://www.nvidia.com/content/dam/en-zz/Solutions/data-center/rtx-pro-6000-blackwell-workstation-edition/workstation-blackwell-rtx-pro-6000-workstation-edition-nvidia-us-3519208-web.pdf', 'https://www.techpowerup.com/gpu-specs/rtx-pro-6000-blackwell.c4272'] } }, |
| |
| { name: 'RX 7900 XTX', series: 'amd', vramGB: 24, bandwidthGBs: 960, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7900xtx.html', 'https://www.techpowerup.com/gpu-specs/radeon-rx-7900-xtx.c3941'], bandwidthGBs: ['https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7900xtx.html', 'https://www.notebookcheck.net/AMD-Radeon-RX-7900-XTX-GPU-Benchmarks-and-Specs.674159.0.html'] } }, |
| { name: 'RX 7900 XT', series: 'amd', vramGB: 20, bandwidthGBs: 800, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7900xt.html', 'https://www.techpowerup.com/gpu-specs/radeon-rx-7900-xt.c3912'], bandwidthGBs: ['https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7900xt.html', 'https://www.notebookcheck.net/AMD-Radeon-RX-7900-XT-GPU-Benchmarks-and-Specs.674155.0.html'] } }, |
| { name: 'RX 7800 XT', series: 'amd', vramGB: 16, bandwidthGBs: 624, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7800-xt.html', 'https://www.techpowerup.com/gpu-specs/radeon-rx-7800-xt.c3839'], bandwidthGBs: ['https://www.amd.com/en/products/graphics/desktops/radeon/7000-series/amd-radeon-rx-7800-xt.html', 'https://videocardz.com/amd/radeon-rx-7000/radeon-rx-7800-xt'] } }, |
| { name: 'RX 9070 XT', series: 'amd', vramGB: 16, bandwidthGBs: 640, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.amd.com/en/products/graphics/desktops/radeon/9000-series/amd-radeon-rx-9070xt.html', 'https://www.techpowerup.com/gpu-specs/radeon-rx-9070-xt.c4229'], bandwidthGBs: ['https://www.amd.com/en/products/graphics/desktops/radeon/9000-series/amd-radeon-rx-9070xt.html', 'https://www.tomshardware.com/pc-components/gpus/amd-radeon-rx-9070-xt-review'] } }, |
| { name: 'RX 9070', series: 'amd', vramGB: 16, bandwidthGBs: 640, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.amd.com/en/products/graphics/desktops/radeon/9000-series/amd-radeon-rx-9070.html', 'https://www.techpowerup.com/gpu-specs/radeon-rx-9070.c4227'], bandwidthGBs: ['https://www.amd.com/en/products/graphics/desktops/radeon/9000-series/amd-radeon-rx-9070.html', 'https://www.tomshardware.com/pc-components/gpus/amd-radeon-rx-9070-xt-review'] } }, |
| { name: 'Radeon PRO W7900', series: 'amd', vramGB: 48, bandwidthGBs: 864, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.amd.com/en/products/graphics/workstations/radeon-pro/w7900.html', 'https://www.techpowerup.com/gpu-specs/radeon-pro-w7900.c4147'], bandwidthGBs: ['https://www.amd.com/content/dam/amd/en/documents/products/graphics/workstation/radeon-pro-w7900-datasheet.pdf', 'https://www.techpowerup.com/gpu-specs/radeon-pro-w7900.c4147'] } }, |
| |
| { name: '2× RTX 3090', series: 'multi', vramGB: 48, bandwidthGBs: 936, count: 2, perCardVramGB: 24, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: [WIKI30, 'https://www.techspot.com/specs/gpu/224809-nvidia-geforce-rtx-3090.html'], bandwidthGBs: [WIKI30, 'https://www.techspot.com/specs/gpu/224809-nvidia-geforce-rtx-3090.html'] } }, |
| { name: '2× RTX 4090', series: 'multi', vramGB: 48, bandwidthGBs: 1008, count: 2, perCardVramGB: 24, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/geforce/graphics-cards/40-series/rtx-4090/', 'https://www.techspot.com/products/graphics-cards/nvidia-geforce-rtx-4090.252744/'], bandwidthGBs: [WIKI40, 'https://www.techspot.com/products/graphics-cards/nvidia-geforce-rtx-4090.252744/'] } }, |
| { name: '4× RTX 3090', series: 'multi', vramGB: 96, bandwidthGBs: 936, count: 4, perCardVramGB: 24, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: [WIKI30, 'https://www.techspot.com/specs/gpu/224809-nvidia-geforce-rtx-3090.html'], bandwidthGBs: [WIKI30, 'https://www.techspot.com/specs/gpu/224809-nvidia-geforce-rtx-3090.html'] } }, |
| |
| { name: 'A100 40GB', series: 'datacenter', vramGB: 40, bandwidthGBs: 1555, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/nvidia-a100-datasheet-us-nvidia-1758950-r4-web.pdf', 'https://en.wikipedia.org/wiki/Ampere_(microarchitecture)'], bandwidthGBs: ['https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/nvidia-a100-datasheet-us-nvidia-1758950-r4-web.pdf', 'https://en.wikipedia.org/wiki/Ampere_(microarchitecture)'] } }, |
| { name: 'A100 80GB', series: 'datacenter', vramGB: 80, bandwidthGBs: 1935, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/a100-80gb-datasheet-update-nvidia-us-1521051-r2-web.pdf', 'https://en.wikipedia.org/wiki/Ampere_(microarchitecture)'], bandwidthGBs: ['https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/a100-80gb-datasheet-update-nvidia-us-1521051-r2-web.pdf', 'https://www.techpowerup.com/gpu-specs/a100-pcie-80-gb.c3821'] } }, |
| { name: 'H100 80GB', series: 'datacenter', vramGB: 80, bandwidthGBs: 3350, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/data-center/h100/', 'https://en.wikipedia.org/wiki/Hopper_(microarchitecture)'], bandwidthGBs: ['https://www.nvidia.com/en-us/data-center/h100/', 'https://en.wikipedia.org/wiki/Hopper_(microarchitecture)'] } }, |
| { name: 'H200 141GB', series: 'datacenter', vramGB: 141, bandwidthGBs: 4800, status: 'VERIFIED', verifiedAt: '2026-07-09', sources: { vramGB: ['https://www.nvidia.com/en-us/data-center/h200/', 'https://en.wikipedia.org/wiki/Hopper_(microarchitecture)'], bandwidthGBs: ['https://www.nvidia.com/en-us/data-center/h200/', 'https://en.wikipedia.org/wiki/Hopper_(microarchitecture)'] } }, |
| { name: 'B200', series: 'datacenter', vramGB: 180, bandwidthGBs: 7700, status: 'VERIFIED', verifiedAt: '2026-07-11', sources: { vramGB: ['https://www.nvidia.com/en-us/data-center/dgx-b200/', 'https://lenovopress.lenovo.com/lp2226-thinksystem-nvidia-b200-180gb-1000w-gpu'], bandwidthGBs: ['https://lenovopress.lenovo.com/lp2226-thinksystem-nvidia-b200-180gb-1000w-gpu', 'https://www.nvidia.com/en-us/data-center/dgx-b200/'] } }, |
| ]; |
| |
| export const GPUS = _GPUS.map((g) => ({ verifiedAt: GPU_VERIFIED_AT, tracks: GPU_TRACKS, ...g })); |
|
|
| |
| |
| |
| export const GPU_QUANTS = [ |
| { tier: 'Q4_K_M', bpw: 4.8944, label: 'Q4_K_M', sub: '4-bit — 가장 인기(가성비)' }, |
| { tier: 'Q5_K_M', bpw: 5.7036, label: 'Q5_K_M', sub: '5-bit — 품질↑' }, |
| { tier: 'Q6_K', bpw: 6.5633, label: 'Q6_K', sub: '6-bit — 고품질' }, |
| { tier: 'Q8_0', bpw: 8.5008, label: 'Q8_0', sub: '8-bit — 거의 무손실' }, |
| { tier: 'FP16', bpw: 16.0005, label: 'FP16', sub: '원본 정밀도' }, |
| ]; |
| export const GGUF_BPW_SOURCE = 'https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md'; |
|
|
| |
| |
| |
| export const ENV_PRESETS = { |
| 'linux-headless': { key: 'linux-headless', label: ['Linux 헤드리스(세컨드 카드)', 'Linux headless (2nd card)'], reserveGB: 0.6, note: ['CUDA 컨텍스트만, 디스플레이 0', 'CUDA context only, no display'] }, |
| 'linux-display': { key: 'linux-display', label: ['Linux + 디스플레이', 'Linux + display'], reserveGB: 1.2, note: ['컴포지터 경량', 'lightweight compositor'] }, |
| 'windows-display': { key: 'windows-display', label: ['Windows + 디스플레이', 'Windows + display'], reserveGB: 2.0, note: ['WDDM + 디스플레이 (보수적·기본)', 'WDDM + display (conservative, default)'] }, |
| }; |
| export const DEFAULT_ENV = 'windows-display'; |
| export const GPU_HEADROOM_RATIO = 0.05; |
|
|
| |
| |
| export function appleDevice(ram) { |
| return { type: 'apple', memoryGB: ram, reserveGB: getOsOverhead(ram) + 2.0, headroomRatio: HEADROOM_RATIO, _os: getOsOverhead(ram) }; |
| } |
| export function gpuDevice(gpu, envKey = DEFAULT_ENV) { |
| const env = ENV_PRESETS[envKey] || ENV_PRESETS[DEFAULT_ENV]; |
| const count = gpu.count || 1; |
| return { type: 'gpu', gpu, env: env.key, memoryGB: gpu.vramGB, bandwidthGBs: gpu.bandwidthGBs, reserveGB: env.reserveGB * count, headroomRatio: GPU_HEADROOM_RATIO, _os: 0, gpuCount: count }; |
| } |
|
|
| |
| |
| |
| export function combineGpus(gpus, envKey = DEFAULT_ENV) { |
| if (!Array.isArray(gpus) || gpus.length === 0) return null; |
| if (gpus.length === 1) return gpuDevice(gpus[0], envKey); |
| const env = ENV_PRESETS[envKey] || ENV_PRESETS[DEFAULT_ENV]; |
| const combo = { |
| name: gpus.map((g) => g.name).join(' + '), |
| vramGB: gpus.reduce((s, g) => s + g.vramGB, 0), |
| bandwidthGBs: Math.min(...gpus.map((g) => g.bandwidthGBs)), |
| series: 'multi', |
| }; |
| const cards = gpus.reduce((s, g) => s + (g.count || 1), 0); |
| return { type: 'gpu', gpu: combo, env: env.key, memoryGB: combo.vramGB, bandwidthGBs: combo.bandwidthGBs, reserveGB: env.reserveGB * cards, headroomRatio: GPU_HEADROOM_RATIO, _os: 0, gpuCount: cards }; |
| } |
| |
| function toDevice(d) { return typeof d === 'number' ? appleDevice(d) : d; } |
| function toQuant(q) { return typeof q === 'number' ? { weightBpw: q, kvBits: q } : { weightBpw: q.weightBpw, kvBits: q.kvBits ?? 16 }; } |
|
|
| |
| |
| export function getOsOverhead(ram) { |
| if (ram <= 24) return 7; |
| if (ram <= 36) return 6.5; |
| if (ram <= 48) return 6; |
| return 6; |
| } |
|
|
| |
| export function slidingSplit(model) { |
| let globalLayers; |
| if (model.globalAttnLayers) { |
| globalLayers = model.globalAttnLayers; |
| } else { |
| const ratio = model.slidingPattern ? parseInt(model.slidingPattern.split(':')[0]) : 5; |
| globalLayers = Math.max(1, Math.round(model.layerCount / (ratio + 1))); |
| } |
| return { globalLayers, localLayers: model.layerCount - globalLayers }; |
| } |
|
|
| export function calcKVCache(model, ctx, bits) { |
| ctx = Math.max(0, Math.floor(Number(ctx)) || 0); |
| if (!model.kvHeads || !model.kvHeadDim || !model.layerCount) { |
| return { totalGB: 0, perTokenKB: 0, kvPerToken: 0, totalBytes: 0, effectiveCtx: 0 }; |
| } |
| const bpe = bits / 8; |
|
|
| |
| |
| |
| |
| |
| if (model.mlaKvLoraRank) { |
| const perLayer = (model.mlaKvLoraRank + (model.mlaRopeDim || 0)) * bpe; |
| const totalBytes = perLayer * model.layerCount * ctx; |
| const marginalPerToken = perLayer * model.layerCount; |
| return { |
| totalGB: totalBytes / 1024 ** 3, |
| perTokenKB: marginalPerToken / 1024, |
| kvPerToken: marginalPerToken, |
| totalBytes, |
| effectiveCtx: ctx, |
| }; |
| } |
|
|
| |
| |
| const perLocal = 2 * model.kvHeads * model.kvHeadDim * bpe; |
| const perGlobal = 2 * (model.globalKvHeads || model.kvHeads) * (model.globalHeadDim || model.kvHeadDim) * bpe; |
|
|
| let totalBytes, marginalPerToken; |
| if ((model.slidingWindow || 0) > 0) { |
| |
| const { globalLayers, localLayers } = slidingSplit(model); |
| totalBytes = perGlobal * globalLayers * ctx + perLocal * localLayers * Math.min(ctx, model.slidingWindow); |
| marginalPerToken = perGlobal * globalLayers; |
| } else { |
| |
| const attnLayers = model.fullAttnLayers || model.layerCount; |
| totalBytes = perLocal * attnLayers * ctx; |
| marginalPerToken = perLocal * attnLayers; |
| } |
| return { |
| totalGB: totalBytes / 1024 ** 3, |
| perTokenKB: marginalPerToken / 1024, |
| kvPerToken: marginalPerToken, |
| totalBytes, |
| effectiveCtx: ctx, |
| }; |
| } |
|
|
| |
| export function benchAvg(benchmarks) { |
| if (!benchmarks) return null; |
| const vals = Object.values(benchmarks).filter((v) => typeof v === 'number' && !isNaN(v)); |
| if (!vals.length) return null; |
| return vals.reduce((s, v) => s + v, 0) / vals.length; |
| } |
|
|
| |
| |
| |
| |
| const quantAdjust = { |
| 'Gemma 4 e2b': { 16: 1.0, 8: 1.15, 4: 1.39 }, |
| 'Gemma 4 e4b': { 16: 1.0, 8: 1.11, 4: 1.29 }, |
| 'Gemma 4 31b': { 16: 1.02, 8: 1.1, 4: 1.2 }, |
| 'Gemma 4 26b A4B': { 16: 1.01, 8: 1.1, 4: 1.2 }, |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| function guardedPleParamsB(model) { |
| return model.pleParams && model.pleParams < model.totalParams ? model.pleParams : 0; |
| } |
| function residentParamsB(model, device) { |
| const ple = guardedPleParamsB(model); |
| return device && device.type === 'gpu' && ple ? model.totalParams - ple : model.totalParams; |
| } |
|
|
| export function calcParamMemory(model, bits, device) { |
| if (!model.totalParams) return { totalGB: 0, activeGB: null }; |
| const bpe = bits / 8; |
| const baseTotalGB = (residentParamsB(model, device) * 1e9 * bpe) / 1024 ** 3; |
| const baseActiveGB = model.activeParams ? (model.activeParams * 1e9 * bpe) / 1024 ** 3 : null; |
| |
| |
| |
| |
| const multiplier = (quantAdjust[model.name] && quantAdjust[model.name][bits]) || 1.0; |
| return { totalGB: baseTotalGB * multiplier, activeGB: baseActiveGB ? baseActiveGB * multiplier : null }; |
| } |
|
|
| |
| |
| export function calcRuntimeOverhead(model, ctx, bitsOrQuant, device) { |
| ctx = Math.max(0, Math.floor(Number(ctx)) || 0); |
| const { weightBpw, kvBits } = toQuant(bitsOrQuant); |
| const paramMem = calcParamMemory(model, weightBpw, device).totalGB; |
| const kvMem = calcKVCache(model, ctx, kvBits).totalGB; |
| const paramOverhead = paramMem * 0.12; |
| const kvOverhead = kvMem * 0.15; |
| const activationOverhead = ctx * 0.00003; |
| |
| const fixedOverhead = device && device.type === 'gpu' ? 0 : 2.0; |
| return { |
| paramOverheadGB: paramOverhead, |
| kvOverheadGB: kvOverhead, |
| activationOverheadGB: activationOverhead, |
| fixedOverheadGB: fixedOverhead, |
| totalGB: paramOverhead + kvOverhead + activationOverhead + fixedOverhead, |
| }; |
| } |
|
|
| export function calcMaxContext(model, deviceOrRam, bitsOrQuant) { |
| if (!model.kvHeads || !model.kvHeadDim || !model.layerCount || !model.totalParams) return 0; |
| const device = toDevice(deviceOrRam); |
| const { weightBpw, kvBits } = toQuant(bitsOrQuant); |
| const attnLayers = model.fullAttnLayers || model.layerCount; |
| const wbpe = weightBpw / 8; |
| const kbpe = kvBits / 8; |
| const quantMultiplier = (quantAdjust[model.name] && quantAdjust[model.name][weightBpw]) || 1.0; |
| const paramBytes = residentParamsB(model, device) * 1e9 * wbpe * quantMultiplier; |
| const budget = |
| device.memoryGB * 1024 ** 3 * (1 - device.headroomRatio) - paramBytes - paramBytes * 0.12 - device.reserveGB * 1024 ** 3; |
| if (budget <= 0) return 0; |
| const overhead = 1.15; |
| const perLocal = 2 * model.kvHeads * model.kvHeadDim * kbpe * overhead; |
| const perGlobal = 2 * (model.globalKvHeads || model.kvHeads) * (model.globalHeadDim || model.kvHeadDim) * kbpe * overhead; |
| if (perLocal <= 0) return 0; |
| const actPerTok = 0.00003 * 1024 ** 3; |
|
|
| |
| if (model.mlaKvLoraRank) { |
| const perTokMla = (model.mlaKvLoraRank + (model.mlaRopeDim || 0)) * kbpe * overhead * model.layerCount; |
| return Math.min(Math.floor(budget / (perTokMla + actPerTok)), model.maxContext); |
| } |
|
|
| if ((model.slidingWindow || 0) > 0) { |
| const { globalLayers, localLayers } = slidingSplit(model); |
| const w = model.slidingWindow; |
| const perTokWithin = perLocal * localLayers + perGlobal * globalLayers; |
| const baseWithAct = (perTokWithin + actPerTok) * w; |
| if (budget <= baseWithAct) { |
| return Math.min(Math.floor(budget / (perTokWithin + actPerTok)), model.maxContext); |
| } |
| |
| const ctx = w + Math.floor((budget - baseWithAct) / (perGlobal * globalLayers + actPerTok)); |
| return Math.min(ctx, model.maxContext); |
| } |
| return Math.min(Math.floor(budget / (perLocal * attnLayers + actPerTok)), model.maxContext); |
| } |
|
|
| |
| |
| |
|
|
| export const HEADROOM_RATIO = 0.2; |
|
|
| |
| export function simulate(model, deviceOrRam, ctx, bitsOrQuant) { |
| const device = toDevice(deviceOrRam); |
| ctx = Math.min(Math.max(1, Math.floor(Number(ctx)) || 1), model.maxContext || Infinity); |
| const { weightBpw, kvBits } = toQuant(bitsOrQuant); |
| const param = calcParamMemory(model, weightBpw, device).totalGB; |
| const kv = calcKVCache(model, ctx, kvBits).totalGB; |
| |
| |
| |
| const pleOffloadGB = device.type === 'gpu' ? (guardedPleParamsB(model) * 1e9 * (weightBpw / 8)) / 1024 ** 3 : 0; |
|
|
| |
| |
| const ov = calcRuntimeOverhead(model, ctx, { weightBpw, kvBits }, device); |
| const rtDyn = ov.paramOverheadGB + ov.kvOverheadGB + ov.activationOverheadGB; |
| const reserve = device.reserveGB; |
| const used = param + kv + rtDyn + reserve; |
| const free = device.memoryGB - used; |
| const headroom = device.memoryGB * device.headroomRatio; |
|
|
| let verdict; |
| if (free < 0) verdict = 'no'; |
| else if (free < headroom) verdict = 'tight'; |
| else verdict = 'yes'; |
|
|
| const os = device._os ?? 0; |
| const rt = rtDyn + ov.fixedOverheadGB; |
|
|
| return { |
| model, |
| device, |
| ram: device.memoryGB, |
| memoryGB: device.memoryGB, |
| ctx, |
| weightBpw, |
| kvBits, |
| quant: { weightBpw, kvBits }, |
| bits: weightBpw, |
| os, |
| param, |
| kv, |
| rt, |
| rtDyn, |
| reserve, |
| system: os + rt, |
| pleOffloadGB, |
| used, |
| free, |
| headroom, |
| verdict, |
| pct: used / device.memoryGB, |
| maxContext: calcMaxContext(model, device, { weightBpw, kvBits }), |
| }; |
| } |
|
|
| |
| |
| |
| |
| export function simulateStack(entries, deviceOrRam) { |
| const device = toDevice(deviceOrRam); |
| const parts = entries.map(({ model, ctx, weightBpw, kvBits }) => { |
| const c = Math.min(ctx, model.maxContext); |
| const kb = kvBits ?? 16; |
| const param = calcParamMemory(model, weightBpw, device).totalGB; |
| const kv = calcKVCache(model, c, kb).totalGB; |
| const ov = calcRuntimeOverhead(model, c, { weightBpw, kvBits: kb }, device); |
| const rtDyn = ov.paramOverheadGB + ov.kvOverheadGB + ov.activationOverheadGB; |
| return { model, ctx: c, weightBpw, kvBits: kb, param, kv, rtDyn, subtotal: param + kv + rtDyn }; |
| }); |
| const reserve = device.reserveGB; |
| const used = parts.reduce((s, p) => s + p.subtotal, 0) + reserve; |
| const free = device.memoryGB - used; |
| const headroom = device.memoryGB * device.headroomRatio; |
| const verdict = free < 0 ? 'no' : free < headroom ? 'tight' : 'yes'; |
| return { |
| parts, device, memoryGB: device.memoryGB, reserve, used, free, headroom, verdict, |
| pct: used / device.memoryGB, |
| param: parts.reduce((s, p) => s + p.param, 0), |
| kv: parts.reduce((s, p) => s + p.kv, 0), |
| rt: parts.reduce((s, p) => s + p.rtDyn, 0), |
| }; |
| } |
|
|
| |
| |
| export function suggestFix(model, ram, ctx, bits, L) { |
| const t = L || ((ko) => ko); |
| |
| const lowerBits = [8, 4].filter((b) => b < bits); |
| for (const b of lowerBits) { |
| if (simulate(model, ram, ctx, b).verdict !== 'no') { |
| return { kind: 'bits', bits: b, text: t(`${b}bit로 양자화하면 들어가요 (품질 소폭 손실).`, `Quantize to ${b}-bit and it fits (small quality cost).`) }; |
| } |
| } |
| |
| const maxCtx = calcMaxContext(model, ram, bits); |
| if (maxCtx >= 1024) { |
| return { kind: 'ctx', ctx: maxCtx, text: t(`컨텍스트를 ${formatTokens(maxCtx, L)}까지 줄이면 들어가요.`, `Shorten context to ${formatTokens(maxCtx, L)} and it fits.`) }; |
| } |
| |
| const allRam = [...new Set(Object.values(MACBOOK_RAM_GROUPS).flat())].sort((a, b) => a - b); |
| const bigger = allRam.find((r) => r > ram && simulate(model, r, ctx, bits).verdict !== 'no'); |
| if (bigger) return { kind: 'ram', ram: bigger, text: t(`${bigger}GB 이상 맥이면 들어가요.`, `A ${bigger}GB+ Mac would fit.`) }; |
| return { kind: 'none', text: t('더 작은 모델이나 더 강한 양자화가 필요해요.', 'You need a smaller model or stronger quantization.') }; |
| } |
|
|
| |
| |
| export function suggestFixGpu(model, device, ctx, quant, L) { |
| const t = L || ((ko) => ko); |
| const kvBits = quant.kvBits ?? 16; |
| |
| const lower = GPU_QUANTS.filter((q) => q.bpw < quant.weightBpw).sort((a, b) => b.bpw - a.bpw); |
| for (const q of lower) { |
| if (simulate(model, device, ctx, { weightBpw: q.bpw, kvBits }).verdict !== 'no') { |
| return { kind: 'gguf', tier: q.tier, text: t(`${q.label}로 양자화하면 들어가요 (품질 소폭 손실).`, `Quantize to ${q.label} and it fits (small quality cost).`) }; |
| } |
| } |
| |
| for (const kb of [8, 4].filter((b) => b < kvBits)) { |
| if (simulate(model, device, ctx, { weightBpw: quant.weightBpw, kvBits: kb }).verdict !== 'no') { |
| return { kind: 'kv', kvBits: kb, text: t(`KV 캐시를 Q${kb}로 낮추면 들어가요 (-ctk/-ctv).`, `Drop the KV cache to Q${kb} (-ctk/-ctv) and it fits.`) }; |
| } |
| } |
| |
| const maxCtx = calcMaxContext(model, device, quant); |
| if (maxCtx >= 1024) { |
| return { kind: 'ctx', ctx: maxCtx, text: t(`컨텍스트를 ${formatTokens(maxCtx, L)}까지 줄이면 들어가요.`, `Shorten context to ${formatTokens(maxCtx, L)} and it fits.`) }; |
| } |
| |
| const bigger = GPUS.filter((g) => !g.count && g.vramGB > device.memoryGB) |
| .sort((a, b) => a.vramGB - b.vramGB) |
| .find((g) => simulate(model, gpuDevice(g, device.env), ctx, quant).verdict !== 'no'); |
| if (bigger) { |
| |
| const label = bigger.name.includes('GB') ? bigger.name : `${bigger.name} (${bigger.vramGB}GB)`; |
| return { kind: 'gpu', gpu: bigger.name, text: t(`${label}급 카드면 들어가요.`, `${label} would fit.`) }; |
| } |
| return { kind: 'none', text: t('더 작은 모델이나 더 강한 양자화가 필요해요.', 'You need a smaller model or stronger quantization.') }; |
| } |
|
|
| |
| |
| export function tokensToKoreanChars(tokens) { |
| return Math.round(tokens / 1.5); |
| } |
| export function tokensToPages(tokens) { |
| return tokensToKoreanChars(tokens) / 500; |
| } |
| export function formatTokens(t, L) { |
| const unit = L ? L('토큰', 'tokens') : '토큰'; |
| if (t >= 1000000) return `${Math.round(t / 100000) / 10}M ${unit}`; |
| if (t >= 1000) return `${Math.round(t / 1000)}K ${unit}`; |
| return `${t} ${unit}`; |
| } |
| export function humanContext(tokens, L) { |
| const pages = tokensToPages(tokens); |
| if (!L) { |
| if (pages >= 1) return `책 약 ${Math.round(pages)}쪽 분량`; |
| return `한국어 약 ${tokensToKoreanChars(tokens).toLocaleString()}자`; |
| } |
| if (pages >= 1) return L(`책 약 ${Math.round(pages)}쪽 분량`, `~${Math.round(pages)} pages`); |
| const words = Math.round(tokens * 0.75); |
| return L(`한국어 약 ${tokensToKoreanChars(tokens).toLocaleString()}자`, `~${words.toLocaleString()} words`); |
| } |
| export function fmtGB(gb) { |
| if (gb == null) return '—'; |
| if (gb >= 100) return gb.toFixed(0); |
| if (gb >= 10) return gb.toFixed(1); |
| return gb.toFixed(1); |
| } |
|
|
| |
| export const OPUS = MODELS.find((m) => m.name.includes('Opus 4.7')); |
| const OPUS_AVG = benchAvg(OPUS.benchmarks) || 1; |
|
|
| |
| export function opusPct(model) { |
| const a = benchAvg(model.benchmarks); |
| if (a == null || OPUS_AVG <= 0) return null; |
| return (a / OPUS_AVG) * 100; |
| } |
|
|
| |
| export function classifyTier(tokens) { |
| if (tokens < 50000) return { key: 'basic', label: '기본', desc: '대부분의 작업에 충분 (~30K)' }; |
| if (tokens < 130000) |
| return { key: 'mid', label: '적정선', desc: '복잡도 높은 작업 (60~130K)' }; |
| return { key: 'heavy', label: '특수 목적', desc: '긴 문서·코드베이스 전체 (130K+)' }; |
| } |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| |
| const CHIP_BANDWIDTH = { |
| 'M1': 68, 'M1 Pro': 200, 'M1 Max': 400, 'M1 Ultra': 800, |
| 'M2': 100, 'M2 Pro': 200, 'M2 Max': 400, 'M2 Ultra': 800, |
| 'M3': 100, 'M3 Pro': 150, 'M3 Max': 400, 'M3 Ultra': 819, |
| 'M4': 120, 'M4 Pro': 273, 'M4 Max': 546, |
| 'M5': 153, 'M5 Pro': 307, 'M5 Max': 614, |
| }; |
| export function chipBandwidth(chip, gpuCores = 40) { |
| if (chip === 'M5 Max') return gpuCores === 32 ? 460 : 614; |
| return CHIP_BANDWIDTH[chip] || 307; |
| } |
|
|
| |
| |
| export function estimateSpeed(model, chipOrDevice, bitsOrWeightBpw, gpuCores = 40) { |
| if (model.isCloud || !model.totalParams) return null; |
| |
| const bwGBs = (chipOrDevice && typeof chipOrDevice === 'object' && chipOrDevice.bandwidthGBs != null) |
| ? chipOrDevice.bandwidthGBs |
| : chipBandwidth(chipOrDevice, gpuCores); |
| const bw = bwGBs * 1e9; |
| const activeB = (model.activeParams || model.totalParams) * 1e9; |
| const bytesPerToken = activeB * (bitsOrWeightBpw / 8); |
| if (bytesPerToken <= 0) return null; |
| return (bw / bytesPerToken) * 0.75; |
| } |
|
|
| |
| |
| |
|
|
| |
| function paramsFromName(id) { |
| const m = id.match(/(\d+(?:\.\d+)?)\s*[bB](?![a-zA-Z])/); |
| return m ? parseFloat(m[1]) : null; |
| } |
|
|
| export function parseHfConfig(id, raw, totalSize) { |
| const c = raw.text_config || raw; |
| const layerCount = c.num_hidden_layers; |
| if (!layerCount) throw new Error('config에 num_hidden_layers 없음'); |
|
|
| const attnHeads = c.num_attention_heads; |
| const kvHeads = c.num_key_value_heads ?? attnHeads ?? 1; |
| const headDim = c.head_dim ?? (c.hidden_size && attnHeads ? Math.round(c.hidden_size / attnHeads) : 128); |
|
|
| |
| const hasSlidingLayers = Array.isArray(c.layer_types) && c.layer_types.some((t) => String(t).includes('sliding')); |
| const slidingActive = |
| (c.sliding_window || 0) > 0 && |
| c.use_sliding_window !== false && |
| (hasSlidingLayers || (c.sliding_window < (c.max_position_embeddings || Infinity))); |
| const sliding = slidingActive ? c.sliding_window : 0; |
|
|
| |
| let fullAttnLayers, globalAttnLayers; |
| if (Array.isArray(c.layer_types)) { |
| const full = c.layer_types.filter((t) => String(t).includes('full')).length; |
| if (full > 0 && full < layerCount) { |
| if (sliding > 0) globalAttnLayers = full; |
| else fullAttnLayers = full; |
| } |
| } |
|
|
| const numExperts = c.num_local_experts || c.num_experts || c.n_routed_experts; |
| const expertsPerToken = c.num_experts_per_tok; |
| const isMoe = !!numExperts; |
|
|
| |
| |
| const pleParams = c.vocab_size_per_layer_input && c.hidden_size_per_layer_input |
| ? (c.vocab_size_per_layer_input * c.hidden_size_per_layer_input * layerCount) / 1e9 |
| : undefined; |
|
|
| |
| |
| const mlaKvLoraRank = c.kv_lora_rank || undefined; |
| const mlaRopeDim = mlaKvLoraRank ? (c.qk_rope_head_dim || 0) : undefined; |
|
|
| |
| let totalParams = null; |
| if (totalSize) { |
| |
| |
| |
| const qc = c.quantization_config; |
| |
| const qbits = c.quantization?.bits ?? qc?.bits ?? (qc?.load_in_4bit ? 4 : qc?.load_in_8bit ? 8 : undefined); |
| const dt = String(c.torch_dtype || '').toLowerCase(); |
| const dtypeBytes = qbits ? qbits / 8 |
| : dt.includes('float32') || dt.includes('fp32') ? 4 : dt.includes('fp8') || dt.includes('int8') ? 1 : 2; |
| totalParams = totalSize / dtypeBytes / 1e9; |
| } |
| if (!totalParams) totalParams = paramsFromName(id); |
|
|
| return { |
| name: id.split('/').pop(), |
| group: 'HuggingFace', |
| custom: true, |
| sourceId: id, |
| tags: isMoe ? ['moe'] : ['dense'], |
| totalParams: totalParams ? +totalParams.toFixed(1) : null, |
| activeParams: null, |
| layerCount, |
| fullAttnLayers, |
| globalAttnLayers, |
| kvHeads, |
| kvHeadDim: headDim, |
| globalHeadDim: c.global_head_dim || undefined, |
| attnHeads, |
| hiddenSize: c.hidden_size, |
| numExperts, |
| expertsPerToken, |
| mlaKvLoraRank, |
| mlaRopeDim, |
| pleParams: pleParams ? +pleParams.toFixed(3) : undefined, |
| maxContext: c.max_position_embeddings || 131072, |
| |
| slidingWindow: mlaKvLoraRank ? undefined : sliding || undefined, |
| slidingPattern: mlaKvLoraRank ? undefined : sliding ? '5:1' : undefined, |
| benchmarks: null, |
| desc: id, |
| }; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| export function naiveKVCache(model, ctx, bits) { |
| if (!model.kvHeads || !model.kvHeadDim || !model.layerCount) return 0; |
| const bytes = 2 * model.kvHeads * model.kvHeadDim * (bits / 8) * model.layerCount * ctx; |
| return bytes / 1024 ** 3; |
| } |
|
|
| |
| export const DATA_UPDATED = '2026-07'; |
|
|