@tokcalc/mcp-server 0.1.3 → 0.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -406
- package/dist/http.js +23504 -0
- package/dist/index.js +21290 -0
- package/package.json +36 -92
- package/.zscripts/build.sh +0 -175
- package/.zscripts/database-runtime-build.sh +0 -33
- package/.zscripts/dev.pid +0 -1
- package/.zscripts/dev.sh +0 -154
- package/.zscripts/mini-services-build.sh +0 -78
- package/.zscripts/mini-services-install.sh +0 -65
- package/.zscripts/mini-services-start.sh +0 -123
- package/.zscripts/python-runtime-build.sh +0 -120
- package/.zscripts/start.sh +0 -145
- package/CAPACITY_STUDY.md +0 -283
- package/CODE_OF_CONDUCT.md +0 -55
- package/CONTRIBUTING.md +0 -177
- package/Caddyfile +0 -23
- package/LICENSE +0 -204
- package/bun.lock +0 -1965
- package/components.json +0 -21
- package/db/custom.db +0 -0
- package/download/README.md +0 -1
- package/download/tokcalc-dark-calculator.png +0 -0
- package/download/tokcalc-dark-default.png +0 -0
- package/download/tokcalc-demo.webm +0 -0
- package/download/tokcalc-github-link.png +0 -0
- package/download/tokcalc-hydration-fixed.png +0 -0
- package/download/tokcalc-issue-resolved.png +0 -0
- package/download/tokcalc-light-mode.png +0 -0
- package/download/tokcalc-light-reference.png +0 -0
- package/download/tokcalc-long-context-qwen.png +0 -0
- package/download/tokcalc-long-context.png +0 -0
- package/download/tokcalc-og-image-preview.png +0 -0
- package/download/tokcalc-phase2-3.png +0 -0
- package/download/tokcalc-plain-english.png +0 -0
- package/download/tokcalc-preview.png +0 -0
- package/download/tokcalc-share-bvb.png +0 -0
- package/download/tokcalc-share-feature.png +0 -0
- package/download/tokcalc-tab-build-vs-buy.png +0 -0
- package/download/tokcalc-tab-calculator.png +0 -0
- package/download/tokcalc-tab-reference.png +0 -0
- package/eslint.config.mjs +0 -50
- package/examples/websocket/frontend.tsx +0 -196
- package/examples/websocket/server.ts +0 -138
- package/mini-services/.gitkeep +0 -0
- package/mini-services/mcp-server/README.md +0 -86
- package/mini-services/mcp-server/bun.lock +0 -202
- package/mini-services/mcp-server/index.ts +0 -504
- package/mini-services/mcp-server/package.json +0 -40
- package/next.config.ts +0 -12
- package/postcss.config.mjs +0 -5
- package/prisma/schema.prisma +0 -32
- package/public/google6f58ca6be85fa903.html +0 -1
- package/public/logo.svg +0 -29
- package/public/manifest.json +0 -51
- package/public/og-icon-256.png +0 -0
- package/public/og.png +0 -0
- package/public/robots.txt +0 -25
- package/public/sitemap.xml +0 -23
- package/public/tokcalc-demo.gif +0 -0
- package/scripts/og-template.html +0 -120
- package/scripts/render-og.mjs +0 -43
- package/server.json +0 -21
- package/src/app/api/pricing/aws/route.ts +0 -186
- package/src/app/api/pricing/azure/route.ts +0 -168
- package/src/app/api/pricing/gcp/route.ts +0 -230
- package/src/app/api/pricing/vast-ai/route.ts +0 -164
- package/src/app/api/route.ts +0 -5
- package/src/app/compare/h100-vs-h200/layout.tsx +0 -30
- package/src/app/compare/h100-vs-h200/page.tsx +0 -328
- package/src/app/globals.css +0 -122
- package/src/app/layout.tsx +0 -276
- package/src/app/page.tsx +0 -2670
- package/src/components/azure-live-pricing.tsx +0 -185
- package/src/components/benchmark-import.tsx +0 -340
- package/src/components/confidence-badge.tsx +0 -116
- package/src/components/live-pricing-comparison.tsx +0 -241
- package/src/components/theme-provider.tsx +0 -11
- package/src/components/theme-toggle.tsx +0 -55
- package/src/components/ui/accordion.tsx +0 -66
- package/src/components/ui/alert-dialog.tsx +0 -157
- package/src/components/ui/alert.tsx +0 -66
- package/src/components/ui/aspect-ratio.tsx +0 -11
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/badge.tsx +0 -46
- package/src/components/ui/breadcrumb.tsx +0 -109
- package/src/components/ui/button.tsx +0 -59
- package/src/components/ui/calendar.tsx +0 -213
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/carousel.tsx +0 -241
- package/src/components/ui/chart.tsx +0 -353
- package/src/components/ui/checkbox.tsx +0 -32
- package/src/components/ui/collapsible.tsx +0 -33
- package/src/components/ui/command.tsx +0 -184
- package/src/components/ui/context-menu.tsx +0 -252
- package/src/components/ui/dialog.tsx +0 -143
- package/src/components/ui/drawer.tsx +0 -135
- package/src/components/ui/dropdown-menu.tsx +0 -257
- package/src/components/ui/form.tsx +0 -167
- package/src/components/ui/hover-card.tsx +0 -44
- package/src/components/ui/input-otp.tsx +0 -77
- package/src/components/ui/input.tsx +0 -21
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/menubar.tsx +0 -276
- package/src/components/ui/navigation-menu.tsx +0 -168
- package/src/components/ui/pagination.tsx +0 -127
- package/src/components/ui/popover.tsx +0 -48
- package/src/components/ui/progress.tsx +0 -31
- package/src/components/ui/radio-group.tsx +0 -45
- package/src/components/ui/resizable.tsx +0 -56
- package/src/components/ui/scroll-area.tsx +0 -58
- package/src/components/ui/select.tsx +0 -185
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/sheet.tsx +0 -139
- package/src/components/ui/sidebar.tsx +0 -726
- package/src/components/ui/skeleton.tsx +0 -13
- package/src/components/ui/slider.tsx +0 -63
- package/src/components/ui/sonner.tsx +0 -25
- package/src/components/ui/switch.tsx +0 -31
- package/src/components/ui/table.tsx +0 -116
- package/src/components/ui/tabs.tsx +0 -66
- package/src/components/ui/textarea.tsx +0 -18
- package/src/components/ui/toast.tsx +0 -129
- package/src/components/ui/toaster.tsx +0 -35
- package/src/components/ui/toggle-group.tsx +0 -73
- package/src/components/ui/toggle.tsx +0 -47
- package/src/components/ui/tooltip.tsx +0 -61
- package/src/components/vast-ai-live-pricing.tsx +0 -176
- package/src/hooks/use-mobile.ts +0 -19
- package/src/hooks/use-toast.ts +0 -194
- package/src/lib/benchmark-parser-sglang.ts +0 -150
- package/src/lib/benchmark-parser-tokcalc.ts +0 -247
- package/src/lib/benchmark-parser-trtllm.ts +0 -152
- package/src/lib/benchmark-parser-vllm.ts +0 -198
- package/src/lib/benchmark-schema.ts +0 -263
- package/src/lib/db.ts +0 -13
- package/src/lib/engine-presets.ts +0 -183
- package/src/lib/price-schema.ts +0 -141
- package/src/lib/token-calc.ts +0 -808
- package/src/lib/track.ts +0 -31
- package/src/lib/url-state.ts +0 -256
- package/src/lib/utils.ts +0 -6
- package/tailwind.config.ts +0 -64
- package/tests/database-runtime-build.sh +0 -75
- package/tests/python-runtime-build.sh +0 -64
- package/tests/python-runtime-container.sh +0 -31
- package/tool-results/bash_1789888171144_2c5381860539.txt +0 -161
- package/tool-results/bash_1789888175925_49c53ba3c61b.txt +0 -191
- package/tool-results/bash_1789888181202_49c53ba3c61b.txt +0 -191
- package/tool-results/bash_1789888195219_4a86a5c91411.txt +0 -200
- package/tool-results/bash_1789888203128_6cca13c71b47.txt +0 -199
- package/tool-results/bash_1789929256963_2a52aff0d0a8.txt +0 -160
- package/tool-results/read_1789888151021_69f58eec6a5b.txt +0 -653
- package/tool-results/read_1789888153837_1d3a8bfc2a94.txt +0 -653
- package/tool-results/read_1789888163087_ccc406d47505.txt +0 -122
- package/tool-results/read_1789888167347_67d1d7c9830a.txt +0 -122
- package/tool-results/read_1789929252529_d90e8f383a25.txt +0 -285
- package/tsconfig.json +0 -42
- package/upload/Pasted Content_1789887800864.txt +0 -652
- package/upload/Pasted Content_1789887909561.txt +0 -652
- package/upload/Pasted Content_1789887918428.txt +0 -652
- package/upload/Pasted Content_1789887959420.txt +0 -652
- package/upload/Pasted Content_1789888020485.txt +0 -652
- package/upload/Pasted Content_1789888058079.txt +0 -652
- package/upload/Pasted Content_1789888885033.txt +0 -686
- package/upload/Pasted Content_1789928912741.txt +0 -285
- package/upload/Pasted Content_1789928938402.txt +0 -285
- package/upload/Pasted Content_1789929160389.txt +0 -285
- package/upload/Pasted Content_1789929176660.txt +0 -285
- package/upload/issue_vision.json +0 -28
- package/upload/pasted_image_1789883175209.png +0 -0
- package/upload/pasted_image_1789899056690.png +0 -0
- package/upload/pasted_image_1789900371483.png +0 -0
- package/upload/pasted_image_1789900472823.png +0 -0
- package/upload/pasted_image_1789900490374.png +0 -0
- package/upload/pasted_image_1789900585552.png +0 -0
- package/upload/pasted_image_1789900606519.png +0 -0
- package/upload/pasted_image_1789901598705.png +0 -0
- package/upload/pasted_image_1789901613545.png +0 -0
- package/upload/pasted_image_1789978382674.png +0 -0
- package/upload/pasted_image_1789978392749.png +0 -0
- package/upload/pasted_image_1789978474879.png +0 -0
- package/upload/pasted_image_1789978523652.png +0 -0
- package/upload/pasted_image_1789984219089.png +0 -0
- package/upload/pasted_image_1789984491896.png +0 -0
- package/upload/pasted_image_1789985017950.png +0 -0
- package/upload/pasted_image_1789985036765.png +0 -0
- package/upload/pasted_image_1789985049848.png +0 -0
- package/upload/pasted_image_1790002427833.png +0 -0
- package/upload/pasted_image_1790002659944.png +0 -0
- package/upload/pasted_image_1790037038476.png +0 -0
- package/upload/screenshot_analysis.json +0 -28
- package/upload/vision_output.json +0 -28
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
|
|
3
|
-
# 配置项
|
|
4
|
-
DIST_DIR="./mini-services-dist"
|
|
5
|
-
|
|
6
|
-
# 存储所有子进程的 PID
|
|
7
|
-
pids=""
|
|
8
|
-
|
|
9
|
-
# 清理函数:优雅关闭所有服务
|
|
10
|
-
cleanup() {
|
|
11
|
-
echo ""
|
|
12
|
-
echo "🛑 正在关闭所有服务..."
|
|
13
|
-
|
|
14
|
-
# 发送 SIGTERM 信号给所有子进程
|
|
15
|
-
for pid in $pids; do
|
|
16
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
17
|
-
service_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
|
|
18
|
-
echo " 关闭进程 $pid ($service_name)..."
|
|
19
|
-
kill -TERM "$pid" 2>/dev/null
|
|
20
|
-
fi
|
|
21
|
-
done
|
|
22
|
-
|
|
23
|
-
# 等待所有进程退出(最多等待 5 秒)
|
|
24
|
-
sleep 1
|
|
25
|
-
for pid in $pids; do
|
|
26
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
27
|
-
# 如果还在运行,等待最多 4 秒
|
|
28
|
-
timeout=4
|
|
29
|
-
while [ $timeout -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
|
|
30
|
-
sleep 1
|
|
31
|
-
timeout=$((timeout - 1))
|
|
32
|
-
done
|
|
33
|
-
# 如果仍然在运行,强制关闭
|
|
34
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
35
|
-
echo " 强制关闭进程 $pid..."
|
|
36
|
-
kill -KILL "$pid" 2>/dev/null
|
|
37
|
-
fi
|
|
38
|
-
fi
|
|
39
|
-
done
|
|
40
|
-
|
|
41
|
-
echo "✅ 所有服务已关闭"
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
main() {
|
|
45
|
-
echo "🚀 开始启动所有 mini services..."
|
|
46
|
-
|
|
47
|
-
# 检查 dist 目录是否存在
|
|
48
|
-
if [ ! -d "$DIST_DIR" ]; then
|
|
49
|
-
echo "ℹ️ 目录 $DIST_DIR 不存在"
|
|
50
|
-
return
|
|
51
|
-
fi
|
|
52
|
-
|
|
53
|
-
# 查找所有 mini-service-*.js 文件
|
|
54
|
-
service_files=""
|
|
55
|
-
for file in "$DIST_DIR"/mini-service-*.js; do
|
|
56
|
-
if [ -f "$file" ]; then
|
|
57
|
-
if [ -z "$service_files" ]; then
|
|
58
|
-
service_files="$file"
|
|
59
|
-
else
|
|
60
|
-
service_files="$service_files $file"
|
|
61
|
-
fi
|
|
62
|
-
fi
|
|
63
|
-
done
|
|
64
|
-
|
|
65
|
-
# 计算服务文件数量
|
|
66
|
-
service_count=0
|
|
67
|
-
for file in $service_files; do
|
|
68
|
-
service_count=$((service_count + 1))
|
|
69
|
-
done
|
|
70
|
-
|
|
71
|
-
if [ $service_count -eq 0 ]; then
|
|
72
|
-
echo "ℹ️ 未找到任何 mini service 文件"
|
|
73
|
-
return
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
echo "📦 找到 $service_count 个服务,开始启动..."
|
|
77
|
-
echo ""
|
|
78
|
-
|
|
79
|
-
# 启动每个服务
|
|
80
|
-
for file in $service_files; do
|
|
81
|
-
service_name=$(basename "$file" .js | sed 's/mini-service-//')
|
|
82
|
-
echo "▶️ 启动服务: $service_name..."
|
|
83
|
-
|
|
84
|
-
# 使用 bun 运行服务(后台运行)
|
|
85
|
-
bun "$file" &
|
|
86
|
-
pid=$!
|
|
87
|
-
if [ -z "$pids" ]; then
|
|
88
|
-
pids="$pid"
|
|
89
|
-
else
|
|
90
|
-
pids="$pids $pid"
|
|
91
|
-
fi
|
|
92
|
-
|
|
93
|
-
# 等待一小段时间检查进程是否成功启动
|
|
94
|
-
sleep 0.5
|
|
95
|
-
if ! kill -0 "$pid" 2>/dev/null; then
|
|
96
|
-
echo "❌ $service_name 启动失败"
|
|
97
|
-
# 从字符串中移除失败的 PID
|
|
98
|
-
pids=$(echo "$pids" | sed "s/\b$pid\b//" | sed 's/ */ /g' | sed 's/^ *//' | sed 's/ *$//')
|
|
99
|
-
else
|
|
100
|
-
echo "✅ $service_name 已启动 (PID: $pid)"
|
|
101
|
-
fi
|
|
102
|
-
done
|
|
103
|
-
|
|
104
|
-
# 计算运行中的服务数量
|
|
105
|
-
running_count=0
|
|
106
|
-
for pid in $pids; do
|
|
107
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
108
|
-
running_count=$((running_count + 1))
|
|
109
|
-
fi
|
|
110
|
-
done
|
|
111
|
-
|
|
112
|
-
echo ""
|
|
113
|
-
echo "🎉 所有服务已启动!共 $running_count 个服务正在运行"
|
|
114
|
-
echo ""
|
|
115
|
-
echo "💡 按 Ctrl+C 停止所有服务"
|
|
116
|
-
echo ""
|
|
117
|
-
|
|
118
|
-
# 等待所有后台进程
|
|
119
|
-
wait
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
main
|
|
123
|
-
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
set -euo pipefail
|
|
4
|
-
|
|
5
|
-
PROJECT_DIR="${PROJECT_DIR:-/home/z/my-project}"
|
|
6
|
-
BUILD_DIR="${BUILD_DIR:?BUILD_DIR is required}"
|
|
7
|
-
PYTHON_VERSION="${PYTHON_VERSION:-3.12}"
|
|
8
|
-
NEXT_DIST_DIR="$BUILD_DIR/next-service-dist"
|
|
9
|
-
PYTHON_RUNTIME_DIR="$BUILD_DIR/python-runtime"
|
|
10
|
-
PYTHON_PACKAGES_DIR="$PYTHON_RUNTIME_DIR/site-packages"
|
|
11
|
-
|
|
12
|
-
has_python_sources() {
|
|
13
|
-
find "$PROJECT_DIR" \
|
|
14
|
-
\( -type d \( -name '.git' \
|
|
15
|
-
-o -name '.next' \
|
|
16
|
-
-o -name '.venv' \
|
|
17
|
-
-o -name 'node_modules' \
|
|
18
|
-
-o -name '__pycache__' \
|
|
19
|
-
-o -name 'mini-services' \
|
|
20
|
-
-o -name 'upload' \
|
|
21
|
-
-o -name 'download' \
|
|
22
|
-
\) -prune \) \
|
|
23
|
-
-o -type f \( -name '*.py' -o -name '*.pyi' \) -print -quit | grep -q .
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if ! has_python_sources \
|
|
27
|
-
&& [ ! -f "$PROJECT_DIR/requirements.txt" ] \
|
|
28
|
-
&& [ ! -f "$PROJECT_DIR/pyproject.toml" ]; then
|
|
29
|
-
echo "ℹ️ 未检测到 Python 源码或依赖清单,跳过 Python runtime 构建"
|
|
30
|
-
exit 0
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
if ! command -v uv >/dev/null 2>&1; then
|
|
34
|
-
echo "❌ 检测到 Python 项目,但构建环境中没有 uv"
|
|
35
|
-
exit 1
|
|
36
|
-
fi
|
|
37
|
-
|
|
38
|
-
echo "🐍 检测到 Python runtime,目标版本: $PYTHON_VERSION"
|
|
39
|
-
mkdir -p "$NEXT_DIST_DIR" "$PYTHON_PACKAGES_DIR"
|
|
40
|
-
|
|
41
|
-
install_requirements() {
|
|
42
|
-
local requirements_file="$1"
|
|
43
|
-
local target_dir="${2:-$PYTHON_PACKAGES_DIR}"
|
|
44
|
-
if [ ! -s "$requirements_file" ]; then
|
|
45
|
-
echo "ℹ️ Python 依赖清单为空,跳过依赖安装"
|
|
46
|
-
return 0
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
echo "📦 根据 $(basename "$requirements_file") 固化 Python 生产依赖..."
|
|
50
|
-
uv pip install \
|
|
51
|
-
--python "$PYTHON_VERSION" \
|
|
52
|
-
--target "$target_dir" \
|
|
53
|
-
--requirements "$requirements_file"
|
|
54
|
-
|
|
55
|
-
# --target 生成的 console scripts 会保留构建机 Python 的绝对 shebang。
|
|
56
|
-
# 改成 Runner 内可解析的 python,并由 start scripts 将该 bin 目录加入 PATH。
|
|
57
|
-
if [ -d "$target_dir/bin" ]; then
|
|
58
|
-
for script in "$target_dir"/bin/*; do
|
|
59
|
-
[ -f "$script" ] || continue
|
|
60
|
-
perl -0pi -e 's/\A#![^\n]*python[^\n]*\n/#!\/usr\/bin\/env python\n/' "$script"
|
|
61
|
-
done
|
|
62
|
-
fi
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
install_pyproject() {
|
|
66
|
-
local project_dir="$1"
|
|
67
|
-
local target_dir="$2"
|
|
68
|
-
local output_name="$3"
|
|
69
|
-
local requirements_file="$PYTHON_RUNTIME_DIR/$output_name"
|
|
70
|
-
|
|
71
|
-
if [ -f "$project_dir/uv.lock" ]; then
|
|
72
|
-
uv export \
|
|
73
|
-
--project "$project_dir" \
|
|
74
|
-
--frozen \
|
|
75
|
-
--no-dev \
|
|
76
|
-
--no-emit-project \
|
|
77
|
-
--format requirements.txt \
|
|
78
|
-
--output-file "$requirements_file"
|
|
79
|
-
else
|
|
80
|
-
uv pip compile \
|
|
81
|
-
"$project_dir/pyproject.toml" \
|
|
82
|
-
--python-version "$PYTHON_VERSION" \
|
|
83
|
-
--output-file "$requirements_file"
|
|
84
|
-
fi
|
|
85
|
-
install_requirements "$requirements_file" "$target_dir"
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if [ -f "$PROJECT_DIR/pyproject.toml" ] && [ -f "$PROJECT_DIR/uv.lock" ]; then
|
|
89
|
-
echo "🔒 使用 pyproject.toml + uv.lock 导出生产依赖..."
|
|
90
|
-
install_pyproject "$PROJECT_DIR" "$PYTHON_PACKAGES_DIR" "requirements.lock.txt"
|
|
91
|
-
elif [ -f "$PROJECT_DIR/requirements.txt" ]; then
|
|
92
|
-
cp "$PROJECT_DIR/requirements.txt" "$PYTHON_RUNTIME_DIR/requirements.txt"
|
|
93
|
-
install_requirements "$PYTHON_RUNTIME_DIR/requirements.txt"
|
|
94
|
-
elif [ -f "$PROJECT_DIR/pyproject.toml" ]; then
|
|
95
|
-
echo "📦 pyproject.toml 未配套 uv.lock,解析生产依赖..."
|
|
96
|
-
install_pyproject "$PROJECT_DIR" "$PYTHON_PACKAGES_DIR" "requirements.txt"
|
|
97
|
-
else
|
|
98
|
-
echo "⚠️ 检测到 Python 源码,但没有 requirements.txt 或 pyproject.toml;仅支持 Python 标准库"
|
|
99
|
-
fi
|
|
100
|
-
|
|
101
|
-
if has_python_sources; then
|
|
102
|
-
echo "📄 复制 Python 源码到部署项目,保持相对路径..."
|
|
103
|
-
(
|
|
104
|
-
cd "$PROJECT_DIR"
|
|
105
|
-
find . \
|
|
106
|
-
\( -type d \( -name '.git' \
|
|
107
|
-
-o -name '.next' \
|
|
108
|
-
-o -name '.venv' \
|
|
109
|
-
-o -name 'node_modules' \
|
|
110
|
-
-o -name '__pycache__' \
|
|
111
|
-
-o -name 'mini-services' \
|
|
112
|
-
-o -name 'upload' \
|
|
113
|
-
-o -name 'download' \
|
|
114
|
-
\) -prune \) \
|
|
115
|
-
-o -type f \( -name '*.py' -o -name '*.pyi' \) -print0 \
|
|
116
|
-
| tar --null -T - -cf -
|
|
117
|
-
) | tar -C "$NEXT_DIST_DIR" -xf -
|
|
118
|
-
fi
|
|
119
|
-
|
|
120
|
-
echo "✅ Python runtime 已固化到部署产物"
|
package/.zscripts/start.sh
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
#!/bin/sh
|
|
2
|
-
|
|
3
|
-
set -e
|
|
4
|
-
|
|
5
|
-
# 获取脚本所在目录
|
|
6
|
-
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
-
BUILD_DIR="$SCRIPT_DIR"
|
|
8
|
-
|
|
9
|
-
# 存储所有子进程的 PID
|
|
10
|
-
pids=""
|
|
11
|
-
|
|
12
|
-
# 清理函数:优雅关闭所有服务
|
|
13
|
-
cleanup() {
|
|
14
|
-
echo ""
|
|
15
|
-
echo "🛑 正在关闭所有服务..."
|
|
16
|
-
|
|
17
|
-
# 发送 SIGTERM 信号给所有子进程
|
|
18
|
-
for pid in $pids; do
|
|
19
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
20
|
-
service_name=$(ps -p "$pid" -o comm= 2>/dev/null || echo "unknown")
|
|
21
|
-
echo " 关闭进程 $pid ($service_name)..."
|
|
22
|
-
kill -TERM "$pid" 2>/dev/null
|
|
23
|
-
fi
|
|
24
|
-
done
|
|
25
|
-
|
|
26
|
-
# 等待所有进程退出(最多等待 5 秒)
|
|
27
|
-
sleep 1
|
|
28
|
-
for pid in $pids; do
|
|
29
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
30
|
-
# 如果还在运行,等待最多 4 秒
|
|
31
|
-
timeout=4
|
|
32
|
-
while [ $timeout -gt 0 ] && kill -0 "$pid" 2>/dev/null; do
|
|
33
|
-
sleep 1
|
|
34
|
-
timeout=$((timeout - 1))
|
|
35
|
-
done
|
|
36
|
-
# 如果仍然在运行,强制关闭
|
|
37
|
-
if kill -0 "$pid" 2>/dev/null; then
|
|
38
|
-
echo " 强制关闭进程 $pid..."
|
|
39
|
-
kill -KILL "$pid" 2>/dev/null
|
|
40
|
-
fi
|
|
41
|
-
fi
|
|
42
|
-
done
|
|
43
|
-
|
|
44
|
-
echo "✅ 所有服务已关闭"
|
|
45
|
-
exit 0
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
echo "🚀 开始启动所有服务..."
|
|
49
|
-
echo ""
|
|
50
|
-
|
|
51
|
-
# 切换到构建目录
|
|
52
|
-
cd "$BUILD_DIR" || exit 1
|
|
53
|
-
|
|
54
|
-
ls -lah
|
|
55
|
-
|
|
56
|
-
DEFAULT_PACKAGED_DB_PATH="/app/db/custom.db"
|
|
57
|
-
DEFAULT_PACKAGED_DATABASE_URL="file:$DEFAULT_PACKAGED_DB_PATH"
|
|
58
|
-
|
|
59
|
-
# Python 依赖在构建阶段安装进部署产物,不复用 Sandbox 的 /home/z/.venv。
|
|
60
|
-
# Next.js 及其启动的子进程都会继承这组路径。
|
|
61
|
-
if [ -d "/app/python-runtime/site-packages" ]; then
|
|
62
|
-
export PYTHONPATH="/app/python-runtime/site-packages:/app/next-service-dist${PYTHONPATH:+:$PYTHONPATH}"
|
|
63
|
-
export PATH="/app/python-runtime/site-packages/bin:$PATH"
|
|
64
|
-
export PYTHONDONTWRITEBYTECODE=1
|
|
65
|
-
export PYTHONUNBUFFERED=1
|
|
66
|
-
echo "🐍 已启用部署包内 Python runtime: $(python --version 2>&1)"
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
# 启动 Next.js 服务器
|
|
70
|
-
if [ -f "./next-service-dist/server.js" ]; then
|
|
71
|
-
echo "🚀 启动 Next.js 服务器..."
|
|
72
|
-
cd next-service-dist/ || exit 1
|
|
73
|
-
|
|
74
|
-
# 设置环境变量
|
|
75
|
-
export NODE_ENV=production
|
|
76
|
-
export PORT="${PORT:-3000}"
|
|
77
|
-
export HOSTNAME="${HOSTNAME:-0.0.0.0}"
|
|
78
|
-
export DATABASE_URL="${DATABASE_URL:-$DEFAULT_PACKAGED_DATABASE_URL}"
|
|
79
|
-
|
|
80
|
-
if [ "$DATABASE_URL" = "$DEFAULT_PACKAGED_DATABASE_URL" ]; then
|
|
81
|
-
if [ ! -f "$DEFAULT_PACKAGED_DB_PATH" ]; then
|
|
82
|
-
echo "❌ 未找到打包后的数据库文件 $DEFAULT_PACKAGED_DB_PATH"
|
|
83
|
-
echo " 为避免生产环境启动到空数据库,启动已终止"
|
|
84
|
-
exit 1
|
|
85
|
-
fi
|
|
86
|
-
|
|
87
|
-
echo "🗄️ 当前使用打包数据库: $DEFAULT_PACKAGED_DB_PATH"
|
|
88
|
-
else
|
|
89
|
-
echo "🗄️ 当前使用外部指定数据库: $DATABASE_URL"
|
|
90
|
-
fi
|
|
91
|
-
|
|
92
|
-
# 后台启动 Next.js
|
|
93
|
-
bun server.js &
|
|
94
|
-
NEXT_PID=$!
|
|
95
|
-
pids="$NEXT_PID"
|
|
96
|
-
|
|
97
|
-
# 等待一小段时间检查进程是否成功启动
|
|
98
|
-
sleep 1
|
|
99
|
-
if ! kill -0 "$NEXT_PID" 2>/dev/null; then
|
|
100
|
-
echo "❌ Next.js 服务器启动失败"
|
|
101
|
-
exit 1
|
|
102
|
-
else
|
|
103
|
-
echo "✅ Next.js 服务器已启动 (PID: $NEXT_PID, Port: $PORT)"
|
|
104
|
-
fi
|
|
105
|
-
|
|
106
|
-
cd ../
|
|
107
|
-
else
|
|
108
|
-
echo "⚠️ 未找到 Next.js 服务器文件: ./next-service-dist/server.js"
|
|
109
|
-
fi
|
|
110
|
-
|
|
111
|
-
# 启动 mini-services
|
|
112
|
-
if [ -f "./mini-services-start.sh" ]; then
|
|
113
|
-
echo "🚀 启动 mini-services..."
|
|
114
|
-
|
|
115
|
-
# 运行启动脚本(从根目录运行,脚本内部会处理 mini-services-dist 目录)
|
|
116
|
-
sh ./mini-services-start.sh &
|
|
117
|
-
MINI_PID=$!
|
|
118
|
-
pids="$pids $MINI_PID"
|
|
119
|
-
|
|
120
|
-
# 等待一小段时间检查进程是否成功启动
|
|
121
|
-
sleep 1
|
|
122
|
-
if ! kill -0 "$MINI_PID" 2>/dev/null; then
|
|
123
|
-
echo "⚠️ mini-services 可能启动失败,但继续运行..."
|
|
124
|
-
else
|
|
125
|
-
echo "✅ mini-services 已启动 (PID: $MINI_PID)"
|
|
126
|
-
fi
|
|
127
|
-
elif [ -d "./mini-services-dist" ]; then
|
|
128
|
-
echo "⚠️ 未找到 mini-services 启动脚本,但目录存在"
|
|
129
|
-
else
|
|
130
|
-
echo "ℹ️ mini-services 目录不存在,跳过"
|
|
131
|
-
fi
|
|
132
|
-
|
|
133
|
-
# 启动 Caddy(如果存在 Caddyfile)
|
|
134
|
-
echo "🚀 启动 Caddy..."
|
|
135
|
-
|
|
136
|
-
# Caddy 作为前台进程运行(主进程)
|
|
137
|
-
echo "✅ Caddy 已启动(前台运行)"
|
|
138
|
-
echo ""
|
|
139
|
-
echo "🎉 所有服务已启动!"
|
|
140
|
-
echo ""
|
|
141
|
-
echo "💡 按 Ctrl+C 停止所有服务"
|
|
142
|
-
echo ""
|
|
143
|
-
|
|
144
|
-
# Caddy 作为主进程运行
|
|
145
|
-
exec caddy run --config Caddyfile --adapter caddyfile
|
package/CAPACITY_STUDY.md
DELETED
|
@@ -1,283 +0,0 @@
|
|
|
1
|
-
# How context length, KV cache, batching, and latency SLOs change the number of concurrent users an LLM deployment can actually support
|
|
2
|
-
|
|
3
|
-
### A capacity-planning analysis across RTX 4090, H100, H200, and B200 for Llama 3.3 70B
|
|
4
|
-
|
|
5
|
-
**By the tokcalc maintainer** · September 2026 · [Live calculator](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h200-sxm&q=fp8&n=1&b=1&p=50000&o=500&cb=1&cbm=1.5) · [GitHub](https://github.com/stevecrates489-commits/tokcalc)
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Why this analysis exists
|
|
10
|
-
|
|
11
|
-
Every "tokens per second" calculator I've found answers the wrong question. They tell you how fast a GPU can generate tokens in isolation. They don't tell you whether you can actually serve your traffic pattern — your context lengths, your concurrency, your latency SLOs — on a real GPU fleet.
|
|
12
|
-
|
|
13
|
-
This analysis asks a different question:
|
|
14
|
-
|
|
15
|
-
> **"How many concurrent users can one GPU (or a small fleet) actually support for Llama 3.3 70B, as context length grows from 4K to 128K?"**
|
|
16
|
-
|
|
17
|
-
The answer is not a single number. It's a curve that drops sharply with context length — because KV cache memory grows linearly, and eventually eats all your VRAM headroom.
|
|
18
|
-
|
|
19
|
-
I built [tokcalc](https://tokcalc.vercel.app) to model this. This article uses the same formulas, same data, same code. Every number below is computed from the open-source calculator — you can reproduce every result by opening the links.
|
|
20
|
-
|
|
21
|
-
---
|
|
22
|
-
|
|
23
|
-
## The setup
|
|
24
|
-
|
|
25
|
-
**Model**: Llama 3.3 70B (80 layers, 8 KV heads, 128 head dim, GQA, 128K max context) — the most-deployed open-weights model in production as of late 2026.
|
|
26
|
-
|
|
27
|
-
**Quantization**: FP8 (1 byte/param) — native on H100/H200/B200, roughly 1.5× faster than FP16 thanks to dedicated FP8 tensor cores, with negligible quality loss per cited benchmarks.
|
|
28
|
-
|
|
29
|
-
**Model weights**: 70.6 GB (70.6B params × 1 byte)
|
|
30
|
-
|
|
31
|
-
**GPUs tested**:
|
|
32
|
-
|
|
33
|
-
| GPU | VRAM | HBM Bandwidth | FP8 Efficiency | Notes |
|
|
34
|
-
|---|---|---|---|---|
|
|
35
|
-
| 4× RTX 4090 | 4×24 = 96 GB | 4×1008 = 4032 GB/s | 1.5× (consumer) | No NVLink (PCIe only) — TP efficiency ~0.6 |
|
|
36
|
-
| H100 SXM5 | 80 GB | 3350 GB/s | 1.5× | NVLink 900 GB/s |
|
|
37
|
-
| H200 SXM5 | 141 GB | 4800 GB/s | 1.5× | NVLink 900 GB/s |
|
|
38
|
-
| B200 SXM | 192 GB | 8000 GB/s | 1.5× (est.) | FP16 dense TFLOPS not published by NVIDIA |
|
|
39
|
-
|
|
40
|
-
**KV cache formula** (per request, per token):
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
KV bytes/token = 2 × layers × kv_heads × head_dim × bytes_per_kv_value
|
|
44
|
-
= 2 × 80 × 8 × 128 × 2
|
|
45
|
-
= 327,680 bytes = 0.32 MB/token
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The factor of 2 stores both Keys and Values. GQA (Grouped-Query Attention) means only 8 KV heads (not 64 query heads) are cached per layer — this is what makes 70B Llama models feasible at long context.
|
|
49
|
-
|
|
50
|
-
**Max concurrent users** at a given context length:
|
|
51
|
-
|
|
52
|
-
```
|
|
53
|
-
max_users = floor((total_vram - model_weights) / (kv_per_token × context_length))
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
---
|
|
57
|
-
|
|
58
|
-
## Finding 1: Context length is the #1 capacity killer — doubling context halves concurrency
|
|
59
|
-
|
|
60
|
-
| Context | KV/request | H100 max users | H200 max users | B200 max users |
|
|
61
|
-
|---|---|---|---|---|
|
|
62
|
-
| 4K | 1.31 GB | 7 | 53 | 92 |
|
|
63
|
-
| 8K | 2.62 GB | 3 | 26 | 46 |
|
|
64
|
-
| 16K | 5.24 GB | 1 | 13 | 23 |
|
|
65
|
-
| 32K | 10.49 GB | 0 | 6 | 11 |
|
|
66
|
-
| 64K | 20.97 GB | 0 | 3 | 5 |
|
|
67
|
-
| 128K | 41.94 GB | 0 | 1 | 2 |
|
|
68
|
-
|
|
69
|
-
Every time you double context length, you halve the number of concurrent users you can serve — because KV cache memory doubles and eats into the VRAM headroom left after loading model weights.
|
|
70
|
-
|
|
71
|
-
This is the most important graph in this analysis. If your users send 32K-token prompts, your H100 fleet can't serve even 1 user — despite the model fitting comfortably in VRAM.
|
|
72
|
-
|
|
73
|
-
[Try this on tokcalc →](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h100-sxm&q=fp8&n=1&b=1&p=32768&o=500)
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## Finding 2: H200 doubles H100 capacity at every context length — VRAM is the bottleneck, not bandwidth
|
|
78
|
-
|
|
79
|
-
| Context | H100 (80 GB) | H200 (141 GB) | Ratio |
|
|
80
|
-
|---|---|---|---|
|
|
81
|
-
| 4K | 7 | 53 | 7.6× |
|
|
82
|
-
| 8K | 3 | 26 | 8.7× |
|
|
83
|
-
| 16K | 1 | 13 | 13× |
|
|
84
|
-
| 32K | 0 | 6 | ∞ |
|
|
85
|
-
| 64K | 0 | 3 | ∞ |
|
|
86
|
-
| 128K | 0 | 1 | ∞ |
|
|
87
|
-
|
|
88
|
-
H200 has 1.76× more VRAM (141 vs 80 GB) and 1.43× more bandwidth (4800 vs 3350 GB/s). But the concurrency improvement is **7-13×** — not 1.76×.
|
|
89
|
-
|
|
90
|
-
Why? Because at 4K context, H100 has only 9.4 GB of free VRAM after loading weights (80 - 70.6 = 9.4 GB). H200 has 70.4 GB free. The ratio of free VRAM is **7.5×** — which matches the concurrency ratio almost exactly.
|
|
91
|
-
|
|
92
|
-
**The implication**: if you're paying for H100s and your users send 8K+ context, you're wasting 80% of your GPU's compute. The bottleneck is memory capacity, not memory bandwidth or FLOPS.
|
|
93
|
-
|
|
94
|
-
[Try H100 vs H200 on tokcalc →](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h200-sxm&q=fp8&n=1&b=1&p=32768&o=500)
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## Finding 3: B200 nearly triples H200 concurrency — but FP16 dense FLOPS remain unpublished
|
|
99
|
-
|
|
100
|
-
| Context | H200 (141 GB) | B200 (192 GB) | Ratio |
|
|
101
|
-
|---|---|---|---|
|
|
102
|
-
| 4K | 53 | 92 | 1.7× |
|
|
103
|
-
| 8K | 26 | 46 | 1.8× |
|
|
104
|
-
| 16K | 13 | 23 | 1.8× |
|
|
105
|
-
| 32K | 6 | 11 | 1.8× |
|
|
106
|
-
| 64K | 3 | 5 | 1.7× |
|
|
107
|
-
| 128K | 1 | 2 | 2× |
|
|
108
|
-
|
|
109
|
-
B200 has 1.36× more VRAM (192 vs 141 GB) and 1.67× more bandwidth (8000 vs 4800 GB/s). The concurrency improvement is ~1.8× — roughly proportional to the VRAM increase (free VRAM: 121.4 vs 70.4 GB = 1.72×).
|
|
110
|
-
|
|
111
|
-
**The important caveat**: NVIDIA does not publish dense FP16/BF16 TFLOPS for B200. The official spec page only exposes FP4 and FP8 metrics. In tokcalc, B200's `flopsTflops` field is set to `null` with a "FP4/FP8 specs only; FP16 dense not publicly reported" note — the calculator uses a conservative fallback estimate.
|
|
112
|
-
|
|
113
|
-
This means B200's **decode throughput** estimate (110 tok/s for Llama 70B FP8) should be treated as an **inferred** value (🟡 confidence dot in tokcalc), not a measured one. If you have real B200 benchmark numbers, [submit them](https://github.com/stevecrates489-commits/tokcalc/issues/new?labels=benchmark+needed).
|
|
114
|
-
|
|
115
|
-
---
|
|
116
|
-
|
|
117
|
-
## Finding 4: 32K is the practical inflection point for single-GPU H100 serving
|
|
118
|
-
|
|
119
|
-
At 32K context, Llama 3.3 70B FP8 on a single H100:
|
|
120
|
-
|
|
121
|
-
```
|
|
122
|
-
model weights: 70.6 GB
|
|
123
|
-
KV cache (32K): 10.49 GB
|
|
124
|
-
total: 81.09 GB
|
|
125
|
-
available VRAM: 80 GB
|
|
126
|
-
→ does not fit (over by 1.09 GB)
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
32K is the wall. Below 32K, H100 can serve 1-7 concurrent users. Above 32K, H100 can't serve even 1 user at full context — you need tensor parallelism (TP×2+) or context parallelism (RingAttention).
|
|
130
|
-
|
|
131
|
-
**Practical implication**: If your RAG pipeline injects 30K tokens of document context, a single H100 can barely fit one request. You need:
|
|
132
|
-
- **TP×2** (2× H100, 160 GB total): 6 users at 32K
|
|
133
|
-
- **TP×4** (4× H100, 320 GB total): 23 users at 32K
|
|
134
|
-
- Or switch to **H200** (141 GB): 6 users at 32K on a single GPU
|
|
135
|
-
|
|
136
|
-
[Try 32K context on tokcalc →](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h100-sxm&q=fp8&n=1&b=1&p=32768&o=500)
|
|
137
|
-
|
|
138
|
-
---
|
|
139
|
-
|
|
140
|
-
## Finding 5: 4× RTX 4090 ≈ 1× H100 for throughput — but worse for concurrency and topology
|
|
141
|
-
|
|
142
|
-
| Metric | 4× RTX 4090 (PCIe) | 1× H100 (SXM) |
|
|
143
|
-
|---|---|---|
|
|
144
|
-
| Total VRAM | 96 GB | 80 GB |
|
|
145
|
-
| Aggregate HBM BW | 4032 GB/s | 3350 GB/s |
|
|
146
|
-
| TP efficiency (PCIe vs NVLink) | ~0.60 | 1.0 (single GPU) |
|
|
147
|
-
| Effective BW | ~2419 GB/s | 3350 GB/s |
|
|
148
|
-
| Decode tok/s (Llama 70B FP8) | ~51 tok/s | ~46 tok/s |
|
|
149
|
-
| Max users @ 4K | 19 | 7 |
|
|
150
|
-
| Max users @ 8K | 9 | 3 |
|
|
151
|
-
| Max users @ 16K | 4 | 1 |
|
|
152
|
-
| Max users @ 32K | 2 | 0 |
|
|
153
|
-
|
|
154
|
-
4× 4090 has more raw VRAM (96 vs 80 GB) and more raw bandwidth (4032 vs 3350 GB/s), so it can serve more concurrent users at short context. But PCIe interconnect means the TP efficiency drops to ~0.60 (vs 1.0 for a single H100) — so per-user throughput is comparable.
|
|
155
|
-
|
|
156
|
-
**The hidden cost**: 4× 4090 draws ~1800W total, requires a 2000W+ PSU, and the PCIe topology creates memory transfer bottlenecks that worsen at longer contexts. For production serving, H100's NVLink topology is dramatically better for multi-GPU workloads.
|
|
157
|
-
|
|
158
|
-
**Bottom line**: 4× 4090 is viable for local inference / hobbyist deployments. For production serving at scale, a single H100 or H200 is a better investment.
|
|
159
|
-
|
|
160
|
-
---
|
|
161
|
-
|
|
162
|
-
## Finding 6: 128K context requires H200 or B200 — and prefill latency becomes the bottleneck
|
|
163
|
-
|
|
164
|
-
At 128K context, Llama 3.3 70B FP8:
|
|
165
|
-
|
|
166
|
-
| GPU | VRAM | Fits? | Max users | Prefill time | Decode tok/s |
|
|
167
|
-
|---|---|---|---|---|---|
|
|
168
|
-
| H100 (80 GB) | 80 | ❌ (needs 112 GB) | 0 | — | — |
|
|
169
|
-
| H200 (141 GB) | 141 | ✅ | 1 | ~12s | 66 tok/s |
|
|
170
|
-
| B200 (192 GB) | 192 | ✅ | 2 | ~7s | 110 tok/s |
|
|
171
|
-
|
|
172
|
-
At 128K context, **prefill latency dominates the user experience**. Even on B200, it takes ~7 seconds to process a 128K-token prompt before the first output token appears. This is because prefill cost grows superlinearly with context length — the attention computation is O(N²) for dense attention:
|
|
173
|
-
|
|
174
|
-
```
|
|
175
|
-
prefill_flops = 2 × N × T + T² × kv_heads × head_dim × layers
|
|
176
|
-
= linear matmul cost + quadratic attention cost
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
At 128K tokens, the attention term (T²) dominates the linear term (T). For models with blockwise attention (RingAttention, FlashAttention-3), the real cost is lower than this estimate — but the fundamental scaling remains superlinear.
|
|
180
|
-
|
|
181
|
-
**Practical implication**: for 128K+ context, you need:
|
|
182
|
-
1. **VRAM**: H200 (141 GB) or B200 (192 GB) — H100 can't fit it
|
|
183
|
-
2. **Topology**: TP×2 minimum — 1 GPU can serve only 1-2 concurrent users
|
|
184
|
-
3. **Latency budget**: accept 5-15s prefill, or use chunked prefill + continuous batching
|
|
185
|
-
4. **Consider**: context parallel (RingAttention) if you need >2 concurrent users at 128K
|
|
186
|
-
|
|
187
|
-
[Try 128K context on tokcalc →](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h200-sxm&q=fp8&n=1&b=1&p=131072&o=500)
|
|
188
|
-
|
|
189
|
-
---
|
|
190
|
-
|
|
191
|
-
## The master chart
|
|
192
|
-
|
|
193
|
-
**Max concurrent users for Llama 3.3 70B FP8, by GPU and context length:**
|
|
194
|
-
|
|
195
|
-
```
|
|
196
|
-
Context 4×4090 H100 H200 B200
|
|
197
|
-
─────── ─────── ────── ────── ──────
|
|
198
|
-
4K 19 7 53 92
|
|
199
|
-
8K 9 3 26 46
|
|
200
|
-
16K 4 1 13 23
|
|
201
|
-
32K 2 0 6 11
|
|
202
|
-
64K 1 0 3 5
|
|
203
|
-
128K 0 0 1 2
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Context doubles → concurrency halves. This is the fundamental scaling law of LLM serving capacity.
|
|
207
|
-
|
|
208
|
-
---
|
|
209
|
-
|
|
210
|
-
## Methodology
|
|
211
|
-
|
|
212
|
-
All numbers are computed by [tokcalc](https://tokcalc.vercel.app) — an open-source LLM serving capacity planner (Apache 2.0). The formulas are:
|
|
213
|
-
|
|
214
|
-
1. **KV cache per request**: `2 × layers × kv_heads × head_dim × bytes_per_kv_value × context_length × batch_size`
|
|
215
|
-
2. **Max concurrency**: `floor((total_vram - model_weights) / kv_per_request)`
|
|
216
|
-
3. **Decode throughput**: `(HBM_BW × η_mem × quant_eff) / model_size` where `η_mem = 0.65` (real-world memory utilization)
|
|
217
|
-
4. **Prefill throughput**: `(FLOPS × η_compute) / (2 × active_params)` where `η_compute = 0.50`
|
|
218
|
-
5. **Long-context prefill correction**: adds O(N²) attention cost beyond 32K context: `attention_flops = T² × kv_heads × head_dim × layers`
|
|
219
|
-
|
|
220
|
-
**Confidence levels** (visible in tokcalc UI as colored dots):
|
|
221
|
-
- 🟢 **Measured**: GPU specs, model architecture fields, API pricing — sourced from official docs
|
|
222
|
-
- 🟢 **Modeled**: Throughput, latency, KV cache, cost — derived from physics-based formulas
|
|
223
|
-
- 🟡 **Inferred**: Continuous batching multiplier (1.5× default), long-context attention correction, B200 FP16 fallback — heuristics with known error bars
|
|
224
|
-
|
|
225
|
-
---
|
|
226
|
-
|
|
227
|
-
## Limitations
|
|
228
|
-
|
|
229
|
-
1. **No real benchmark data**: All throughput numbers are **modeled estimates**, not measured. Real-world performance depends on engine (vLLM, SGLang, TensorRT-LLM), model revision, driver version, kernel optimizations, and request mix. Expect 20-40% deviation from these estimates. The goal is ballpark capacity planning, not precise performance prediction.
|
|
230
|
-
|
|
231
|
-
2. **Continuous batching multiplier is a heuristic**: The 1.5× default is conservative. vLLM reported 14-24× vs HF Transformers and 2.2-2.5× vs TGI in their original benchmarks. The SOSP paper found 2-4× typical vs FasterTransformer/Orca. Real-world gains depend on request arrival distribution, prompt/output ratio, and scheduler configuration.
|
|
232
|
-
|
|
233
|
-
3. **Attention cost is approximate**: The O(N²) correction assumes dense attention. Real models use FlashAttention-3, blockwise attention, or RingAttention — which reduce the actual cost. For RingAttention specifically, the prefill time estimate is pessimistic.
|
|
234
|
-
|
|
235
|
-
4. **B200 FP16 dense FLOPS is unpublished**: NVIDIA only publishes FP4/FP8 specs for Blackwell. tokcalc uses a conservative fallback estimate — B200's decode throughput should be treated as 🟡 inferred, not measured.
|
|
236
|
-
|
|
237
|
-
5. **No engine-aware presets yet**: tokcalc currently models engine-agnostic formulas. vLLM, SGLang, and TensorRT-LLM each have different kernel optimizations, scheduler defaults, and quantization support that materially affect real-world numbers. Engine-aware presets are on the roadmap.
|
|
238
|
-
|
|
239
|
-
6. **MoE models**: DeepSeek V3 (671B total / 37B active) and Qwen 3 235B-A22B are MoE — tokcalc correctly uses active params for throughput but total params for VRAM. The KV cache formula is the same.
|
|
240
|
-
|
|
241
|
-
---
|
|
242
|
-
|
|
243
|
-
## Disclosure
|
|
244
|
-
|
|
245
|
-
I built and maintain tokcalc. I'm posting this analysis because I want feedback on the workload assumptions and benchmark methodology. tokcalc is Apache-2.0 and free to use. Every formula is in the [source code](https://github.com/stevecrates489-commits/tokcalc/blob/main/src/lib/token-calc.ts) — nothing is hidden.
|
|
246
|
-
|
|
247
|
-
If any of these numbers look wrong enough to be dangerous, please [open an issue](https://github.com/stevecrates489-commits/tokcalc/issues/new) with:
|
|
248
|
-
- Which GPU × model × context combination you tested
|
|
249
|
-
- What engine you used (vLLM, SGLang, TensorRT-LLM, llama.cpp)
|
|
250
|
-
- Your measured throughput / latency
|
|
251
|
-
- Your configuration flags
|
|
252
|
-
|
|
253
|
-
I'll calibrate the formulas against your data and credit you in the contributor list.
|
|
254
|
-
|
|
255
|
-
---
|
|
256
|
-
|
|
257
|
-
## Try it yourself
|
|
258
|
-
|
|
259
|
-
Every scenario in this article is a clickable link that opens tokcalc with the exact configuration pre-loaded:
|
|
260
|
-
|
|
261
|
-
- [Llama 3.3 70B FP8 on H100, 4K context](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h100-sxm&q=fp8&n=1&b=1&p=4096&o=500)
|
|
262
|
-
- [Llama 3.3 70B FP8 on H200, 32K context](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=h200-sxm&q=fp8&n=1&b=1&p=32768&o=500)
|
|
263
|
-
- [Llama 3.3 70B FP8 on B200, 128K context](https://tokcalc.vercel.app/#t=calculator&m=llama3-3-70b&g=b200-sxm&q=fp8&n=1&b=1&p=131072&o=500)
|
|
264
|
-
- [Build-vs-Buy: Llama 3.3 70B self-host vs GPT-4o API](https://tokcalc.vercel.app/#t=build-vs-buy&sm=llama3-3-70b&sg=h200-sxm&sq=fp8&sn=1&u=50&b=8&i=5000&o=500&r=10000&ap=openai&am=gpt-4o)
|
|
265
|
-
|
|
266
|
-
Change any input and the URL updates live. Share the URL with colleagues — they'll see your exact scenario.
|
|
267
|
-
|
|
268
|
-
---
|
|
269
|
-
|
|
270
|
-
## References
|
|
271
|
-
|
|
272
|
-
- **PagedAttention / vLLM paper**: Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention", SOSP 2023. [arxiv.org/abs/2309.06180](https://arxiv.org/abs/2309.06180)
|
|
273
|
-
- **Continuous batching**: "Continuous Batching for LLM Inference", Anyscale, 2023. [anyscale.com/blog/continuous-batching-llm-inference](https://www.anyscale.com/blog/continuous-batching-llm-inference)
|
|
274
|
-
- **RingAttention**: Liu et al., "Ring Attention with Blockwise Attention for Near-Infinite Context", 2023. [arxiv.org/abs/2310.01889](https://arxiv.org/abs/2310.01889)
|
|
275
|
-
- **H100 specs**: [nvidia.com/en-us/data-center/h100](https://www.nvidia.com/en-us/data-center/h100)
|
|
276
|
-
- **H200 specs**: [nvidia.com/en-us/data-center/h200](https://www.nvidia.com/en-us/data-center/h200)
|
|
277
|
-
- **B200 specs**: [nvidia.com/en-us/data-center/blackwell-architecture](https://www.nvidia.com/en-us/data-center/blackwell-architecture) (FP4/FP8 only; FP16 dense not published)
|
|
278
|
-
- **Llama 3.3 70B config**: [huggingface.co/meta-llama/Llama-3.3-70B-Instruct](https://huggingface.co/meta-llama/Llama-3.3-70B-Instruct)
|
|
279
|
-
- **FP8 on H100**: "FP8 Quantization: The Power of the Tensor Float 32 Data Format", NVIDIA, 2024.
|
|
280
|
-
|
|
281
|
-
---
|
|
282
|
-
|
|
283
|
-
*tokcalc is open-source (Apache 2.0). If you found this analysis useful, [star the repo](https://github.com/stevecrates489-commits/tokcalc) and share it with someone planning an LLM deployment.*
|