-
Notifications
You must be signed in to change notification settings - Fork 257
Refresh DeepSeek-V4 B200 SGLang AgentX MTP #2578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8b73f94
f77282c
fd8b28d
7f66a19
9914a1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,272 @@ | ||
| #!/usr/bin/env bash | ||
| set -eo pipefail | ||
| set -x | ||
|
|
||
| # Agentic trace replay for DeepSeek-V4-Pro FP4 on B200 with native EAGLE MTP. | ||
| # Throughput uses the committed golden synthetic AL; eval retains real target | ||
| # verification. | ||
| # | ||
| # KV_OFFLOADING=dram requires KV_OFFLOAD_BACKEND=hicache. | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| INFERENCEX_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" | ||
| export INFMAX_CONTAINER_WORKSPACE="${INFMAX_CONTAINER_WORKSPACE:-/workspace}" | ||
|
|
||
| # The B200 DeepSeek-V4 Blackwell image installs SGLang editable under | ||
| # /workspace, so its launcher mounts InferenceX at /ix instead. Resolve the | ||
| # agentic tooling and results against the actual repository mount so the image | ||
| # can keep its /workspace install and GitHub Actions can collect the outputs. | ||
| if [[ ! -d "$INFMAX_CONTAINER_WORKSPACE/utils/aiperf" ]]; then | ||
| export INFMAX_CONTAINER_WORKSPACE="$INFERENCEX_ROOT" | ||
| fi | ||
| if [[ "${RESULT_DIR:-}" == /workspace/* && "$INFMAX_CONTAINER_WORKSPACE" != /workspace ]]; then | ||
| export RESULT_DIR="$INFMAX_CONTAINER_WORKSPACE/${RESULT_DIR#/workspace/}" | ||
| fi | ||
| source "$INFERENCEX_ROOT/benchmarks/benchmark_lib.sh" | ||
|
|
||
| export AIPERF_REQUIRED_SERVER_METRIC_PREFIX="sglang:" | ||
|
|
||
| check_env_vars MODEL TP CONC KV_OFFLOADING TOTAL_CPU_DRAM_GB RESULT_DIR DURATION EP_SIZE DP_ATTENTION | ||
|
|
||
| if [[ -n "${SLURM_JOB_ID:-}" ]]; then | ||
| echo "JOB $SLURM_JOB_ID running on ${SLURMD_NODENAME:-unknown}" | ||
| fi | ||
|
|
||
| if [[ -n "${MODEL_PATH:-}" ]]; then | ||
| if [[ ! -d "$MODEL_PATH" || -z "$(ls -A "$MODEL_PATH" 2>/dev/null)" ]]; then | ||
| hf download "$MODEL" --local-dir "$MODEL_PATH" | ||
| fi | ||
| else | ||
| hf download "$MODEL" | ||
| export MODEL_PATH="$MODEL" | ||
| fi | ||
| nvidia-smi | ||
|
|
||
| resolve_trace_source | ||
|
|
||
| # Keep AIPerf's Transformers-main dependency from replacing the older | ||
| # Transformers build pinned by the B200-specialized SGLang image. The server | ||
| # always launches with the image's original interpreter; AIPerf and result | ||
| # processing use the isolated environment when InferenceX is mounted at /ix. | ||
| SGLANG_PYTHON="$(command -v python3)" | ||
| if [[ "$INFMAX_CONTAINER_WORKSPACE" != /workspace ]]; then | ||
| AGENTIC_VENV="${AGENTIC_VENV:-/tmp/inferencex-agentic-venv}" | ||
| "$SGLANG_PYTHON" -m venv "$AGENTIC_VENV" | ||
| export PATH="$AGENTIC_VENV/bin:$PATH" | ||
| fi | ||
| install_agentic_deps | ||
|
|
||
| SERVER_LOG="$RESULT_DIR/server.log" | ||
| mkdir -p "$RESULT_DIR" | ||
|
|
||
| export SGLANG_ENABLE_UNIFIED_RADIX_TREE=1 | ||
| export SGLANG_OPT_UNIFIED_CACHE_FREE_OUT_OF_WINDOW_SLOTS=1 | ||
|
|
||
| CACHE_ARGS=() | ||
| if require_agentic_kv_offload_backend hicache; then | ||
| # DeepSeek V4 HiCache currently rejects --hicache-size and supports | ||
| # capacity control only through a host/device token-capacity ratio. | ||
| # DSv4 exposes capacity as a host/device token ratio rather than bytes. | ||
| # B200 ratio=8 stays below the configured host-memory capacity for the | ||
| # currently supported TP8 shape. | ||
| DEFAULT_HICACHE_RATIO=8 | ||
| HICACHE_RATIO="${HICACHE_RATIO:-$DEFAULT_HICACHE_RATIO}" | ||
| if [ "$HICACHE_RATIO" -gt "$DEFAULT_HICACHE_RATIO" ]; then | ||
| echo "Error: HICACHE_RATIO=$HICACHE_RATIO exceeds configured limit $DEFAULT_HICACHE_RATIO" >&2 | ||
| exit 1 | ||
| fi | ||
| HICACHE_WRITE_POLICY="${HICACHE_WRITE_POLICY:-write_through}" | ||
| HICACHE_IO_BACKEND="${HICACHE_IO_BACKEND:-direct}" | ||
| HICACHE_MEM_LAYOUT="${HICACHE_MEM_LAYOUT:-page_first_direct}" | ||
| CACHE_ARGS=( | ||
| --enable-hierarchical-cache | ||
| --hicache-ratio "$HICACHE_RATIO" | ||
| --hicache-write-policy "$HICACHE_WRITE_POLICY" | ||
| --hicache-io-backend "$HICACHE_IO_BACKEND" | ||
| --hicache-mem-layout "$HICACHE_MEM_LAYOUT" | ||
| ) | ||
| echo "HiCache DSv4 CPU tier: ratio=$HICACHE_RATIO, capacity=${TOTAL_CPU_DRAM_GB} GB, write_policy=$HICACHE_WRITE_POLICY, io_backend=$HICACHE_IO_BACKEND, mem_layout=$HICACHE_MEM_LAYOUT" | ||
| fi | ||
|
|
||
| USE_SGLANG_ROUTER=false | ||
| SGLANG_BACKEND_PORT="$PORT" | ||
| ROUTER_LOG="$RESULT_DIR/router.log" | ||
| if [ "$DP_ATTENTION" = "true" ]; then | ||
| USE_SGLANG_ROUTER=true | ||
| export AIPERF_HTTP_X_SMG_ROUTING_KEY_FROM_CORRELATION_ID=true | ||
| SGLANG_BACKEND_PORT=$((PORT + 1)) | ||
| SGLANG_ROUTER_METRICS_PORT=$((PORT + 10000)) | ||
| SGLANG_ROUTER_CMD=("$SGLANG_PYTHON" -m sglang_router.launch_router) | ||
| fi | ||
|
|
||
| PARALLEL_ARGS=(--tp "$TP") | ||
| METRICS_ARGS=(--enable-metrics --enable-cache-report) | ||
| CHUNKED_PREFILL_SIZE=8192 | ||
| if [ "$DP_ATTENTION" = "true" ]; then | ||
| DEEPEP_CONFIG='{"normal_dispatch":{"num_sms":96},"normal_combine":{"num_sms":96}}' | ||
| export SGLANG_OPT_USE_DEEPGEMM_MEGA_MOE=1 | ||
| export SGLANG_OPT_FIX_HASH_MEGA_MOE=1 | ||
| export SGLANG_OPT_USE_FAST_MASK_EP=1 | ||
| export SGLANG_OPT_FIX_MEGA_MOE_MEMORY=1 | ||
| export SGLANG_OPT_DEEPGEMM_MEGA_MOE_NUM_MAX_TOKENS_PER_RANK=4096 | ||
| export SGLANG_OPT_FIX_NEXTN_MEGA_MOE=1 | ||
| export SGLANG_DEEPEP_NUM_MAX_DISPATCH_TOKENS_PER_RANK=0 | ||
| PARALLEL_ARGS+=( | ||
| --dp "$TP" | ||
| --tokenizer-worker-num "$TP" | ||
| --enable-dp-attention | ||
| --enable-dp-attention-local-control-broadcast | ||
| --incremental-streaming-output | ||
| --stream-interval 20 | ||
| --dist-init-addr "127.0.0.1:$((PORT + 2000))" | ||
| --ep-size "$EP_SIZE" | ||
| --moe-a2a-backend deepep | ||
| --deepep-config "$DEEPEP_CONFIG" | ||
| ) | ||
| CHUNKED_PREFILL_SIZE=32768 | ||
| else | ||
| PARALLEL_ARGS+=( | ||
| --moe-runner-backend flashinfer_mxfp4 | ||
| --disable-flashinfer-autotune | ||
| ) | ||
| fi | ||
|
|
||
| MODEL_ARGS=() | ||
| # The B200-specialized image deadlocks immediately after weight loading when | ||
| # forced through the B300 compressed-attention/page-size overrides. | ||
| # DeepGEMM's DSv4 indexer needs a multi-GiB temporary allocation at long | ||
| # contexts. Leave the same HBM headroom used by the B300 recipe so a nearly | ||
| # full GPU KV cache does not OOM while HiCache is spilling to host memory. | ||
| MEM_FRACTION_STATIC=0.88 | ||
|
|
||
| # AgentX concurrency counts live session trees, not individual requests. | ||
| # Allow subagent fan-out to exceed CONC without clipping request bursts. | ||
| MAX_RUNNING_REQUESTS=$((2 * CONC)) | ||
| CUDA_GRAPH_MAX_BS=$CONC | ||
| [ "$CUDA_GRAPH_MAX_BS" -gt 64 ] && CUDA_GRAPH_MAX_BS=64 | ||
|
|
||
| export PYTHONNOUSERSITE=1 | ||
| export TORCH_CUDA_ARCH_LIST=10.0 | ||
| # Agentic warmup dispatches hundreds of large prompts at once. SGLang's | ||
| # tokenizer process can leave request bytes unacknowledged for longer than | ||
| # AIPerf's 30-second TCP_USER_TIMEOUT while it admits that initial burst, | ||
| # causing Linux to abort otherwise-live localhost connections. Keep the | ||
| # six-hour request timeout unchanged, but allow up to 15 minutes for TCP | ||
| # progress before declaring the connection dead. | ||
| export AIPERF_HTTP_TCP_USER_TIMEOUT=900000 | ||
| # Outlast AIPerf's pooled connections so an inter-turn idle gap cannot race | ||
| # Uvicorn's five-second keep-alive closure. | ||
| export SGLANG_TIMEOUT_KEEP_ALIVE=900 | ||
| export SGLANG_JIT_DEEPGEMM_FAST_WARMUP=1 | ||
| export SGLANG_OPT_SWA_SPLIT_LEAF_ON_INSERT=1 | ||
| export SGLANG_OPT_USE_JIT_NORM=1 | ||
| export SGLANG_OPT_USE_JIT_INDEXER_METADATA=1 | ||
| export SGLANG_OPT_USE_TOPK_V2=1 | ||
| export SGLANG_OPT_USE_CUSTOM_ALL_REDUCE_V2=1 | ||
| if [ "${EVAL_ONLY}" != "true" ]; then | ||
| export SGLANG_SIMULATE_ACC_LEN=2.49 | ||
| export SGLANG_SIMULATE_ACC_METHOD=match-expected | ||
| export SGLANG_SIMULATE_ACC_TOKEN_MODE=real-draft-token | ||
| fi | ||
| TRITON_PTXAS_PATH=$(find \ | ||
| /usr/local/cuda* \ | ||
| /usr/local/lib/python*/dist-packages/nvidia \ | ||
| /usr/local/lib/python*/site-packages/nvidia \ | ||
| -type f -name ptxas -perm -u+x -print -quit 2>/dev/null || true) | ||
| if [ -n "$TRITON_PTXAS_PATH" ]; then | ||
| export TRITON_PTXAS_PATH | ||
| echo "Using ptxas for Triton: $TRITON_PTXAS_PATH" | ||
| fi | ||
| SGLANG_CMD=( | ||
| "$SGLANG_PYTHON" -m sglang.launch_server | ||
| --model-path "$MODEL_PATH" | ||
| --served-model-name "$MODEL" | ||
| --host 0.0.0.0 | ||
| --port "$SGLANG_BACKEND_PORT" | ||
| --trust-remote-code | ||
| "${PARALLEL_ARGS[@]}" | ||
| --mem-fraction-static "$MEM_FRACTION_STATIC" | ||
| --swa-full-tokens-ratio 0.1 | ||
| --max-running-requests "$MAX_RUNNING_REQUESTS" | ||
| --cuda-graph-max-bs "$CUDA_GRAPH_MAX_BS" | ||
| --chunked-prefill-size "$CHUNKED_PREFILL_SIZE" | ||
| --tool-call-parser deepseekv4 | ||
| --reasoning-parser deepseek-v4 | ||
| --chat-template "$SCRIPT_DIR/../chat_templates/deepseek_v4_thinking.jinja" | ||
| --watchdog-timeout 1800 | ||
| --speculative-algorithm EAGLE | ||
| --speculative-num-steps 3 | ||
| --speculative-eagle-topk 1 | ||
| --speculative-num-draft-tokens 4 | ||
| # The B200 checkpoint lives on Lustre. Partition sequential prefetching | ||
| # across local ranks so post-load weight repacking reads from page cache | ||
| # instead of issuing redundant fragmented mmap faults from every rank. | ||
| --weight-loader-prefetch-checkpoints | ||
| "${MODEL_ARGS[@]}" | ||
| "${METRICS_ARGS[@]}" | ||
| "${CACHE_ARGS[@]}" | ||
| ) | ||
|
|
||
| write_command "$RESULT_DIR/sglang_command.txt" "${SGLANG_CMD[@]}" | ||
|
|
||
| { | ||
| echo "=== SGLANG_* env vars at launch ===" | ||
| env | grep -E '^SGLANG_' | sort | ||
| echo "===================================" | ||
| } | tee "$SERVER_LOG" | ||
|
|
||
| echo "Starting SGLang server for B200..." | ||
| "${SGLANG_CMD[@]}" >> "$SERVER_LOG" 2>&1 & | ||
| SERVER_PID=$! | ||
| echo "Server PID: $SERVER_PID" | ||
|
|
||
| capture_cache_metrics() { | ||
| { | ||
| echo "=== SGLang cache metrics snapshot $(date --iso-8601=seconds) ===" | ||
| curl -fsS "http://localhost:$SGLANG_BACKEND_PORT/metrics" 2>/dev/null \ | ||
| | grep -E '^(sglang:(cache_hit_rate|cached_tokens_total|prompt_tokens_total|hicache_host_used_tokens|hicache_host_total_tokens|token_usage|num_requests_running|num_requests_waiting))' \ | ||
| || true | ||
| echo "============================================================" | ||
| } >> "$SERVER_LOG" | ||
| } | ||
|
|
||
| wait_for_ready \ | ||
| --endpoint "http://localhost:$SGLANG_BACKEND_PORT/health" \ | ||
| --log "$SERVER_LOG" \ | ||
| --pid "$SERVER_PID" | ||
|
|
||
| if [ "$USE_SGLANG_ROUTER" = "true" ]; then | ||
| echo "Starting SGLang router on port $PORT for $TP DP ranks..." | ||
| "${SGLANG_ROUTER_CMD[@]}" \ | ||
| --worker-urls "http://localhost:$SGLANG_BACKEND_PORT" \ | ||
| --policy consistent_hashing \ | ||
| --request-id-headers x-correlation-id \ | ||
| --dp-aware \ | ||
| --host 0.0.0.0 \ | ||
| --port "$PORT" \ | ||
| --prometheus-host 127.0.0.1 \ | ||
| --prometheus-port "$SGLANG_ROUTER_METRICS_PORT" \ | ||
| --connect-timeout-secs 900 \ | ||
| --request-timeout-secs 14400 \ | ||
| --disable-health-check \ | ||
| --disable-retries > "$ROUTER_LOG" 2>&1 & | ||
| ROUTER_PID=$! | ||
| echo "Router PID: $ROUTER_PID" | ||
| wait_for_ready \ | ||
| --endpoint "http://localhost:$PORT/health" \ | ||
| --log "$ROUTER_LOG" \ | ||
| --pid "$ROUTER_PID" | ||
| fi | ||
|
|
||
| if [ "${#METRICS_ARGS[@]}" -gt 0 ]; then | ||
| capture_cache_metrics | ||
| trap capture_cache_metrics EXIT | ||
| fi | ||
|
Comment on lines
+261
to
+264
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 The EXIT trap guard Extended reasoning...The bug: METRICS_ARGS=(--enable-metrics --enable-cache-report)It is never reassigned, appended to, or emptied anywhere else in the script — a grep for if [ "${#METRICS_ARGS[@]}" -gt 0 ]; then
capture_cache_metrics
trap capture_cache_metrics EXIT
fiSince the array always has exactly 2 elements, Why it's misleading rather than merely redundant: the script has one real precedent for this shape a few lines earlier — Step-by-step proof:
Impact: none functionally — this doesn't change program behavior since the branch is always taken anyway. It's purely a readability/maintainability nit: a future editor could plausibly try to make metrics capture conditional by clearing Fix: either (a) drop the |
||
|
|
||
| if [ "${EVAL_ONLY}" = "true" ]; then | ||
| run_eval --port "$PORT" | ||
| else | ||
| build_replay_cmd "$RESULT_DIR" | ||
| REPLAY_CMD+=" --server-metrics http://localhost:$SGLANG_BACKEND_PORT/metrics" | ||
| run_agentic_replay_and_write_outputs "$RESULT_DIR" | ||
| fi | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 In eval-only mode, the empty
AGENTIC_VENVcreated at dsv4_fp4_b200_sglang_mtp.sh:52-56 shadowspython3beforetransformersis installed into it, soget_native_max_context_length's bare-python3probe (benchmark_lib.sh:908) fails silently and falls back to a hardcoded 16384 instead of DeepSeek-V4-Pro's real native context. For this specific recipe the fallback is harmless in practice (the single-node agentic eval runs GSM8K, whose prompts/outputs fit well inside 16384), but the detection is still silently wrong and worth fixing, e.g. by creating the venv with--system-site-packagesso the native-context probe still sees the system transformers.Extended reasoning...
The mechanism is real. When
INFMAX_CONTAINER_WORKSPACE != /workspace(the documented normal case for this B200 image, since it mounts InferenceX at/ix), the script runs"$SGLANG_PYTHON" -m venv "$AGENTIC_VENV"with no--system-site-packages, then prepends$AGENTIC_VENV/bintoPATH. This venv starts completely empty.install_agentic_deps(benchmark_lib.sh:1826) builds a separateAIPERF_VENVviauv --python "$AIPERF_PYTHON"and never touchesAGENTIC_VENV, so the new venv stays empty until_install_lm_eval_depseventually runspython3 -m pip install ... lm-eval[api]into it.In
EVAL_ONLY=truemode,run_eval(benchmark_lib.sh:1689-1690) callscompute_eval_context_lengthbefore_install_lm_eval_depsruns (that install happens later, insiderun_lm_eval, at line 1008).compute_eval_context_lengthcallsget_native_max_context_length(line 908), which shells out to a barepython3 -c '... from transformers import AutoConfig ...'. SinceAGENTIC_VENV/binis first onPATHand empty at this point, the import raises, theexceptswallows it, and the probe prints0. BecauseMAX_MODEL_LENis unconditionally unset for agentic callers (benchmark_lib.sh:77) and this recipe never callssetup_eval_context,compute_eval_context_lengthseesbenchmark_ctx=0andnative_max=0and falls through toeval_ctx=${MAX_MODEL_LEN:-16384}=16384(line 946), emitting aWARNto stderr along the way.Step-by-step proof:
INFMAX_CONTAINER_WORKSPACEresolves to/ix(not/workspace) for this image, so the script hits theifbranch and createsAGENTIC_VENVempty, thenexport PATH="$AGENTIC_VENV/bin:$PATH".EVAL_ONLY=true→ the script callsrun_eval --port "$PORT".run_evalseesEVAL_MAX_MODEL_LENunset → callscompute_eval_context_length "$MODEL" "${MAX_MODEL_LEN:-0}"withMAX_MODEL_LENunset (→0).compute_eval_context_lengthcallsget_native_max_context_length, which runs barepython3 -c '...AutoConfig...'.python3now resolves inside the emptyAGENTIC_VENV,transformersisn't installed there yet, import fails, function prints0.benchmark_ctxandnative_maxare0→ fallback branch:eval_ctx=16384,EVAL_MAX_MODEL_LEN=16384.run_lm_evalpassesmax_length=16384and computesmax_output_tokens=16384-4096=12288forlm_eval --model_args/--gen_kwargs./workspacecase), barepython3resolves to the image's system Python, which already hastransformersinstalled, so the probe would have returned DeepSeek-V4-Pro's real (larger) native context instead of0.Where the original framing overreaches (addressing the refutation). One verifier objection is correct and should be acknowledged:
AGENTIC_VENVis not simply redundant dead weight._install_lm_eval_deps(benchmark_lib.sh:870-887) and thelm_evalinvocation itself (line 1029) use barepython3 -m pip install --break-system-packages .../python3 -m lm_eval. Without something ahead of the system Python onPATH, those bare-python3calls would install/upgradelm-eval[api](and transitivelytransformers) directly into the SGLang server's system interpreter — exactly the corruption the script's own comment says it's trying to avoid. So the venv does serve a real isolation purpose for the eval path; it isn't redundant withAIPERF_VENV.The refutation's second point is also correct and matters for grading severity: this config has
multinode: false, and perutils/matrix_logic/generate_sweep_configs.py:289-291/405-406, single-node agentic entries run their eval through the GSM8K lm-eval path, not SWE-bench. GSM8K prompts and chain-of-thought generations are far shorter than 16384 tokens, andmax_output_tokensstill comes out to 12288 either way, so for this recipe's actual eval point there is no truncation or observable score impact — the fallback happens to be harmless here.Why it's still worth flagging. The detection is genuinely broken (a documented-intent violation per benchmark_lib.sh:70-71, "agentic replays must use the model's native context limit"), it fails silently (only a stderr
WARN, no exit), and it's not obviously scoped to "harmless for GSM8K" from the call site — the same pattern will silently misconfigure any future agentic recipe/eval-task combination where the actual context matters, and it's easy to overlook since nothing crashes. A simple, low-risk fix: createAGENTIC_VENVwith--system-site-packages(so the bare-python3native-context probe still resolvestransformersfrom the image's system install whilepip installs continue to prefer the venv's own site-packages), or resolve the native-context probe via$SGLANG_PYTHONexplicitly instead of a barepython3.