Update setup_deepseek_v4_sm89.sh
Browse files- setup_deepseek_v4_sm89.sh +144 -12
setup_deepseek_v4_sm89.sh
CHANGED
|
@@ -63,12 +63,38 @@ SERVED_NAME="DeepSeek-V4-Flash" # name exposed on the OpenAI-compatible API
|
|
| 63 |
PORT=8002
|
| 64 |
GPUS="0,1,2,3"
|
| 65 |
TP_SIZE=4
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
NUM_SPEC_TOKENS=1 # MTP draft depth (1 = validated config)
|
| 71 |
-
REASONING_EFFORT="
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
SERVICE_USER="$(id -un)" # user baked into the generated systemd unit
|
| 73 |
|
| 74 |
PYTHON_BIN="python3.12" # venv interpreter (wheel is cp312)
|
|
@@ -344,11 +370,35 @@ cat > "$PROJECT_DIR/serve.sh" <<SERVE_EOF
|
|
| 344 |
# letting hub ref resolution run under HF_HUB_OFFLINE: an interrupted
|
| 345 |
# online check can leave refs/main pointing at a snapshot that was never
|
| 346 |
# downloaded (LocalEntryNotFoundError at boot).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
# * MTP measured at 115-133 tok/s (~1.5-1.65x over the ~80 tok/s baseline),
|
| 348 |
# 96.6-100% draft acceptance. To run WITHOUT speculative decoding, delete
|
| 349 |
# the --speculative-config line (and optionally --max-num-batched-tokens).
|
| 350 |
-
# * First requests JIT-compile
|
| 351 |
-
# spikes)
|
|
|
|
| 352 |
set -euo pipefail
|
| 353 |
cd "\$(dirname "\$0")"
|
| 354 |
|
|
@@ -396,6 +446,7 @@ exec env -u VLLM_TRITON_MLA_SPARSE_HEAD_BLOCK_SIZE \\
|
|
| 396 |
PATH="$CUDA_HOME_DIR/bin:\$PATH" \\
|
| 397 |
FLASHINFER_DISABLE_VERSION_CHECK=1 \\
|
| 398 |
VLLM_USE_FLASHINFER_SAMPLER=0 \\
|
|
|
|
| 399 |
HF_HUB_OFFLINE=1 \\
|
| 400 |
CUDA_VISIBLE_DEVICES=$GPUS \\
|
| 401 |
"\$(dirname "\$0")/.venv/bin/vllm" serve "\$MODEL_PATH" \\
|
|
@@ -429,6 +480,69 @@ echo
|
|
| 429 |
CANARY_EOF
|
| 430 |
chmod +x "$PROJECT_DIR/canary.sh"
|
| 431 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 432 |
# ----------------------------- GENERATE systemd unit -------------------------
|
| 433 |
log "Writing $PROJECT_DIR/deepseek-v4-flash.service"
|
| 434 |
cat > "$PROJECT_DIR/deepseek-v4-flash.service" <<UNIT_EOF
|
|
@@ -438,6 +552,13 @@ cat > "$PROJECT_DIR/deepseek-v4-flash.service" <<UNIT_EOF
|
|
| 438 |
# supervises it. systemd's clean service environment additionally guarantees
|
| 439 |
# no stale shell-profile exports can leak in.
|
| 440 |
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
# Install:
|
| 442 |
# sudo cp $PROJECT_DIR/deepseek-v4-flash.service /etc/systemd/system/
|
| 443 |
# sudo systemctl daemon-reload
|
|
@@ -466,7 +587,16 @@ ExecStartPre=/usr/bin/nvidia-smi
|
|
| 466 |
|
| 467 |
ExecStart=$PROJECT_DIR/serve.sh
|
| 468 |
|
| 469 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 470 |
# Weight load alone is ~80 s (+ ~5 s MTP drafter); don't hammer restarts.
|
| 471 |
RestartSec=15
|
| 472 |
# Give the engine time to tear down 4 TP workers and NCCL cleanly on stop.
|
|
@@ -491,13 +621,15 @@ Next steps:
|
|
| 491 |
run once:
|
| 492 |
$PROJECT_DIR/.venv/bin/huggingface-cli download "$MODEL"
|
| 493 |
2. Start the server: $PROJECT_DIR/serve.sh
|
| 494 |
-
Expect ~80 s of weight loading (+ ~5 s for the MTP drafter),
|
| 495 |
-
Triton JIT compiles during warmup and the first requests, then
|
| 496 |
'Application startup complete'.
|
| 497 |
-
3.
|
|
|
|
|
|
|
|
|
|
| 498 |
A correct one-sentence answer naming Muenchen = numerics are sound.
|
| 499 |
Watch the SpecDecoding metrics lines: draft acceptance should sit
|
| 500 |
around 95-100% with mean acceptance length near 2.00.
|
| 501 |
-
|
| 502 |
$PROJECT_DIR/deepseek-v4-flash.service
|
| 503 |
NEXT_EOF
|
|
|
|
| 63 |
PORT=8002
|
| 64 |
GPUS="0,1,2,3"
|
| 65 |
TP_SIZE=4
|
| 66 |
+
# MEMORY/THROUGHPUT: validated corner from a day of SWE-bench Verified pilots
|
| 67 |
+
# (2026-07-23). KV is the thin slice on this model -- weights + activations +
|
| 68 |
+
# cudagraph pools eat almost the whole 48 GB card. The values below trade
|
| 69 |
+
# against each other; see the generated serve.sh header for the full story.
|
| 70 |
+
# Short version:
|
| 71 |
+
# * util 0.97 leaves ~1.68 GiB/GPU free at idle, ~0.5-0.8 GiB under a long
|
| 72 |
+
# trajectory -- THIN. It is above the Triton JIT-OOM line only because
|
| 73 |
+
# warmup.sh pre-compiles the kernel shapes at startup and the systemd unit
|
| 74 |
+
# auto-restarts+re-warms. For an UNATTENDED or shared deployment, prefer
|
| 75 |
+
# GPU_MEM_UTIL=0.95 (restores ~2.4 GiB headroom; identical throughput at
|
| 76 |
+
# seq=1). See serve.sh header.
|
| 77 |
+
# * max-model-len 98304 fits KV and covers agentic trajectories (~30-100k);
|
| 78 |
+
# 262144 will NOT boot (KV-fit ValueError). Prompt + max_tokens must be
|
| 79 |
+
# <= this, so keep the harness's requested output tokens modest.
|
| 80 |
+
# * max-num-seqs 1: at seq>1, multiple 30-100k trajectories don't fit KV at
|
| 81 |
+
# once and vLLM preempts + RE-PREFILLS from scratch (prefix-cache hit
|
| 82 |
+
# collapses <40%, ~10x wall-clock blowup). One resident stream (prefix hit
|
| 83 |
+
# ~85-91%) wins decisively here.
|
| 84 |
+
# * batched-tokens 4096: raising it toggles chunked prefill, whose buffers
|
| 85 |
+
# come out of the KV pool -- not free on this card.
|
| 86 |
+
# Pilot results (50-instance SWE-bench Verified, error_instances=0):
|
| 87 |
+
# 64k/8192-out=56% | 96k/8192-out=60% (best) | 96k/high-effort=60% (no gain)
|
| 88 |
+
MAX_MODEL_LEN=98304
|
| 89 |
+
GPU_MEM_UTIL=0.97
|
| 90 |
+
MAX_NUM_SEQS=1
|
| 91 |
+
MAX_NUM_BATCHED_TOKENS=4096 # 4096 tested; raising toggles chunked prefill (eats KV)
|
| 92 |
NUM_SPEC_TOKENS=1 # MTP draft depth (1 = validated config)
|
| 93 |
+
REASONING_EFFORT="medium" # default chat-template reasoning effort.
|
| 94 |
+
# high effort showed NO resolved-rate gain over
|
| 95 |
+
# medium on the pilots (60% both) at ~40% more
|
| 96 |
+
# wall-clock and more step-limit failures.
|
| 97 |
+
WARMUP_WAIT_MAX=300 # warmup.sh: seconds to wait for the API before giving up
|
| 98 |
SERVICE_USER="$(id -un)" # user baked into the generated systemd unit
|
| 99 |
|
| 100 |
PYTHON_BIN="python3.12" # venv interpreter (wheel is cp312)
|
|
|
|
| 370 |
# letting hub ref resolution run under HF_HUB_OFFLINE: an interrupted
|
| 371 |
# online check can leave refs/main pointing at a snapshot that was never
|
| 372 |
# downloaded (LocalEntryNotFoundError at boot).
|
| 373 |
+
# * MEMORY IS THE KNIFE-EDGE on this model (incident 2026-07-23). KV is the
|
| 374 |
+
# thin slice after weights/activations/cudagraphs; two failure modes bound
|
| 375 |
+
# the tuning window:
|
| 376 |
+
# - JIT-OOM (too little headroom): Triton compiles a kernel per prompt
|
| 377 |
+
# shape and loading the cubin (cuModuleLoadData) needs driver-level free
|
| 378 |
+
# VRAM outside the torch pool. At high util a never-seen shape mid-run
|
| 379 |
+
# OOM'd the load in _tf32_hc_prenorm_gemm and killed a worker, taking a
|
| 380 |
+
# 2h benchmark down. Mitigated by warmup.sh (pre-compiles shapes at
|
| 381 |
+
# startup) + systemd auto-restart+re-warm.
|
| 382 |
+
# - KV-fit refusal (too little KV): boot fails if one full max-model-len
|
| 383 |
+
# sequence won't fit the pool (262144 needs ~1.84 GiB/GPU -> NO BOOT).
|
| 384 |
+
# Current values: util 0.97, max-model-len 98304, max-num-seqs 1. MEASURED
|
| 385 |
+
# ~1.68 GiB/GPU free at idle, ~0.5-0.8 GiB under a long trajectory -- thin.
|
| 386 |
+
# For UNATTENDED/shared serving, drop util to 0.95 (restores ~2.4 GiB
|
| 387 |
+
# headroom, same throughput at seq=1). Do NOT raise max-model-len toward
|
| 388 |
+
# 262k without lowering util -- that reopens the JIT-OOM hole.
|
| 389 |
+
# * max-num-seqs 1: at seq>1 the KV pool can't hold multiple 30-100k agentic
|
| 390 |
+
# trajectories, so vLLM preempts and RE-PREFILLS evicted sequences (prefix
|
| 391 |
+
# hit <40%, ~10x wall-clock blowup). seq=1 keeps one context resident
|
| 392 |
+
# (prefix hit ~85-91%) decoding continuously; it wins decisively here.
|
| 393 |
+
# * PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True keeps the torch pool from
|
| 394 |
+
# overgrowing past its profiled peak on long prefills and eating the JIT
|
| 395 |
+
# headroom. (If graph capture ever rejects it on a future wheel, drop it.)
|
| 396 |
# * MTP measured at 115-133 tok/s (~1.5-1.65x over the ~80 tok/s baseline),
|
| 397 |
# 96.6-100% draft acceptance. To run WITHOUT speculative decoding, delete
|
| 398 |
# the --speculative-config line (and optionally --max-num-batched-tokens).
|
| 399 |
+
# * First requests JIT-compile Triton kernels per shape (one-time latency
|
| 400 |
+
# spikes). Run ./warmup.sh once after every server start so the shapes
|
| 401 |
+
# compile at minute 0, not minute 100 of an unattended run.
|
| 402 |
set -euo pipefail
|
| 403 |
cd "\$(dirname "\$0")"
|
| 404 |
|
|
|
|
| 446 |
PATH="$CUDA_HOME_DIR/bin:\$PATH" \\
|
| 447 |
FLASHINFER_DISABLE_VERSION_CHECK=1 \\
|
| 448 |
VLLM_USE_FLASHINFER_SAMPLER=0 \\
|
| 449 |
+
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True \\
|
| 450 |
HF_HUB_OFFLINE=1 \\
|
| 451 |
CUDA_VISIBLE_DEVICES=$GPUS \\
|
| 452 |
"\$(dirname "\$0")/.venv/bin/vllm" serve "\$MODEL_PATH" \\
|
|
|
|
| 480 |
CANARY_EOF
|
| 481 |
chmod +x "$PROJECT_DIR/canary.sh"
|
| 482 |
|
| 483 |
+
# ----------------------------- GENERATE warmup.sh ----------------------------
|
| 484 |
+
# Forces the Triton per-shape specializations to JIT-compile at startup, in
|
| 485 |
+
# fresh VRAM headroom and under supervision, instead of lazily mid-run into a
|
| 486 |
+
# thin pool (incident 2026-07-23). Wired into the systemd unit's ExecStartPost.
|
| 487 |
+
log "Writing $PROJECT_DIR/warmup.sh"
|
| 488 |
+
cat > "$PROJECT_DIR/warmup.sh" <<WARMUP_EOF
|
| 489 |
+
#!/usr/bin/env bash
|
| 490 |
+
# Triton-specialization warmup for the DeepSeek-V4-Flash server.
|
| 491 |
+
# -- generated by setup_deepseek_v4_sm89.sh
|
| 492 |
+
#
|
| 493 |
+
# The wait-for-server loop is BOUNDED (WAIT_MAX, default ${WARMUP_WAIT_MAX}s):
|
| 494 |
+
# as ExecStartPost, an unbounded loop would pin the unit in
|
| 495 |
+
# 'activating (start-post)' with a dead main PID if the engine dies during
|
| 496 |
+
# init. Bail fast so systemd can fail and Restart= act.
|
| 497 |
+
#
|
| 498 |
+
# Usage: ./warmup.sh [host] [port] (defaults: 127.0.0.1 $PORT)
|
| 499 |
+
# WAIT_MAX=<seconds> overrides the server-wait bound.
|
| 500 |
+
set -euo pipefail
|
| 501 |
+
HOST="\${1:-127.0.0.1}"
|
| 502 |
+
PORT="\${2:-$PORT}"
|
| 503 |
+
URL="http://\$HOST:\$PORT/v1/chat/completions"
|
| 504 |
+
MODEL="$SERVED_NAME"
|
| 505 |
+
WAIT_MAX="\${WAIT_MAX:-$WARMUP_WAIT_MAX}"
|
| 506 |
+
MAXLEN="$MAX_MODEL_LEN"
|
| 507 |
+
|
| 508 |
+
echo "==> waiting for server at \$HOST:\$PORT (up to \${WAIT_MAX}s) ..."
|
| 509 |
+
deadline=\$(( \$(date +%s) + WAIT_MAX ))
|
| 510 |
+
until curl -sf "http://\$HOST:\$PORT/v1/models" >/dev/null 2>&1; do
|
| 511 |
+
if [ "\$(date +%s)" -ge "\$deadline" ]; then
|
| 512 |
+
echo "XX server not reachable after \${WAIT_MAX}s -- engine likely died during init; giving up" >&2
|
| 513 |
+
exit 1
|
| 514 |
+
fi
|
| 515 |
+
sleep 5
|
| 516 |
+
done
|
| 517 |
+
echo "==> server is up"
|
| 518 |
+
|
| 519 |
+
req() { # req <approx_token_count>
|
| 520 |
+
local n="\$1"
|
| 521 |
+
local prompt payload
|
| 522 |
+
prompt="\$(yes 'the' | head -n "\$n" | tr '\n' ' ')"
|
| 523 |
+
# Build JSON via printf to avoid nested-quote escaping hazards.
|
| 524 |
+
payload="\$(printf '{"model":"%s","messages":[{"role":"user","content":"%s"}],"max_tokens":16,"temperature":0}' "\$MODEL" "\$prompt")"
|
| 525 |
+
curl -s -o /dev/null -w " HTTP %{http_code} in %{time_total}s (n=\$n)\n" \\
|
| 526 |
+
"\$URL" -H 'Content-Type: application/json' --data "\$payload"
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
# Sweep shapes up to (but not over) --max-model-len; an over-cap request just
|
| 530 |
+
# 400s and compiles nothing useful. Top entry sits just under MAXLEN.
|
| 531 |
+
echo "==> sequential shape sweep (long ones take a while; that is the point)"
|
| 532 |
+
top=\$(( MAXLEN > 4096 ? MAXLEN - 4096 : MAXLEN / 2 ))
|
| 533 |
+
for n in 16 100 511 1024 2000 4096 8192 16384 32768 65536 "\$top"; do
|
| 534 |
+
[ "\$n" -le "\$MAXLEN" ] && req "\$n"
|
| 535 |
+
done
|
| 536 |
+
|
| 537 |
+
echo "==> warmup done. Driver-level headroom per GPU (want >= ~1 GiB free):"
|
| 538 |
+
if command -v nvidia-smi >/dev/null 2>&1; then
|
| 539 |
+
nvidia-smi --query-gpu=index,memory.used,memory.total --format=csv
|
| 540 |
+
else
|
| 541 |
+
echo " (nvidia-smi not on PATH here; check on the host)"
|
| 542 |
+
fi
|
| 543 |
+
WARMUP_EOF
|
| 544 |
+
chmod +x "$PROJECT_DIR/warmup.sh"
|
| 545 |
+
|
| 546 |
# ----------------------------- GENERATE systemd unit -------------------------
|
| 547 |
log "Writing $PROJECT_DIR/deepseek-v4-flash.service"
|
| 548 |
cat > "$PROJECT_DIR/deepseek-v4-flash.service" <<UNIT_EOF
|
|
|
|
| 552 |
# supervises it. systemd's clean service environment additionally guarantees
|
| 553 |
# no stale shell-profile exports can leak in.
|
| 554 |
#
|
| 555 |
+
# Incident 2026-07-23: a Triton JIT module load OOM'd and killed a worker
|
| 556 |
+
# mid-run; the API server then exited *cleanly* (main PID rc 0), so the old
|
| 557 |
+
# Restart=on-failure never fired and the port stayed dead. Hence Restart=always
|
| 558 |
+
# below -- for an inference server a crash that looks like a clean exit is
|
| 559 |
+
# still a crash; only 'systemctl stop' should keep it down. ExecStartPost runs
|
| 560 |
+
# warmup.sh after every (re)start so Triton specializations compile up front.
|
| 561 |
+
#
|
| 562 |
# Install:
|
| 563 |
# sudo cp $PROJECT_DIR/deepseek-v4-flash.service /etc/systemd/system/
|
| 564 |
# sudo systemctl daemon-reload
|
|
|
|
| 587 |
|
| 588 |
ExecStart=$PROJECT_DIR/serve.sh
|
| 589 |
|
| 590 |
+
# Warm the Triton shape specializations after EVERY start (incl. auto-restart).
|
| 591 |
+
# warmup.sh waits for the API itself and is bounded, so it cannot hang the
|
| 592 |
+
# unit; '-' prefix + timeout mean a wedged warmup never fails the start.
|
| 593 |
+
ExecStartPost=-/usr/bin/timeout 900 $PROJECT_DIR/warmup.sh
|
| 594 |
+
# Budget must cover ExecStartPre + weight load + bounded warmup.
|
| 595 |
+
TimeoutStartSec=1200
|
| 596 |
+
|
| 597 |
+
# Restart=always, NOT on-failure: a JIT-OOM crash can present as a clean exit
|
| 598 |
+
# (see incident note above); only an explicit 'systemctl stop' should end it.
|
| 599 |
+
Restart=always
|
| 600 |
# Weight load alone is ~80 s (+ ~5 s MTP drafter); don't hammer restarts.
|
| 601 |
RestartSec=15
|
| 602 |
# Give the engine time to tear down 4 TP workers and NCCL cleanly on stop.
|
|
|
|
| 621 |
run once:
|
| 622 |
$PROJECT_DIR/.venv/bin/huggingface-cli download "$MODEL"
|
| 623 |
2. Start the server: $PROJECT_DIR/serve.sh
|
| 624 |
+
Expect ~80 s of weight loading (+ ~5 s for the MTP drafter), then
|
|
|
|
| 625 |
'Application startup complete'.
|
| 626 |
+
3. Warm up (once/start): $PROJECT_DIR/warmup.sh
|
| 627 |
+
Compiles Triton shapes up front and prints free VRAM/GPU (want >= ~1 GiB).
|
| 628 |
+
Runs automatically via ExecStartPost under the systemd unit.
|
| 629 |
+
4. Smoke test: $PROJECT_DIR/canary.sh
|
| 630 |
A correct one-sentence answer naming Muenchen = numerics are sound.
|
| 631 |
Watch the SpecDecoding metrics lines: draft acceptance should sit
|
| 632 |
around 95-100% with mean acceptance length near 2.00.
|
| 633 |
+
5. Run as a service: see header of
|
| 634 |
$PROJECT_DIR/deepseek-v4-flash.service
|
| 635 |
NEXT_EOF
|